Corda Settler UI-尝试通过API发送问题义务请求时出现错误404

时间:2019-07-11 08:34:42

标签: javascript api kotlin blockchain corda

基本上,我已经为corda-settler创建了自己的结算栏,现在我正在尝试创建一个用于演示的UI。我在尝试使用API​​调用创建义务时遇到问题。我正在使用内置的jettywebserver,并使用javascript编写了自己的API,以发送请求。希望就我可能在哪里做错的问题寻求建议/见解,我对此还比较陌生,不知道所提供的以下代码是否可以帮助大家在这个问题上给我更好的建议,但是如果有更多信息,请告诉我需要提供。预先谢谢你!

这是我创建的用户界面(抱歉,信誉不足,无法直接发布图像):

https://imgur.com/xPUQdUj

,然后单击“创建义务”按钮,将返回:

https://imgur.com/3X1FPp6

在我看来,我好像没有正确获取/调用API,但由于代码对我来说很好,我只是无法弄清楚出了什么问题。

这是我的index.html文件:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Settler CorDapp</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
          integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link rel="stylesheet" type="text/css" href="css/index.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0-rc.1/angular.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/2.2.0/ui-bootstrap-tpls.min.js"></script>

    <script src="js/main.js"></script>
    <script src="js/createObligationModal.js"></script>
    <script src="js/settleModal.js"></script>

</head>


<body ng-app="demoAppModule" ng-controller="DemoAppCtrl as demoApp">

<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">{{demoApp.thisNode}}</a>
        </div>
        <button ng-click="demoApp.openCreateObligationModal()" type="button" class="btn btn-primary navbar-btn">Create Obligation</button>
        <button ng-click="demoApp.refresh()" type="button" class="btn btn-default navbar-btn"><span
                class="glyphicon glyphicon-refresh"></span></button>
    </div>
</nav>

<script type="text/ng-template" id="createObligationModal.html">
    <div class="modal-header">
        <h4 class="modal-title">Add new Obligation</h4>
    </div>

    <form>
        <div class="modal-body">
            <div class="form-group">
                <label for="createObligationCounterparty" class="control-label">Counter-party:</label>
                <select ng-model="createObligationModal.form.counterparty" class="form-control" id="createObligationCounterparty"
                        ng-options="peer as peer for peer in createObligationModal.peers">
                </select>
            </div>
            <div class="form-group">
                <label for="createObligationCurrency" class="control-label">Currency (ISO code):</label>
                <input type="text" ng-model="createObligationModal.form.currency" class="form-control" id="createObligationCurrency">
            </div>
            <div class="form-group">
                <label for="createObligationAmount" class="control-label">Amount (Int):</label>
                <input type="text" ng-model="createObligationModal.form.amount" class="form-control" id="createObligationAmount">
            </div>
            <div class="form-group">
                <label for="createObligationDueDate" class="control-label">Due Date:</label>
                <input type="date" ng-model="createObligationModal.form.duedate" class="form-control" id="createObligationDueDate" min="2020-01-01" max="2022-12-31">
            </div>


            <div ng-show="createObligationModal.formError" class="form-group">
                <div class="alert alert-danger" role="alert">
                    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
                    <span class="sr-only">Error:</span>
                    Enter valid Obligation creation parameters
                </div>
            </div>
        </div>
        <div class="modal-footer">
            <button ng-click="createObligationModal.cancel()" type="button" class="btn btn-default">Close</button>
            <button ng-click="createObligationModal.create()" type="button" class="btn btn-primary">Create Obligation</button>
        </div>
    </form>
</script>

<script type="text/ng-template" id="createObligationMsgModal.html">
    <div class="modal-body" id="create-Obligation-modal-body">
        {{ createObligationMsgModal.message }}
    </div>
</script>

