WIP everything compiles

This commit is contained in:
Chris Barratt 2023-01-13 17:35:53 +00:00
parent 1eb51b7fcc
commit 3195235b36
21 changed files with 894 additions and 8 deletions

View File

@ -0,0 +1,63 @@
package com.r3.developers.csdetemplate.utxoexample.contracts;
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.transaction.UtxoLedgerTransaction;
import org.jetbrains.annotations.NotNull;
import java.security.PublicKey;
import java.util.Set;
import static java.util.Objects.*;
public class ChatContract implements Contract {
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(@NotNull UtxoLedgerTransaction transaction) throws IllegalArgumentException {
Command command = requireNonNull( transaction.getCommands().get(0), "Require a single command");
if(command.getClass() == Create.class) {
requireThat(transaction.getInputContractStates().isEmpty(), "When command is Create there should be no input state");
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");
}
else {
throw new IllegalArgumentException("Unsupported command");
}
/*
// This seems to be make Intellij unhappy.
switch(command.getClass()) {
case Create.class:
requireThat(transaction.getInputContractStates().isEmpty(), "When command is Create there should be no input state");
requireThat(transaction.getOutputContractStates().size() == 1, "When command is Create there should be one and only one output state");
break;
case 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");
break;
default:
throw new IllegalArgumentException("Unsupported command");
}
*/
}
private void requireThat(boolean asserted, String errorMessage) throws IllegalArgumentException {
if(!asserted) {
throw new IllegalArgumentException("Failed requirement: " + errorMessage);
}
}
}

View File

@ -0,0 +1,103 @@
package com.r3.developers.csdetemplate.utxoexample.states;
import com.r3.developers.csdetemplate.utxoexample.contracts.ChatContract;
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.*;
/*
@BelongsToContract(ChatContract::class)
data class ChatState(
val id : UUID = UUID.randomUUID(),
val chatName: String,
val messageFrom: MemberX500Name,
val message: String,
override val participants: List<PublicKey>) : ContractState {
fun updateMessage(messageFrom: MemberX500Name, message: String) = copy(messageFrom = messageFrom, message = message)
}
*/
@BelongsToContract(ChatContract.class)
public class ChatState implements ContractState {
public ChatState() {
}
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;
}
public ChatState(String chatName,
MemberX500Name messageFrom,
String message,
List<PublicKey> participants
) {
this(UUID.randomUUID(),
chatName,
messageFrom,
message,
participants);
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getChatName() {
return chatName;
}
public void setChatName(String chatName) {
this.chatName = chatName;
}
public MemberX500Name getMessageFrom() {
return messageFrom;
}
public void setMessageFrom(MemberX500Name messageFrom) {
this.messageFrom = messageFrom;
}
public String getMessage() {
return message;
}
@NotNull
@Override
public List<PublicKey> getParticipants() {
return participants;
}
public void setParticipants(List<PublicKey> participants) {
this.participants = participants;
}
private UUID id;
private String chatName;
private MemberX500Name messageFrom;
private String message;
List<PublicKey> participants;
public ChatState updateMessage(MemberX500Name name, String message) {
return new ChatState(chatName, name, message, participants);
}
}

View File

@ -22,6 +22,3 @@ rootProject.name = 'csde-cordapp-template-java'
include ':workflows' include ':workflows'
include ':contracts' include ':contracts'

View File

@ -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.annotations.CordaSerializable;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;

View File

@ -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.flows.*;
import net.corda.v5.application.marshalling.JsonMarshallingService; import net.corda.v5.application.marshalling.JsonMarshallingService;

View File

@ -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.CordaInject;
import net.corda.v5.application.flows.InitiatedBy; import net.corda.v5.application.flows.InitiatedBy;

View File

@ -1,4 +1,4 @@
package com.r3.developers.csdetemplate.workflows; package com.r3.developers.csdetemplate.flowexample.workflows;
import net.corda.v5.base.types.MemberX500Name; import net.corda.v5.base.types.MemberX500Name;

View File

