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