diff --git a/chaincode/fabcar/java/.gitignore b/chaincode/fabcar/java/.gitignore
new file mode 100644
index 0000000000..7005557f93
--- /dev/null
+++ b/chaincode/fabcar/java/.gitignore
@@ -0,0 +1,61 @@
+
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# Gradle
+.gradle
+/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+# Eclipse files
+.project
+.classpath
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+.externalToolBuilders/
+*.launch
diff --git a/chaincode/fabcar/java/README.md b/chaincode/fabcar/java/README.md
new file mode 100644
index 0000000000..581c0a4cec
--- /dev/null
+++ b/chaincode/fabcar/java/README.md
@@ -0,0 +1,14 @@
+# Java FabCar contract sample
+
+The directions for using this sample are documented in the Hyperledger Fabric
+[Writing Your First Application](https://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html) tutorial.
+
+The tutorial is based on JavaScript, however the same concepts are applicable when using Java.
+
+To install and instantiate the Java version of `FabCar`, use the following command instead of the command shown in the [Launch the network](https://hyperledger-fabric.readthedocs.io/en/release-1.4/write_first_app.html#launch-the-network) section of the tutorial:
+
+```
+./startFabric.sh javascript
+```
+
+*NOTE:* After navigating to the documentation, choose the documentation version that matches your version of Fabric
diff --git a/chaincode/fabcar/java/build.gradle b/chaincode/fabcar/java/build.gradle
new file mode 100644
index 0000000000..37d2b3ae10
--- /dev/null
+++ b/chaincode/fabcar/java/build.gradle
@@ -0,0 +1,81 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+plugins {
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '2.0.4'
+ id 'java-library'
+ id 'jacoco'
+}
+
+group 'org.hyperledger.fabric.samples'
+version '1.0-SNAPSHOT'
+
+dependencies {
+ implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.0.0-SNAPSHOT'
+ implementation 'com.owlike:genson:1.5'
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
+ testImplementation 'org.assertj:assertj-core:3.11.1'
+ testImplementation 'org.mockito:mockito-core:2.+'
+}
+
+repositories {
+ maven {
+ url "https://nexus.hyperledger.org/content/repositories/snapshots/"
+ }
+ jcenter()
+ maven {
+ url 'https://jitpack.io'
+ }
+}
+
+checkstyle {
+ toolVersion '8.21'
+ configFile file("config/checkstyle/checkstyle.xml")
+}
+
+checkstyleMain {
+ source ='src/main/java'
+}
+
+checkstyleTest {
+ source ='src/test/java'
+}
+
+shadowJar {
+ baseName = 'chaincode'
+ version = null
+ classifier = null
+ manifest {
+ attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
+ }
+}
+
+jacocoTestCoverageVerification {
+ afterEvaluate {
+ classDirectories = files(classDirectories.files.collect {
+ fileTree(dir: it, exclude: [
+ 'org/hyperledger/fabric/samples/fabcar/Start.*'
+ ])
+ })
+ }
+ violationRules {
+ rule {
+ limit {
+ minimum = 1.0
+ }
+ }
+ }
+
+ finalizedBy jacocoTestReport
+}
+
+test {
+ useJUnitPlatform()
+ testLogging {
+ events "passed", "skipped", "failed"
+ }
+}
+
+check.dependsOn jacocoTestCoverageVerification
diff --git a/chaincode/fabcar/java/config/checkstyle/checkstyle.xml b/chaincode/fabcar/java/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..94317559e7
--- /dev/null
+++ b/chaincode/fabcar/java/config/checkstyle/checkstyle.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chaincode/fabcar/java/config/checkstyle/suppressions.xml b/chaincode/fabcar/java/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..8c44b0a039
--- /dev/null
+++ b/chaincode/fabcar/java/config/checkstyle/suppressions.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/chaincode/fabcar/java/gradle/wrapper/gradle-wrapper.jar b/chaincode/fabcar/java/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..29953ea141
Binary files /dev/null and b/chaincode/fabcar/java/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/chaincode/fabcar/java/gradle/wrapper/gradle-wrapper.properties b/chaincode/fabcar/java/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..e0b3fb8d70
--- /dev/null
+++ b/chaincode/fabcar/java/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/chaincode/fabcar/java/gradlew b/chaincode/fabcar/java/gradlew
new file mode 100755
index 0000000000..cccdd3d517
--- /dev/null
+++ b/chaincode/fabcar/java/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/chaincode/fabcar/java/gradlew.bat b/chaincode/fabcar/java/gradlew.bat
new file mode 100644
index 0000000000..e95643d6a2
--- /dev/null
+++ b/chaincode/fabcar/java/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/chaincode/fabcar/java/settings.gradle b/chaincode/fabcar/java/settings.gradle
new file mode 100644
index 0000000000..4d04f71e08
--- /dev/null
+++ b/chaincode/fabcar/java/settings.gradle
@@ -0,0 +1,5 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+rootProject.name = 'java-chaincode-bootstrap'
diff --git a/chaincode/fabcar/java/src/main/java/org/hyperledger/fabric/samples/fabcar/Car.java b/chaincode/fabcar/java/src/main/java/org/hyperledger/fabric/samples/fabcar/Car.java
new file mode 100644
index 0000000000..a67204a746
--- /dev/null
+++ b/chaincode/fabcar/java/src/main/java/org/hyperledger/fabric/samples/fabcar/Car.java
@@ -0,0 +1,79 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.fabcar;
+
+import java.util.Objects;
+
+import org.hyperledger.fabric.contract.annotation.DataType;
+import org.hyperledger.fabric.contract.annotation.Property;
+
+import com.owlike.genson.annotation.JsonProperty;
+
+@DataType()
+public final class Car {
+
+ @Property()
+ private final String make;
+
+ @Property()
+ private final String model;
+
+ @Property()
+ private final String color;
+
+ @Property()
+ private final String owner;
+
+ public String getMake() {
+ return make;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public Car(@JsonProperty("make") final String make, @JsonProperty("model") final String model,
+ @JsonProperty("color") final String color, @JsonProperty("owner") final String owner) {
+ this.make = make;
+ this.model = model;
+ this.color = color;
+ this.owner = owner;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if ((obj == null) || (getClass() != obj.getClass())) {
+ return false;
+ }
+
+ Car other = (Car) obj;
+
+ return Objects.deepEquals(new String[] {getMake(), getModel(), getColor(), getOwner()},
+ new String[] {other.getMake(), other.getModel(), other.getColor(), other.getOwner()});
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getMake(), getModel(), getColor(), getOwner());
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + " [make=" + make + ", model="
+ + model + ", color=" + color + ", owner=" + owner + "]";
+ }
+}
diff --git a/chaincode/fabcar/java/src/main/java/org/hyperledger/fabric/samples/fabcar/FabCar.java b/chaincode/fabcar/java/src/main/java/org/hyperledger/fabric/samples/fabcar/FabCar.java
new file mode 100644
index 0000000000..a4e8b35394
--- /dev/null
+++ b/chaincode/fabcar/java/src/main/java/org/hyperledger/fabric/samples/fabcar/FabCar.java
@@ -0,0 +1,190 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.fabcar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.hyperledger.fabric.contract.Context;
+import org.hyperledger.fabric.contract.ContractInterface;
+import org.hyperledger.fabric.contract.annotation.Contact;
+import org.hyperledger.fabric.contract.annotation.Contract;
+import org.hyperledger.fabric.contract.annotation.Default;
+import org.hyperledger.fabric.contract.annotation.Info;
+import org.hyperledger.fabric.contract.annotation.License;
+import org.hyperledger.fabric.contract.annotation.Transaction;
+import org.hyperledger.fabric.shim.ChaincodeException;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.KeyValue;
+import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
+
+import com.owlike.genson.Genson;
+
+/**
+ * Java implementation of the Fabric Car Contract described in the Writing Your
+ * First Application tutorial
+ */
+@Contract(
+ name = "FabCar",
+ info = @Info(
+ title = "FabCar contract",
+ description = "The hyperlegendary car contract",
+ version = "0.0.1-SNAPSHOT",
+ license = @License(
+ name = "Apache 2.0 License",
+ url = "http://www.apache.org/licenses/LICENSE-2.0.html"),
+ contact = @Contact(
+ email = "f.carr@example.com",
+ name = "F Carr",
+ url = "https://hyperledger.example.com")))
+@Default
+public final class FabCar implements ContractInterface {
+
+ private final Genson genson = new Genson();
+
+ private enum FabCarErrors {
+ CAR_NOT_FOUND,
+ CAR_ALREADY_EXISTS
+ }
+
+ /**
+ * Retrieves a car with the specified key from the ledger.
+ *
+ * @param ctx the transaction context
+ * @param key the key
+ * @return the Car found on the ledger if there was one
+ */
+ @Transaction()
+ public Car queryCar(final Context ctx, final String key) {
+ ChaincodeStub stub = ctx.getStub();
+ String carState = stub.getStringState(key);
+
+ if (carState.isEmpty()) {
+ String errorMessage = String.format("Car %s does not exist", key);
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage, FabCarErrors.CAR_NOT_FOUND.toString());
+ }
+
+ Car car = genson.deserialize(carState, Car.class);
+
+ return car;
+ }
+
+ /**
+ * Creates some initial Cars on the ledger.
+ *
+ * @param ctx the transaction context
+ */
+ @Transaction()
+ public void initLedger(final Context ctx) {
+ ChaincodeStub stub = ctx.getStub();
+
+ String[] carData = {
+ "{ \"make\": \"Toyota\", \"model\": \"Prius\", \"color\": \"blue\", \"owner\": \"Tomoko\" }",
+ "{ \"make\": \"Ford\", \"model\": \"Mustang\", \"color\": \"red\", \"owner\": \"Brad\" }",
+ "{ \"make\": \"Hyundai\", \"model\": \"Tucson\", \"color\": \"green\", \"owner\": \"Jin Soo\" }",
+ "{ \"make\": \"Volkswagen\", \"model\": \"Passat\", \"color\": \"yellow\", \"owner\": \"Max\" }",
+ "{ \"make\": \"Tesla\", \"model\": \"S\", \"color\": \"black\", \"owner\": \"Adrian\" }",
+ "{ \"make\": \"Peugeot\", \"model\": \"205\", \"color\": \"purple\", \"owner\": \"Michel\" }",
+ "{ \"make\": \"Chery\", \"model\": \"S22L\", \"color\": \"white\", \"owner\": \"Aarav\" }",
+ "{ \"make\": \"Fiat\", \"model\": \"Punto\", \"color\": \"violet\", \"owner\": \"Pari\" }",
+ "{ \"make\": \"Tata\", \"model\": \"nano\", \"color\": \"indigo\", \"owner\": \"Valeria\" }",
+ "{ \"make\": \"Holden\", \"model\": \"Barina\", \"color\": \"brown\", \"owner\": \"Shotaro\" }"
+ };
+
+ for (int i = 0; i < carData.length; i++) {
+ String key = String.format("CAR%03d", i);
+
+ Car car = genson.deserialize(carData[i], Car.class);
+ String carState = genson.serialize(car);
+ stub.putStringState(key, carState);
+ }
+ }
+
+ /**
+ * Creates a new car on the ledger.
+ *
+ * @param ctx the transaction context
+ * @param key the key for the new car
+ * @param make the make of the new car
+ * @param model the model of the new car
+ * @param color the color of the new car
+ * @param owner the owner of the new car
+ * @return the created Car
+ */
+ @Transaction()
+ public Car createCar(final Context ctx, final String key, final String make, final String model,
+ final String color, final String owner) {
+ ChaincodeStub stub = ctx.getStub();
+
+ String carState = stub.getStringState(key);
+ if (!carState.isEmpty()) {
+ String errorMessage = String.format("Car %s already exists", key);
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage, FabCarErrors.CAR_ALREADY_EXISTS.toString());
+ }
+
+ Car car = new Car(make, model, color, owner);
+ carState = genson.serialize(car);
+ stub.putStringState(key, carState);
+
+ return car;
+ }
+
+ /**
+ * Retrieves every car between CAR0 and CAR999 from the ledger.
+ *
+ * @param ctx the transaction context
+ * @return array of Cars found on the ledger
+ */
+ @Transaction()
+ public Car[] queryAllCars(final Context ctx) {
+ ChaincodeStub stub = ctx.getStub();
+
+ final String startKey = "CAR0";
+ final String endKey = "CAR999";
+ List cars = new ArrayList();
+
+ QueryResultsIterator results = stub.getStateByRange(startKey, endKey);
+
+ for (KeyValue result: results) {
+ Car car = genson.deserialize(result.getStringValue(), Car.class);
+ cars.add(car);
+ }
+
+ Car[] response = cars.toArray(new Car[cars.size()]);
+
+ return response;
+ }
+
+ /**
+ * Changes the owner of a car on the ledger.
+ *
+ * @param ctx the transaction context
+ * @param key the key
+ * @param newOwner the new owner
+ * @return the updated Car
+ */
+ @Transaction()
+ public Car changeCarOwner(final Context ctx, final String key, final String newOwner) {
+ ChaincodeStub stub = ctx.getStub();
+
+ String carState = stub.getStringState(key);
+
+ if (carState.isEmpty()) {
+ String errorMessage = String.format("Car %s does not exist", key);
+ System.out.println(errorMessage);
+ throw new ChaincodeException(errorMessage, FabCarErrors.CAR_NOT_FOUND.toString());
+ }
+
+ Car car = genson.deserialize(carState, Car.class);
+
+ Car newCar = new Car(car.getMake(), car.getModel(), car.getColor(), newOwner);
+ String newCarState = genson.serialize(newCar);
+ stub.putStringState(key, newCarState);
+
+ return newCar;
+ }
+}
diff --git a/chaincode/fabcar/java/src/test/java/org/hyperledger/fabric/samples/fabcar/CarTest.java b/chaincode/fabcar/java/src/test/java/org/hyperledger/fabric/samples/fabcar/CarTest.java
new file mode 100644
index 0000000000..5c7b4fcff6
--- /dev/null
+++ b/chaincode/fabcar/java/src/test/java/org/hyperledger/fabric/samples/fabcar/CarTest.java
@@ -0,0 +1,74 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.fabcar;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+public final class CarTest {
+
+ @Nested
+ class Equality {
+
+ @Test
+ public void isReflexive() {
+ Car car = new Car("Toyota", "Prius", "blue", "Tomoko");
+
+ assertThat(car).isEqualTo(car);
+ }
+
+ @Test
+ public void isSymmetric() {
+ Car carA = new Car("Toyota", "Prius", "blue", "Tomoko");
+ Car carB = new Car("Toyota", "Prius", "blue", "Tomoko");
+
+ assertThat(carA).isEqualTo(carB);
+ assertThat(carB).isEqualTo(carA);
+ }
+
+ @Test
+ public void isTransitive() {
+ Car carA = new Car("Toyota", "Prius", "blue", "Tomoko");
+ Car carB = new Car("Toyota", "Prius", "blue", "Tomoko");
+ Car carC = new Car("Toyota", "Prius", "blue", "Tomoko");
+
+ assertThat(carA).isEqualTo(carB);
+ assertThat(carB).isEqualTo(carC);
+ assertThat(carA).isEqualTo(carC);
+ }
+
+ @Test
+ public void handlesInequality() {
+ Car carA = new Car("Toyota", "Prius", "blue", "Tomoko");
+ Car carB = new Car("Ford", "Mustang", "red", "Brad");
+
+ assertThat(carA).isNotEqualTo(carB);
+ }
+
+ @Test
+ public void handlesOtherObjects() {
+ Car carA = new Car("Toyota", "Prius", "blue", "Tomoko");
+ String carB = "not a car";
+
+ assertThat(carA).isNotEqualTo(carB);
+ }
+
+ @Test
+ public void handlesNull() {
+ Car car = new Car("Toyota", "Prius", "blue", "Tomoko");
+
+ assertThat(car).isNotEqualTo(null);
+ }
+ }
+
+ @Test
+ public void toStringIdentifiesCar() {
+ Car car = new Car("Toyota", "Prius", "blue", "Tomoko");
+
+ assertThat(car.toString()).isEqualTo("Car@61a77e4f [make=Toyota, model=Prius, color=blue, owner=Tomoko]");
+ }
+}
diff --git a/chaincode/fabcar/java/src/test/java/org/hyperledger/fabric/samples/fabcar/FabCarTest.java b/chaincode/fabcar/java/src/test/java/org/hyperledger/fabric/samples/fabcar/FabCarTest.java
new file mode 100644
index 0000000000..0579a5380c
--- /dev/null
+++ b/chaincode/fabcar/java/src/test/java/org/hyperledger/fabric/samples/fabcar/FabCarTest.java
@@ -0,0 +1,262 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.hyperledger.fabric.samples.fabcar;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.ThrowableAssert.catchThrowable;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.hyperledger.fabric.contract.Context;
+import org.hyperledger.fabric.shim.ChaincodeException;
+import org.hyperledger.fabric.shim.ChaincodeStub;
+import org.hyperledger.fabric.shim.ledger.KeyValue;
+import org.hyperledger.fabric.shim.ledger.QueryResultsIterator;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.InOrder;
+
+public final class FabCarTest {
+
+ private final class MockKeyValue implements KeyValue {
+
+ private final String key;
+ private final String value;
+
+ MockKeyValue(final String key, final String value) {
+ super();
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public String getKey() {
+ return this.key;
+ }
+
+ @Override
+ public String getStringValue() {
+ return this.value;
+ }
+
+ @Override
+ public byte[] getValue() {
+ return this.value.getBytes();
+ }
+
+ }
+
+ private final class MockCarResultsIterator implements QueryResultsIterator {
+
+ private final List carList;
+
+ MockCarResultsIterator() {
+ super();
+
+ carList = new ArrayList();
+
+ carList.add(new MockKeyValue("CAR000",
+ "{\"color\":\"blue\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}"));
+ carList.add(new MockKeyValue("CAR001",
+ "{\"color\":\"red\",\"make\":\"Ford\",\"model\":\"Mustang\",\"owner\":\"Brad\"}"));
+ carList.add(new MockKeyValue("CAR002",
+ "{\"color\":\"green\",\"make\":\"Hyundai\",\"model\":\"Tucson\",\"owner\":\"Jin Soo\"}"));
+ carList.add(new MockKeyValue("CAR007",
+ "{\"color\":\"violet\",\"make\":\"Fiat\",\"model\":\"Punto\",\"owner\":\"Pari\"}"));
+ carList.add(new MockKeyValue("CAR009",
+ "{\"color\":\"brown\",\"make\":\"Holden\",\"model\":\"Barina\",\"owner\":\"Shotaro\"}"));
+ }
+
+ @Override
+ public Iterator iterator() {
+ return carList.iterator();
+ }
+
+ @Override
+ public void close() throws Exception {
+ // do nothing
+ }
+
+ }
+
+ @Test
+ public void invokeUnknownTransaction() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.unknownTransaction(ctx);
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Undefined contract method called");
+ assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo(null);
+
+ verifyZeroInteractions(ctx);
+ }
+
+ @Nested
+ class InvokeQueryCarTransaction {
+
+ @Test
+ public void whenCarExists() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState("CAR000"))
+ .thenReturn("{\"color\":\"blue\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}");
+
+ Car car = contract.queryCar(ctx, "CAR000");
+
+ assertThat(car).isEqualTo(new Car("Toyota", "Prius", "blue", "Tomoko"));
+ }
+
+ @Test
+ public void whenCarDoesNotExist() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState("CAR000")).thenReturn("");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.queryCar(ctx, "CAR000");
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Car CAR000 does not exist");
+ assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("CAR_NOT_FOUND".getBytes());
+ }
+ }
+
+ @Test
+ void invokeInitLedgerTransaction() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+
+ contract.initLedger(ctx);
+
+ InOrder inOrder = inOrder(stub);
+ inOrder.verify(stub).putStringState("CAR000",
+ "{\"color\":\"blue\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}");
+ inOrder.verify(stub).putStringState("CAR001",
+ "{\"color\":\"red\",\"make\":\"Ford\",\"model\":\"Mustang\",\"owner\":\"Brad\"}");
+ inOrder.verify(stub).putStringState("CAR002",
+ "{\"color\":\"green\",\"make\":\"Hyundai\",\"model\":\"Tucson\",\"owner\":\"Jin Soo\"}");
+ inOrder.verify(stub).putStringState("CAR003",
+ "{\"color\":\"yellow\",\"make\":\"Volkswagen\",\"model\":\"Passat\",\"owner\":\"Max\"}");
+ inOrder.verify(stub).putStringState("CAR004",
+ "{\"color\":\"black\",\"make\":\"Tesla\",\"model\":\"S\",\"owner\":\"Adrian\"}");
+ inOrder.verify(stub).putStringState("CAR005",
+ "{\"color\":\"purple\",\"make\":\"Peugeot\",\"model\":\"205\",\"owner\":\"Michel\"}");
+ inOrder.verify(stub).putStringState("CAR006",
+ "{\"color\":\"white\",\"make\":\"Chery\",\"model\":\"S22L\",\"owner\":\"Aarav\"}");
+ inOrder.verify(stub).putStringState("CAR007",
+ "{\"color\":\"violet\",\"make\":\"Fiat\",\"model\":\"Punto\",\"owner\":\"Pari\"}");
+ inOrder.verify(stub).putStringState("CAR008",
+ "{\"color\":\"indigo\",\"make\":\"Tata\",\"model\":\"nano\",\"owner\":\"Valeria\"}");
+ inOrder.verify(stub).putStringState("CAR009",
+ "{\"color\":\"brown\",\"make\":\"Holden\",\"model\":\"Barina\",\"owner\":\"Shotaro\"}");
+ }
+
+ @Nested
+ class InvokeCreateCarTransaction {
+
+ @Test
+ public void whenCarExists() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState("CAR000"))
+ .thenReturn("{\"color\":\"blue\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.createCar(ctx, "CAR000", "Nissan", "Leaf", "green", "Siobhán");
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Car CAR000 already exists");
+ assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("CAR_ALREADY_EXISTS".getBytes());
+ }
+
+ @Test
+ public void whenCarDoesNotExist() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState("CAR000")).thenReturn("");
+
+ Car car = contract.createCar(ctx, "CAR000", "Nissan", "Leaf", "green", "Siobhán");
+
+ assertThat(car).isEqualTo(new Car("Nissan", "Leaf", "green", "Siobhán"));
+ }
+ }
+
+ @Test
+ void invokeQueryAllCarsTransaction() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStateByRange("CAR0", "CAR999")).thenReturn(new MockCarResultsIterator());
+
+ Car[] cars = contract.queryAllCars(ctx);
+
+ final List expectedCars = new ArrayList();
+ expectedCars.add(new Car("Toyota", "Prius", "blue", "Tomoko"));
+ expectedCars.add(new Car("Ford", "Mustang", "red", "Brad"));
+ expectedCars.add(new Car("Hyundai", "Tucson", "green", "Jin Soo"));
+ expectedCars.add(new Car("Fiat", "Punto", "violet", "Pari"));
+ expectedCars.add(new Car("Holden", "Barina", "brown", "Shotaro"));
+
+ assertThat(cars).containsExactlyElementsOf(expectedCars);
+ }
+
+ @Nested
+ class ChangeCarOwnerTransaction {
+
+ @Test
+ public void whenCarExists() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState("CAR000"))
+ .thenReturn("{\"color\":\"blue\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}");
+
+ Car car = contract.changeCarOwner(ctx, "CAR000", "Dr Evil");
+
+ assertThat(car).isEqualTo(new Car("Toyota", "Prius", "blue", "Dr Evil"));
+ }
+
+ @Test
+ public void whenCarDoesNotExist() {
+ FabCar contract = new FabCar();
+ Context ctx = mock(Context.class);
+ ChaincodeStub stub = mock(ChaincodeStub.class);
+ when(ctx.getStub()).thenReturn(stub);
+ when(stub.getStringState("CAR000")).thenReturn("");
+
+ Throwable thrown = catchThrowable(() -> {
+ contract.changeCarOwner(ctx, "CAR000", "Dr Evil");
+ });
+
+ assertThat(thrown).isInstanceOf(ChaincodeException.class).hasNoCause()
+ .hasMessage("Car CAR000 does not exist");
+ assertThat(((ChaincodeException) thrown).getPayload()).isEqualTo("CAR_NOT_FOUND".getBytes());
+ }
+ }
+}
diff --git a/fabcar/startFabric.sh b/fabcar/startFabric.sh
index 5035875812..6fe36dc86b 100755
--- a/fabcar/startFabric.sh
+++ b/fabcar/startFabric.sh
@@ -15,6 +15,9 @@ CC_SRC_LANGUAGE=`echo "$CC_SRC_LANGUAGE" | tr [:upper:] [:lower:]`
if [ "$CC_SRC_LANGUAGE" = "go" -o "$CC_SRC_LANGUAGE" = "golang" ]; then
CC_RUNTIME_LANGUAGE=golang
CC_SRC_PATH=github.com/hyperledger/fabric-samples/chaincode/fabcar/go
+elif [ "$CC_SRC_LANGUAGE" = "java" ]; then
+ CC_RUNTIME_LANGUAGE=java
+ CC_SRC_PATH=/opt/gopath/src/github.com/hyperledger/fabric-samples/chaincode/fabcar/java
elif [ "$CC_SRC_LANGUAGE" = "javascript" ]; then
CC_RUNTIME_LANGUAGE=node # chaincode runtime language is node.js
CC_SRC_PATH=/opt/gopath/src/github.com/hyperledger/fabric-samples/chaincode/fabcar/javascript