<script type="text/ng-template" id="settleModal.html">
    <div class="modal-header">
        <h4 class="modal-title">Settle Obligation</h4>
    </div>
    <form>
        <div class="modal-body">
            <div class="form-group">
                <label for="settleCurrency" class="control-label">Currency (ISO code):</label>
                <input type="text" ng-model="settleModal.form.currency" class="form-control" id="settleCurrency">
            </div>
            <div class="form-group">
                <label for="settleAmount" class="control-label">Amount (Int):</label>
                <input type="text" ng-model="settleModal.form.amount" class="form-control" id="settleAmount">
            </div>
            <div ng-show="settleModal.formError" class="form-group">
                <div class="alert alert-danger" role="alert">
                    <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
                    <span class="sr-only">Error:</span>
                    Enter valid Obligation settle parameters.
                </div>
            </div>
        </div>
        <div class="modal-footer">
            <button ng-click="settleModal.cancel()" type="button" class="btn btn-default">Close</button>
            <button ng-click="settleModal.settle()" type="button" class="btn btn-primary">Settle</button>
        </div>
    </form>
</script>

<script type="text/ng-template" id="settleMsgModal.html">
    <div class="modal-body" id="settle-modal-body">
        {{ settleMsgModal.message }}
    </div>
</script>

<div class="row">
    <div class="col-md-1"></div>
    <div class="col-md-10">
        <div ng-show="!demoApp.obligations.length" class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">There are no recorded Obligations</h3>
            </div>
            <div class="panel-body">Use the "Create Obligation" button to send an Obligation to a peer.</div>
        </div>

        <div ng-show="demoApp.obligations.length" class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">Recorded Obligations:</h3>
            </div>

            <div class="panel-body">
                <table class="table">
                    <thead>
                    <tr>
                        <th>From</th>
                        <th>To</th>
                        <th>Amount</th>
                        <th>Paid</th>
                        <th>Actions</th>
                    </tr>
                    </thead>
                    <tbody>
                    <tr ng-repeat="obligation in demoApp.obligations">
                        <td class="vert-align">{{obligation.obligee.substring(0,30)}}</td>
                        <td class="vert-align">{{obligation.obligor.substring(0,30)}}</td>
                        <td class="vert-align">{{obligation.faceAmount}}</td>
                        <td class="vert-align">{{obligation.amountPaid}}</td>
                        <td>
                            <div class="btn-group" role="group">
                                <button ng-click="demoApp.openSettleModal(obligation.linearId.id)" type="button" class="btn btn-primary">Settle
                                </button>
                            </div>
                        </td>
                    </tr>
                    </tbody>
                </table>
            </div>

        </div>
        <div class="col-md-1"></div>
    </div>
</div>

</body>

</html>

分别是我的ObligationPlugin.ktObligationApi.kt文件:     包com.r3.corda.finance.obligation

import net.corda.core.messaging.CordaRPCOps
import net.corda.webserver.services.WebServerPluginRegistry
import java.util.function.Function

class ObligationPlugin : WebServerPluginRegistry {
    override val webApis: List<Function<CordaRPCOps, out Any>> = listOf(Function(::ObligationApi))
    override val staticServeDirs: Map<String, String> = mapOf(
            "obligation" to javaClass.classLoader.getResource("settlerWeb").toExternalForm()
    )
}

package com.r3.corda.finance.obligation

import com.r3.corda.finance.obligation.flows.CreateObligation
import com.r3.corda.finance.obligation.states.Obligation
import net.corda.core.contracts.Amount
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.getOrThrow
import java.util.*
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.QueryParam
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response

@Path("obligation")
class ObligationApi(val rpcOps: CordaRPCOps) {

    private val myIdentity = rpcOps.nodeInfo().legalIdentities.first()

    @GET
    @Path("me")
    @Produces(MediaType.APPLICATION_JSON)
    fun me() = mapOf("me" to myIdentity)

    @GET
    @Path("peers")
    @Produces(MediaType.APPLICATION_JSON)
    fun peers() = mapOf("peers" to rpcOps.networkMapSnapshot()
            .filter { nodeInfo -> nodeInfo.legalIdentities.first() != myIdentity }
            .map { it.legalIdentities.first().name.organisation })

