Added commentary
This commit is contained in:
		
							parent
							
								
									6d21703462
								
							
						
					
					
						commit
						7fb3791c80
					
				@ -2,6 +2,11 @@ package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Class to hold the ListChatFlow results.
 | 
				
			||||||
 | 
					// The ChatState(s) cannot be returned directly as the JsonMarshallingService can only serialize simple classes
 | 
				
			||||||
 | 
					// that the underlying Jackson serializer recognises, hence creating a DTO style object which consists only of Strings
 | 
				
			||||||
 | 
					// and a UUID. It is possible to create custom serializers for the JsonMarshallingService, but this beyond the scope
 | 
				
			||||||
 | 
					// of this simple example.
 | 
				
			||||||
public class ChatStateResults {
 | 
					public class ChatStateResults {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private UUID id;
 | 
					    private UUID id;
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ import java.util.UUID;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import static java.util.Objects.*;
 | 
					import static java.util.Objects.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// See Chat CorDapp Design section of the getting started docs for a description of this flow.
 | 
				
			||||||
public class CreateNewChatFlow implements RPCStartableFlow {
 | 
					public class CreateNewChatFlow implements RPCStartableFlow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
 | 
					    private final static Logger log = LoggerFactory.getLogger(CreateNewChatFlow.class);
 | 
				
			||||||
@ -37,12 +38,14 @@ public class CreateNewChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public MemberLookup memberLookup;
 | 
					    public MemberLookup memberLookup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public UtxoLedgerService ledgerService;
 | 
					    public UtxoLedgerService ledgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public NotaryLookup notaryLookup;
 | 
					    public NotaryLookup notaryLookup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // FlowEngine service is required to run SubFlows.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public FlowEngine flowEngine;
 | 
					    public FlowEngine flowEngine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,14 +57,17 @@ public class CreateNewChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
        log.info("CreateNewChatFlow.call() called");
 | 
					        log.info("CreateNewChatFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            // Obtain the deserialized input arguments to the flow from the requestBody.
 | 
				
			||||||
            CreateNewChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, CreateNewChatFlowArgs.class);
 | 
					            CreateNewChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, CreateNewChatFlowArgs.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get MemberInfos for the Vnode running the flow and the otherMember.
 | 
				
			||||||
            MemberInfo myInfo = memberLookup.myInfo();
 | 
					            MemberInfo myInfo = memberLookup.myInfo();
 | 
				
