From de64fa4a52189b32b29032461c34babdd54c03b3 Mon Sep 17 00:00:00 2001
From: parisyup <66366646+parisyup@users.noreply.github.com>
Date: Thu, 2 May 2024 15:45:45 +0400
Subject: [PATCH] added customStateJSONandQuery for Java
---
.../customeStateJSONandQuery/.ci/Jenkinsfile | 11 +
.../.ci/nightly/JenkinsfileSnykScan | 6 +
.../customeStateJSONandQuery/.gitignore | 91 +++++
.../runConfigurations/DebugCorDapp.run.xml | 15 +
java-samples/customeStateJSONandQuery/.snyk | 14 +
.../FlowManagementUI/Dockerfile | 5 +
.../FlowManagementUI/README.md | 84 +++++
.../FlowManagementUI/app.py | 12 +
.../FlowManagementUI/requirements.txt | 1 +
.../FlowManagementUI/static/Scripts/script.js | 322 ++++++++++++++++++
.../FlowManagementUI/static/css/main.css | 202 +++++++++++
.../FlowManagementUI/templates/index.html | 87 +++++
.../customeStateJSONandQuery/README.md | 199 +++++++++++
.../customeStateJSONandQuery/build.gradle | 74 ++++
.../config/combined-worker-compose.yaml | 87 +++++
.../config/gradle-plugin-default-key.pem | 13 +
.../config/log4j2.xml | 52 +++
.../config/r3-ca-key.pem | 32 ++
.../config/static-network-config.json | 23 ++
.../contracts/build.gradle | 87 +++++
.../apples/contracts/AppleCommands.java | 9 +
.../apples/contracts/AppleStampContract.java | 38 +++
.../contracts/BasketOfApplesContract.java | 66 ++++
.../developers/apples/states/AppleStamp.java | 47 +++
.../apples/states/BasketOfApples.java | 53 +++
.../contracts/ChatContract.java | 72 ++++
.../states/ChatJsonFactory.java | 37 ++
.../states/ChatState.java | 55 +++
.../states/CustomChatQuery.java | 25 ++
.../gradle.properties | 73 ++++
.../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63375 bytes
.../gradle/wrapper/gradle-wrapper.properties | 7 +
java-samples/customeStateJSONandQuery/gradlew | 248 ++++++++++++++
.../customeStateJSONandQuery/gradlew.bat | 92 +++++
.../customeStateJSONandQuery/settings.gradle | 26 ++
.../workflows/build.gradle | 96 ++++++
.../CreateAndIssueAppleStampFlow.java | 102 ++++++
.../CreateAndIssueAppleStampRequest.java | 32 ++
...CreateAndIssueAppleStampResponderFlow.java | 33 ++
.../apples/workflows/PackageApplesFlow.java | 80 +++++
.../workflows/PackageApplesRequest.java | 32 ++
.../apples/workflows/RedeemApplesFlow.java | 130 +++++++
.../apples/workflows/RedeemApplesRequest.java | 34 ++
.../workflows/RedeemApplesResponderFlow.java | 32 ++
.../workflows/ChatStateResults.java | 41 +++
.../workflows/CreateNewChatFlow.java | 118 +++++++
.../workflows/CreateNewChatFlowArgs.java | 30 ++
.../workflows/FinalizeChatResponderFlow.java | 75 ++++
.../workflows/FinalizeChatSubFlow.java | 71 ++++
.../workflows/GetChatFlow.java | 120 +++++++
.../workflows/GetChatFlowArgs.java | 24 ++
.../workflows/ListChatsByCustomQueryFlow.java | 68 ++++
.../workflows/ListChatsFlow.java | 59 ++++
.../workflows/MessageAndSender.java | 22 ++
.../workflows/UpdateChatFlow.java | 120 +++++++
.../workflows/UpdateChatFlowArgs.java | 24 ++
.../flowexample/workflows/Message.java | 28 ++
.../flowexample/workflows/MyFirstFlow.java | 96 ++++++
.../workflows/MyFirstFlowResponder.java | 62 ++++
.../workflows/MyFirstFlowStartArgs.java | 19 ++
60 files changed, 3813 insertions(+)
create mode 100644 java-samples/customeStateJSONandQuery/.ci/Jenkinsfile
create mode 100644 java-samples/customeStateJSONandQuery/.ci/nightly/JenkinsfileSnykScan
create mode 100644 java-samples/customeStateJSONandQuery/.gitignore
create mode 100644 java-samples/customeStateJSONandQuery/.run/runConfigurations/DebugCorDapp.run.xml
create mode 100644 java-samples/customeStateJSONandQuery/.snyk
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/Dockerfile
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/README.md
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/app.py
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/requirements.txt
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/static/Scripts/script.js
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/static/css/main.css
create mode 100644 java-samples/customeStateJSONandQuery/FlowManagementUI/templates/index.html
create mode 100644 java-samples/customeStateJSONandQuery/README.md
create mode 100644 java-samples/customeStateJSONandQuery/build.gradle
create mode 100644 java-samples/customeStateJSONandQuery/config/combined-worker-compose.yaml
create mode 100644 java-samples/customeStateJSONandQuery/config/gradle-plugin-default-key.pem
create mode 100644 java-samples/customeStateJSONandQuery/config/log4j2.xml
create mode 100644 java-samples/customeStateJSONandQuery/config/r3-ca-key.pem
create mode 100644 java-samples/customeStateJSONandQuery/config/static-network-config.json
create mode 100644 java-samples/customeStateJSONandQuery/contracts/build.gradle
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleCommands.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleStampContract.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/BasketOfApplesContract.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/AppleStamp.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/BasketOfApples.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/contracts/ChatContract.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatJsonFactory.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatState.java
create mode 100644 java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/CustomChatQuery.java
create mode 100644 java-samples/customeStateJSONandQuery/gradle.properties
create mode 100644 java-samples/customeStateJSONandQuery/gradle/wrapper/gradle-wrapper.jar
create mode 100644 java-samples/customeStateJSONandQuery/gradle/wrapper/gradle-wrapper.properties
create mode 100644 java-samples/customeStateJSONandQuery/gradlew
create mode 100644 java-samples/customeStateJSONandQuery/gradlew.bat
create mode 100644 java-samples/customeStateJSONandQuery/settings.gradle
create mode 100644 java-samples/customeStateJSONandQuery/workflows/build.gradle
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/CreateAndIssueAppleStampFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/CreateAndIssueAppleStampRequest.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/CreateAndIssueAppleStampResponderFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/PackageApplesFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/PackageApplesRequest.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/RedeemApplesFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/RedeemApplesRequest.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/apples/workflows/RedeemApplesResponderFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/ChatStateResults.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/CreateNewChatFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/CreateNewChatFlowArgs.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/FinalizeChatResponderFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/FinalizeChatSubFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/GetChatFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/GetChatFlowArgs.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/ListChatsByCustomQueryFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/ListChatsFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/MessageAndSender.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/UpdateChatFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/workflows/UpdateChatFlowArgs.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/flowexample/workflows/Message.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/flowexample/workflows/MyFirstFlow.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/flowexample/workflows/MyFirstFlowResponder.java
create mode 100644 java-samples/customeStateJSONandQuery/workflows/src/main/java/com/r3/developers/cordapptemplate/flowexample/workflows/MyFirstFlowStartArgs.java
diff --git a/java-samples/customeStateJSONandQuery/.ci/Jenkinsfile b/java-samples/customeStateJSONandQuery/.ci/Jenkinsfile
new file mode 100644
index 0000000..560ec37
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/.ci/Jenkinsfile
@@ -0,0 +1,11 @@
+@Library('corda-shared-build-pipeline-steps@5.2') _
+
+cordaPipeline(
+ publishRepoPrefix: '',
+ slimBuild: true,
+ runUnitTests: false,
+ dedicatedJobForSnykDelta: false,
+ slackChannel: '#corda-corda5-dev-ex-build-notifications',
+ gitHubComments: false,
+ javaVersion: '17'
+ )
diff --git a/java-samples/customeStateJSONandQuery/.ci/nightly/JenkinsfileSnykScan b/java-samples/customeStateJSONandQuery/.ci/nightly/JenkinsfileSnykScan
new file mode 100644
index 0000000..c2ae031
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/.ci/nightly/JenkinsfileSnykScan
@@ -0,0 +1,6 @@
+@Library('corda-shared-build-pipeline-steps@5.1') _
+
+cordaSnykScanPipeline (
+ snykTokenId: 'r3-snyk-corda5',
+ snykAdditionalCommands: "--all-sub-projects -d"
+)
diff --git a/java-samples/customeStateJSONandQuery/.gitignore b/java-samples/customeStateJSONandQuery/.gitignore
new file mode 100644
index 0000000..b9a99ba
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/.gitignore
@@ -0,0 +1,91 @@
+
+# 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
+
+# Ignore temporary data files
+*.dat
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/.run/runConfigurations/DebugCorDapp.run.xml b/java-samples/customeStateJSONandQuery/.run/runConfigurations/DebugCorDapp.run.xml
new file mode 100644
index 0000000..1d8da82
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/.run/runConfigurations/DebugCorDapp.run.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/.snyk b/java-samples/customeStateJSONandQuery/.snyk
new file mode 100644
index 0000000..c04521f
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/.snyk
@@ -0,0 +1,14 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.25.0
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
+ignore:
+ SNYK-JAVA-ORGJETBRAINSKOTLIN-2393744:
+ - '*':
+ reason: >-
+ This vulnerability relates to information exposure via creation of
+ temporary files (via Kotlin functions) with insecure permissions.
+ Corda does not use any of the vulnerable functions so it is not
+ susceptible to this vulnerability
+ expires: 2023-10-19T17:15:26.836Z
+ created: 2023-02-02T17:15:26.839Z
+patch: {}
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/Dockerfile b/java-samples/customeStateJSONandQuery/FlowManagementUI/Dockerfile
new file mode 100644
index 0000000..016bc21
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/Dockerfile
@@ -0,0 +1,5 @@
+FROM python
+WORKDIR /app
+COPY . /app
+RUN pip install -r requirements.txt
+CMD ["python3", "app.py"]
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/README.md b/java-samples/customeStateJSONandQuery/FlowManagementUI/README.md
new file mode 100644
index 0000000..fffff29
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/README.md
@@ -0,0 +1,84 @@
+# Corda 5 CorDapp Flow Management Tool
+
+
+This user guide provides step-by-step instructions on using the Corda 5 flow management tool. This article will help you learn how to connect the running corDapp, make flow calls, configure flow queries, and retrieve results.
+
+## Prerequisites
+* Install and run Python and Flask framework. link.
+
+* Prepare your local Corda 5 environment. (By default, the Flow Management Tool is looking to connect to https://localhost:8888/api/v5_2/swagger#/ with Login: Admin and Password: Admin.)
+
+* Clong the Flow Management Tool repository. FlowManagementUI: main
+
+## Set Up
+
+1. Assuming your local Corda 5 environment is populated and the swagger endpoint is at: https://localhost:8888/api/v5_2/swagger#/
+
+2. Navigate to where you downloaded the Corda 5 Flow Management Tool
+
+3. To run the framework
+ * Navigate to the file name using cd command.
+ * use the python app.py command to run it.
+ ![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/f0c3bf59-8180-48a0-91cc-80f2d260e530)
+
+ * Later on, click on the IP Address which will open the Interface:
+
+![image(4)](https://github.com/parisyup/FlowManagementUI/assets/66366646/8d88e37c-edbb-4d6d-8bcd-d773e818a106)
+
+
+The Flow Management Tool should be automatically connected with the CorDapp running locally from your CSDE. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp from CSDE.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/5a2356f2-cd14-489c-abd0-4afe0bf0d251)
+
+## Set Up With Docker
+
+1- Open up Command Prompt
+
+2- Navigate to the application folder using the CD commands
+
+3- Ensure that Docker application is open and build the image using the following command:
+`docker build -t your-image-name .`
+
+Make sure to include the dot at the end of the command
+
+the `your-image-name` at the end of the command can be whatever you like but make sure to use the same name in the next step
+
+4- Run the docker image using the following command:
+`docker run --rm -it --expose 8888 -p 5000:5000 your-image-name`
+
+5- You can access the website by using https://localhost:5000 or https://127.0.0.1:5000
+
+## Using the Flow Management Tool
+
+### Selecting the Flow Initiator
+
+As the first step of using the Flow Management Tool, you would need to select the Flow Initiator. The Flow Initiator indicates which vNode will be triggering the flow. If you wish to have Alice to run a transaction to Bob, select the X500Name of Alice. The selected vNode’s shortHash (Corda 5 Network participant identifier) will also be shown below the dropdown list to signify your selection.
+
+### Function 1: To Make a Flow Call
+
+1. Click on "Flow Call" tab in the application.
+2. Paste the your JSON format request body into the request input box.
+3. Click Post button to trigger the call.
+
+![image(5)](https://github.com/parisyup/FlowManagementUI/assets/66366646/c65195a6-0a70-4354-804e-37884f657746)
+
+
+### Function 2: To Configure Flow Query
+
+1. Click on the “Flow Query” tab.
+2. Choose whether to query a single flow or all flows at the selected Flow Initiator.
+3. If you choose to query all of the flows, select “Query All Flows“ then click “Get“.
+
+![image](https://github.com/parisyup/FlowManagementUI/assets/51169685/0482cfa4-7ee1-42f2-8786-2d8ad80b2936)
+4. If you choose to query a single flow, select “Query Single Flow“, please add the ClientID in specified filed.
+5. Click on “Get” to retrieve the result.
+
+![image(6)](https://github.com/parisyup/FlowManagementUI/assets/66366646/13e979b0-f76e-4f2c-9d55-81be8880890b)
+
+If you have any suggestions or questions, feel free to give us your feedback through Github for a better experience in the future!
+
+## Conclusion
+In summary, our project introduces a specialized flow management layer on top of Swagger for Corda developers. We understand the challenges developers face in testing Corda applications due to the complexity of commands, our solution focuses on simplifying the process.
+
+Our all-in-one flow management system provides developers with a unified platform, streamlining development and enhancing efficiency. A key feature allows developers to run flows directly from an externally developed website and monitor their status in real-time, offering a user-friendly and practical solution for Corda developers. Overall, our project aims to make Corda development more accessible and tailored to the specific needs of flow management.
+
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/app.py b/java-samples/customeStateJSONandQuery/FlowManagementUI/app.py
new file mode 100644
index 0000000..5d3e3f6
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/app.py
@@ -0,0 +1,12 @@
+from flask import Flask
+from flask import render_template
+app = Flask(__name__)
+
+
+@app.route('/')
+def home(): # put application's code here
+ return render_template("index.html")
+
+if __name__ == '__main__':
+ app.run(debug=True, host='0.0.0.0')
+
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/requirements.txt b/java-samples/customeStateJSONandQuery/FlowManagementUI/requirements.txt
new file mode 100644
index 0000000..8ab6294
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/requirements.txt
@@ -0,0 +1 @@
+flask
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/static/Scripts/script.js b/java-samples/customeStateJSONandQuery/FlowManagementUI/static/Scripts/script.js
new file mode 100644
index 0000000..8fd4ec0
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/static/Scripts/script.js
@@ -0,0 +1,322 @@
+// This script contains functions for making API requests, handling data, and updating the UI.
+
+// Variable to store the selected X500Name from the dropdown
+let selectedX500Name;
+
+// Variable to indicate whether data is currently being loaded from the pull request
+let loading = false;
+
+// Function to make a GET request to an external API and return the data
+function getData() {
+ // Replace the URL with the actual API endpoint
+ return fetch('https://jsonplaceholder.typicode.com/todos/1')
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+
+ // Return specific data fields
+ return {
+ id: data.id,
+ title: data.title
+ };
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get the data and display it on the page
+function getDataAndDisplay() {
+ getData()
+ .then(result => {
+ // Update the result div with the data
+ document.getElementById('result').innerHTML = `
+
ID: ${result.id}
+
Title: ${result.title}
+ `;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a GET request to retrieve CPI data
+function getCPI() {
+ const url = 'https://localhost:8888/api/v1/cpi';
+ // Perform the GET request with authorization headers
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('API Result:', data);
+ // Further processing of the data can be done here
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to get all virtual nodes and populate a dropdown with the data
+function getAllVirtualNodes() {
+ const url = 'https://localhost:8888/api/v1/virtualnode';
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Extract virtualNodes from the API response
+ const virtualNodes = data.virtualNodes;
+ console.log('API Result:', virtualNodes);
+
+ // Process the data and populate the dropdown
+ if (Array.isArray(virtualNodes)) {
+ const dropdown = document.getElementById('itemDropdown');
+ dropdown.innerHTML = '';
+ dropdown.innerHTML += '';
+
+ virtualNodes.forEach(item => {
+ // Display each item on the console
+ console.log('Item X500Name:', item.holdingIdentity.x500Name);
+ console.log('Item ShortHash:', item.holdingIdentity.shortHash);
+
+ // Create an option element and add it to the dropdown
+ const option = document.createElement('option');
+ option.value = item.holdingIdentity.shortHash;
+ option.text = item.holdingIdentity.x500Name;
+ dropdown.appendChild(option);
+ });
+
+ // Add event listener to the dropdown to detect changes
+ dropdown.addEventListener('change', function () {
+ selectedX500Name = this.value;
+ console.log('Selected ShortHash:', selectedX500Name);
+
+ // Call a function or update a variable based on the selected item
+ handleDropdownChange(selectedX500Name);
+ });
+
+ } else {
+ console.warn('API Result is not an array.');
+ }
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Initialize the virtual nodes dropdown on page load
+getAllVirtualNodes();
+
+// function to handle dropdown change
+function handleDropdownChange(selectedX500Name) {
+ console.log('Handling dropdown change for Item ID:', selectedX500Name);
+ getSelectedVNode();
+ // Additional actions based on the selected item can be performed here
+}
+
+// Function to get the selected virtual node and display it
+function getSelectedVNode() {
+ const displayElement = document.getElementById('selectedX500Display');
+ displayElement.textContent = `Selected X500: ${selectedX500Name}`;
+}
+
+// Function to get all flow results based on the selected virtual node
+function getAllFlowResult() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ const flowStatusResponses = data.flowStatusResponses;
+ console.log('API Result:', flowStatusResponses);
+
+ // Convert the JSON object to a string for display
+ const jsonString = JSON.stringify(flowStatusResponses, null, 2);
+
+ // Display the JSON string in the result div
+ document.getElementById('idResutl').innerHTML = `
${jsonString}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to make a POST request with a request body
+async function postCallFlow() {
+ let postBtn = $('#postBtn');
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}`;
+
+ // Change the button text to indicate loading
+ postBtn.html('Loading...');
+
+ try {
+ // Perform the POST request with the provided request body
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4=',
+ 'Content-Type': 'application/json'
+ },
+ body: `${document.getElementById('requestBody').value}`
+ });
+
+ // Parse the response as JSON
+ const data = await response.json();
+
+ if (!data.ok) {
+ console.log(data.status);
+
+ // Check if the response status is not OK (2xx range)
+ if (data.status == "409" || data.status == "400") {
+ let flowStatusResponse = "title: " + data.title + "\nStatus: " + data.status;
+
+ // Display additional details for 400 status
+ if (data.status == "400") {
+ flowStatusResponse += "\nDetails: \n Cause: " + data.details.cause + "\n Reason: " + data.details.reason;
+ }
+
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponse}
`;
+ return;
+ }
+ }
+
+ let msg = "null";
+ let typ = "null";
+
+ // Construct a string with the flow status responses
+ let flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('queryResult').innerHTML = `
${flowStatusResponses}
`;
+ } catch (error) {
+ console.log('Error:', error);
+ } finally {
+ // Restore the button text after the operation is complete
+ postBtn.html('Post');
+ }
+}
+
+// Function to display an item on the page
+function displayItemOnPage(item) {
+ const resultDiv = document.getElementById('queryResult');
+
+ // Create a new element to display the item
+ const itemElement = document.createElement('div');
+ itemElement.innerHTML = `
+
QueryResult: ${item}
+ `;
+
+ // Append the new element to the result div
+ resultDiv.appendChild(itemElement);
+}
+
+// Function to open a specific tab by hiding/showing content
+function openTab(tabName) {
+ // Hide all tab content
+ var tabContents = document.getElementsByClassName("tab-content");
+ for (var i = 0; i < tabContents.length; i++) {
+ tabContents[i].style.display = "none";
+ document.getElementById(tabContents[i].id + "-tab").style.backgroundColor = "rgb(52,152,219)";
+ }
+
+ // Show the selected tab content
+ var selectedTab = document.getElementById(tabName);
+ if (selectedTab) {
+ selectedTab.style.display = "block";
+ document.getElementById(tabName + "-tab").style.backgroundColor = "rgb(173,216,230)";
+ }
+}
+
+// Function to display a flow for a specific virtual node
+function oneFlow() {
+ const url = `https://localhost:8888/api/v1/flow/${selectedX500Name}/${document.getElementById('clientID').value}`;
+
+ // Validate clientID input
+ if (document.getElementById('clientID').value == "") {
+ alert("Please input a clientId");
+ return;
+ }
+
+ // Perform a GET request to display a flow
+ return fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/json',
+ 'Authorization': 'Basic YWRtaW46YWRtaW4='
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ let msg = "null";
+ let typ = "null";
+
+ // Check if the flow status is "FAILED" and extract error details
+ if (data.flowStatus == "FAILED") {
+ msg = data.flowError.message;
+ typ = data.flowError.type;
+ }
+
+ // Construct a string with the flow status responses
+ const flowStatusResponses = "client request ID: " + data.clientRequestId +
+ "\nFlow Result " + data.flowResult +
+ "\nFlow Error Message: " + msg +
+ "\nflow error type: " + typ +
+ "\nFlow ID: " + data.flowId +
+ "\nFlow status: " + data.flowStatus +
+ "\nHolding identity short hash: " + data.holdingIdentityShortHash +
+ "\nTime stamp: " + data.timestamp;
+
+ console.log('API Result:', flowStatusResponses);
+
+ // Display the flow status responses in the result div
+ document.getElementById('idResutl').innerHTML = `
${flowStatusResponses}
`;
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+}
+
+// Function to determine which flow-related action to execute based on user input
+function executeButtonFlow() {
+ // Check the value of the dropdown to determine which action to perform
+ if (document.getElementById("dropdown").value == "option1") {
+ getAllFlowResult();
+ } else {
+ oneFlow();
+ }
+}
+
+function queryDropDownChange(){
+ if (document.getElementById("dropdown").value == "option1") {
+ document.getElementById("clientID").style.display = 'none';
+ }else{
+ document.getElementById("clientID").style.display = 'block';
+
+ }
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/static/css/main.css b/java-samples/customeStateJSONandQuery/FlowManagementUI/static/css/main.css
new file mode 100644
index 0000000..5e4d849
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/static/css/main.css
@@ -0,0 +1,202 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background-color: #f4f4f4;
+ color: #333;
+ margin: 50px; /* Add margin to the entire body */
+ padding: 0;
+}
+
+h1 {
+ text-align: center;
+ color: #0e0c0c;
+}
+
+/* Style for the label */
+label {
+ display: block;
+ margin-bottom: 10px;
+ font-weight: bold;
+ color: #333;
+}
+/* Style for the dropdown button */
+select {
+ width: 20%;
+ padding: 8px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#itemDropdown {
+ width: 40%;
+ padding: 10px;
+ box-sizing: border-box;
+}
+
+#clientID{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+ color: #555;
+}
+
+#OneFlowButon{
+ padding: 8px;
+ margin-bottom: 15px;
+ margin-right: 10px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 14px;
+}
+
+#result {
+ margin-bottom: 10px;
+}
+
+/* Button styling */
+button {
+ background-color: #3498db;
+ color: #fff;
+ padding: 10px 25px;
+ font-size: 16px;
+ border: 2px;
+ border-radius: 15px;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+button:hover {
+ background-color: #2980b9;
+}
+
+/* Tab styling */
+.tab {
+ list-style-type: none; /* Remove default list styles */
+ display: inline-block; /* Display tabs inline */
+ padding: 2px 00px; /* Add padding to the tabs */
+ margin: 0 1px; /* Add margin between tabs */
+ cursor: pointer; /* Change cursor to pointer on hover */
+}
+
+
+.tab li {
+ flex: 1;
+ text-align: center;
+ padding: 10px;
+ background-color: #3498db;
+ color: #fff;
+ border-radius: 8px 15px 0 0;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+}
+
+.tab li:hover {
+ background-color: #2980b9;
+}
+
+/* Tab content styling */
+.tab-content {
+ display: none;
+ padding: 20px;
+ border: 1px solid #3498db;
+ border-radius: 0 0 5px 5px;
+ background-color: #fff;
+}
+
+.styled-input {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ box-sizing: border-box;
+}
+
+.output {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ box-sizing: border-box;
+}
+
+#idResutl {
+ border: 1px solid #3498db;
+ padding: 10px;
+ border-radius: 5px;
+ background-color: #fff;
+ margin-top: 10px;
+ height: 290px;
+ max-height: 290px; /* Set a maximum height for the scroll box */
+ overflow-y: auto; /* Enable vertical scrolling if content exceeds the box height */
+}
+
+/* Responsive design */
+@media screen and (max-width: 600px) {
+ .tab li {
+ border-radius: 5px;
+ margin-bottom: 5px;
+ }
+ .tab-content {
+ border-radius: 5px;
+ }
+}
+#call {
+ display: -ms-inline-flexbox;
+ flex-wrap: wrap;
+
+}
+
+/* Style for side-by-side input boxes */
+.flowcall-container {
+ display: flex;
+}
+
+.input-box, .text-box {
+ width: 150px; /* Set the desired width */
+ margin-right: 10px; /* Optional: Add margin for spacing between input boxes */
+ border-radius: 5px;
+ border: 1px solid #3498db;
+}
+
+.queryoption-container{
+ display: flex;
+}
+
+
+
+#requestBody {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+ margin-right: 10px; /* Add some margin between the input and button */
+}
+
+#postBtn {
+ flex: 0 0 auto; /* Don't allow the button to grow or shrink */
+ margin-top: 10px; /* Add some margin between the button and result box */
+ margin-right: 10px; /* Add some margin between the button and result box */
+ width: 500px; /* Set the desired width */
+ height: 50px; /* Set the desired height */
+}
+
+#queryResult {
+ flex: 1;
+ box-sizing: border-box;
+ width: 100px; /* Set the desired width */
+ height: 300px; /* Set the desired height */
+ padding: 10px; /* Optional: Add padding for better aesthetics */
+}
+.content-box{
+ padding: 10px; /* Add padding to the content boxes */
+}
+
+
diff --git a/java-samples/customeStateJSONandQuery/FlowManagementUI/templates/index.html b/java-samples/customeStateJSONandQuery/FlowManagementUI/templates/index.html
new file mode 100644
index 0000000..44df7ac
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/FlowManagementUI/templates/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+ Flask Frontend Example
+
+
+
+
+
+
+
+
+
+
+
+
+
Flow Management APIs
+
+
+
+
+
+
+
+
Please select a flow initiator
+
+
+
+
+
Flow Call
+
Flow Query
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Result will be displayed here
+
+
+
diff --git a/java-samples/customeStateJSONandQuery/README.md b/java-samples/customeStateJSONandQuery/README.md
new file mode 100644
index 0000000..435f834
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/README.md
@@ -0,0 +1,199 @@
+# cordapp-template-java (Corda v5.2)
+
+
+## This template repository provides:
+
+- A pre-setup Cordapp Project which you can use as a starting point to develop your own prototypes.
+
+- A base Gradle configuration which brings in the dependencies you need to write and test a Corda 5 Cordapp.
+
+- A set of Gradle helper tasks, provided by the [Corda runtime gradle plugin](https://github.com/corda/corda-runtime-os/tree/release/os/5.2/tools/corda-runtime-gradle-plugin#readme), which speed up and simplify the development and deployment process.
+
+- Debug configuration for debugging a local Corda cluster.
+
+- The MyFirstFlow code which forms the basis of this getting started documentation, this is located in package com.r3.developers.cordapptemplate.flowexample
+
+- A UTXO example in package com.r3.developers.cordapptemplate.customeStateJSONandQuery packages
+
+- Ability to configure the Members of the Local Corda Network.
+
+To find out how to use the template, please refer to the *CorDapp Template* subsection within the *Developing Applications* section in the latest Corda 5 documentation at https://docs.r3.com/
+
+## Prerequisite
+1. Java 17
+2. Corda-cli (v5.2), Download [here](https://github.com/corda/corda-runtime-os/releases/tag/release-5.2.0.0). You need to install Java 17 first.
+3. Docker Desktop
+
+## Setting up
+
+1. We will begin our test deployment with clicking the `startCorda`. This task will load up the combined Corda workers in docker.
+ A successful deployment will allow you to open the REST APIs at: https://localhost:8888/api/v5_2/swagger#. You can test out some of the
+ functions to check connectivity. (GET /cpi function call should return an empty list as for now.)
+2. We will now deploy the cordapp with a click of `vNodeSetup` task. Upon successful deployment of the CPI, the GET /cpi function call should now return the meta data of the cpi you just upload
+
+## Flow Management Tool[Optional]
+We had developed a simple GUI for you to interact with the cordapp. You can access the website by using https://localhost:5000 or https://127.0.0.1:5000. The Flow Management Tool will automatically connect with the CorDapp running locally from your Corda cluster. You can test the connection by click on the dropdown list at the Flow Initiator section. You should be able to see the vNodes of your started CorDapp. You can easily trigger and query a Corda flow.
+
+
+
+![image](https://github.com/corda/cordapp-template-kotlin/assets/51169685/88e6568e-49b4-46a8-b1e1-34140bcf03a9)
+
+
+## Running the Chat app
+We have built a simple one to one chat app to demo some functionalities of the next gen Corda platform.
+
+In this app you can:
+1. Create a new chat with a counterparty. `CreateNewChatFlow`
+2. List out the chat entries you had. `ListChatsFlow`
+3. Individually query out the history of one chat entry. `GetChatFlowArgs`
+4. Continue chatting within the chat entry with the counterparty. `UpdateChatFlow`
+
+
+
+
+### Running the chat app
+
+In Corda 5, flows will be triggered via `POST /flow/{holdingidentityshorthash}` and flow result will need to be view at `GET /flow/{holdingidentityshorthash}/{clientrequestid}`
+* holdingidentityshorthash: the id of the network participants, ie Bob, Alice, Charlie. You can view all the short hashes of the network member with another gradle task called `listVNodes`
+* clientrequestid: the id you specify in the flow requestBody when you trigger a flow.
+
+#### Step 1: Create Chat Entry
+Pick a VNode identity to initiate the chat, and get its short hash. (Let's pick Alice. Dont pick Bob because Bob is the person who we will have the chat with).
+
+Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
+```
+{
+ "clientRequestId": "create-1",
+ "flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.CreateNewChatFlow",
+ "requestBody": {
+ "chatName":"Chat with Bob",
+ "otherMember":"CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
+ "message": "Hello Bob"
+ }
+}
+```
+
+After trigger the create-chat flow, hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the short hash(Alice's hash) and clientrequestid to view the flow result
+
+#### Step 2: List the chat
+In order to continue the chat, we would need the chat ID. This step will bring out all the chat entries this entity (Alice) has.
+Go to `POST /flow/{holdingidentityshorthash}`, enter the identity short hash(Alice's hash) and request body:
+```
+{
+ "clientRequestId": "list-1",
+ "flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.ListChatsFlow",
+ "requestBody": {}
+}
+```
+After trigger the list-chats flow, again, we need to hop to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and check the result. As the screenshot shows, in the response body,
+we will see a list of chat entries, but it currently only has one entry. And we can see the id of the chat entry. Let's record that id.
+
+
+#### Step 3: Continue the chat with `UpdateChatFlow`
+In this step, we will continue the chat between Alice and Bob.
+Goto `POST /flow/{holdingidentityshorthash}`, enter the identity short hash and request body. Note that here we can have either Alice or Bob's short hash. If you enter Alice's hash,
+this message will be recorded as a message from Alice, vice versa. And the id field is the chat entry id we got from the previous step.
+```
+{
+ "clientRequestId": "update-1",
+ "flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.UpdateChatFlow",
+ "requestBody": {
+ "id":" ** fill in id **",
+ "message": "How are you today?"
+ }
+}
+```
+And as for the result of this flow, go to `GET /flow/{holdingidentityshorthash}/{clientrequestid}` and enter the required fields.
+
+#### Step 4: See the whole chat history of one chat entry
+After a few back and forth of the messaging, you can view entire chat history by calling GetChatFlow.
+
+```
+{
+ "clientRequestId": "get-1",
+ "flowClassName": "com.r3.developers.cordapptemplate.customeStateJSONandQuery.workflows.GetChatFlow",
+ "requestBody": {
+ "id":" ** fill in id **",
+ "numberOfRecords":"4"
+ }
+}
+```
+And as for the result, you need to go to the Get API again and enter the short hash and client request ID.
+
+Thus, we have concluded a full run through of the chat app.
+
+## Saving a run as JSON
+
+#### Description
+The ChatJsonFactory is a custom JSON factory designed to facilitate the serialization and deserialization of ChatState objects to and from JSON format within a Corda application.
+
+#### Usage
+This factory is utilized within Corda applications to convert ChatState objects to JSON format after each use.
+It is integrated into the Corda framework, ensuring seamless interoperability with other components of the application.
+
+#### Functionality
+getStateType(): Specifies the type of state the factory is responsible for, in this case, ChatState.
+create(ChatState state, JsonMarshallingService jsonMarshallingService): Generates a JSON representation of a ChatState object by constructing a map and serializing it to JSON format using the provided JsonMarshallingService constants
+
+ID: Key name for the identifier of the chat.
+
+CHATNAME: Key name for the name of the chat.
+
+MESSAGE: Key name for the content of the message.
+
+MESSAGEFROM: Key name for the sender of the message.
+
+#### Integration
+The app is integrated automatically by corda when implementing ContractStateVaultJsonFactory for the class.
+It will run whenever a flow runs for the chatState
+
+## Running the query
+
+#### Description
+The CustomChatQuery class provides custom named queries for querying Vault states related to a chat application within a Corda application.
+These queries are designed to facilitate specific data retrieval operations from the Corda vault which were stored
+using the JSON function previously mentioned.
+
+#### Usage
+This component is integrated into Corda applications to execute custom named queries for retrieving chat-related states from the Corda vault.
+It enables developers to perform targeted data retrieval operations efficiently.
+
+#### Functionality
+create(VaultNamedQueryBuilderFactory vaultNamedQueryBuilderFactory): Defines custom named queries for querying the Corda vault.
+GET_ALL_MSG: Retrieves all messages from the vault.
+GET_MSG_FROM: Retrieves messages from a specific sender.
+
+### Integration
+#### Process
+The integration process began with the instantiation of the ListChatByCustomQueryFlow class within the application codebase.
+This class provides the necessary functionality to execute custom named queries against the Corda vault from the saved JSONs.
+
+Subsequently, the custom query GET_MSG_FROM was executed using the query method provided by VaultService.
+This involved setting parameters for the query, such as specifying the name of the sender whose messages were to be retrieved in this
+application it is hard coded to alice.
+Additionally, optional parameters like timestamp limits and result limits were configured as per the application's requirements.
+
+Once the query execution was completed, the results were processed to extract the relevant ChatState objects.
+This step involved mapping the query results to extract the desired states from the Corda vault.
+
+Following the retrieval of ChatState objects, they were converted into a human-readable format or DTOs suitable for presentation purposes.
+This ensured that the retrieved chat messages were formatted in a manner understandable by users or other components of the application.
+
+Finally, the results were serialized to JSON format using a JsonMarshallingService.
+This step prepared the results for transmission as a response,
+enabling other components or external systems to consume the data in a standardized format.
+
+#### Calling a query
+
+After creating the query it can be run using the following REST call using Bob's id:
+
+```
+{
+ "clientRequestId": "customlist-1",
+ "flowClassName": "com.r3.developers.cordapptemplate.utxoexample.workflows.ListChatsByCustomQueryFlow",
+ "requestBody": {}
+}
+```
+
+Which will display all messages sent by just Alice.
+So even if Bob received messages from someone else it will only display the messages sent by Alice
diff --git a/java-samples/customeStateJSONandQuery/build.gradle b/java-samples/customeStateJSONandQuery/build.gradle
new file mode 100644
index 0000000..88f4106
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/build.gradle
@@ -0,0 +1,74 @@
+import static org.gradle.api.JavaVersion.VERSION_17
+import static org.gradle.jvm.toolchain.JavaLanguageVersion.of
+
+plugins {
+ id 'org.jetbrains.kotlin.jvm'
+ id 'net.corda.cordapp.cordapp-configuration'
+ id 'org.jetbrains.kotlin.plugin.jpa'
+ id 'java'
+ id 'maven-publish'
+ id 'net.corda.gradle.plugin'
+}
+
+allprojects {
+ group 'com.r3.developers.cordapptemplate'
+ version '1.0-SNAPSHOT'
+
+ // Configure Corda runtime gradle plugin
+ cordaRuntimeGradlePlugin {
+ notaryVersion = cordaNotaryPluginsVersion
+ notaryCpiName = "NotaryServer"
+ corDappCpiName = "MyCorDapp"
+ cpiUploadTimeout = "30000"
+ vnodeRegistrationTimeout = "60000"
+ cordaProcessorTimeout = "300000"
+ workflowsModuleName = "workflows"
+ cordaClusterURL = "https://localhost:8888"
+ cordaRestUser = "admin"
+ cordaRestPasswd ="admin"
+ composeFilePath = "config/combined-worker-compose.yaml"
+ networkConfigFile = "config/static-network-config.json"
+ r3RootCertFile = "config/r3-ca-key.pem"
+ skipTestsDuringBuildCpis = "false"
+ cordaRuntimePluginWorkspaceDir = "workspace"
+ cordaBinDir = "${System.getProperty("user.home")}/.corda/corda5"
+ cordaCliBinDir = "${System.getProperty("user.home")}/.corda/cli"
+ }
+
+ java {
+ toolchain {
+ languageVersion = of(VERSION_17.majorVersion.toInteger())
+ }
+ withSourcesJar()
+ }
+
+ // Declare the set of Java compiler options we need to build a CorDapp.
+ tasks.withType(JavaCompile) {
+ // -parameters - Needed for reflection and serialization to work correctly.
+ options.compilerArgs += [
+ "-parameters"
+ ]
+ }
+
+ repositories {
+ // All dependencies are held in Maven Central
+ mavenLocal()
+ mavenCentral()
+ }
+
+ tasks.withType(Test).configureEach {
+ useJUnitPlatform()
+ }
+
+}
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ artifactId "corda-dev-template-java-sample"
+ groupId project.group
+ artifact jar
+ }
+ }
+}
+
diff --git a/java-samples/customeStateJSONandQuery/config/combined-worker-compose.yaml b/java-samples/customeStateJSONandQuery/config/combined-worker-compose.yaml
new file mode 100644
index 0000000..da2495d
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/config/combined-worker-compose.yaml
@@ -0,0 +1,87 @@
+version: '2'
+services:
+ postgresql:
+ image: postgres:14.10
+ restart: unless-stopped
+ tty: true
+ environment:
+ - POSTGRES_USER=postgres
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=cordacluster
+ ports:
+ - 5432:5432
+
+ kafka:
+ image: confluentinc/cp-kafka:7.6.0
+ ports:
+ - 9092:9092
+ environment:
+ KAFKA_NODE_ID: 1
+ CLUSTER_ID: ZDFiZmU3ODUyMzRiNGI3NG
+ KAFKA_PROCESS_ROLES: broker,controller
+ KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,DOCKER_INTERNAL://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092,DOCKER_INTERNAL://kafka:29092
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,DOCKER_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ KAFKA_INTER_BROKER_LISTENER_NAME: DOCKER_INTERNAL
+ KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
+ KAFKA_DEFAULT_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
+ KAFKA_AUTO_CREATE_TOPICS_ENABLE: "true"
+
+ kafka-create-topics:
+ image: openjdk:17-jdk
+ depends_on:
+ - kafka
+ volumes:
+ - ${CORDA_CLI:-~/.corda/cli}:/opt/corda-cli
+ working_dir: /opt/corda-cli
+ command: [
+ "java",
+ "-jar",
+ "corda-cli.jar",
+ "topic",
+ "-b=kafka:29092",
+ "create",
+ "connect"
+ ]
+
+ corda:
+ image: corda/corda-os-combined-worker-kafka:5.2.0.0
+ depends_on:
+ - postgresql
+ - kafka
+ - kafka-create-topics
+ volumes:
+ - ../config:/config
+ - ../logs:/logs
+ environment:
+ JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
+ LOG4J_CONFIG_FILE: config/log4j2.xml
+ CONSOLE_LOG_LEVEL: info
+ ENABLE_LOG4J2_DEBUG: false
+ command: [
+ "-mbus.busType=KAFKA",
+ "-mbootstrap.servers=kafka:29092",
+ "-spassphrase=password",
+ "-ssalt=salt",
+ "-ddatabase.user=user",
+ "-ddatabase.pass=password",
+ "-ddatabase.jdbc.url=jdbc:postgresql://postgresql:5432/cordacluster",
+ "-ddatabase.jdbc.directory=/opt/jdbc-driver/"
+ ]
+ ports:
+ - 8888:8888
+ - 7004:7004
+ - 5005:5005
+
+ flow-management-tool:
+ depends_on:
+ - corda
+ build:
+ context: ../FlowManagementUI
+ dockerfile: Dockerfile
+ ports:
+ - 5000:5000
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/config/gradle-plugin-default-key.pem b/java-samples/customeStateJSONandQuery/config/gradle-plugin-default-key.pem
new file mode 100644
index 0000000..5294bbd
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/config/gradle-plugin-default-key.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB7zCCAZOgAwIBAgIEFyV7dzAMBggqhkjOPQQDAgUAMFsxCzAJBgNVBAYTAkdC
+MQ8wDQYDVQQHDAZMb25kb24xDjAMBgNVBAoMBUNvcmRhMQswCQYDVQQLDAJSMzEe
+MBwGA1UEAwwVQ29yZGEgRGV2IENvZGUgU2lnbmVyMB4XDTIwMDYyNTE4NTI1NFoX
+DTMwMDYyMzE4NTI1NFowWzELMAkGA1UEBhMCR0IxDzANBgNVBAcTBkxvbmRvbjEO
+MAwGA1UEChMFQ29yZGExCzAJBgNVBAsTAlIzMR4wHAYDVQQDExVDb3JkYSBEZXYg
+Q29kZSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQDjSJtzQ+ldDFt
+pHiqdSJebOGPZcvZbmC/PIJRsZZUF1bl3PfMqyG3EmAe0CeFAfLzPQtf2qTAnmJj
+lGTkkQhxo0MwQTATBgNVHSUEDDAKBggrBgEFBQcDAzALBgNVHQ8EBAMCB4AwHQYD
+VR0OBBYEFLMkL2nlYRLvgZZq7GIIqbe4df4pMAwGCCqGSM49BAMCBQADSAAwRQIh
+ALB0ipx6EplT1fbUKqgc7rjH+pV1RQ4oKF+TkfjPdxnAAiArBdAI15uI70wf+xlL
+zU+Rc5yMtcOY4/moZUq36r0Ilg==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/config/log4j2.xml b/java-samples/customeStateJSONandQuery/config/log4j2.xml
new file mode 100644
index 0000000..4e297be
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/config/log4j2.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/config/r3-ca-key.pem b/java-samples/customeStateJSONandQuery/config/r3-ca-key.pem
new file mode 100644
index 0000000..a803613
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/config/r3-ca-key.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
+RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
+ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
+xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
+ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
+DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
+jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
+CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
+EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
+fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
+uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
+chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
+9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
+SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
+fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
+sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
+cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
+0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
+4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
+r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
+/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
+gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/config/static-network-config.json b/java-samples/customeStateJSONandQuery/config/static-network-config.json
new file mode 100644
index 0000000..b0f2519
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/config/static-network-config.json
@@ -0,0 +1,23 @@
+[
+ {
+ "x500Name" : "CN=Alice, OU=Test Dept, O=R3, L=London, C=GB",
+ "cpi" : "MyCorDapp"
+ },
+ {
+ "x500Name" : "CN=Bob, OU=Test Dept, O=R3, L=London, C=GB",
+ "cpi" : "MyCorDapp"
+ },
+ {
+ "x500Name" : "CN=Charlie, OU=Test Dept, O=R3, L=London, C=GB",
+ "cpi" : "MyCorDapp"
+ },
+ {
+ "x500Name" : "CN=Dave, OU=Test Dept, O=R3, L=London, C=GB",
+ "cpi" : "MyCorDapp"
+ },
+ {
+ "x500Name" : "CN=NotaryRep1, OU=Test Dept, O=R3, L=London, C=GB",
+ "cpi" : "NotaryServer",
+ "serviceX500Name": "CN=NotaryService, OU=Test Dept, O=R3, L=London, C=GB"
+ }
+]
diff --git a/java-samples/customeStateJSONandQuery/contracts/build.gradle b/java-samples/customeStateJSONandQuery/contracts/build.gradle
new file mode 100644
index 0000000..af7e199
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/build.gradle
@@ -0,0 +1,87 @@
+
+plugins {
+ // Include the cordapp-cpb plugin. This automatically includes the cordapp-cpk plugin as well.
+ // These extend existing build environment so that CPB and CPK files can be built.
+ // This includes a CorDapp DSL that allows the developer to supply metadata for the CorDapp
+ // required by Corda.
+ id 'net.corda.plugins.cordapp-cpb2'
+ id 'org.jetbrains.kotlin.jvm'
+ id 'maven-publish'
+}
+
+// Declare dependencies for the modules we will use.
+// A cordaProvided declaration is required for anything that we use that the Corda API provides.
+// This is required to allow us to build CorDapp modules as OSGi bundles that CPI and CPB files are built on.
+dependencies {
+
+ cordaProvided 'org.jetbrains.kotlin:kotlin-osgi-bundle'
+
+ // Declare a "platform" so that we use the correct set of dependency versions for the version of the
+ // Corda API specified.
+ cordaProvided platform("net.corda:corda-api:$cordaApiVersion")
+
+ // If using transistive dependencies this will provide most of Corda-API:
+ // cordaProvided 'net.corda:corda-application'
+
+ // Alternatively we can explicitly specify all our Corda-API dependencies:
+ cordaProvided 'net.corda:corda-base'
+ cordaProvided 'net.corda:corda-application'
+ cordaProvided 'net.corda:corda-crypto'
+ cordaProvided 'net.corda:corda-membership'
+ // cordaProvided 'net.corda:corda-persistence'
+ cordaProvided 'net.corda:corda-serialization'
+ cordaProvided 'net.corda:corda-ledger-utxo'
+ cordaProvided 'net.corda:corda-ledger-consensual'
+
+ // CorDapps that use the UTXO ledger must include at least one notary client plugin
+ cordapp "com.r3.corda.notary.plugin.nonvalidating:notary-plugin-non-validating-client:$cordaNotaryPluginsVersion"
+
+ // The CorDapp uses the slf4j logging framework. Corda-API provides this so we need a 'cordaProvided' declaration.
+ cordaProvided 'org.slf4j:slf4j-api'
+
+ // 3rd party libraries
+ // Required
+ testImplementation "org.slf4j:slf4j-simple:$slf4jVersion"
+ testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
+
+ // Optional but used by example tests.
+ testImplementation "org.mockito:mockito-core:$mockitoVersion"
+ testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
+ testImplementation "org.hamcrest:hamcrest-library:$hamcrestVersion"
+// testImplementation "com.r3.corda.ledger.utxo:contract-testing:$contractTestingVersion"
+}
+
+// The CordApp section.
+// This is part of the DSL provided by the corda plugins to define metadata for our CorDapp.
+// Each component of the CorDapp would get its own CorDapp section in the build.gradle file for the component’s
+// subproject.
+// This is required by the corda plugins to build the CorDapp.
+cordapp {
+ // "targetPlatformVersion" and "minimumPlatformVersion" are intended to specify the preferred
+ // and earliest versions of the Corda platform that the CorDapp will run on respectively.
+ // Enforced versioning has not implemented yet so we need to pass in a dummy value for now.
+ // The platform version will correspond to and be roughly equivalent to the Corda API version.
+ targetPlatformVersion = platformVersion.toInteger()
+ minimumPlatformVersion = platformVersion.toInteger()
+
+ // The cordapp section contains either a workflow or contract subsection depending on the type of component.
+ // Declares the type and metadata of the CPK (this CPB has one CPK).
+ contract {
+ name contractsModule
+ versionId 1
+ licence cordappLicense
+ vendor cordappVendorName
+ }
+}
+
+// Use the name of the contract module as the name of the generated CPK and CPB.
+archivesBaseName = cordapp.contract.name.isPresent() ? cordapp.contract.name.get() : contractsModule
+
+publishing {
+ publications {
+ maven(MavenPublication) {
+ from components.cordapp
+ }
+ }
+}
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleCommands.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleCommands.java
new file mode 100644
index 0000000..2746877
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleCommands.java
@@ -0,0 +1,9 @@
+package com.r3.developers.apples.contracts;
+
+import net.corda.v5.ledger.utxo.Command;
+
+public interface AppleCommands extends Command {
+ public class Issue implements AppleCommands { };
+ public class Redeem implements AppleCommands { };
+ public class PackBasket implements AppleCommands { };
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleStampContract.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleStampContract.java
new file mode 100644
index 0000000..a57002f
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/AppleStampContract.java
@@ -0,0 +1,38 @@
+package com.r3.developers.apples.contracts;
+
+import com.r3.developers.apples.states.AppleStamp;
+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.transaction.UtxoLedgerTransaction;
+
+public class AppleStampContract implements Contract {
+
+ @Override
+ public void verify(UtxoLedgerTransaction transaction) {
+ // Extract the command from the transaction
+ // Verify the transaction according to the intention of the transaction
+ final Command command = transaction.getCommands().get(0);
+ if (command instanceof AppleCommands.Issue) {
+ AppleStamp output = transaction.getOutputStates(AppleStamp.class).get(0);
+ require(
+ transaction.getOutputContractStates().size() == 1,
+ "This transaction should only have one AppleStamp state as output"
+ );
+ require(
+ !output.getStampDesc().isBlank(),
+ "The output AppleStamp state should have clear description of the type of redeemable goods");
+ } else if (command instanceof AppleCommands.Redeem) {
+ // Transaction verification will happen in BasketOfApplesContract
+ } else {
+ //Unrecognised Command Type
+ throw new IllegalArgumentException(String.format("Incorrect type of AppleStamp commands: %s", command.getClass().toString()));
+ }
+ }
+
+ private void require(boolean asserted, String errorMessage) {
+ if (!asserted) {
+ throw new CordaRuntimeException(errorMessage);
+ }
+ }
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/BasketOfApplesContract.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/BasketOfApplesContract.java
new file mode 100644
index 0000000..b19802c
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/contracts/BasketOfApplesContract.java
@@ -0,0 +1,66 @@
+package com.r3.developers.apples.contracts;
+
+import com.r3.developers.apples.states.AppleStamp;
+import com.r3.developers.apples.states.BasketOfApples;
+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.transaction.UtxoLedgerTransaction;
+import java.util.List;
+
+public class BasketOfApplesContract implements Contract {
+
+ @Override
+ public void verify(UtxoLedgerTransaction transaction) {
+ // Extract the command from the transaction
+ final Command command = transaction.getCommands().get(0);
+
+ if (command instanceof AppleCommands.PackBasket) {
+ //Retrieve the output state of the transaction
+ BasketOfApples output = transaction.getOutputStates(BasketOfApples.class).get(0);
+ require(
+ transaction.getOutputContractStates().size() == 1,
+ "This transaction should only output one BasketOfApples state"
+ );
+ require(
+ !output.getDescription().isBlank(),
+ "The output BasketOfApples state should have clear description of Apple product"
+ );
+ require(
+ output.getWeight() > 0,
+ "The output BasketOfApples state should have non zero weight"
+ );
+ } else if (command instanceof AppleCommands.Redeem) {
+ require(
+ transaction.getInputContractStates().size() == 2,
+ "This transaction should consume two states"
+ );
+
+ // Retrieve the inputs to this transaction, which should be exactly one AppleStamp
+ // and one BasketOfApples
+ List stampInputs = transaction.getInputStates(AppleStamp.class);
+ List basketInputs = transaction.getInputStates(BasketOfApples.class);
+
+ require(
+ !stampInputs.isEmpty() && !basketInputs.isEmpty(),
+ "This transaction should have exactly one AppleStamp and one BasketOfApples input state"
+ );
+ require(
+ stampInputs.get(0).getIssuer().equals(basketInputs.get(0).getFarm()),
+ "The issuer of the Apple stamp should be the producing farm of this basket of apple"
+ );
+ require(
+ basketInputs.get(0).getWeight() > 0,
+ "The basket of apple has to weigh more than 0"
+ );
+ } else {
+ throw new IllegalArgumentException(String.format("Incorrect type of BasketOfApples commands: %s", command.getClass().toString()));
+ }
+ }
+
+ private void require(boolean asserted, String errorMessage) {
+ if (!asserted) {
+ throw new CordaRuntimeException(errorMessage);
+ }
+ }
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/AppleStamp.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/AppleStamp.java
new file mode 100644
index 0000000..61dfd09
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/AppleStamp.java
@@ -0,0 +1,47 @@
+package com.r3.developers.apples.states;
+
+import com.r3.developers.apples.contracts.AppleStampContract;
+import net.corda.v5.base.annotations.ConstructorForDeserialization;
+import net.corda.v5.ledger.utxo.BelongsToContract;
+import net.corda.v5.ledger.utxo.ContractState;
+import java.security.PublicKey;
+import java.util.*;
+
+@BelongsToContract(AppleStampContract.class)
+public class AppleStamp implements ContractState {
+ private UUID id;
+ private String stampDesc;
+ private PublicKey issuer;
+ private PublicKey holder;
+ private List participants;
+
+ @ConstructorForDeserialization
+ public AppleStamp(UUID id, String stampDesc, PublicKey issuer, PublicKey holder, List participants) {
+ this.id = id;
+ this.stampDesc = stampDesc;
+ this.issuer = issuer;
+ this.holder = holder;
+ this.participants = participants;
+ }
+
+ @Override
+ public List getParticipants() {
+ return participants;
+ }
+
+ public UUID getId() {
+ return this.id;
+ }
+
+ public String getStampDesc() {
+ return this.stampDesc;
+ }
+
+ public PublicKey getIssuer() {
+ return this.issuer;
+ }
+
+ public PublicKey getHolder() {
+ return this.holder;
+ }
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/BasketOfApples.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/BasketOfApples.java
new file mode 100644
index 0000000..67b1936
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/apples/states/BasketOfApples.java
@@ -0,0 +1,53 @@
+package com.r3.developers.apples.states;
+
+import com.r3.developers.apples.contracts.BasketOfApplesContract;
+import net.corda.v5.base.annotations.ConstructorForDeserialization;
+import net.corda.v5.ledger.utxo.BelongsToContract;
+import net.corda.v5.ledger.utxo.ContractState;
+import java.security.PublicKey;
+import java.util.List;
+
+@BelongsToContract(BasketOfApplesContract.class)
+public class BasketOfApples implements ContractState {
+ private String description;
+ private PublicKey farm;
+ private PublicKey owner;
+ private int weight;
+ private List participants;
+
+ @ConstructorForDeserialization
+ public BasketOfApples(
+ String description, PublicKey farm, PublicKey owner, int weight, List participants
+ ) {
+ this.description = description;
+ this.farm = farm;
+ this.owner = owner;
+ this.weight = weight;
+ this.participants = participants;
+ }
+
+ public List getParticipants() {
+ return participants;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public PublicKey getFarm() {
+ return this.farm;
+ }
+
+ public PublicKey getOwner() {
+ return this.owner;
+ }
+
+ public int getWeight() {
+ return this.weight;
+ }
+
+ public BasketOfApples changeOwner(PublicKey buyer) {
+ List participants = List.of(farm, buyer);
+ return new BasketOfApples(this.description, this.farm, buyer, this.weight, participants);
+ }
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/contracts/ChatContract.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/contracts/ChatContract.java
new file mode 100644
index 0000000..d2b661a
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/contracts/ChatContract.java
@@ -0,0 +1,72 @@
+package com.r3.developers.cordapptemplate.customeStateJSONandQuery.contracts;
+
+import com.r3.developers.cordapptemplate.customeStateJSONandQuery.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.transaction.UtxoLedgerTransaction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class ChatContract implements Contract {
+
+ private final static Logger log = LoggerFactory.getLogger(ChatContract.class);
+
+ // Use constants to hold the error messages
+ // This allows the tests to use them, meaning if they are updated you won't need to fix tests just because the wording was updated
+ static final String REQUIRE_SINGLE_COMMAND = "Require a single command.";
+ static final String UNKNOWN_COMMAND = "Unsupported command";
+ static final String OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS = "The output state should have two and only two participants.";
+ static final String TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS = "The transaction should have been signed by both participants.";
+
+ static final String CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES = "When command is Create there should be no input states.";
+ static final String CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Create there should be one and only one output state.";
+
+ static final String UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE = "When command is Update there should be one and only one input state.";
+ static final String UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE = "When command is Update there should be one and only one output state.";
+ static final String UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE = "When command is Update id must not change.";
+ static final String UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE = "When command is Update chatName must not change.";
+ static final String UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE = "When command is Update participants must not change.";
+
+ public static class Create implements Command { }
+ public static class Update implements Command { }
+
+ @Override
+ public void verify(UtxoLedgerTransaction transaction) {
+
+ requireThat( transaction.getCommands().size() == 1, REQUIRE_SINGLE_COMMAND);
+ Command command = transaction.getCommands().get(0);
+
+ ChatState output = transaction.getOutputStates(ChatState.class).get(0);
+
+ requireThat(output.getParticipants().size() == 2, OUTPUT_STATE_SHOULD_ONLY_HAVE_TWO_PARTICIPANTS);
+ requireThat(transaction.getSignatories().containsAll(output.getParticipants()), TRANSACTION_SHOULD_BE_SIGNED_BY_ALL_PARTICIPANTS);
+
+ if(command.getClass() == Create.class) {
+ requireThat(transaction.getInputContractStates().isEmpty(), CREATE_COMMAND_SHOULD_HAVE_NO_INPUT_STATES);
+ requireThat(transaction.getOutputContractStates().size() == 1, CREATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE);
+ }
+ else if(command.getClass() == Update.class) {
+ requireThat(transaction.getInputContractStates().size() == 1, UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_INPUT_STATE);
+ requireThat(transaction.getOutputContractStates().size() == 1, UPDATE_COMMAND_SHOULD_HAVE_ONLY_ONE_OUTPUT_STATE);
+
+ ChatState input = transaction.getInputStates(ChatState.class).get(0);
+ requireThat(input.getId().equals(output.getId()), UPDATE_COMMAND_ID_SHOULD_NOT_CHANGE);
+ requireThat(input.getChatName().equals(output.getChatName()), UPDATE_COMMAND_CHATNAME_SHOULD_NOT_CHANGE);
+ requireThat(
+ input.getParticipants().containsAll(output.getParticipants()) &&
+ output.getParticipants().containsAll(input.getParticipants()),
+ UPDATE_COMMAND_PARTICIPANTS_SHOULD_NOT_CHANGE);
+ }
+ else {
+ throw new CordaRuntimeException(UNKNOWN_COMMAND);
+ }
+ }
+
+ private void requireThat(boolean asserted, String errorMessage) {
+ if(!asserted) {
+ throw new CordaRuntimeException("Failed requirement: " + errorMessage);
+ }
+ }
+}
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatJsonFactory.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatJsonFactory.java
new file mode 100644
index 0000000..77ff965
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatJsonFactory.java
@@ -0,0 +1,37 @@
+package com.r3.developers.cordapptemplate.customeStateJSONandQuery.states;
+
+import net.corda.v5.application.marshalling.JsonMarshallingService;
+import net.corda.v5.ledger.utxo.query.json.ContractStateVaultJsonFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// This class represents a custom JSON factory for serializing/deserializing ChatState objects to JSON format after each use.
+public class ChatJsonFactory implements ContractStateVaultJsonFactory {
+
+ // This function specifies the type of state this factory is responsible for.
+ @Override
+ public Class getStateType() {
+ return ChatState.class;
+ }
+
+ // Constants used for JSON key names.
+ private static final String ID = "Id";
+ private static final String CHATNAME = "chatName";
+ private static final String MESSAGE = "messageContent";
+ private static final String MESSAGEFROM = "messageContentFrom";
+
+ // This function creates a JSON representation of a ChatState object.
+ @Override
+ public String create(ChatState state, JsonMarshallingService jsonMarshallingService) {
+ // Constructs a map representing the ChatState object using the provided constants.
+ Map jsonMap = new HashMap<>();
+ jsonMap.put(ID, state.getId());
+ jsonMap.put(CHATNAME, state.getChatName());
+ jsonMap.put(MESSAGE, state.getMessage());
+ jsonMap.put(MESSAGEFROM, state.getMessageFrom());
+
+ // Uses the JsonMarshallingService's format() function to serialize the map to JSON.
+ return jsonMarshallingService.format(jsonMap);
+ }
+}
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatState.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatState.java
new file mode 100644
index 0000000..9403516
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/ChatState.java
@@ -0,0 +1,55 @@
+package com.r3.developers.cordapptemplate.customeStateJSONandQuery.states;
+
+import com.r3.developers.cordapptemplate.customeStateJSONandQuery.contracts.ChatContract;
+import net.corda.v5.base.annotations.ConstructorForDeserialization;
+import net.corda.v5.base.types.MemberX500Name;
+import net.corda.v5.ledger.utxo.BelongsToContract;
+import net.corda.v5.ledger.utxo.ContractState;
+
+import java.security.PublicKey;
+import java.util.*;
+
+@BelongsToContract(ChatContract.class)
+public class ChatState implements ContractState {
+
+ private UUID id;
+ private String chatName;
+ private MemberX500Name messageFrom;
+ private String message;
+ public List participants;
+
+ // Allows serialisation and to use a specified UUID.
+ @ConstructorForDeserialization
+ public ChatState(UUID id,
+ String chatName,
+ MemberX500Name messageFrom,
+ String message,
+ List participants) {
+ this.id = id;
+ this.chatName = chatName;
+ this.messageFrom = messageFrom;
+ this.message = message;
+ this.participants = participants;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+ public String getChatName() {
+ return chatName;
+ }
+ public MemberX500Name getMessageFrom() {
+ return messageFrom;
+ }
+ public String getMessage() {
+ return message;
+ }
+
+ public List getParticipants() {
+ return participants;
+ }
+
+ public ChatState updateMessage(MemberX500Name name, String message) {
+ return new ChatState(id, chatName, name, message, participants);
+ }
+}
\ No newline at end of file
diff --git a/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/CustomChatQuery.java b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/CustomChatQuery.java
new file mode 100644
index 0000000..cf9efcc
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/contracts/src/main/java/com/r3/developers/cordapptemplate/customeStateJSONandQuery/states/CustomChatQuery.java
@@ -0,0 +1,25 @@
+package com.r3.developers.cordapptemplate.customeStateJSONandQuery.states;
+
+import net.corda.v5.ledger.utxo.query.VaultNamedQueryFactory;
+import net.corda.v5.ledger.utxo.query.registration.VaultNamedQueryBuilderFactory;
+import org.jetbrains.annotations.NotNull;
+
+public class CustomChatQuery implements VaultNamedQueryFactory {
+
+
+ @Override
+ public void create(@NotNull VaultNamedQueryBuilderFactory vaultNamedQueryBuilderFactory) {
+
+ vaultNamedQueryBuilderFactory.create("GET_ALL_MSG")
+ .whereJson(
+ "WHERE visible_states.custom_representation ? 'com.r3.developers.cordapptemplate.utxoexample.states.ChatState' "
+ )
+ .register();
+
+ vaultNamedQueryBuilderFactory.create("GET_MSG_FROM")
+ .whereJson(
+ "WHERE visible_states.custom_representation -> 'com.r3.developers.cordapptemplate.utxoexample.states.ChatState' ->> 'messageContentFrom' = :nameOfSender"
+ )
+ .register();
+ }
+}
diff --git a/java-samples/customeStateJSONandQuery/gradle.properties b/java-samples/customeStateJSONandQuery/gradle.properties
new file mode 100644
index 0000000..dc9c265
--- /dev/null
+++ b/java-samples/customeStateJSONandQuery/gradle.properties
@@ -0,0 +1,73 @@
+kotlin.code.style=official
+
+# Specify the version of the Corda-API to use.
+# This needs to match the version supported by the Corda Cluster the CorDapp will run on.
+cordaApiVersion=5.2.0.52
+
+# Specify the version of the notary plugins to use.
+# Currently packaged as part of corda-runtime-os, so should be set to a corda-runtime-os version.
+cordaNotaryPluginsVersion=5.2.0.0
+
+# Specify the version of the cordapp-cpb and cordapp-cpk plugins
+cordaPluginsVersion=7.0.4
+
+# Specify the version of the Corda runtime Gradle plugin to use
+cordaGradlePluginVersion=5.2.0.0
+
+# Specify the name of the workflows module
+# This will be the name of the generated cpk and cpb files
+workflowsModule=workflows
+
+# Specify the name of the contracts module
+# This will be the name of the generated cpk and cpb files
+contractsModule=contracts
+
+# Specify the location of where Corda 5 binaries can be downloaded
+# Relative path from user.home
+cordaBinariesDirectory = .corda/corda5
+
+# Specify the location of where Corda 5 CLI binaries can be downloaded
+# Relative path from user.home
+cordaCliBinariesDirectory = .corda/cli
+
+# Metadata for the CorDapp.
+cordappLicense="Apache License, Version 2.0"
+cordappVendorName="R3"
+
+# For the time being this just needs to be set to a dummy value.
+platformVersion = 999
+
+# Version of Kotlin to use.
+# We recommend using a version close to that used by Corda-API.
+kotlinVersion = 1.7.21
+
+# Do not use default dependencies.
+kotlin.stdlib.default.dependency=false
+
+# Test Tooling Dependency Versions
+junitVersion = 5.10.0
+mockitoKotlinVersion=4.0.0
+mockitoVersion=4.6.1
+hamcrestVersion=2.2
+assertjVersion = 3.24.1
+contractTestingVersion=1.0.0-beta-+
+jacksonVersion=2.15.2
+slf4jVersion=1.7.36
+
+# Specify the maximum amount of time allowed for the CPI upload
+# As your CorDapp grows you might need to increase this
+# Value is in milliseconds
+cpiUploadDefault=10000
+
+# Specify the length of time, in milliseconds, that Corda waits for an individual event to process.
+# Keep at -1 to use the default. Refer to the Corda Api Docs for the exact value.
+processorTimeout=-1
+
+# Specify the maximum amount of time allowed to check all vNodes are registered
+# Value is in milliseconds
+vnodeRegistrationTimeoutDefault=30000
+
+# Specify if you want to run the contracts and workflows tests as part of the corda-runtime-plugin-cordapp > buildCpis task
+# False by default, will execute the tests every time you stand the template up - gives extra protection
+# Set to true to skip the tests, making the launching process quicker. You will be responsible for running workflow tests yourself
+skipTestsDuringBuildCpis=false
diff --git a/java-samples/customeStateJSONandQuery/gradle/wrapper/gradle-wrapper.jar b/java-samples/customeStateJSONandQuery/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..033e24c4cdf41af1ab109bc7f253b2b887023340
GIT binary patch
literal 63375
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≫L+crb^Bz
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