    @GET
    @Path("issue-obligation")
    fun issueObligation(@QueryParam(value = "party") party: String,
                        @QueryParam(value = "currency") currency: String,
                        @QueryParam(value = "amount") amount: Int,
                        @QueryParam(value = "duedate") duedate: Date
    ): Response {
        // 1. Get party objects for the counterparty.
        val obligorIdentity = rpcOps.partiesFromName(party, exactMatch = false).singleOrNull()
                ?: throw IllegalStateException("Couldn't lookup node identity for $party.")
        // 2. Create an amount object.
        val issueAmount = Amount(amount.toLong() * 100, Currency.getInstance(currency))
        // 3. Convert duedate to unix timestamp (in seconds)
        val unixTime = (duedate.time) / 1000
        // 4. Retrieve obligee identity
//        val obligeeIdentity = myIdentity

        // 5. Start the IssueObligation flow. We block and wait for the flow to return.
        val (status, message) = try {
            val flowHandle = rpcOps.startFlowDynamic(
                    CreateObligation.Initiator::class.java,
                    issueAmount,
                    CreateObligation.InitiatorRole.OBLIGEE,
                    obligorIdentity,
                    unixTime,
                    true
            )

            val result = flowHandle.returnValue.getOrThrow()
            flowHandle.close()
            Response.Status.CREATED to "Transaction id ${result.id} committed to ledger.\n${result.outputs}"
        } catch (e: Exception) {
            Response.Status.BAD_REQUEST to e.message
        }

        // 4. Return the result.
        return Response.status(status).entity(message).build()
    }

    @GET
    @Path("obligations")
    @Produces(MediaType.APPLICATION_JSON)
    fun obligations(): List<Obligation<*>> {
        val statesAndRefs = rpcOps.vaultQuery(Obligation::class.java).states
        return statesAndRefs
                .map { stateAndRef -> stateAndRef.state.data }
                .map { state ->
                    // We map the anonymous lender and borrower to well-known identities if possible.
                    val possiblyWellKnownLender = rpcOps.wellKnownPartyFromAnonymous(state.obligee) ?: state.obligee
                    val possiblyWellKnownBorrower = rpcOps.wellKnownPartyFromAnonymous(state.obligor) ?: state.obligor

                    Obligation(state.faceAmount,
                            possiblyWellKnownBorrower,
                            possiblyWellKnownLender,
                            state.dueBy,
                            state.createdAt,
                            state.settlementMethod,
                            state.payments,
                            state.linearId)
                }
    }
}

,以下分别是main.jscreateObligationModal.js文件:

"use strict";

// Define your backend here.
angular.module('demoAppModule', ['ui.bootstrap']).controller('DemoAppCtrl', function ($http, $location, $uibModal) {
    const demoApp = this;

    const apiBaseURL = "/api/obligation/";

    // Retrieves the identity of this and other nodes.
    let peers = [];
    $http.get(apiBaseURL + "me").then((response) => demoApp.thisNode = response.data.me);
    $http.get(apiBaseURL + "peers").then((response) => peers = response.data.peers);

    /** Displays the obligation creation modal. */
    demoApp.openCreateObligationModal = () => {
        const createObligationModal = $uibModal.open({
            templateUrl: 'createObligationModal.html',
            controller: 'CreateObligationModalCtrl',
            controllerAs: 'createObligationModal',
            resolve: {
                demoApp: () => demoApp,
                apiBaseURL: () => apiBaseURL,
                peers: () => peers,
                refreshCallback: () => demoApp.refresh
            }
        });

        // Ignores the modal result events.
        createObligationModal.result.then(() => {
        }, () => {
        });
    };

    /** Displays the Obligation settlement modal. */
    demoApp.openSettleModal = (id) => {
        const settleModal = $uibModal.open({
            templateUrl: 'settleModal.html',
            controller: 'SettleModalCtrl',
            controllerAs: 'settleModal',
            resolve: {
                demoApp: () => demoApp,
                apiBaseURL: () => apiBaseURL,
                id: () => id,
                refreshCallback: () => demoApp.refresh
            }
        });

        settleModal.result.then(() => {
        }, () => {
        });
    };

    /** Refreshes the front-end. */
    demoApp.refresh = () => {
        // Update the list of Obligations.
        $http.get(apiBaseURL + "obligations").then((response) => demoApp.obligations =
            Object.keys(response.data).map((key) => response.data[key]));
    }

    demoApp.refresh();
});

// Causes the webapp to ignore unhandled modal dismissals.
angular.module('demoAppModule').config(['$qProvider', function ($qProvider) {
    $qProvider.errorOnUnhandledRejections(false);
}]);

