Skip to content

Commit

Permalink
Java off-chain data store sample using Fabric Gateway (#749)
Browse files Browse the repository at this point in the history
Also minor implementation changes to TypeScript sample for better consistency between implementations.

Signed-off-by: Mark S. Lewis <mark_lewis@uk.ibm.com>
  • Loading branch information
bestbeforetoday authored May 26, 2022
1 parent 94867dd commit 636a273
Show file tree
Hide file tree
Showing 40 changed files with 1,930 additions and 62 deletions.
11 changes: 11 additions & 0 deletions ci/scripts/run-test-network-basic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,14 @@ SIMULATED_FAILURE_COUNT=1 npm start getAllAssets transact getAllAssets listen
SIMULATED_FAILURE_COUNT=1 npm start listen
popd
stopNetwork

# Run off-chain data Java application
createNetwork
print "Initializing Typescript off-chain data application"
pushd ../off_chain_data/application-java
rm -f app/checkpoint.json app/store.log
print "Running the output app"
SIMULATED_FAILURE_COUNT=1 ./gradlew run --quiet --args='getAllAssets transact getAllAssets listen'
SIMULATED_FAILURE_COUNT=1 ./gradlew run --quiet --args=listen
popd
stopNetwork
40 changes: 30 additions & 10 deletions off_chain_data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ This sample uses the block event listening capability of the [Fabric Gateway cli

The client application provides several "commands" that can be invoked using the command-line:

- **getAllAssets**: Retrieve the current details of all assets recorded on the ledger. See `application-typescript/src/getAllAssets.ts`.
- **listen**: Listen for block events, and use them to replicate ledger updates in an off-chain data store. See `application-typescript/src/listen.ts`.
- **transact**: Submit a set of transactions to create, modify and delete assets. See `application-typescript/src/transact.ts`.

To keep the sample code concise, the **listen** command writes ledger updates to an output file named `store.log` in the current working directory. A real implementation could write ledger updates directly to an off-chain data store of choice. You can inspect the information captured in this file as you run the sample.
- **getAllAssets**: Retrieve the current details of all assets recorded on the ledger. See:
- TypeScript: `application-typescript/src/getAllAssets.ts`
- Java: `application-java/app/src/main/java/GetAllAssets.java`
- **listen**: Listen for block events, and use them to replicate ledger updates in an off-chain data store. See:
- TypeScript: `application-typescript/src/listen.ts`
- Java: `application-java/app/src/main/java/Transact.java`
- **transact**: Submit a set of transactions to create, modify and delete assets. See:
- TypeScript: `application-typescript/src/transact.ts`
- Java: `application-java/app/src/main/java/Transact.java`

To keep the sample code concise, the **listen** command writes ledger updates to an output file named `store.log` in the current working directory (which for the Java sample is the `application-java/app` directory). A real implementation could write ledger updates directly to an off-chain data store of choice. You can inspect the information captured in this file as you run the sample.

Note that the **listen** command is is restartable and will resume event listening after the last successfully processed block / transaction. This is achieved using a checkpointer to persist the current listening position. Checkpoint state is persisted to a file named `checkpoint.json` in the current working directory. If no checkpoint state is present, event listening begins from the start of the ledger (block number zero).

Expand All @@ -34,13 +40,13 @@ The Fabric test network is used to deploy and run this sample. Follow these step

1. Create the test network and a channel (from the `test-network` folder).

```
```bash
./network.sh up createChannel -c mychannel -ca
```

1. Deploy one of the asset-transfer-basic smart contract implementations (from the `test-network` folder).

```
```bash
# To deploy the TypeScript chaincode implementation
./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript

Expand All @@ -53,31 +59,45 @@ The Fabric test network is used to deploy and run this sample. Follow these step

1. Populate the ledger with some assets and use eventing to capture ledger updates (from the `off_chain_data` folder).

```
```bash
# To run the TypeScript sample application
cd application-typescript
npm install
npm start transact listen

# To run the Java sample application
cd application-java
./gradlew run --quiet --args='transact listen'
```

1. Interrupt the listener process using **Control-C**.

1. View the current world state of the blockchain (from the `off_chain_data` folder). You may want to compare the results to the ledger updates captured by the listener in the `store.log` file.

```
```bash
# To run the TypeScript sample application
cd application-typescript
npm --silent start getAllAssets

# To run the Java sample application
cd application-java
./gradlew run --quiet --args=getAllAssets
```

1. Make some more ledger updates, then observe listener resume capability (from the `off_chain_data` folder). Note from the transaction IDs recorded to the console that the listener resumes from exactly after the last successfully processed transaction.

```
```bash
# To run the TypeScript sample application
cd application-typescript
npm start transact
SIMULATED_FAILURE_COUNT=5 npm start listen
npm start listen

# To run the Java sample application
cd application-java
./gradlew run --quiet --args=transact
SIMULATED_FAILURE_COUNT=5 ./gradlew run --quiet --args=listen
./gradlew run --quiet --args=listen
```

1. Interrupt the listener process using **Control-C**.
Expand Down
12 changes: 12 additions & 0 deletions off_chain_data/application-java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build

# IntelliJ IDEA files
.idea

# Files generated by the application at runtime
checkpoint.json
store.log
38 changes: 38 additions & 0 deletions off_chain_data/application-java/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

plugins {
id 'application' // Support for building a CLI application in Java.
id 'checkstyle'
}

repositories {
mavenCentral()
maven {
url 'https://hyperledger-fabric.jfrog.io/artifactory/fabric-maven'
}
}

dependencies {
// implementation 'com.google.guava:guava:30.1.1-jre'
implementation 'io.grpc:grpc-netty-shaded:1.46.0'
implementation 'org.hyperledger.fabric:fabric-gateway:1.0.2-dev-20220518-1'
implementation 'com.google.code.gson:gson:2.9.0'
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}

checkstyle {
toolVersion '10.2'
}

application {
mainClass = 'App'
}
78 changes: 78 additions & 0 deletions off_chain_data/application-java/app/src/main/java/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import io.grpc.ManagedChannel;

public final class App {
private static final long SHUTDOWN_TIMEOUT_SECONDS = 3;
private static final Map<String, Command> COMMANDS = Map.ofEntries(
Map.entry("getAllAssets", new GetAllAssets()),
Map.entry("transact", new Transact()),
Map.entry("listen", new Listen())
);

private final List<String> commandNames;
private final PrintStream out = System.out;

App(final String[] args) {
commandNames = List.of(args);
}

public void run() throws Exception {
List<Command> commands = getCommands();
ManagedChannel grpcChannel = Connections.newGrpcConnection();
try {
for (Command command : commands) {
command.run(grpcChannel);
}
} finally {
grpcChannel.shutdownNow().awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
}

private List<Command> getCommands() {
List<Command> commands = commandNames.stream()
.map(name -> {
Command command = COMMANDS.get(name);
if (command == null) {
printUsage();
throw new IllegalArgumentException("Unknown command: " + name);
}
return command;
})
.collect(Collectors.toList());

if (commands.isEmpty()) {
printUsage();
throw new IllegalArgumentException("Missing command");
}

return commands;
}

private void printUsage() {
out.println("Arguments: <command1> [<command2> ...]");
out.println("Available commands: " + COMMANDS.keySet());
}

public static void main(final String[] args) {
try {
new App(args).run();
} catch (ExpectedException e) {
e.printStackTrace(System.out);
} catch (Exception e) {
System.err.print("\nUnexpected application error: ");
e.printStackTrace();
System.exit(1);
}
}
}
57 changes: 57 additions & 0 deletions off_chain_data/application-java/app/src/main/java/Asset.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Object representation of an asset. Note that the private member variable names don't follow the normal Java naming
* convention as they map to the JSON format expected by the smart contract.
*/
public final class Asset {
private final String ID; // checkstyle:ignore-line:MemberName
private String Color; // checkstyle:ignore-line:MemberName
private int Size; // checkstyle:ignore-line:MemberName
private String Owner; // checkstyle:ignore-line:MemberName
private int AppraisedValue; // checkstyle:ignore-line:MemberName

public Asset(final String id) {
this.ID = id;
}

public String getId() {
return ID;
}

public String getColor() {
return Color;
}

public void setColor(final String color) {
this.Color = color;
}

public int getSize() {
return Size;
}

public void setSize(final int size) {
this.Size = size;
}

public String getOwner() {
return Owner;
}

public void setOwner(final String owner) {
this.Owner = owner;
}

public int getAppraisedValue() {
return AppraisedValue;
}

public void setAppraisedValue(final int appraisedValue) {
this.AppraisedValue = appraisedValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

import java.nio.charset.StandardCharsets;
import java.util.List;

import com.google.gson.Gson;
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.SubmitException;

public final class AssetTransferBasic {
private static final Gson GSON = new Gson();
private final Contract contract;

public AssetTransferBasic(final Contract contract) {
this.contract = contract;
}

public void createAsset(final Asset asset) throws EndorseException, CommitException, SubmitException, CommitStatusException {
contract.submitTransaction(
"CreateAsset",
asset.getId(),
asset.getColor(),
Integer.toString(asset.getSize()),
asset.getOwner(),
Integer.toString(asset.getAppraisedValue())
);
}

public String transferAsset(final String id, final String newOwner) throws EndorseException, CommitException, SubmitException, CommitStatusException {
byte[] resultBytes = contract.submitTransaction("TransferAsset", id, newOwner);
return new String(resultBytes, StandardCharsets.UTF_8);
}

public void deleteAsset(final String id) throws EndorseException, CommitException, SubmitException, CommitStatusException {
contract.submitTransaction("DeleteAsset", id);
}

public List<Asset> getAllAssets() throws EndorseException, CommitException, SubmitException, CommitStatusException {
byte[] resultBytes = contract.submitTransaction("GetAllAssets");
String resultJson = new String(resultBytes, StandardCharsets.UTF_8);
Asset[] assets = GSON.fromJson(resultJson, Asset[].class);
return assets != null ? List.of(assets) : List.of();
}
}
Loading

0 comments on commit 636a273

Please sign in to comment.