WIP everything compiles
This commit is contained in:
parent
1eb51b7fcc
commit
3195235b36
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -1,4 +1,4 @@
|
||||
package com.r3.developers.csdetemplate.workflows;
|
||||
package com.r3.developers.csdetemplate.flowexample.workflows;
|
||||
|
||||
import net.corda.v5.base.types.MemberX500Name;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
*/
|
@ -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;
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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": {}
|
||||
}
|
||||
*/
|
@ -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() {}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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