@ -0,0 +1,45 @@
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.ledger.utxo.UtxoLedgerService;
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.r3.developers.csdetemplate.utxoexample.workflows.ResponderValidationHelpers.checkForBannedWords;
import static com.r3.developers.csdetemplate.utxoexample.workflows.ResponderValidationHelpers.checkMessageFromMatchesCounterparty;
@InitiatedBy(protocol = "append-chat-protocol")
class AppendChatResponderFlow implements ResponderFlow {
private final Logger log = LoggerFactory.getLogger(AppendChatResponderFlow.class);
@CordaInject
public UtxoLedgerService utxoLedgerService;
@Suspendable
@Override
public void call(@NotNull FlowSession session) {
try {
UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService.receiveFinality(session, ledgerTransaction ->
{
ChatState state = (ChatState) ledgerTransaction.getInputContractStates().get(0);
if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, session.getCounterparty())) {
throw new IllegalStateException("Failed verification");
}
log.info("Verified the transaction - " + ledgerTransaction.getId());
});
log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
}
catch(Exception e)
{
log.warn("Exceptionally finished responder flow", e);
}
}
}

View File

@ -0,0 +1,59 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
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 = "append-chat-protocol")
public class AppendChatSubFlow implements SubFlow<String> {
public AppendChatSubFlow() {}
public AppendChatSubFlow(UtxoSignedTransaction signedTransaction, MemberX500Name otherMember) {
this.signedTransaction = signedTransaction;
this.otherMember = otherMember;
}
private final Logger log = LoggerFactory.getLogger(AppendChatSubFlow.class);
@CordaInject
public UtxoLedgerService ledgerService;
@CordaInject
public FlowMessaging flowMessaging;
@Override
@Suspendable
public String call() {
log.info("AppendChatFlow.call() called");
FlowSession session = flowMessaging.initiateFlow(otherMember);
String retVal;
try {
UtxoSignedTransaction finalizedSignedTransaction = ledgerService.finalize(
signedTransaction,
List.of(session)
);
retVal = finalizedSignedTransaction.getId().toString();
log.info("Success! Response: " + retVal);
} catch (Exception e) {
log.warn("Finality failed", e);
retVal = "Finality failed, " + e.getMessage();
}
return retVal;
}
private UtxoSignedTransaction signedTransaction;
private MemberX500Name otherMember;
}

View File

@ -0,0 +1,51 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import java.util.UUID;
public class ChatStateResults {
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 void setId(UUID id) {
this.id = id;
}
public String getChatName() {
return chatName;
}
public void setChatName(String chatName) {
this.chatName = chatName;
}
public String getMessageFromName() {
return messageFromName;
}
public void setMessageFromName(String messageFromName) {
this.messageFromName = messageFromName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
private UUID id;
private String chatName;
private String messageFromName;
private String message;
}

View File

@ -0,0 +1,118 @@
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.application.messaging.FlowMessaging;
import net.corda.v5.base.annotations.Suspendable;
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.jetbrains.annotations.NotNull;
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.List;
import java.util.Objects;
import java.util.function.Predicate;
import static java.util.Objects.*;
@InitiatingFlow(protocol = "create-chat-protocol")
public class CreateNewChatFlow implements RPCStartableFlow {
private final Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public MemberLookup memberLookup;
@CordaInject
public UtxoLedgerService ledgerService;
@CordaInject
public NotaryLookup notaryLookup;
@CordaInject
public FlowMessaging flowMessaging;
@CordaInject
public FlowEngine flowEngine;
@NotNull
@Suspendable
@Override
public String call(@NotNull RPCRequestData requestBody) throws IllegalArgumentException {
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())),
"can't find other member"
);
ChatState chatState = new ChatState(
flowArgs.getChatName(),
myInfo.getName(),
flowArgs.getMessage(),
Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
);
NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
Predicate<MemberInfo> myPred = memberInfo -> Objects.equals(
memberInfo.getMemberProvidedContext().get("corda.notary.service.name"),
notary.getName().toString()
);
List<MemberInfo> lmi = memberLookup.lookup();
MemberInfo thing = lmi.stream().filter(myPred).iterator().next();
PublicKey notaryKey = thing.getLedgerKeys().get(0);
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 AppendChatSubFlow(signedTransaction, otherMember.getName()));
}
catch (Exception e) {
log.warn("Failed to process utxo flow for request body '$requestBody' because:'${e.message}'");
throw e;
}
}
}
/*
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"
}
}
*/

