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 ':workflows'
|
||||||
include ':contracts'
|
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.annotations.CordaSerializable;
|
||||||
import net.corda.v5.base.types.MemberX500Name;
|
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.flows.*;
|
||||||
import net.corda.v5.application.marshalling.JsonMarshallingService;
|
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.CordaInject;
|
||||||
import net.corda.v5.application.flows.InitiatedBy;
|
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;
|
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.RequestData;
|
||||||
import net.corda.simulator.SimulatedVirtualNode;
|
import net.corda.simulator.SimulatedVirtualNode;
|
Loading…
Reference in New Issue
Block a user