From fc250d3bd56b9135abad7676abb0e3a713e3dcb0 Mon Sep 17 00:00:00 2001 From: Francesco Vlacancich Date: Fri, 23 Jul 2021 10:32:00 +0100 Subject: [PATCH] update java chaincode for JSON serialization determinism Signed-off-by: fraVlaca --- .../samples/assettransfer/AssetTransfer.java | 107 +++++------------ .../main/java/org/example/AssetTransfer.java | 109 +++++------------- 2 files changed, 61 insertions(+), 155 deletions(-) diff --git a/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java b/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java index e9e321f2..798cfde5 100644 --- a/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java +++ b/asset-transfer-basic/chaincode-java/src/main/java/org/hyperledger/fabric/samples/assettransfer/AssetTransfer.java @@ -10,6 +10,8 @@ import java.util.TreeMap; import java.util.Map; import java.util.HashMap; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; @@ -22,11 +24,12 @@ 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 org.hyperledger.fabric.shim.ledger.QueryResultsIterator; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; +import com.owlike.genson.Genson; @Contract( name = "basic", @@ -44,8 +47,8 @@ import com.google.gson.Gson; @Default public final class AssetTransfer implements ContractInterface { - private Gson gson = new Gson(); - private ObjectMapper om = new ObjectMapper(); + private final Genson genson = new Genson(); + private ObjectMapper om = new ObjectMapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);; private enum AssetTransferErrors { ASSET_NOT_FOUND, @@ -58,7 +61,7 @@ public final class AssetTransfer implements ContractInterface { * @param ctx the transaction context */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public void InitLedger(final Context ctx) { + public void InitLedger(final Context ctx) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); CreateAsset(ctx, "asset1", "blue", 5, "Tomoko", 300); @@ -83,7 +86,7 @@ public final class AssetTransfer implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size, - final String owner, final int appraisedValue) { + final String owner, final int appraisedValue) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); if (AssetExists(ctx, assetID)) { @@ -93,28 +96,10 @@ public final class AssetTransfer implements ContractInterface { } Asset asset = new Asset(assetID, color, size, owner, appraisedValue); - //deserializing your json into a sorted map, and then serialize the map to get the sorted-by-key json string. - //TreeMap will enforce a sorting order for you, independent of what order they're supplied in - String json = gson.toJson(asset); - try { - om.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - Map map = om.readValue(json, HashMap.class); - String sortedJson = om.writeValueAsString(map); - stub.putStringState(assetID, sortedJson); - } catch (Exception e) { - } - /*String json = gson.toJson(asset); - TreeMap map = gson.fromJson(json, TreeMap.class); - String sortedJson = gson.toJson(map); - stub.putStringState(assetID, sortedJson);*/ - //could use JSON Object approach instead - /*Map sortedMap = gson.fromJson(json, TreeMap.class); //this will auto-sort by key alphabetically - JSONObject sortedJSON = new JSONObject(); //recreate new JSON object for sorted result - for (Map.Entry entry : sortedMap.entrySet()) { - sortedJSON.put(entry.getKey(), entry.getValue()); - } - stub.putState(assetID, sortedJson); //wuold do it if possible to write JSONObject to stste */ + //Use a Jackson ObjectMapper to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = om.writeValueAsString(asset); + stub.putStringState(assetID, sortedJson); return asset; } @@ -127,7 +112,7 @@ public final class AssetTransfer implements ContractInterface { * @return the asset found on the ledger if there was one */ @Transaction(intent = Transaction.TYPE.EVALUATE) - public Asset ReadAsset(final Context ctx, final String assetID) { + public Asset ReadAsset(final Context ctx, final String assetID) throws JsonProcessingException,IOException{ ChaincodeStub stub = ctx.getStub(); String assetJSON = stub.getStringState(assetID); @@ -136,8 +121,8 @@ public final class AssetTransfer implements ContractInterface { System.out.println(errorMessage); throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); } - - Asset asset = gson.fromJson(assetJSON, Asset.class); + + Asset asset = genson.deserialize(assetJSON, Asset.class); return asset; } @@ -154,7 +139,7 @@ public final class AssetTransfer implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size, - final String owner, final int appraisedValue) { + final String owner, final int appraisedValue) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); if (!AssetExists(ctx, assetID)) { @@ -164,26 +149,10 @@ public final class AssetTransfer implements ContractInterface { } Asset newAsset = new Asset(assetID, color, size, owner, appraisedValue); - String json = gson.toJson(newAsset); - try { - om.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - Map map = om.readValue(json, HashMap.class); - String sortedJson = om.writeValueAsString(map);//could also use gson instead - stub.putStringState(assetID, sortedJson); - } catch (Exception e) { - } - /*String json = gson.toJson(asset); - TreeMap map = gson.fromJson(json, TreeMap.class); - String sortedJson = gson.toJson(map); - stub.putStringState(assetID, sortedJson);*/ - //could use JSON Object approach instead - /*Map sortedMap = gson.fromJson(json, TreeMap.class); //this will auto-sort by key alphabetically - JSONObject sortedJSON = new JSONObject(); //recreate new JSON object for sorted result - for (Map.Entry entry : sortedMap.entrySet()) { - sortedJSON.put(entry.getKey(), entry.getValue()); - } - stub.putState(assetID, sortedJson); //wuold do it if possible to write JSONObject to stste */ + //Use a Jackson ObjectMapper to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = om.writeValueAsString(newAsset); + stub.putStringState(assetID, sortedJson); return newAsset; } @@ -231,7 +200,7 @@ public final class AssetTransfer implements ContractInterface { * @return the updated asset */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public Asset TransferAsset(final Context ctx, final String assetID, final String newOwner) { + public Asset TransferAsset(final Context ctx, final String assetID, final String newOwner) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); String assetJSON = stub.getStringState(assetID); @@ -241,29 +210,13 @@ public final class AssetTransfer implements ContractInterface { throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); } - Asset asset = gson.fromJson(assetJSON, Asset.class); - + //Asset asset = gson.fromJson(assetJSON, Asset.class); + Asset asset = genson.deserialize(assetJSON, Asset.class); Asset newAsset = new Asset(asset.getAssetID(), asset.getColor(), asset.getSize(), newOwner, asset.getAppraisedValue()); - String json = gson.toJson(newAsset); - try { - om.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - Map map = om.readValue(json, HashMap.class); - String sortedJson = om.writeValueAsString(map); - stub.putStringState(assetID, sortedJson); - } catch (Exception e) { - } - /*String json = gson.toJson(asset); - TreeMap map = gson.fromJson(json, TreeMap.class); - String sortedJson = gson.toJson(map); - stub.putStringState(assetID, sortedJson);*/ - //could use JSON Object approach instead - /*Map sortedMap = gson.fromJson(json, TreeMap.class); //this will auto-sort by key alphabetically - JSONObject sortedJSON = new JSONObject(); //recreate new JSON object for sorted result - for (Map.Entry entry : sortedMap.entrySet()) { - sortedJSON.put(entry.getKey(), entry.getValue()); - } - stub.putState(assetID, sortedJson); //wuold do it if possible to write JSONObject to stste */ + //Use a Jackson ObjectMapper to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = om.writeValueAsString(newAsset); + stub.putStringState(assetID, sortedJson); return newAsset; } @@ -275,7 +228,7 @@ public final class AssetTransfer implements ContractInterface { * @return array of assets found on the ledger */ @Transaction(intent = Transaction.TYPE.EVALUATE) - public String GetAllAssets(final Context ctx) { + public String GetAllAssets(final Context ctx) throws JsonProcessingException,IOException{ ChaincodeStub stub = ctx.getStub(); List queryResults = new ArrayList(); @@ -287,13 +240,13 @@ public final class AssetTransfer implements ContractInterface { QueryResultsIterator results = stub.getStateByRange("", ""); for (KeyValue result: results) { - Asset asset = gson.fromJson(result.getStringValue(), Asset.class); + Asset asset = genson.deserialize(result.getStringValue(), Asset.class); + System.out.println(asset); queryResults.add(asset); - System.out.println(asset.toString()); } - final String response = gson.toJson(queryResults); - + //Set pretty printing of json + final String response = genson.serialize(queryResults); return response; } } \ No newline at end of file diff --git a/asset-transfer-basic/java/src/main/java/org/example/AssetTransfer.java b/asset-transfer-basic/java/src/main/java/org/example/AssetTransfer.java index 0b0d621d..798cfde5 100644 --- a/asset-transfer-basic/java/src/main/java/org/example/AssetTransfer.java +++ b/asset-transfer-basic/java/src/main/java/org/example/AssetTransfer.java @@ -10,6 +10,8 @@ import java.util.TreeMap; import java.util.Map; import java.util.HashMap; +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; @@ -22,11 +24,12 @@ 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 org.hyperledger.fabric.shim.ledger.QueryResultsIterator; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.Gson; +import com.owlike.genson.Genson; @Contract( name = "basic", @@ -44,8 +47,8 @@ import com.google.gson.Gson; @Default public final class AssetTransfer implements ContractInterface { - private Gson gson = new Gson(); - private ObjectMapper om = new ObjectMapper(); + private final Genson genson = new Genson(); + private ObjectMapper om = new ObjectMapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);; private enum AssetTransferErrors { ASSET_NOT_FOUND, @@ -58,7 +61,7 @@ public final class AssetTransfer implements ContractInterface { * @param ctx the transaction context */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public void InitLedger(final Context ctx) { + public void InitLedger(final Context ctx) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); CreateAsset(ctx, "asset1", "blue", 5, "Tomoko", 300); @@ -83,7 +86,7 @@ public final class AssetTransfer implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size, - final String owner, final int appraisedValue) { + final String owner, final int appraisedValue) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); if (AssetExists(ctx, assetID)) { @@ -93,28 +96,10 @@ public final class AssetTransfer implements ContractInterface { } Asset asset = new Asset(assetID, color, size, owner, appraisedValue); - //deserializing your json into a sorted map, and then serialize the map to get the sorted-by-key json string. - //TreeMap will enforce a sorting order for you, independent of what order they're supplied in - String json = gson.toJson(asset); - try { - om.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - Map map = om.readValue(json, HashMap.class); - String sortedJson = om.writeValueAsString(map); - stub.putStringState(assetID, sortedJson); - } catch (Exception e) { - } - /*String json = gson.toJson(asset); - TreeMap map = gson.fromJson(json, TreeMap.class); - String sortedJson = gson.toJson(map); - stub.putStringState(assetID, sortedJson);*/ - //could use JSON Object approach instead - /*Map sortedMap = gson.fromJson(json, TreeMap.class); //this will auto-sort by key alphabetically - JSONObject sortedJSON = new JSONObject(); //recreate new JSON object for sorted result - for (Map.Entry entry : sortedMap.entrySet()) { - sortedJSON.put(entry.getKey(), entry.getValue()); - } - stub.putState(assetID, sortedJson); //wuold do it if possible to write JSONObject to stste */ + //Use a Jackson ObjectMapper to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = om.writeValueAsString(asset); + stub.putStringState(assetID, sortedJson); return asset; } @@ -127,7 +112,7 @@ public final class AssetTransfer implements ContractInterface { * @return the asset found on the ledger if there was one */ @Transaction(intent = Transaction.TYPE.EVALUATE) - public Asset ReadAsset(final Context ctx, final String assetID) { + public Asset ReadAsset(final Context ctx, final String assetID) throws JsonProcessingException,IOException{ ChaincodeStub stub = ctx.getStub(); String assetJSON = stub.getStringState(assetID); @@ -136,8 +121,8 @@ public final class AssetTransfer implements ContractInterface { System.out.println(errorMessage); throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); } - - Asset asset = gson.fromJson(assetJSON, Asset.class); + + Asset asset = genson.deserialize(assetJSON, Asset.class); return asset; } @@ -154,7 +139,7 @@ public final class AssetTransfer implements ContractInterface { */ @Transaction(intent = Transaction.TYPE.SUBMIT) public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size, - final String owner, final int appraisedValue) { + final String owner, final int appraisedValue) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); if (!AssetExists(ctx, assetID)) { @@ -164,26 +149,10 @@ public final class AssetTransfer implements ContractInterface { } Asset newAsset = new Asset(assetID, color, size, owner, appraisedValue); - String json = gson.toJson(newAsset); - try { - om.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - Map map = om.readValue(json, HashMap.class); - String sortedJson = om.writeValueAsString(map);//could also use gson instead - stub.putStringState(assetID, sortedJson); - } catch (Exception e) { - } - /*String json = gson.toJson(asset); - TreeMap map = gson.fromJson(json, TreeMap.class); - String sortedJson = gson.toJson(map); - stub.putStringState(assetID, sortedJson);*/ - //could use JSON Object approach instead - /*Map sortedMap = gson.fromJson(json, TreeMap.class); //this will auto-sort by key alphabetically - JSONObject sortedJSON = new JSONObject(); //recreate new JSON object for sorted result - for (Map.Entry entry : sortedMap.entrySet()) { - sortedJSON.put(entry.getKey(), entry.getValue()); - } - stub.putState(assetID, sortedJson); //wuold do it if possible to write JSONObject to stste */ + //Use a Jackson ObjectMapper to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = om.writeValueAsString(newAsset); + stub.putStringState(assetID, sortedJson); return newAsset; } @@ -231,7 +200,7 @@ public final class AssetTransfer implements ContractInterface { * @return the updated asset */ @Transaction(intent = Transaction.TYPE.SUBMIT) - public Asset TransferAsset(final Context ctx, final String assetID, final String newOwner) { + public Asset TransferAsset(final Context ctx, final String assetID, final String newOwner) throws JsonProcessingException{ ChaincodeStub stub = ctx.getStub(); String assetJSON = stub.getStringState(assetID); @@ -241,29 +210,13 @@ public final class AssetTransfer implements ContractInterface { throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString()); } - Asset asset = gson.fromJson(assetJSON, Asset.class); - + //Asset asset = gson.fromJson(assetJSON, Asset.class); + Asset asset = genson.deserialize(assetJSON, Asset.class); Asset newAsset = new Asset(asset.getAssetID(), asset.getColor(), asset.getSize(), newOwner, asset.getAppraisedValue()); - String json = gson.toJson(newAsset); - try { - om.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); - Map map = om.readValue(json, HashMap.class); - String sortedJson = om.writeValueAsString(map); - stub.putStringState(assetID, sortedJson); - } catch (Exception e) { - } - /*String json = gson.toJson(asset); - TreeMap map = gson.fromJson(json, TreeMap.class); - String sortedJson = gson.toJson(map); - stub.putStringState(assetID, sortedJson);*/ - //could use JSON Object approach instead - /*Map sortedMap = gson.fromJson(json, TreeMap.class); //this will auto-sort by key alphabetically - JSONObject sortedJSON = new JSONObject(); //recreate new JSON object for sorted result - for (Map.Entry entry : sortedMap.entrySet()) { - sortedJSON.put(entry.getKey(), entry.getValue()); - } - stub.putState(assetID, sortedJson); //wuold do it if possible to write JSONObject to stste */ + //Use a Jackson ObjectMapper to conver the Asset into string, sort it alphabetically and serialize it into a json string + String sortedJson = om.writeValueAsString(newAsset); + stub.putStringState(assetID, sortedJson); return newAsset; } @@ -275,7 +228,7 @@ public final class AssetTransfer implements ContractInterface { * @return array of assets found on the ledger */ @Transaction(intent = Transaction.TYPE.EVALUATE) - public String GetAllAssets(final Context ctx) { + public String GetAllAssets(final Context ctx) throws JsonProcessingException,IOException{ ChaincodeStub stub = ctx.getStub(); List queryResults = new ArrayList(); @@ -287,13 +240,13 @@ public final class AssetTransfer implements ContractInterface { QueryResultsIterator results = stub.getStateByRange("", ""); for (KeyValue result: results) { - Asset asset = gson.fromJson(result.getStringValue(), Asset.class); + Asset asset = genson.deserialize(result.getStringValue(), Asset.class); + System.out.println(asset); queryResults.add(asset); - System.out.println(asset.toString()); } - final String response = gson.toJson(queryResults); - + //Set pretty printing of json + final String response = genson.serialize(queryResults); return response; } -} +} \ No newline at end of file