View File

@ -0,0 +1,41 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
public class CreateNewChatFlowArgs{
public CreateNewChatFlowArgs() {}
public CreateNewChatFlowArgs(String chatName, String message, String otherMember) {
this.chatName = chatName;
this.message = message;
this.otherMember = otherMember;
}
public String getChatName() {
return chatName;
}
public void setChatName(String chatName) {
this.chatName = chatName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getOtherMember() {
return otherMember;
}
public void setOtherMember(String otherMember) {
this.otherMember = otherMember;
}
private String chatName;
private String message;
private String otherMember;
}

View File

@ -0,0 +1,98 @@
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.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 com.r3.developers.csdetemplate.utxoexample.workflows.utilities.CorDappHelpers.findAndExpectExactlyOne;
import static java.util.Objects.*;
public class GetChatFlow implements RPCStartableFlow {
private final Logger log = LoggerFactory.getLogger(GetChatFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public UtxoLedgerService ledgerService;
@NotNull
@Override
@Suspendable
public String call(RPCRequestData requestBody) throws IllegalArgumentException {
GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
List<StateAndRef<ChatState>> stateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
StateAndRef<ChatState> state = findAndExpectExactlyOne(stateAndRefs,
stateAndRef -> stateAndRef.getState().getContractState().getId() == flowArgs.getId(),
"did not find an unique ChatState"
);
return jsonMarshallingService.format(resolveMessagesFromBackchain(state, flowArgs.getNumberOfRecords() ));
}
@NotNull
@Suspendable
private List<GetChatResponse> resolveMessagesFromBackchain(StateAndRef<?> stateAndRef, int numberOfRecords) throws IllegalArgumentException {
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"
);
ChatState output = findAndExpectExactlyOne(
transaction.getOutputStates(ChatState.class),
"Expecting one and only one ChatState output for transaction " + transactionId
);
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 IllegalArgumentException("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"
}
}
*/

View File

@ -0,0 +1,32 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import java.util.UUID;
public class GetChatFlowArgs {
public GetChatFlowArgs() {}
public GetChatFlowArgs(UUID id, int numberOfRecords ) {
this.id = id;
this.numberOfRecords = numberOfRecords;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public int getNumberOfRecords() {
return numberOfRecords;
}
public void setNumberOfRecords(int numberOfRecords) {
this.numberOfRecords = numberOfRecords;
}
private UUID id;
private int numberOfRecords;
}

View File

@ -0,0 +1,28 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
public class GetChatResponse {
public GetChatResponse() {}
public GetChatResponse(String messageFrom, String message) {
this.messageFrom = messageFrom;
this.message = message;
}
public String getMessageFrom() {
return messageFrom;
}
public void setMessageFrom(String messageFrom) {
this.messageFrom = messageFrom;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
private String messageFrom;
private String message;
}

View File

@ -0,0 +1,60 @@
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.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.application.messaging.FlowMessaging;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.ledger.common.NotaryLookup;
import net.corda.v5.ledger.utxo.StateAndRef;
import net.corda.v5.ledger.utxo.UtxoLedgerService;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
public class ListChatsFlow implements RPCStartableFlow{
private final Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public UtxoLedgerService utxoLedgerService;
@NotNull
@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": {}
}
*/

View File

@ -0,0 +1,25 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import com.r3.developers.csdetemplate.utxoexample.states.ChatState;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.base.types.MemberX500Name;
import java.util.Arrays;
import java.util.List;
public final class ResponderValidationHelpers {
public final static List<String> bannedWords = Arrays.asList("banana", "apple", "pear");
@Suspendable
public static boolean checkForBannedWords(String str) {
return bannedWords.stream().anyMatch(str::contains);
}
@Suspendable
public static boolean checkMessageFromMatchesCounterparty(ChatState state, MemberX500Name otherMember) {
return state.getMessageFrom() == otherMember;
}
// This class just introduces a scope for some helper functions and should not be instantiated.
private ResponderValidationHelpers() {}
}

View File

@ -0,0 +1,110 @@
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.application.messaging.FlowMessaging;
import net.corda.v5.base.annotations.Suspendable;
import net.corda.v5.ledger.common.NotaryLookup;
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.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.stream.Collectors;
import static com.r3.developers.csdetemplate.utxoexample.workflows.utilities.CorDappHelpers.findAndExpectExactlyOne;
import static java.util.Objects.*;
public class UpdateChatFlow implements RPCStartableFlow {
private final Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
@CordaInject
public JsonMarshallingService jsonMarshallingService;
@CordaInject
public MemberLookup memberLookup;
@CordaInject
public UtxoLedgerService ledgerService;
@CordaInject
public NotaryLookup notaryLookup;
@CordaInject
public FlowMessaging flowMessaging;
@CordaInject
public FlowEngine flowEngine;
@NotNull
@Suspendable
@Override
public String call(RPCRequestData requestBody) throws IllegalArgumentException{
log.info("UpdateNewChatFlow.call() called");
try {
UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class);
// look up state (this is very inefficient)
StateAndRef<ChatState> stateAndRef = findAndExpectExactlyOne(
ledgerService.findUnconsumedStatesByType(ChatState.class),
sAndR -> sAndR.getState().getContractState().getId() == flowArgs.getId(),
"Multiple or zero Chat states with id " + flowArgs.getId() + " found"
);
MemberInfo myInfo = memberLookup.myInfo();
ChatState state = stateAndRef.getState().getContractState();
List<MemberInfo> members = state.getParticipants().stream().map(
it -> requireNonNull(memberLookup.lookup(it), "Member not found from Key")
).collect(Collectors.toList());
// Now we want to check that there is only
/*
val otherMember = (members - myInfo).singleOrNull()
?: throw Exception("Should be only one participant other than the initiator")
*/
// NEED TO ADD CHECKS
members.remove(myInfo);
MemberInfo otherMember = members.get(0);
// This needs to be a deep copy?
ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage());
UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
.setNotary(stateAndRef.getState().getNotary())
.setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
.addOutputState(newChatState)
.addInputState(stateAndRef.getRef())
.addCommand(new ChatContract.Update())
.addSignatories(newChatState.getParticipants());
@SuppressWarnings("DEPRECATION")
UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
return flowEngine.subFlow(new AppendChatSubFlow(signedTransaction, otherMember.getName()));
} catch (Exception e) {
log.warn("Failed to process utxo flow for request body '$requestBody' because:'${e.message}'");
throw e;
}
}
}

View File

@ -0,0 +1,32 @@
package com.r3.developers.csdetemplate.utxoexample.workflows;
import java.util.UUID;
public class UpdateChatFlowArgs {
public UpdateChatFlowArgs() {}
public UpdateChatFlowArgs(UUID id, String message) {
this.id = id;
this.message = message;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
private UUID id;
private String message;
}

View File

@ -0,0 +1,24 @@
package com.r3.developers.csdetemplate.utxoexample.workflows.utilities;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class CorDappHelpers {
public static <T> T findAndExpectExactlyOne(Collection<T> collection, Predicate<? super T> filterFn, String exceptionMsg) throws IllegalArgumentException
{
Collection<T> results = collection.stream().filter(filterFn).collect(Collectors.toList());
if(results.size() != 1){
throw new IllegalArgumentException(exceptionMsg);
//throw new Exception(exceptionMsg);
}
return results.iterator().next();
}
public static <T> T findAndExpectExactlyOne(Collection<T> collection, String exceptionMsg) throws IllegalArgumentException {
return findAndExpectExactlyOne(collection, e -> true, exceptionMsg);
}
}

View File

@ -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.RequestData;
import net.corda.simulator.SimulatedVirtualNode; import net.corda.simulator.SimulatedVirtualNode;