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