"use strict";

angular.module('demoAppModule').controller('CreateObligationModalCtrl', function ($http, $uibModalInstance, $uibModal, apiBaseURL, peers, refreshCallback) {
    const createObligationModal = this;

    createObligationModal.peers = peers;
    createObligationModal.form = {};
    createObligationModal.formError = false;

    /** Validate and create an Obligation. */
    createObligationModal.create = () => {
        if (invalidFormInput()) {
            createObligationModal.formError = true;
        } else {
            createObligationModal.formError = false;

            const party = createObligationModal.form.counterparty;
            const currency = createObligationModal.form.currency;
            const amount = createObligationModal.form.amount;
            const duedate = createObligationModal.form.duedate;

            $uibModalInstance.close();

            // We define the Obligation creation endpoint.
            const issueObligationEndpoint =
                apiBaseURL +
                `issue-obligation?party=${party}&currency=${currency}&amount=${amount}&duedate=${duedate}`;

            // We hit the endpoint to create the Obligation and handle success/failure responses.
            $http.get(issueObligationEndpoint).then(
                (result) => {
                    createObligationModal.displayMessage(result);
                    refreshCallback();
                },
                (result) => {
                    createObligationModal.displayMessage(result);
                    refreshCallback();
                }
            );
        }
    };

    /** Displays the success/failure response from attempting to create an Obligation. */
    createObligationModal.displayMessage = (message) => {
        const createObligationMsgModal = $uibModal.open({
            templateUrl: 'createObligationMsgModal.html',
            controller: 'createObligationMsgModalCtrl',
            controllerAs: 'createObligationMsgModal',
            resolve: {
                message: () => message
            }
        });

        // No behaviour on close / dismiss.
        createObligationMsgModal.result.then(() => {}, () => {});
    };

    /** Closes the Obligation creation modal. */
    createObligationModal.cancel = () => $uibModalInstance.dismiss();

    // Validates the Obligation.
    function invalidFormInput() {
        return isNaN(createObligationModal.form.amount) || (createObligationModal.form.counterparty === undefined);
    }
});

// Controller for the success/fail modal.
angular.module('demoAppModule').controller('createObligationMsgModalCtrl', function ($uibModalInstance, message) {
    const createObligationMsgModal = this;
    createObligationMsgModal.message = message.data;
});

继续...

1 个答案:

答案 0 :(得分:0)

以下是在PartyA节点上生成的Web日志的摘要:

[INFO ] 2019-07-11T07:08:01,782Z [main] server.AbstractConnector.doStart - Started ServerConnector@179af25f{HTTP/1.1,[http/1.1]}{0.0.0.0:10007} {}
[INFO ] 2019-07-11T07:08:01,782Z [main] server.Server.doStart - Started @77789ms {}
[INFO ] 2019-07-11T07:08:01,782Z [main] internal.NodeWebServer.initWebServer - Starting webserver on address localhost:10007 {}
[WARN ] 2019-07-11T07:14:55,332Z [qtp1745174877-127] server.HttpChannel.handleException - /api/obligation/issue-obligation {}
javax.servlet.ServletException: javax.servlet.ServletException: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.r3.corda.finance.obligation.ObligationApi.issueObligation, parameter party
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.Server.handle(Server.java:561) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:334) [jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251) [jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279) [jetty-io-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:104) [jetty-io-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124) [jetty-io-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:247) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:140) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:243) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:679) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:597) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at java.lang.Thread.run(Unknown Source) [?:1.8.0_212]
Caused by: javax.servlet.ServletException: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.r3.corda.finance.obligation.ObligationApi.issueObligation, parameter party
    at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:489) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    ... 14 more
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.r3.corda.finance.obligation.ObligationApi.issueObligation, parameter party
    at com.r3.corda.finance.obligation.ObligationApi.issueObligation(ObligationApi.kt) ~[cordapp-contracts-states-0.1.jar:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_212]
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_212]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_212]
    at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_212]
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:160) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:326) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    ... 14 more
[WARN ] 2019-07-11T07:18:53,277Z [qtp1745174877-127] server.HttpChannel.handleException - /api/obligation/issue-obligation {}
javax.servlet.ServletException: javax.servlet.ServletException: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.r3.corda.finance.obligation.ObligationApi.issueObligation, parameter party
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.Server.handle(Server.java:561) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:334) [jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251) [jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279) [jetty-io-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:104) [jetty-io-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124) [jetty-io-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:247) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:140) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:243) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:679) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:597) [jetty-util-9.4.7.v20170914.jar:9.4.7.v20170914]
    at java.lang.Thread.run(Unknown Source) [?:1.8.0_212]
