SpringBoot: Flow Polling
This commit is contained in:
parent
5035a16930
commit
a0c91ee9ce
@ -3,6 +3,8 @@ package djmil.cordacheckers.cordaclient;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@ -12,25 +14,27 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
import djmil.cordacheckers.cordaclient.dao.HoldingIdentity;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNode;
|
||||||
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
import djmil.cordacheckers.cordaclient.dao.VirtualNodeList;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
import djmil.cordacheckers.cordaclient.dao.flow.RequestBody;
|
||||||
|
import djmil.cordacheckers.cordaclient.dao.flow.ResponseBody;
|
||||||
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
import djmil.cordacheckers.cordaclient.dao.flow.arguments.Empty;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CordaClient {
|
public class CordaClient {
|
||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
|
private final ObjectMapper jsonMapper;
|
||||||
|
|
||||||
public CordaClient(RestTemplate restTemplate) {
|
public CordaClient(RestTemplate restTemplate, ObjectMapper jsonMapper) {
|
||||||
//System.out.println("Creating REST Service");
|
//System.out.println("Creating REST Service");
|
||||||
// this.restTemplate = restTemplateBuilder
|
// this.restTemplate = restTemplateBuilder
|
||||||
// .basicAuthentication("admin", "admin")
|
// .basicAuthentication("admin", "admin")
|
||||||
// .build();
|
// .build();
|
||||||
this.restTemplate = restTemplate;
|
this.restTemplate = restTemplate;
|
||||||
|
this.jsonMapper = jsonMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<VirtualNode> getVirtualNodeList() {
|
public List<VirtualNode> getVirtualNodeList() {
|
||||||
@ -59,41 +63,121 @@ public class CordaClient {
|
|||||||
* Obtain list of unconsumed (active) GameProposals
|
* Obtain list of unconsumed (active) GameProposals
|
||||||
* @param holdingIdentity
|
* @param holdingIdentity
|
||||||
* @return GameProposals list in JSON form
|
* @return GameProposals list in JSON form
|
||||||
* @throws JsonProcessingException
|
|
||||||
*/
|
*/
|
||||||
public String listGameProposals(HoldingIdentity holdingIdentity) throws JsonProcessingException {
|
public String listGameProposals(HoldingIdentity holdingIdentity) {
|
||||||
// Request authorization header
|
|
||||||
HttpHeaders headers = basicAuthorizationHeader();
|
|
||||||
|
|
||||||
|
final RequestBody requestBody = new RequestBody(
|
||||||
|
"list-" + UUID.randomUUID(),
|
||||||
|
"djmil.cordacheckers.gameproposal.ListFlow",
|
||||||
|
new Empty()
|
||||||
|
);
|
||||||
|
|
||||||
|
final String gameProposalsJsonString = cordaFlowExecute(
|
||||||
|
holdingIdentity,
|
||||||
|
requestBody
|
||||||
|
);
|
||||||
|
|
||||||
RequestBody body = new RequestBody("list-2", "djmil.cordacheckers.gameproposal.ListFlow", new Empty());
|
return gameProposalsJsonString;
|
||||||
|
}
|
||||||
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
private String cordaFlowExecute(HoldingIdentity holdingIdentity, RequestBody requestBody) {
|
||||||
|
|
||||||
String json = mapper.writeValueAsString(body);
|
try {
|
||||||
|
final String requestBodyJson = this.jsonMapper.writeValueAsString(requestBody);
|
||||||
|
|
||||||
if (json != null)
|
final ResponseBody startedFlow = cordaFlowPost(
|
||||||
return json;
|
holdingIdentity,
|
||||||
// new String("{\n" + //
|
requestBodyJson
|
||||||
// " \"clientRequestId\": \"list-1\",\n" + //
|
);
|
||||||
// " \"flowClassName\": \"djmil.cordacheckers.gameproposal.ListFlow\",\n" + //
|
|
||||||
// " \"requestBody\": {}\n" + //
|
|
||||||
// "}");
|
|
||||||
|
|
||||||
System.out.println("HiH "+holdingIdentity.shortHash()+"\nRequst JSON = "+json);
|
final String flowExecutionResult = cordaFlowPoll(startedFlow);
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// At this point, real production code, probably should convert data between CordaFlow
|
||||||
|
// abstarction into ReactApp abstraction. Instead, to limit boring json shuffling, all
|
||||||
|
// family of Corda.List flows were deliberatly designed to return frontend frendly JSONs.
|
||||||
|
// At the same time, all other Corda flows, simply return plain text string with
|
||||||
|
// operation result.
|
||||||
|
return flowExecutionResult;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to perform "+requestBody.flowClassName()
|
||||||
|
+". Reason "+e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request
|
private ResponseBody cordaFlowPost(HoldingIdentity holdingIdentity, String requestBodyJson) {
|
||||||
final HttpEntity<String> request = new HttpEntity<>(json, headers);
|
final HttpHeaders requestHeaders = basicAuthorizationHeader();
|
||||||
|
|
||||||
ResponseEntity<String> resp = this.restTemplate.exchange(
|
final HttpEntity<String> request = new HttpEntity<>(requestBodyJson, requestHeaders);
|
||||||
"https://localhost:8888/api/v1/flow/"+holdingIdentity.shortHash(),
|
|
||||||
|
final ResponseEntity<ResponseBody> responce = this.restTemplate.exchange(
|
||||||
|
"https://localhost:8888/api/v1/flow/" + holdingIdentity.shortHash(),
|
||||||
HttpMethod.POST,
|
HttpMethod.POST,
|
||||||
request,
|
request,
|
||||||
String.class);
|
ResponseBody.class
|
||||||
|
);
|
||||||
|
|
||||||
return resp.getBody();
|
if (!responce.getStatusCode().is2xxSuccessful()) {
|
||||||
|
throw new RuntimeException("CordaClient.startCordaFlow: unexpected request status "
|
||||||
|
+responce.getStatusCode()) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ResponseBody responseBody = requireNonNull(
|
||||||
|
responce.getBody(),
|
||||||
|
"CordaClient.startCordaFlow: empty getBody()"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!responseBody.isFlowStarted()) {
|
||||||
|
throw new RuntimeException("CordaClient.startCordaFlow: Unexpected status: "
|
||||||
|
+responseBody.flowStatus() + ": "
|
||||||
|
+responseBody.flowError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cordaFlowPoll(ResponseBody startedFlow) throws InterruptedException {
|
||||||
|
final HttpHeaders requestHeaders = basicAuthorizationHeader();
|
||||||
|
|
||||||
|
final HttpEntity<String> request = new HttpEntity<>(requestHeaders);
|
||||||
|
|
||||||
|
for (int retry = 0; retry < 6; retry++) {
|
||||||
|
// Give Corda cluster some time to process our request
|
||||||
|
TimeUnit.SECONDS.sleep(retry*retry +1); // 1 2 5 8 17 33 sec
|
||||||
|
|
||||||
|
final ResponseEntity<ResponseBody> responce = this.restTemplate.exchange(
|
||||||
|
"https://localhost:8888/api/v1/flow/"
|
||||||
|
+ startedFlow.holdingIdentityShortHash()+"/"
|
||||||
|
+ startedFlow.clientRequestId(),
|
||||||
|
HttpMethod.GET,
|
||||||
|
request,
|
||||||
|
ResponseBody.class
|
||||||
|
);
|
||||||
|
|
||||||
|
if (responce.getStatusCode() != HttpStatus.OK) {
|
||||||
|
throw new RuntimeException("CordaClient.cordaFlowPoll: unexpected request status "
|
||||||
|
+responce.getStatusCode()) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ResponseBody responseBody = requireNonNull(
|
||||||
|
responce.getBody(),
|
||||||
|
"CordaClient.cordaFlowPoll: empty getBody()"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (responseBody.isFlowCompleted() && responseBody.flowResult() != null) {
|
||||||
|
System.out.println("Completed "+responseBody.flowResult());
|
||||||
|
return responseBody.flowResult();
|
||||||
|
} else
|
||||||
|
if (responseBody.flowError() != null) {
|
||||||
|
return "Flow execution error: " +responseBody.flowError();
|
||||||
|
} else
|
||||||
|
if (!responseBody.isFlowRunning()) {
|
||||||
|
return "Unexpect ResponseBody status: " +responseBody.flowStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "CordaClient.cordaFlowPoll: retry limit";
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpHeaders basicAuthorizationHeader() {
|
private HttpHeaders basicAuthorizationHeader() {
|
||||||
|
@ -8,4 +8,18 @@ public record ResponseBody(
|
|||||||
String flowResult,
|
String flowResult,
|
||||||
String flowError,
|
String flowError,
|
||||||
String timestamp
|
String timestamp
|
||||||
) { }
|
) {
|
||||||
|
|
||||||
|
public boolean isFlowStarted() {
|
||||||
|
return this.flowStatus.equals("START_REQUESTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlowRunning() {
|
||||||
|
return this.flowStatus.equals("RUNNING");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFlowCompleted() {
|
||||||
|
return this.flowStatus.equals("COMPLETED");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -32,7 +32,8 @@ public class CordaClientTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testListGameProposals() throws JsonProcessingException {
|
void testListGameProposals() throws JsonProcessingException {
|
||||||
String resp = cordaClient.listGameProposals(holdingIdentityResolver.getByCommonName("alice"));
|
String resp = cordaClient.listGameProposals(
|
||||||
|
holdingIdentityResolver.getByCommonName("alice"));
|
||||||
|
|
||||||
System.out.println("testListGameProposals "+ resp);
|
System.out.println("testListGameProposals "+ resp);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user