[英]Corda Settler UI - Error 404 when trying to send an issue-obligation request via API
Basically, I have created my own settlement rail for corda-settler and now I'm trying to create an UI for demonstration. 基本上,我已经为corda-settler创建了自己的结算栏,现在我正在尝试创建一个用于演示的UI。 I'm having an issue trying to create an obligation using the API call.
我在尝试使用API调用创建义务时遇到问题。 I'm using the built-in jettywebserver and coded my own API using javascript to send the request.
我正在使用内置的jettywebserver,并使用javascript编写了自己的API,以发送请求。 Hope to seek some advice/insight on where I might be doing it wrong, I'm relatively new to this and not sure if the following codes provided would help you all to better advise me on this issue but please let me know if more information needs to be provided.
希望就我可能在哪里做错的问题寻求建议/见解,我对此还比较陌生,不知道所提供的以下代码是否可以帮助大家在这个问题上给我更好的建议,但是如果有更多信息,请告诉我需要提供。 Thank you in advance!
先感谢您!
This is the UI that I have created (sorry, not enough reputation to post an image directly): 这是我创建的用户界面(很抱歉,声誉不足,无法直接发布图像):
https://imgur.com/xPUQdUj https://imgur.com/xPUQdUj
and upon clicking Create Obligation button, this returns: 在单击创建义务按钮时,将返回:
https://imgur.com/3X1FPp6 https://imgur.com/3X1FPp6
It seems to me like I'm not getting/calling the API properly but I just can't figure out what went wrong because the codes seem fine to me. 在我看来,我没有正确获取/调用API,但我只是想不出出什么问题,因为代码对我来说似乎很好。
This is my index.html
file: 这是我的
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>
Here's my ObligationPlugin.kt
and ObligationApi.kt
files, respectively: package com.r3.corda.finance.obligation 这分别是我的
ObligationPlugin.kt
和ObligationApi.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)
}
}
}
and the followings are main.js
and createObligationModal.js
files, respectively: 以下分别是
main.js
和createObligationModal.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}¤cy=${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;
});
continue... 继续...
Below is a snippet of the web logs generated on PartyA node: 以下是在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
And the Obligation data class: 和义务数据类:
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
}
}
Creating obligation, updating settlement method and settling an obligation off ledger flows are all running fine on the terminal. 在终端上,创建义务,更新结算方法以及从分类账流中结算义务都可以正常运行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.