Caused by: javax.servlet.ServletException: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.r3.corda.finance.obligation.ObligationApi.issueObligation, parameter party
    at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:489) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    ... 14 more
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.r3.corda.finance.obligation.ObligationApi.issueObligation, parameter party
    at com.r3.corda.finance.obligation.ObligationApi.issueObligation(ObligationApi.kt) ~[cordapp-contracts-states-0.1.jar:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_212]
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_212]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_212]
    at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_212]
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:160) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:326) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317) ~[jersey-common-2.25.jar:?]
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154) ~[jersey-server-2.25.jar:?]
    at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:473) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:427) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:388) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:341) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:228) ~[jersey-container-servlet-core-2.25.jar:?]
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:535) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473) ~[jetty-servlet-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126) ~[jetty-server-9.4.7.v20170914.jar:9.4.7.v20170914]
    ... 14 more

和“义务”数据类:

package com.r3.corda.finance.obligation.states

import com.r3.corda.finance.obligation.types.Money
import com.r3.corda.finance.obligation.types.Payment
import com.r3.corda.finance.obligation.types.PaymentStatus
import com.r3.corda.finance.obligation.types.SettlementMethod
import net.corda.core.contracts.Amount
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import java.time.Instant

/**
 * Obligation Settlement Assumptions:
 *
 * 1. Obligation are just for fiat currency and digital currency for the time being. Support for arbitrary token states
 *    can be added later e.g. shares.
 * 2. Obligations are given a face value in some currency or digital currency e.g. BTC or GBP.
 * 3. Obligations can be settled in a currency or digital currency other than the one specified for the face value.
 *    - If a different currency is used to the face value currency, then a conversion is done for the time the
 *      obligation was first raised.
 *    - This process requires a novation of the obligation from one currency to another.
 * 4. Obligations can be paid down with multiple payments.
 * 5. Obligations can only be paid down with one currency or digital currency type.
 *    - For example, if one currency is initially used, it must be used for the remainder of the payments.
 * 6. The obligee specifies which token states are acceptable for payment on ledger and which settlement rails are
 *    appropriate for off-ledger.
 *    - Only one settlement method can be supplied at any one time.
 * 7. Obligations are considered settled when the sum of all payments in the face value currency equal the face value.
 * 8. Obligations are considered in default if they are not fully paid by the dueDate, if one is specified.
 *
 */