			||||||
            MemberInfo otherMember = requireNonNull(
 | 
					            MemberInfo otherMember = requireNonNull(
 | 
				
			||||||
                    memberLookup.lookup(MemberX500Name.parse(flowArgs.getOtherMember())),
 | 
					                    memberLookup.lookup(MemberX500Name.parse(flowArgs.getOtherMember())),
 | 
				
			||||||
                    "MemberLookup can't find otherMember specified in flow arguments."
 | 
					                    "MemberLookup can't find otherMember specified in flow arguments."
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Create the ChatState from the input arguments and member information.
 | 
				
			||||||
            ChatState chatState = new ChatState(
 | 
					            ChatState chatState = new ChatState(
 | 
				
			||||||
                    UUID.randomUUID(),
 | 
					                    UUID.randomUUID(),
 | 
				
			||||||
                    flowArgs.getChatName(),
 | 
					                    flowArgs.getChatName(),
 | 
				
			||||||
@ -70,8 +76,8 @@ public class CreateNewChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
                    Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
 | 
					                    Arrays.asList(myInfo.getLedgerKeys().get(0), otherMember.getLedgerKeys().get(0))
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Obtain the Notary name and public key.
 | 
				
			||||||
            NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
 | 
					            NotaryInfo notary = notaryLookup.getNotaryServices().iterator().next();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            PublicKey notaryKey = null;
 | 
					            PublicKey notaryKey = null;
 | 
				
			||||||
            for(MemberInfo memberInfo: memberLookup.lookup()){
 | 
					            for(MemberInfo memberInfo: memberLookup.lookup()){
 | 
				
			||||||
                if(Objects.equals(
 | 
					                if(Objects.equals(
 | 
				
			||||||
@ -81,11 +87,13 @@ public class CreateNewChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            // Note, in Java CorDapps only unchecked RuntimeExceptions can be thrown not
 | 
				
			||||||
 | 
					            // declared checked exceptions as this changes the method signature and breaks override.
 | 
				
			||||||
            if(notaryKey == null) {
 | 
					            if(notaryKey == null) {
 | 
				
			||||||
                throw new CordaRuntimeException("No notary PublicKey found");
 | 
					                throw new CordaRuntimeException("No notary PublicKey found");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Use UTXOTransactionBuilder to build up the draft transaction.
 | 
				
			||||||
            UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
 | 
					            UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
 | 
				
			||||||
                    .setNotary(new Party(notary.getName(), notaryKey))
 | 
					                    .setNotary(new Party(notary.getName(), notaryKey))
 | 
				
			||||||
                    .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
					                    .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
				
			||||||
@ -93,11 +101,17 @@ public class CreateNewChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
                    .addCommand(new ChatContract.Create())
 | 
					                    .addCommand(new ChatContract.Create())
 | 
				
			||||||
                    .addSignatories(chatState.getParticipants());
 | 
					                    .addSignatories(chatState.getParticipants());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Convert the transaction builder to a UTXOSignedTransaction and sign with this Vnode's first Ledger key.
 | 
				
			||||||
 | 
					            // Note, toSignedTransaction() is currently a placeholder method, hence being marked as deprecated.
 | 
				
			||||||
            @SuppressWarnings("DEPRECATION")
 | 
					            @SuppressWarnings("DEPRECATION")
 | 
				
			||||||
            UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
 | 
					            UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Call FinalizeChatSubFlow which will finalise the transaction.
 | 
				
			||||||
 | 
					            // If successful the flow will return a String of the created transaction id,
 | 
				
			||||||
 | 
					            // if not successful it will return an error message.
 | 
				
			||||||
            return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
 | 
					            return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // Catch any exceptions, log them and rethrow the exception.
 | 
				
			||||||
        catch (Exception e) {
 | 
					        catch (Exception e) {
 | 
				
			||||||
            log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
 | 
					            log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
 | 
				
			||||||
            throw new CordaRuntimeException(e.getMessage());
 | 
					            throw new CordaRuntimeException(e.getMessage());
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
					package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A class to hold the deserialized arguments required to start the flow.
 | 
				
			||||||
public class CreateNewChatFlowArgs{
 | 
					public class CreateNewChatFlowArgs{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Serialisation service requires a default constructor
 | 
					    // Serialisation service requires a default constructor
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import net.corda.v5.application.flows.InitiatedBy;
 | 
				
			|||||||
import net.corda.v5.application.flows.ResponderFlow;
 | 
					import net.corda.v5.application.flows.ResponderFlow;
 | 
				
			||||||
import net.corda.v5.application.messaging.FlowSession;
 | 
					import net.corda.v5.application.messaging.FlowSession;
 | 
				
			||||||
import net.corda.v5.base.annotations.Suspendable;
 | 
					import net.corda.v5.base.annotations.Suspendable;
 | 
				
			||||||
 | 
					import net.corda.v5.base.exceptions.CordaRuntimeException;
 | 
				
			||||||
import net.corda.v5.base.types.MemberX500Name;
 | 
					import net.corda.v5.base.types.MemberX500Name;
 | 
				
			||||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
 | 
					import net.corda.v5.ledger.utxo.UtxoLedgerService;
 | 
				
			||||||
import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
 | 
					import net.corda.v5.ledger.utxo.transaction.UtxoSignedTransaction;
 | 
				
			||||||
@ -16,10 +17,15 @@ import org.slf4j.LoggerFactory;
 | 
				
			|||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// See Chat CorDapp Design section of the getting started docs for a description of this flow.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//@InitiatingBy declares the protocol which will be used to link the initiator to the responder.
 | 
				
			||||||
@InitiatedBy(protocol = "finalize-chat-protocol")
 | 
					@InitiatedBy(protocol = "finalize-chat-protocol")
 | 
				
			||||||
public class FinalizeChatResponderFlow implements ResponderFlow {
 | 
					public class FinalizeChatResponderFlow implements ResponderFlow {
 | 
				
			||||||
    private final static Logger log = LoggerFactory.getLogger(FinalizeChatResponderFlow.class);
 | 
					    private final static Logger log = LoggerFactory.getLogger(FinalizeChatResponderFlow.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public UtxoLedgerService utxoLedgerService;
 | 
					    public UtxoLedgerService utxoLedgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,17 +36,24 @@ public class FinalizeChatResponderFlow implements ResponderFlow {
 | 
				
			|||||||
        log.info("FinalizeChatResponderFlow.call() called");
 | 
					        log.info("FinalizeChatResponderFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            // Defines the lambda validator used in receiveFinality below.
 | 
				
			||||||
            UtxoTransactionValidator txValidator = ledgerTransaction -> {
 | 
					            UtxoTransactionValidator txValidator = ledgerTransaction -> {
 | 
				
			||||||
                ChatState state = (ChatState) ledgerTransaction.getOutputContractStates().get(0);
 | 
					                ChatState state = (ChatState) ledgerTransaction.getOutputContractStates().get(0);
 | 
				
			||||||
 | 
					                // Uses checkForBannedWords() and checkMessageFromMatchesCounterparty() functions
 | 
				
			||||||
 | 
					                // to check whether to sign the transaction.
 | 
				
			||||||
                if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, session.getCounterparty())) {
 | 
					                if (checkForBannedWords(state.getMessage()) || !checkMessageFromMatchesCounterparty(state, session.getCounterparty())) {
 | 
				
			||||||
                    throw new IllegalStateException("Failed verification");
 | 
					                    throw new CordaRuntimeException("Failed verification");
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                log.info("Verified the transaction - " + ledgerTransaction.getId());
 | 
					                log.info("Verified the transaction - " + ledgerTransaction.getId());
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Calls receiveFinality() function which provides the responder to the finalise() function
 | 
				
			||||||
 | 
					            // in the Initiating Flow. Accepts a lambda validator containing the business logic to decide whether
 | 
				
			||||||
 | 
					            // responder should sign the Transaction.
 | 
				
			||||||
            UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService.receiveFinality(session, txValidator);
 | 
					            UtxoSignedTransaction finalizedSignedTransaction = utxoLedgerService.receiveFinality(session, txValidator);
 | 
				
			||||||
            log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
 | 
					            log.info("Finished responder flow - " + finalizedSignedTransaction.getId());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // Soft fails the flow and log the exception.
 | 
				
			||||||
        catch(Exception e)
 | 
					        catch(Exception e)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            log.warn("Exceptionally finished responder flow", e);
 | 
					            log.warn("Exceptionally finished responder flow", e);
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,9 @@ import org.slf4j.LoggerFactory;
 | 
				
			|||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// See Chat CorDapp Design section of the getting started docs for a description of this flow.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @InitiatingFlow declares the protocol which will be used to link the initiator to the responder.
 | 
				
			||||||
@InitiatingFlow(protocol = "finalize-chat-protocol")
 | 
					@InitiatingFlow(protocol = "finalize-chat-protocol")
 | 
				
			||||||
public class FinalizeChatSubFlow implements SubFlow<String> {
 | 
					public class FinalizeChatSubFlow implements SubFlow<String> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,6 +28,7 @@ public class FinalizeChatSubFlow implements SubFlow<String> {
 | 
				
			|||||||
        this.otherMember = otherMember;
 | 
					        this.otherMember = otherMember;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public UtxoLedgerService ledgerService;
 | 
					    public UtxoLedgerService ledgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -37,8 +41,12 @@ public class FinalizeChatSubFlow implements SubFlow<String> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        log.info("FinalizeChatFlow.call() called");
 | 
					        log.info("FinalizeChatFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Initiates a session with the other Member.
 | 
				
			||||||
        FlowSession session = flowMessaging.initiateFlow(otherMember);
 | 
					        FlowSession session = flowMessaging.initiateFlow(otherMember);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Calls the Corda provided finalise() function which gather signatures from the counterparty,
 | 
				
			||||||
 | 
					        // notarises the transaction and persists the transaction to each party's vault.
 | 
				
			||||||
 | 
					        // On success returns the id of the transaction created. (This is different to the ChatState id)
 | 
				
			||||||
        String result;
 | 
					        String result;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            List<FlowSession> sessionsList = Arrays.asList(session);
 | 
					            List<FlowSession> sessionsList = Arrays.asList(session);
 | 
				
			||||||
@ -51,11 +59,13 @@ public class FinalizeChatSubFlow implements SubFlow<String> {
 | 
				
			|||||||
            result = finalizedSignedTransaction.getId().toString();
 | 
					            result = finalizedSignedTransaction.getId().toString();
 | 
				
			||||||
            log.info("Success! Response: " + result);
 | 
					            log.info("Success! Response: " + result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        }
 | 
				
			||||||
 | 
					        // Soft fails the flow and returns the error message without throwing a flow exception.
 | 
				
			||||||
 | 
					        catch (Exception e) {
 | 
				
			||||||
            log.warn("Finality failed", e);
 | 
					            log.warn("Finality failed", e);
 | 
				
			||||||
            result = "Finality failed, " + e.getMessage();
 | 
					            result = "Finality failed, " + e.getMessage();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // Returns the transaction id converted as a string
 | 
				
			||||||
        return result;
 | 
					        return result;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,6 @@ import net.corda.v5.crypto.SecureHash;
 | 
				
			|||||||
import net.corda.v5.ledger.utxo.StateAndRef;
 | 
					import net.corda.v5.ledger.utxo.StateAndRef;
 | 
				
			||||||
import net.corda.v5.ledger.utxo.UtxoLedgerService;
 | 
					import net.corda.v5.ledger.utxo.UtxoLedgerService;
 | 
				
			||||||
import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
 | 
					import net.corda.v5.ledger.utxo.transaction.UtxoLedgerTransaction;
 | 
				
			||||||
import org.jetbrains.annotations.NotNull;
 | 
					 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,6 +18,7 @@ import java.util.*;
 | 
				
			|||||||
import static java.util.Objects.*;
 | 
					import static java.util.Objects.*;
 | 
				
			||||||
import static java.util.stream.Collectors.toList;
 | 
					import static java.util.stream.Collectors.toList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// See Chat CorDapp Design section of the getting started docs for a description of this flow.
 | 
				
			||||||
public class GetChatFlow implements RPCStartableFlow {
 | 
					public class GetChatFlow implements RPCStartableFlow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
 | 
					    private final static Logger log = LoggerFactory.getLogger(GetChatFlow.class);
 | 
				
			||||||
@ -26,6 +26,7 @@ public class GetChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public JsonMarshallingService jsonMarshallingService;
 | 
					    public JsonMarshallingService jsonMarshallingService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public UtxoLedgerService ledgerService;
 | 
					    public UtxoLedgerService ledgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,46 +35,65 @@ public class GetChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
    @Suspendable
 | 
					    @Suspendable
 | 
				
			||||||
    public String call(RPCRequestData requestBody)  {
 | 
					    public String call(RPCRequestData requestBody)  {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Obtain the deserialized input arguments to the flow from the requestBody.
 | 
				
			||||||
        GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
 | 
					        GetChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, GetChatFlowArgs.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Look up the latest unconsumed ChatState with the given id.
 | 
				
			||||||
 | 
					        // Note, this code brings all unconsumed states back, then filters them.
 | 
				
			||||||
 | 
					        // This is an inefficient way to perform this operation when there are a large number of chats.
 | 
				
			||||||
 | 
					        // Note, you will get this error if you input an id which has no corresponding ChatState (common error).
 | 
				
			||||||
        List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
 | 
					        List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
 | 
				
			||||||
        List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
 | 
					        List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
 | 
				
			||||||
                .filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
 | 
					                .filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
 | 
				
			||||||
        if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
 | 
					        if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
 | 
				
			||||||
        StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
 | 
					        StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Calls resolveMessagesFromBackchain() which retrieves the chat history from the backchain.
 | 
				
			||||||
        return jsonMarshallingService.format(resolveMessagesFromBackchain(chatStateAndRef, flowArgs.getNumberOfRecords() ));
 | 
					        return jsonMarshallingService.format(resolveMessagesFromBackchain(chatStateAndRef, flowArgs.getNumberOfRecords() ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @NotNull
 | 
					    // resoveMessageFromBackchain() starts at the stateAndRef provided, which represents the unconsumed head of the
 | 
				
			||||||
 | 
					    // backchain for this particular chat, then walks the chain backwards for the number of transaction specified in
 | 
				
			||||||
 | 
					    // the numberOfRecords argument. For each transaction it adds the MessageAndSender representing the
 | 
				
			||||||
 | 
					    // message and who sent it to a list which is then returned.
 | 
				
			||||||
    @Suspendable
 | 
					    @Suspendable
 | 
				
			||||||
    private List<GetChatResponse> resolveMessagesFromBackchain(StateAndRef<?> stateAndRef, int numberOfRecords) {
 | 
					    private List<MessageAndSender> resolveMessagesFromBackchain(StateAndRef<?> stateAndRef, int numberOfRecords) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<GetChatResponse> messages = new LinkedList<>();
 | 
					        // Set up a Mutable List to collect the MessageAndSender(s)
 | 
				
			||||||
 | 
					        List<MessageAndSender> messages = new LinkedList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Set up initial conditions for walking the backchain.
 | 
				
			||||||
        StateAndRef<?> currentStateAndRef = stateAndRef;
 | 
					        StateAndRef<?> currentStateAndRef = stateAndRef;
 | 
				
			||||||
        int recordsToFetch = numberOfRecords;
 | 
					        int recordsToFetch = numberOfRecords;
 | 
				
			||||||
        boolean moreBackchain = true;
 | 
					        boolean moreBackchain = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Continue to loop until the start of the backchain or enough records have been retrieved.
 | 
				
			||||||
        while (moreBackchain) {
 | 
					        while (moreBackchain) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Obtain the transaction id from the current StateAndRef and fetch the transaction from the vault.
 | 
				
			||||||
            SecureHash transactionId = currentStateAndRef.getRef().getTransactionHash();
 | 
					            SecureHash transactionId = currentStateAndRef.getRef().getTransactionHash();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            UtxoLedgerTransaction transaction = requireNonNull(
 | 
					            UtxoLedgerTransaction transaction = requireNonNull(
 | 
				
			||||||
                 ledgerService.findLedgerTransaction(transactionId),
 | 
					                 ledgerService.findLedgerTransaction(transactionId),
 | 
				
			||||||
                 "Transaction " +  transactionId + " not found."
 | 
					                 "Transaction " +  transactionId + " not found."
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get the output state from the transaction and use it to create a MessageAndSender Object which
 | 
				
			||||||
 | 
					            // is appended to the mutable list.
 | 
				
			||||||
            List<ChatState> chatStates = transaction.getOutputStates(ChatState.class);
 | 
					            List<ChatState> chatStates = transaction.getOutputStates(ChatState.class);
 | 
				
			||||||
            if (chatStates.size() != 1) throw new CordaRuntimeException(
 | 
					            if (chatStates.size() != 1) throw new CordaRuntimeException(
 | 
				
			||||||
                    "Expecting one and only one ChatState output for transaction " + transactionId + ".");
 | 
					                    "Expecting one and only one ChatState output for transaction " + transactionId + ".");
 | 
				
			||||||
            ChatState output = chatStates.get(0);
 | 
					            ChatState output = chatStates.get(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            messages.add(new GetChatResponse(output.getMessageFrom().toString(), output.getMessage()));
 | 
					            messages.add(new MessageAndSender(output.getMessageFrom().toString(), output.getMessage()));
 | 
				
			||||||
 | 
					            // Decrement the number of records to fetch.
 | 
				
			||||||
            recordsToFetch--;
 | 
					            recordsToFetch--;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get the reference to the input states.
 | 
				
			||||||
            List<StateAndRef<?>> inputStateAndRefs = transaction.getInputStateAndRefs();
 | 
					            List<StateAndRef<?>> inputStateAndRefs = transaction.getInputStateAndRefs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if there are no more input states (start of chain) or we have retrieved enough records.
 | 
				
			||||||
 | 
					            // Check the transaction is not malformed by having too many input states.
 | 
				
			||||||
 | 
					            // Set the currentStateAndRef to the input StateAndRef, then repeat the loop.
 | 
				
			||||||
	        if (inputStateAndRefs.isEmpty() || recordsToFetch == 0) {
 | 
						        if (inputStateAndRefs.isEmpty() || recordsToFetch == 0) {
 | 
				
			||||||
	            moreBackchain = false;
 | 
						            moreBackchain = false;
 | 
				
			||||||
	        } else if (inputStateAndRefs.size() > 1) {
 | 
						        } else if (inputStateAndRefs.size() > 1) {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A class to hold the deserialized arguments required to start the flow.
 | 
				
			||||||
public class GetChatFlowArgs {
 | 
					public class GetChatFlowArgs {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private UUID id;
 | 
					    private UUID id;
 | 
				
			||||||
 | 
				
			|||||||
@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory;
 | 
				
			|||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// See Chat CorDapp Design section of the getting started docs for a description of this flow.
 | 
				
			||||||
public class ListChatsFlow implements RPCStartableFlow{
 | 
					public class ListChatsFlow implements RPCStartableFlow{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
 | 
					    private final static Logger log = LoggerFactory.getLogger(ListChatsFlow.class);
 | 
				
			||||||
@ -21,6 +22,7 @@ public class ListChatsFlow implements RPCStartableFlow{
 | 
				
			|||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public JsonMarshallingService jsonMarshallingService;
 | 
					    public JsonMarshallingService jsonMarshallingService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public UtxoLedgerService utxoLedgerService;
 | 
					    public UtxoLedgerService utxoLedgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,6 +32,7 @@ public class ListChatsFlow implements RPCStartableFlow{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        log.info("ListChatsFlow.call() called");
 | 
					        log.info("ListChatsFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Queries the VNode's vault for unconsumed states and converts the result to a serializable DTO.
 | 
				
			||||||
        List<StateAndRef<ChatState>> states = utxoLedgerService.findUnconsumedStatesByType(ChatState.class);
 | 
					        List<StateAndRef<ChatState>> states = utxoLedgerService.findUnconsumedStatesByType(ChatState.class);
 | 
				
			||||||
        List<ChatStateResults> results = states.stream().map( stateAndRef ->
 | 
					        List<ChatStateResults> results = states.stream().map( stateAndRef ->
 | 
				
			||||||
            new ChatStateResults(
 | 
					            new ChatStateResults(
 | 
				
			||||||
@ -40,6 +43,7 @@ public class ListChatsFlow implements RPCStartableFlow{
 | 
				
			|||||||
                    )
 | 
					                    )
 | 
				
			||||||
        ).collect(Collectors.toList());
 | 
					        ).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Uses the JsonMarshallingService's format() function to serialize the DTO to Json.
 | 
				
			||||||
        return jsonMarshallingService.format(results);
 | 
					        return jsonMarshallingService.format(results);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,13 @@
 | 
				
			|||||||
package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
					package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class GetChatResponse {
 | 
					// A class to pair the messageFrom and message together.
 | 
				
			||||||
 | 
					public class MessageAndSender {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String messageFrom;
 | 
					    private String messageFrom;
 | 
				
			||||||
    private String message;
 | 
					    private String message;
 | 
				
			||||||
    public GetChatResponse() {}
 | 
					    public MessageAndSender() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public GetChatResponse(String messageFrom, String message) {
 | 
					    public MessageAndSender(String messageFrom, String message) {
 | 
				
			||||||
        this.messageFrom = messageFrom;
 | 
					        this.messageFrom = messageFrom;
 | 
				
			||||||
        this.message = message;
 | 
					        this.message = message;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -25,6 +25,7 @@ import java.util.List;
 | 
				
			|||||||
import static java.util.Objects.*;
 | 
					import static java.util.Objects.*;
 | 
				
			||||||
import static java.util.stream.Collectors.toList;
 | 
					import static java.util.stream.Collectors.toList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// See Chat CorDapp Design section of the getting started docs for a description of this flow.
 | 
				
			||||||
public class UpdateChatFlow implements RPCStartableFlow {
 | 
					public class UpdateChatFlow implements RPCStartableFlow {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
 | 
					    private final static Logger log = LoggerFactory.getLogger(UpdateChatFlow.class);
 | 
				
			||||||
@ -35,9 +36,11 @@ public class UpdateChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public MemberLookup memberLookup;
 | 
					    public MemberLookup memberLookup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Injects the UtxoLedgerService to enable the flow to make use of the Ledger API.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public UtxoLedgerService ledgerService;
 | 
					    public UtxoLedgerService ledgerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // FlowEngine service is required to run SubFlows.
 | 
				
			||||||
    @CordaInject
 | 
					    @CordaInject
 | 
				
			||||||
    public FlowEngine flowEngine;
 | 
					    public FlowEngine flowEngine;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,29 +51,34 @@ public class UpdateChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
        log.info("UpdateNewChatFlow.call() called");
 | 
					        log.info("UpdateNewChatFlow.call() called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
					            // Obtain the deserialized input arguments to the flow from the requestBody.
 | 
				
			||||||
             UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class);
 | 
					             UpdateChatFlowArgs flowArgs = requestBody.getRequestBodyAs(jsonMarshallingService, UpdateChatFlowArgs.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Look up the latest unconsumed ChatState with the given id.
 | 
				
			||||||
 | 
					            // Note, this code brings all unconsumed states back, then filters them.
 | 
				
			||||||
 | 
					            // This is an inefficient way to perform this operation when there are a large number of chats.
 | 
				
			||||||
 | 
					            // Note, you will get this error if you input an id which has no corresponding ChatState (common error).
 | 
				
			||||||
            List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
 | 
					            List<StateAndRef<ChatState>> chatStateAndRefs = ledgerService.findUnconsumedStatesByType(ChatState.class);
 | 
				
			||||||
            List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
 | 
					            List<StateAndRef<ChatState>> chatStateAndRefsWithId = chatStateAndRefs.stream()
 | 
				
			||||||
                    .filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
 | 
					                    .filter(sar -> sar.getState().getContractState().getId().equals(flowArgs.getId())).collect(toList());
 | 
				
			||||||
            if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
 | 
					            if (chatStateAndRefsWithId.size() != 1) throw new CordaRuntimeException("Multiple or zero Chat states with id " + flowArgs.getId() + " found");
 | 
				
			||||||
            StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
 | 
					            StateAndRef<ChatState> chatStateAndRef = chatStateAndRefsWithId.get(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get MemberInfos for the Vnode running the flow and the otherMember.
 | 
				
			||||||
            MemberInfo myInfo = memberLookup.myInfo();
 | 
					            MemberInfo myInfo = memberLookup.myInfo();
 | 
				
			||||||
            ChatState state = chatStateAndRef.getState().getContractState();
 | 
					            ChatState state = chatStateAndRef.getState().getContractState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            List<MemberInfo> members = state.getParticipants().stream().map(
 | 
					            List<MemberInfo> members = state.getParticipants().stream().map(
 | 
				
			||||||
                    it -> requireNonNull(memberLookup.lookup(it), "Member not found from public Key "+ it + ".")
 | 
					                    it -> requireNonNull(memberLookup.lookup(it), "Member not found from public Key "+ it + ".")
 | 
				
			||||||
            ).collect(toList());
 | 
					            ).collect(toList());
 | 
				
			||||||
 | 
					 | 
				
			||||||
            members.remove(myInfo);
 | 
					            members.remove(myInfo);
 | 
				
			||||||
            if(members.size() != 1) throw new RuntimeException("Should be only one participant other than the initiator");
 | 
					            if(members.size() != 1) throw new RuntimeException("Should be only one participant other than the initiator");
 | 
				
			||||||
 | 
					 | 
				
			||||||
            MemberInfo otherMember = members.get(0);
 | 
					            MemberInfo otherMember = members.get(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Create a new ChatState using the updateMessage helper function.
 | 
				
			||||||
            ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage());
 | 
					            ChatState newChatState = state.updateMessage(myInfo.getName(), flowArgs.getMessage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Use UTXOTransactionBuilder to build up the draft transaction.
 | 
				
			||||||
            UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
 | 
					            UtxoTransactionBuilder txBuilder = ledgerService.getTransactionBuilder()
 | 
				
			||||||
                    .setNotary(chatStateAndRef.getState().getNotary())
 | 
					                    .setNotary(chatStateAndRef.getState().getNotary())
 | 
				
			||||||
                    .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
					                    .setTimeWindowBetween(Instant.now(), Instant.now().plusMillis(Duration.ofDays(1).toMillis()))
 | 
				
			||||||
@ -79,11 +87,20 @@ public class UpdateChatFlow implements RPCStartableFlow {
 | 
				
			|||||||
                    .addCommand(new ChatContract.Update())
 | 
					                    .addCommand(new ChatContract.Update())
 | 
				
			||||||
                    .addSignatories(newChatState.getParticipants());
 | 
					                    .addSignatories(newChatState.getParticipants());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Convert the transaction builder to a UtxoSignedTransaction and sign with this Vnode's first Ledger key.
 | 
				
			||||||
 | 
					            // Note, toSignedTransaction() is currently a placeholder method, hence being marked as deprecated.
 | 
				
			||||||
            @SuppressWarnings("DEPRECATION")
 | 
					            @SuppressWarnings("DEPRECATION")
 | 
				
			||||||
            UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
 | 
					            UtxoSignedTransaction signedTransaction = txBuilder.toSignedTransaction(myInfo.getLedgerKeys().get(0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Call FinalizeChatSubFlow which will finalise the transaction.
 | 
				
			||||||
 | 
					            // If successful the flow will return a String of the created transaction id,
 | 
				
			||||||
 | 
					            // if not successful it will return an error message.
 | 
				
			||||||
            return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
 | 
					            return flowEngine.subFlow(new FinalizeChatSubFlow(signedTransaction, otherMember.getName()));
 | 
				
			||||||
        } catch (Exception e) {
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Catch any exceptions, log them and rethrow the exception.
 | 
				
			||||||
 | 
					        catch (Exception e) {
 | 
				
			||||||
            log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
 | 
					            log.warn("Failed to process utxo flow for request body " + requestBody + " because: " + e.getMessage());
 | 
				
			||||||
            throw e;
 | 
					            throw e;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@ package com.r3.developers.csdetemplate.utxoexample.workflows;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A class to hold the deserialized arguments required to start the flow.
 | 
				
			||||||
public class UpdateChatFlowArgs {
 | 
					public class UpdateChatFlowArgs {
 | 
				
			||||||
    public UpdateChatFlowArgs() {}
 | 
					    public UpdateChatFlowArgs() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user