Merge pull request #14 from corda/mattb/CORE-8063-review-and-complete-kotlin-to-java-conversion
Mattb/core 8063 review and complete kotlin to java conversion
This commit is contained in:
commit
3fcf9a1c49
88
.gitignore
vendored
Normal file
88
.gitignore
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
|
||||
# Eclipse, ctags, Mac metadata, log files
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
tags
|
||||
.DS_Store
|
||||
*.log
|
||||
*.orig
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
|
||||
.gradle
|
||||
local.properties
|
||||
.gradletasknamecache
|
||||
|
||||
# General build files
|
||||
**/build/*
|
||||
|
||||
lib/quasar.jar
|
||||
|
||||
**/logs/*
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
|
||||
|
||||
*.iml
|
||||
|
||||
## Directory-based project format:
|
||||
.idea/*.xml
|
||||
.idea/.name
|
||||
.idea/copyright
|
||||
.idea/inspectionProfiles
|
||||
.idea/libraries
|
||||
.idea/shelf
|
||||
.idea/dataSources
|
||||
.idea/markdown-navigator
|
||||
.idea/runConfigurations
|
||||
.idea/dictionaries
|
||||
|
||||
|
||||
# Include the -parameters compiler option by default in IntelliJ required for serialization.
|
||||
!.idea/codeStyleSettings.xml
|
||||
|
||||
|
||||
## File-based project format:
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
**/out/
|
||||
/classes/
|
||||
|
||||
|
||||
|
||||
# vim
|
||||
*.swp
|
||||
*.swn
|
||||
*.swo
|
||||
|
||||
|
||||
|
||||
# Directory generated during Resolve and TestOSGi gradle tasks
|
||||
bnd/
|
||||
|
||||
# Ignore Gradle build output directory
|
||||
build
|
||||
/.idea/codeStyles/codeStyleConfig.xml
|
||||
/.idea/codeStyles/Project.xml
|
||||
|
||||
|
||||
|
||||
# Ignore Visual studio directory
|
||||
bin/
|
||||
|
||||
|
||||
|
||||
*.cpi
|
||||
*.cpb
|
||||
*.cpk
|
||||
workspace/**
|
||||
#CordaPID.dat
|
||||
#*.pem
|
||||
#*.pfx
|
||||
#CPIFileStatus*.json
|
||||
#GroupPolicy.json
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -2,19 +2,15 @@ import static org.gradle.api.JavaVersion.VERSION_11
|
||||
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
|
||||
id 'net.corda.cordapp.cordapp-configuration'
|
||||
|
||||
id 'org.jetbrains.kotlin.plugin.jpa'
|
||||
|
||||
id 'java'
|
||||
id 'maven-publish'
|
||||
|
||||
id 'csde'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group 'com.r3.hellocorda'
|
||||
group 'com.r3.developers.csdetemplate'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
def javaVersion = VERSION_11
|
||||
|
@ -10,6 +10,7 @@
|
||||
// todo: add test corda running/live task
|
||||
// todo: add a test to check docker is running and display error if not + halt start corda
|
||||
// todo: add a clean corda task.
|
||||
// todo: fix logging level and make it configurable.
|
||||
|
||||
|
||||
import com.r3.csde.CordaLifeCycleHelper
|
||||
|
@ -0,0 +1,71 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.contracts;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.Command;
|
||||
import net.corda.v5.ledger.utxo.Contract;
|
||||
import net.corda.v5.ledger.utxo.ContractState;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Objects.*;
|
||||
|
||||
// todo: START here
|
||||
|
||||
public class ChatContract implements Contract {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ChatContract.class);
|
||||
|
||||
public static class Create implements Command { }
|
||||
public static class Update implements Command { }
|
||||
|
||||
// @Override
|
||||
// public boolean isRelevant(@NotNull ContractState state, @NotNull Set<? extends PublicKey> myKeys) {
|
||||
// return Contract.super.isRelevant(state, myKeys);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void verify(UtxoLedgerTransaction transaction) {
|
||||
|
||||
// Command command = requireNonNull( transaction.getCommands().get(0), "Require a single command"); // this doesn't ensure there is one command
|
||||
requireThat( transaction.getCommands().size() == 1, "Require a single command.");
|
||||
Command command = transaction.getCommands().get(0);
|
||||
|
||||
ChatState output = transaction.getOutputStates(ChatState.class).get(0);
|
||||
|
||||
requireThat(output.getParticipants().size() == 2, "The output state should have two and only two participants.");
|
||||
|
||||
if(command.getClass() == Create.class) {
|
||||
requireThat(transaction.getInputContractStates().isEmpty(), "When command is Create there should be no input states.");
|
||||
requireThat(transaction.getOutputContractStates().size() == 1, "When command is Create there should be one and only one output state.");
|
||||
}
|
||||
else if(command.getClass() == Update.class) {
|
||||
requireThat(transaction.getInputContractStates().size() == 1, "When command is Update there should be one and only one input state.");
|
||||
requireThat(transaction.getOutputContractStates().size() == 1, "When command is Update there should be one and only one output state.");
|
||||
|
||||
ChatState input = transaction.getInputStates(ChatState.class).get(0);
|
||||
requireThat(input.getId().equals(output.getId()), "When command is Update id must not change.");
|
||||
requireThat(input.getChatName().equals(output.getChatName()), "When command is Update chatName must not change.");
|
||||
requireThat(
|
||||
input.getParticipants().containsAll(output.getParticipants()) &&
|
||||
output.getParticipants().containsAll(input.getParticipants()),
|
||||
"When command is Update participants must not change.");
|
||||
}
|
||||
else {
|
||||
throw new CordaRuntimeException("Unsupported command");
|
||||
}
|
||||
}
|
||||
|
||||
private void requireThat(boolean asserted, String errorMessage) {
|
||||
if(!asserted) {
|
||||
throw new CordaRuntimeException("Failed requirement: " + errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.states;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||
import net.corda.v5.base.annotations.ConstructorForDeserialization;
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.BelongsToContract;
|
||||
import net.corda.v5.ledger.utxo.ContractState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.*;
|
||||
|
||||
// todo: Clear out commented code
|
||||
|
||||
//@CordaSerializable
|
||||
@BelongsToContract(ChatContract.class)
|
||||
public class ChatState implements ContractState {
|
||||
|
||||
private UUID id;
|
||||
private String chatName;
|
||||
private MemberX500Name messageFrom;
|
||||
private String message;
|
||||
public List<PublicKey> participants;
|
||||
|
||||
// public ChatState() { // todo why do we need this?
|
||||
// }
|
||||
|
||||
// Allows serialisation and to use a specified UUID.
|
||||
@ConstructorForDeserialization
|
||||
public ChatState(UUID id,
|
||||
String chatName,
|
||||
MemberX500Name messageFrom,
|
||||
String message,
|
||||
List<PublicKey> participants) {
|
||||
this.id = id;
|
||||
this.chatName = chatName;
|
||||
this.messageFrom = messageFrom;
|
||||
this.message = message;
|
||||
this.participants = participants;
|
||||
}
|
||||
|
||||
// // Convenience constructor for initial ChatState objects that need a new UUID generated.
|
||||
// public ChatState(String chatName,
|
||||
// MemberX500Name messageFrom,
|
||||
// String message,
|
||||
// List<PublicKey> participants) {
|
||||
// this(UUID.randomUUID(),
|
||||
// chatName,
|
||||
// messageFrom,
|
||||
// message,
|
||||
// participants);
|
||||
// }
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
public String getChatName() {
|
||||
return chatName;
|
||||
}
|
||||
public MemberX500Name getMessageFrom() {
|
||||
return messageFrom;
|
||||
}
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
// @NotNull
|
||||
// @Override
|
||||
public List<PublicKey> getParticipants() {
|
||||
return participants;
|
||||
}
|
||||
|
||||
|
||||
public ChatState updateMessage(MemberX500Name name, String message) {
|
||||
return new ChatState(id, chatName, name, message, participants);
|
||||
}
|
||||
//
|
||||
// // todo: why is this overridden
|
||||
// @Override
|
||||
// public String toString() {
|
||||
// return ChatState.class.getName() +
|
||||
// "(id=" + id +
|
||||
// ", chatName=" + chatName +
|
||||
// ", messageFrom=" + messageFrom +
|
||||
// ", message=" + message +
|
||||
// ", participants=" + participants +
|
||||
// ")";
|
||||
// }
|
||||
}
|
@ -22,6 +22,3 @@ rootProject.name = 'csde-cordapp-template-java'
|
||||
include ':workflows'
|
||||
include ':contracts'
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.base.annotations.CordaSerializable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
@ -7,6 +7,10 @@ import net.corda.v5.base.types.MemberX500Name;
|
||||
// send it from one virtual node to another.
|
||||
@CordaSerializable
|
||||
public class Message {
|
||||
|
||||
private MemberX500Name sender;
|
||||
private String message;
|
||||
|
||||
public Message(MemberX500Name sender, String message) {
|
||||
this.sender = sender;
|
||||
this.message = message;
|
||||
@ -20,6 +24,5 @@ public class Message {
|
||||
return message;
|
||||
}
|
||||
|
||||
public MemberX500Name sender;
|
||||
public String message;
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.*;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
@ -19,7 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class MyFirstFlow implements RPCStartableFlow {
|
||||
|
||||
// Log messages from the flows for debugging.
|
||||
private final Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||
private final static Logger log = LoggerFactory.getLogger(MyFirstFlow.class);
|
||||
|
||||
// Corda has a set of injectable services which are injected into the flow at runtime.
|
||||
// Flows declare them with @CordaInjectable, then the flows have access to their services.
|
||||
@ -58,7 +58,7 @@ public class MyFirstFlow implements RPCStartableFlow {
|
||||
MyFirstFlowStartArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, MyFirstFlowStartArgs.class);
|
||||
|
||||
// Obtain the MemberX500Name of the counterparty.
|
||||
MemberX500Name otherMember = flowArgs.otherMember;
|
||||
MemberX500Name otherMember = flowArgs.getOtherMember();
|
||||
|
||||
// Get our identity from the MemberLookup service.
|
||||
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||
@ -67,7 +67,7 @@ public class MyFirstFlow implements RPCStartableFlow {
|
||||
Message message = new Message(otherMember, "Hello from " + ourIdentity + ".");
|
||||
|
||||
// Log the message to be sent.
|
||||
log.info("MFF: message.message: " + message.message);
|
||||
log.info("MFF: message.message: " + message.getMessage());
|
||||
|
||||
// Start a flow session with the otherMember using the FlowMessaging service.
|
||||
// The otherMember's virtual node will run the corresponding MyFirstFlowResponder responder flow.
|
||||
@ -82,16 +82,15 @@ public class MyFirstFlow implements RPCStartableFlow {
|
||||
// The return value of a RPCStartableFlow must always be a String. This will be passed
|
||||
// back as the REST RPC response when the status of the flow is queried on Corda, or as the return
|
||||
// value from the flow when testing using the simulator.
|
||||
return response.message;
|
||||
return response.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "r1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.workflows.MyFirstFlow",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.flowexample.workflows.MyFirstFlow",
|
||||
"requestData": {
|
||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatedBy;
|
||||
@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory;
|
||||
public class MyFirstFlowResponder implements ResponderFlow {
|
||||
|
||||
// Log messages from the flows for debugging.
|
||||
private final Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
|
||||
private final static Logger log = LoggerFactory.getLogger(MyFirstFlowResponder.class);
|
||||
|
||||
// MemberLookup looks for information about members of the virtual network which
|
||||
// this CorDapp operates in.
|
||||
@ -44,7 +44,7 @@ public class MyFirstFlowResponder implements ResponderFlow {
|
||||
Message receivedMessage = session.receive(Message.class);
|
||||
|
||||
// Log the message as a proxy for performing some useful operation on it.
|
||||
log.info("MFF: Message received from " + receivedMessage.sender + ":" + receivedMessage.message);
|
||||
log.info("MFF: Message received from " + receivedMessage.getSender() + ":" + receivedMessage.getMessage());
|
||||
|
||||
// Get our identity from the MemberLookup service.
|
||||
MemberX500Name ourIdentity = memberLookup.myInfo().getName();
|
||||
@ -54,19 +54,9 @@ public class MyFirstFlowResponder implements ResponderFlow {
|
||||
"Hello " + session.getCounterparty().getCommonName() + ", best wishes from " + ourIdentity.getCommonName());
|
||||
|
||||
// Log the response to be sent.
|
||||
log.info("MFF: response.message: " + response.message);
|
||||
log.info("MFF: response.message: " + response.getMessage());
|
||||
|
||||
// Send the response via the send method on the flow session
|
||||
session.send(response);
|
||||
}
|
||||
}
|
||||
/*
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "r1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.MyFirstFlow",
|
||||
"requestData": {
|
||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB"
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,15 +1,19 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
||||
// A class to hold the arguments required to start the flow
|
||||
public class MyFirstFlowStartArgs {
|
||||
public MemberX500Name otherMember;
|
||||
private MemberX500Name otherMember;
|
||||
|
||||
// The JSON Marshalling Service, which handles serialisation, needs this constructor.
|
||||
public MyFirstFlowStartArgs() {}
|
||||
|
||||
public MyFirstFlowStartArgs(MemberX500Name otherMember) {
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
// The JSON Marshalling Service, which handles serialisation, needs this constructor.
|
||||
public MyFirstFlowStartArgs() {}
|
||||
public MemberX500Name getOtherMember() {
|
||||
return otherMember;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatStateResults {
|
||||
|
||||
private UUID id;
|
||||
private String chatName;
|
||||
private String messageFromName;
|
||||
private String message;
|
||||
|
||||
public ChatStateResults() {}
|
||||
|
||||
public ChatStateResults(UUID id, String chatName, String messageFromName, String message) {
|
||||
this.id = id;
|
||||
this.chatName = chatName;
|
||||
this.messageFromName = messageFromName;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getChatName() {
|
||||
return chatName;
|
||||
}
|
||||
|
||||
public String getMessageFromName() {
|
||||
return messageFromName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.*;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.common.NotaryLookup;
|
||||
import net.corda.v5.ledger.common.Party;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
import net.corda.v5.membership.MemberInfo;
|
||||
import net.corda.v5.membership.NotaryInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Objects.*;
|
||||
|
||||
public class CreateNewChatFlow implements RPCStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@CordaInject
|
||||
public NotaryLookup notaryLookup;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call( RPCRequestData requestBody) {
|
||||
|
||||
log.info("CreateNewChatFlow.call() called");
|
||||
|
||||
try {
|
||||
CreateNewChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, CreateNewChatFlowArgs.class);
|
||||
|
||||
MemberInfo myInfo = memberLookup.myInfo();
|
||||
MemberInfo otherMember = requireNonNull(
|
||||
memberLookup.lookup(MemberX500Name.parse(flowArgs.getOtherMember())),
|
||||
"MemberLookup can't find otherMember specified in flow arguments."
|
||||
);
|
||||
|
||||
ChatState chatState = new ChatState(
|
||||
UUID.randomUUID(),
|
||||
flowArgs.getChatName(),
|
||||
myInfo.getName(),
|
||||
flowArgs.getMessage(),
|
||||
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
|
||||
);
|
||||
|
||||
NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
|
||||
|
||||
PublicKey notaryKey = null;
|
||||
for(MemberInfo memberInfo: memberLookup.lookup()){
|
||||
if(Objects.equals(
|
||||
memberInfo.getMemberProvidedContext().get("corda.notary.service.name"),
|
||||
notary.getName().toString())) {
|
||||
notaryKey = memberInfo.getLedgerKeys().get(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(notaryKey == null) {
|
||||
throw new CordaRuntimeException("No notary PublicKey found");
|
||||
}
|
||||
|
||||
UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
|
||||
.setNotary(new Party(notary.getName(), notaryKey))
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addOutputState(chatState)
|
||||
.addCommand(new ChatContract.Create())
|
||||
.addSignatories(chatState.getParticipants());
|
||||
|
||||
@SuppressWarnings("DEPRECATION")
|
||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
|
||||
|
||||
return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
|
||||
throw new CordaRuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "create-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.CreateNewChatFlow",
|
||||
"requestData": {
|
||||
"chatName":"Chat with Bob",
|
||||
"otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
|
||||
"message": "Hello Bob"
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,29 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
public class CreateNewChatFlowArgs{
|
||||
|
||||
// Serialisation service requires a default constructor
|
||||
public CreateNewChatFlowArgs() {}
|
||||
|
||||
private String chatName;
|
||||
private String message;
|
||||
private String otherMember;
|
||||
|
||||
public CreateNewChatFlowArgs(String chatName, String message, String otherMember) {
|
||||
this.chatName = chatName;
|
||||
this.message = message;
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
public String getChatName() {
|
||||
return chatName;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getOtherMember() {
|
||||
return otherMember;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.InitiatedBy;
|
||||
import net.corda.v5.application.flows.ResponderFlow;
|
||||
import net.corda.v5.application.messaging.FlowSession;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionValidator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@InitiatedBy(protocol = "finalize-chat-protocol")
|
||||
public class FinalizeChatResponderFlow implements ResponderFlow {
|
||||
private final static Logger log = LoggerFactory.getLogger(FinalizeChatResponderFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public void call(FlowSession session) {
|
||||
|
||||
log.info("FinalizeChatResponderFlow.call() called");
|
||||
|
||||
try {
|
||||
UtxoTransactionValidator txValidator = ledgerTransaction -> {
|
||||
ChatState state = (ChatState) ledgerTransaction.getOutputContractStates().get(0);
|
||||
if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, session.getCounterparty())) {
|
||||
throw new IllegalStateException("Failed verification");
|
||||
}
|
||||
log.info("Verified the transaction - " + ledgerTransaction.getId());
|
||||
};
|
||||
|
||||
UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService.receiveFinality(session, txValidator);
|
||||
log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
log.warn("Exceptionally finished responder flow", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suspendable
|
||||
Boolean checkForBannedWords(String str) {
|
||||
List<String> bannedWords = Arrays.asList("banana", "apple", "pear");
|
||||
return bannedWords.stream().anyMatch(str::contains);
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
Boolean checkMessageFromMatchesCounterparty(ChatState state, MemberX500Name otherMember) {
|
||||
return state.getMessageFrom().equals(otherMember);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import net.corda.v5.application.flows.*;
|
||||
import net.corda.v5.application.messaging.FlowMessaging;
|
||||
import net.corda.v5.application.messaging.FlowSession;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@InitiatingFlow(protocol = "finalize-chat-protocol")
|
||||
public class FinalizeChatSubFlow implements SubFlow<String> {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(FinalizeChatSubFlow.class);
|
||||
private final UtxoSignedTransaction signedTransaction;
|
||||
private final MemberX500Name otherMember;
|
||||
|
||||
public FinalizeChatSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name otherMember) {
|
||||
this.signedTransaction = signedTransaction;
|
||||
this.otherMember = otherMember;
|
||||
}
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowMessaging flowMessaging;
|
||||
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call() {
|
||||
|
||||
log.info("FinalizeChatFlow.call() called");
|
||||
|
||||
FlowSession session = flowMessaging.initiateFlow(otherMember);
|
||||
|
||||
String result;
|
||||
try {
|
||||
List<FlowSession> sessionsList = Arrays.asList(session);
|
||||
|
||||
UtxoSignedTransaction finalizedSignedTransaction = ledgerService.finalize(
|
||||
signedTransaction,
|
||||
sessionsList
|
||||
);
|
||||
|
||||
result = finalizedSignedTransaction.getId().toString();
|
||||
log.info("Success! Response: " + result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("Finality failed", e);
|
||||
result = "Finality failed, " + e.getMessage();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.RPCRequestData;
|
||||
import net.corda.v5.application.flows.RPCStartableFlow;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.crypto.SecureHash;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import static java.util.Objects.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
public class GetChatFlow implements RPCStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
// @NotNull
|
||||
@Override
|
||||
@Suspendable
|
||||
public String call(RPCRequestData requestBody) {
|
||||
|
||||
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
|
||||
|
||||
List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
|
||||
List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
|
||||
if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
|
||||
StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
|
||||
|
||||
return jsonMarshallingService.format(resolveMessagesFromBackchain(chatStateAndRef, flowArgs.getNumberOfRecords() ));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Suspendable
|
||||
private List<GetChatResponse> resolveMessagesFromBackchain(StateAndRef<?> stateAndRef, int numberOfRecords) {
|
||||
|
||||
List<GetChatResponse> messages = new LinkedList<>();
|
||||
|
||||
StateAndRef<?> currentStateAndRef = stateAndRef;
|
||||
int recordsToFetch = numberOfRecords;
|
||||
boolean moreBackchain = true;
|
||||
|
||||
while (moreBackchain) {
|
||||
|
||||
SecureHash transactionId = currentStateAndRef.getRef().getTransactionHash();
|
||||
|
||||
UtxoLedgerTransaction transaction = requireNonNull(
|
||||
ledgerService.findLedgerTransaction(transactionId),
|
||||
"Transaction " + transactionId + " not found."
|
||||
);
|
||||
|
||||
List<ChatState> chatStates = transaction.getOutputStates(ChatState.class);
|
||||
if (chatStates.size() != 1) throw new CordaRuntimeException(
|
||||
"Expecting one and only one ChatState output for transaction " + transactionId + ".");
|
||||
ChatState output = chatStates.get(0);
|
||||
|
||||
messages.add(new GetChatResponse(output.getMessageFrom().toString(), output.getMessage()));
|
||||
recordsToFetch--;
|
||||
|
||||
List<StateAndRef<?>> inputStateAndRefs = transaction.getInputStateAndRefs();
|
||||
|
||||
if (inputStateAndRefs.isEmpty() || recordsToFetch == 0) {
|
||||
moreBackchain = false;
|
||||
} else if (inputStateAndRefs.size() > 1) {
|
||||
throw new CordaRuntimeException("More than one input state found for transaction " + transactionId + ".");
|
||||
} else {
|
||||
currentStateAndRef = inputStateAndRefs.get(0);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "get-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.GetChatFlow",
|
||||
"requestData": {
|
||||
"id":"** fill in id **",
|
||||
"numberOfRecords":"4"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class GetChatFlowArgs {
|
||||
|
||||
private UUID id;
|
||||
private int numberOfRecords;
|
||||
public GetChatFlowArgs() {}
|
||||
|
||||
public GetChatFlowArgs(UUID id, int numberOfRecords ) {
|
||||
this.id = id;
|
||||
this.numberOfRecords = numberOfRecords;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int getNumberOfRecords() {
|
||||
return numberOfRecords;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
public class GetChatResponse {
|
||||
|
||||
private String messageFrom;
|
||||
private String message;
|
||||
public GetChatResponse() {}
|
||||
|
||||
public GetChatResponse(String messageFrom, String message) {
|
||||
this.messageFrom = messageFrom;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessageFrom() {
|
||||
return messageFrom;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.RPCRequestData;
|
||||
import net.corda.v5.application.flows.RPCStartableFlow;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ListChatsFlow implements RPCStartableFlow{
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService utxoLedgerService;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(RPCRequestData requestBody) {
|
||||
|
||||
log.info("ListChatsFlow.call() called");
|
||||
|
||||
List<StateAndRef<ChatState>> states = utxoLedgerService.findUnconsumedStatesByType(ChatState.class);
|
||||
List<ChatStateResults> results = states.stream().map( stateAndRef ->
|
||||
new ChatStateResults(
|
||||
stateAndRef.getState().getContractState().getId(),
|
||||
stateAndRef.getState().getContractState().getChatName(),
|
||||
stateAndRef.getState().getContractState().getMessageFrom().toString(),
|
||||
stateAndRef.getState().getContractState().getMessage()
|
||||
)
|
||||
).collect(Collectors.toList());
|
||||
|
||||
return jsonMarshallingService.format(results);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "list-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.ListChatsFlow",
|
||||
"requestData": {}
|
||||
}
|
||||
*/
|
@ -0,0 +1,103 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
|
||||
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
|
||||
import net.corda.v5.application.flows.CordaInject;
|
||||
import net.corda.v5.application.flows.FlowEngine;
|
||||
import net.corda.v5.application.flows.RPCRequestData;
|
||||
import net.corda.v5.application.flows.RPCStartableFlow;
|
||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
||||
import net.corda.v5.application.membership.MemberLookup;
|
||||
import net.corda.v5.base.annotations.Suspendable;
|
||||
import net.corda.v5.base.exceptions.CordaRuntimeException;
|
||||
import net.corda.v5.ledger.utxo.StateAndRef;
|
||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
|
||||
import net.corda.v5.ledger.utxo.transaction.UtxoTransactionBuilder;
|
||||
import net.corda.v5.membership.MemberInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
public class UpdateChatFlow implements RPCStartableFlow {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
|
||||
|
||||
@CordaInject
|
||||
public JsonMarshallingService jsonMarshallingService;
|
||||
|
||||
@CordaInject
|
||||
public MemberLookup memberLookup;
|
||||
|
||||
@CordaInject
|
||||
public UtxoLedgerService ledgerService;
|
||||
|
||||
@CordaInject
|
||||
public FlowEngine flowEngine;
|
||||
|
||||
@Suspendable
|
||||
@Override
|
||||
public String call(RPCRequestData requestBody) {
|
||||
|
||||
log.info("UpdateNewChatFlow.call() called");
|
||||
|
||||
try {
|
||||
UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class);
|
||||
|
||||
List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
|
||||
List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
|
||||
.filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
|
||||
if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
|
||||
StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
|
||||
|
||||
|
||||
MemberInfo myInfo = memberLookup.myInfo();
|
||||
ChatState state = chatStateAndRef.getState().getContractState();
|
||||
|
||||
List<MemberInfo> members = state.getParticipants().stream().map(
|
||||
it -> requireNonNull(memberLookup.lookup(it), "Member not found from public Key "+ it + ".")
|
||||
).collect(toList());
|
||||
|
||||
members.remove(myInfo);
|
||||
if(members.size() != 1) throw new RuntimeException("Should be only one participant other than the initiator");
|
||||
|
||||
MemberInfo otherMember = members.get(0);
|
||||
|
||||
ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage());
|
||||
|
||||
UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
|
||||
.setNotary(chatStateAndRef.getState().getNotary())
|
||||
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
|
||||
.addOutputState(newChatState)
|
||||
.addInputState(chatStateAndRef.getRef())
|
||||
.addCommand(new ChatContract.Update())
|
||||
.addSignatories(newChatState.getParticipants());
|
||||
|
||||
@SuppressWarnings("DEPRECATION")
|
||||
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
|
||||
|
||||
return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
RequestBody for triggering the flow via http-rpc:
|
||||
{
|
||||
"clientRequestId": "update-1",
|
||||
"flowClassName": "com.r3.developers.csdetemplate.utxoexample.workflows.UpdateChatFlow",
|
||||
"requestData": {
|
||||
"id":" ** fill in id **",
|
||||
"message": "How are you today?"
|
||||
}
|
||||
}
|
||||
*/
|
@ -0,0 +1,23 @@
|
||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class UpdateChatFlowArgs {
|
||||
public UpdateChatFlowArgs() {}
|
||||
|
||||
private UUID id;
|
||||
private String message;
|
||||
|
||||
public UpdateChatFlowArgs(UUID id, String message) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.simulator.RequestData;
|
||||
import net.corda.simulator.SimulatedVirtualNode;
|
Loading…
Reference in New Issue
Block a user