data class Obligation<T : Money>(
        /** Obligations are always denominated in some token type as we need a reference for FX purposes. */
        val faceAmount: Amount<T>,
        /** The payer. Can be pseudo-anonymous. */
        val obligor: AbstractParty,
        /** The beneficiary. Can be pseudo-anonymous. */
        val obligee: AbstractParty,
        /** When the obligation should be paid by. May not always be required. */
        val dueBy: Instant? = null,
        /** The time when the obligation was created. */
        val createdAt: Instant = Instant.now(),
        /** Settlement methods the obligee will accept: On ledger, off ledger (crypto, swift, PISP, paypal, etc.). */
        val settlementMethod: SettlementMethod? = null,
        /** The obligation can be paid in parts. This lists all payments in respect of this obligation */
        val payments: List<Payment<out Money>> = emptyList(),
        /** Unique identifier for the obligation. */
        override val linearId: UniqueIdentifier = UniqueIdentifier()
) : LinearState {

    @CordaSerializable
    enum class SettlementStatus { UNSETTLED, SETTLED, PARTIALLY_SETTLED }

    /** Always returns the obligor and obligee. */
    override val participants: List<AbstractParty> get() = listOf(obligee, obligor)

    /** The sum of amounts for all payments. */
    val amountPaid: Amount<T>
        get() = payments
                .filter { it.status == PaymentStatus.SETTLED }
                .map { it.amount }
                .fold(Amount.zero(faceAmount.token)) { acc, amount -> acc + amount as Amount<T> }

    /** A defaulted obligation is one where the current time is greater than the [dueBy] time. */
    val inDefault: Boolean get() = dueBy?.let { Instant.now() > dueBy } ?: false

    /** Returns the current state of the obligation. */
    val settlementStatus: SettlementStatus
        get() {
            return when {
                payments.isEmpty() -> SettlementStatus.UNSETTLED
                payments.isNotEmpty() && amountPaid < faceAmount -> SettlementStatus.PARTIALLY_SETTLED
                payments.isNotEmpty() && amountPaid == faceAmount -> SettlementStatus.SETTLED
                else -> throw IllegalStateException("Shouldn't reach here.")
            }
        }

    /** For adding or changing the settlement method. */
    fun withSettlementMethod(settlementMethod: SettlementMethod?): Obligation<T> {
        return if (settlementStatus != SettlementStatus.UNSETTLED) {
            throw IllegalStateException("Cannot change settlement method after a payment has been made.")
        } else copy(settlementMethod = settlementMethod)
    }

    /** Update the due by date. */
    fun withDueByDate(dueBy: Instant) = copy(dueBy = dueBy)

    /** Update the due by date. */
    fun withNewCounterparty(oldParty: AbstractParty, newParty: AbstractParty): Obligation<T> {
        return when {
            obligee == oldParty -> copy(obligee = newParty)
            obligor == oldParty -> copy(obligor = newParty)
            else -> throw IllegalArgumentException("The oldParty is not recognised as a participant in this obligation.")
        }
    }

    fun <U : Money> withNewFaceValueToken(newAmount: Amount<U>): Obligation<U> {
        return if (payments.isEmpty()) {
            Obligation(newAmount, obligor, obligee, dueBy, createdAt, settlementMethod, emptyList(), linearId)
        } else {
            throw IllegalStateException("The faceValue token type cannot be updated after payments have been made.")
        }
    }

    /** Update the amount, keeping the token type the same. */
    fun withNewFaceValueQuantity(newAmount: Amount<T>): Obligation<T> {
        return if (newAmount < amountPaid) {
            throw IllegalStateException("Can't reduce the faceAmount to less than the current amountPaid.")
        } else copy(faceAmount = newAmount)
    }

    /** Add a new payment to the payments list. */
    fun withPayment(payment: Payment<T>): Obligation<T> {
        val newAmountPaid = amountPaid + payment.amount
        return if (newAmountPaid > faceAmount) {
            throw IllegalStateException("You cannot over pay an obligation.")
        } else {
            copy(payments = payments + payment)
        }
    }

    private fun resolveParty(resolver: (AbstractParty) -> Party, abstractParty: AbstractParty): Party {
        return abstractParty as? Party ?: resolver(abstractParty)
    }

    /** Returns the obligation with well known identities. */
    fun withWellKnownIdentities(resolver: (AbstractParty) -> Party): Obligation<T> {
        return copy(obligee = resolveParty(resolver, obligee), obligor = resolveParty(resolver, obligor))
    }

    override fun toString(): String {
        val obligeeString = (obligee as? Party)?.name?.organisation
                ?: obligee.owningKey.toStringShort().substring(0, 10)
        val obligorString = (obligor as? Party)?.name?.organisation
                ?: obligor.owningKey.toStringShort().substring(0, 10)
        val settlementMethod = if (settlementMethod == null) "No settlement method added" else settlementMethod.toString()
        var paymentString = ""
        if (payments.isNotEmpty()) {
            payments.forEach { paymentString += "\n\t\t\t$it" }
        } else {
            paymentString = "\n\t\t\tNo payments made."
        }
        return "Obligation($linearId): $obligorString owes $obligeeString $faceAmount ($amountPaid paid)." +
                "\n\t\tSettlement status: $settlementStatus" +
                "\n\t\tSettlementMethod: $settlementMethod" +
                "\n\t\tPayments:" +
                paymentString
    }

}

创建义务,更新结算方法和结算账本流中的义务在终端上都运行良好。