From c5356ac6c8364fde3a706fd119d96adab16b1b10 Mon Sep 17 00:00:00 2001 From: Satheesh Kathamuthu Date: Fri, 12 Aug 2016 17:26:00 +0530 Subject: [PATCH] Table implementation in java shim with example This will fix issue FAB-215 https://jira.hyperledger.org/browse/FAB-215 Change-Id: I2cfcd4b08596195e3acaff97db8b8234a899cad1 Signed-off-by: Satheesh Kathamuthu --- Makefile | 6 +- bddtests/java_shim.feature | 93 +- core/chaincode/shim/java/build.gradle | 17 +- .../hyperledger/java/shim/ChaincodeStub.java | 985 ++++++++---------- .../shim/{chaincode.pb.go => table.pb.go} | 4 +- .../shim/{chaincode.proto => table.proto} | 3 +- .../chaincode/java/TableExample/build.gradle | 70 ++ .../src/main/java/example/TableExample.java | 194 ++++ settings.gradle | 1 + 9 files changed, 784 insertions(+), 589 deletions(-) rename core/chaincode/shim/{chaincode.pb.go => table.pb.go} (99%) rename core/chaincode/shim/{chaincode.proto => table.proto} (92%) create mode 100644 examples/chaincode/java/TableExample/build.gradle create mode 100644 examples/chaincode/java/TableExample/src/main/java/example/TableExample.java diff --git a/Makefile b/Makefile index e202464703d..ea62f3764b1 100644 --- a/Makefile +++ b/Makefile @@ -202,6 +202,10 @@ build/image/ccenv/.dummy: build/image/ccenv/bin/protoc-gen-go build/image/ccenv/ @touch $@ # Special override for java-image +# Following items are packed and sent to docker context while building image +# 1. Java shim layer source code +# 2. Proto files used to generate java classes +# 3. Gradle settings file build/image/javaenv/.dummy: Makefile $(JAVASHIM_DEPS) @echo "Building docker javaenv-image" @mkdir -p $(@D) @@ -214,7 +218,7 @@ build/image/javaenv/.dummy: Makefile $(JAVASHIM_DEPS) # 2. Proto files used to generate java classes # 3. Gradle settings file @git ls-files core/chaincode/shim/java | tar -jcT - > $(@D)/javashimsrc.tar.bz2 - @git ls-files protos settings.gradle | tar -jcT - > $(@D)/protos.tar.bz2 + @git ls-files protos core/chaincode/shim/table.proto settings.gradle | tar -jcT - > $(@D)/protos.tar.bz2 docker build -t $(PROJECT_NAME)-javaenv $(@D) docker tag $(PROJECT_NAME)-javaenv $(PROJECT_NAME)-javaenv:$(DOCKER_TAG) @touch $@ diff --git a/bddtests/java_shim.feature b/bddtests/java_shim.feature index d83f6d5a489..4310d5c761a 100644 --- a/bddtests/java_shim.feature +++ b/bddtests/java_shim.feature @@ -26,44 +26,43 @@ #@chaincodeImagesUpToDate @preV1 -Feature: SimpleSample Java example - +Feature: Java chaincode example Scenario: java SimpleSample chaincode example single peer Given we compose "docker-compose-1.yml" When requesting "/chain" from "vp0" Then I should get a JSON response with "height" = "1" - When I deploy lang chaincode "examples/chaincode/java/SimpleSample" of "JAVA" with ctor "init" to "vp0" - | arg1 | arg2 | arg3 | arg4 | - | a | 100 | b | 200 | - Then I should have received a chaincode name - Then I wait up to "300" seconds for transaction to be committed to all peers + When I deploy lang chaincode "examples/chaincode/java/SimpleSample" of "JAVA" with ctor "init" to "vp0" + | arg1 | arg2 | arg3 | arg4 | + | a | 100 | b | 200 | + Then I should have received a chaincode name + Then I wait up to "300" seconds for transaction to be committed to all peers - When requesting "/chain" from "vp0" - Then I should get a JSON response with "height" = "2" + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "2" When I query chaincode "SimpleSample" function name "query" on "vp0": |arg1| | a | - Then I should get a JSON response with "result.message" = "{'Name':'a','Amount':'100'}" + Then I should get a JSON response with "result.message" = "{'Name':'a','Amount':'100'}" When I invoke chaincode "SimpleSample" function name "transfer" on "vp0" - |arg1|arg2|arg3| - | a | b | 10 | - Then I should have received a transactionID - Then I wait up to "25" seconds for transaction to be committed to all peers + |arg1|arg2|arg3| + | a | b | 10 | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers - When requesting "/chain" from "vp0" - Then I should get a JSON response with "height" = "3" + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "3" When I query chaincode "SimpleSample" function name "query" on "vp0": |arg1| | a | - Then I should get a JSON response with "result.message" = "{'Name':'a','Amount':'90'}" + Then I should get a JSON response with "result.message" = "{'Name':'a','Amount':'90'}" When I query chaincode "SimpleSample" function name "query" on "vp0": |arg1| | b | - Then I should get a JSON response with "result.message" = "{'Name':'b','Amount':'210'}" + Then I should get a JSON response with "result.message" = "{'Name':'b','Amount':'210'}" Scenario: java RangeExample chaincode single peer Given we compose "docker-compose-1.yml" @@ -117,4 +116,60 @@ Scenario: java RangeExample chaincode single peer When I query chaincode "RangeExample" function name "keys" on "vp0": || || - Then I should get a JSON response with "result.message" = "[a]" \ No newline at end of file + Then I should get a JSON response with "result.message" = "[a]" + + Scenario: Java TableExample chaincode single peer + Given we compose "docker-compose-1.yml" + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "1" + When I deploy lang chaincode "examples/chaincode/java/TableExample" of "JAVA" with ctor "init" to "vp0" + || + || + Then I should have received a chaincode name + Then I wait up to "30" seconds for transaction to be committed to all peers + + When requesting "/chain" from "vp0" + Then I should get a JSON response with "height" = "2" + When I invoke chaincode "TableExample" function name "insert" on "vp0" + |arg1|arg2| + | 0 | Alice | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers + When I invoke chaincode "TableExample" function name "insert" on "vp0" + |arg1|arg2| + | 1 | Bob | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers + When I invoke chaincode "TableExample" function name "insert" on "vp0" + |arg1|arg2| + | 2 | Charlie | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers + + When I query chaincode "TableExample" function name "get" on "vp0": + |arg1| + | 0 | + Then I should get a JSON response with "result.message" = "Alice" + + When I query chaincode "TableExample" function name "get" on "vp0": + |arg1| + | 2 | + Then I should get a JSON response with "result.message" = "Charlie" + When I invoke chaincode "TableExample" function name "update" on "vp0" + |arg1|arg2| + | 2 | Chaitra | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers + When I query chaincode "TableExample" function name "get" on "vp0": + |arg1| + | 2 | + Then I should get a JSON response with "result.message" = "Chaitra" + When I invoke chaincode "TableExample" function name "delete" on "vp0" + |arg1| + | 2 | + Then I should have received a transactionID + Then I wait up to "25" seconds for transaction to be committed to all peers + When I query chaincode "TableExample" function name "get" on "vp0": + |arg1| + | 2 | + Then I should get a JSON response with "result.message" = "No record found !" diff --git a/core/chaincode/shim/java/build.gradle b/core/chaincode/shim/java/build.gradle index 8f8b3fece0a..095ad4c7906 100644 --- a/core/chaincode/shim/java/build.gradle +++ b/core/chaincode/shim/java/build.gradle @@ -83,12 +83,21 @@ task copyToLib(type: Copy) { task copyProtos(type:Copy){ - into "${projectDir}/src/main/proto" - from "${rootDir}/protos" - include '**/chaincodeevent.proto' - include '**/chaincode.proto' + + from ("${rootDir}/protos"){ + include '**/chaincodeevent.proto' + include '**/chaincode.proto' + } + from ("../") { + duplicatesStrategy.EXCLUDE + include '**/table.proto' + exclude 'java' + } + into "${projectDir}/src/main/proto" + } + tasks['build'].mustRunAfter tasks['copyProtos'] build.dependsOn(copyProtos) build.finalizedBy(copyToLib) diff --git a/core/chaincode/shim/java/src/main/java/org/hyperledger/java/shim/ChaincodeStub.java b/core/chaincode/shim/java/src/main/java/org/hyperledger/java/shim/ChaincodeStub.java index 3b9931a8b00..c529316a8cb 100644 --- a/core/chaincode/shim/java/src/main/java/org/hyperledger/java/shim/ChaincodeStub.java +++ b/core/chaincode/shim/java/src/main/java/org/hyperledger/java/shim/ChaincodeStub.java @@ -17,579 +17,440 @@ package org.hyperledger.java.shim; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hyperledger.protos.Chaincode; +import org.hyperledger.protos.TableProto; + +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; - -import java.util.List; +import static org.hyperledger.protos.TableProto.ColumnDefinition.Type.STRING; public class ChaincodeStub { + private static Log logger = LogFactory.getLog(ChaincodeStub.class); + private final String uuid; + private final Handler handler; + + public ChaincodeStub(String uuid, Handler handler) { + this.uuid = uuid; + this.handler = handler; + } + + /** + * Gets the UUID of this stub + * + * @return the id used to identify this communication channel + */ + public String getUuid() { + return uuid; + } + + /** + * Get the state of the provided key from the ledger, and returns is as a string + * + * @param key the key of the desired state + * @return the String value of the requested state + */ + public String getState(String key) { + return handler.handleGetState(key, uuid).toStringUtf8(); + } + + /** + * Puts the given state into a ledger, automatically wrapping it in a ByteString + * + * @param key reference key + * @param value value to be put + */ + public void putState(String key, String value) { + handler.handlePutState(key, ByteString.copyFromUtf8(value), uuid); + } + + /** + * Deletes the state of the given key from the ledger + * + * @param key key of the state to be deleted + */ + public void delState(String key) { + handler.handleDeleteState(key, uuid); + } + + /** + * Given a start key and end key, this method returns a map of items with value converted to UTF-8 string. + * + * @param startKey + * @param endKey + * @return + */ + public Map rangeQueryState(String startKey, String endKey) { + Map retMap = new HashMap<>(); + for (Map.Entry item : rangeQueryRawState(startKey, endKey).entrySet()) { + retMap.put(item.getKey(), item.getValue().toStringUtf8()); + } + return retMap; + } + + /** + * This method is same as rangeQueryState, except it returns value in ByteString, useful in cases where + * serialized object can be retrieved. + * + * @param startKey + * @param endKey + * @return + */ + public Map rangeQueryRawState(String startKey, String endKey) { + Map map = new HashMap<>(); + for (Chaincode.RangeQueryStateKeyValue mapping : handler.handleRangeQueryState( + startKey, endKey, uuid).getKeysAndValuesList()) { + map.put(mapping.getKey(), mapping.getValue()); + } + return map; + } + + /** + * @param chaincodeName + * @param function + * @param args + * @return + */ + public String invokeChaincode(String chaincodeName, String function, List args) { + return handler.handleInvokeChaincode(chaincodeName, function, args, uuid).toStringUtf8(); + } + + /** + * @param chaincodeName + * @param function + * @param args + * @return + */ + public String queryChaincode(String chaincodeName, String function, List args) { + return handler.handleQueryChaincode(chaincodeName, function, args, uuid).toStringUtf8(); + } + + //------RAW CALLS------ + + /** + * @param key + * @return + */ + public ByteString getRawState(String key) { + return handler.handleGetState(key, uuid); + } + + /** + * @param key + * @param value + */ + public void putRawState(String key, ByteString value) { + handler.handlePutState(key, value, uuid); + } - private final String uuid; - private final Handler handler; - - public ChaincodeStub(String uuid, Handler handler) { - this.uuid = uuid; - this.handler = handler; - } - - /** - * Gets the UUID of this stub - * @return the id used to identify this communication channel - */ - public String getUuid() { - return uuid; - } - - /** - * Get the state of the provided key from the ledger, and returns is as a string - * @param key the key of the desired state - * @return the String value of the requested state - */ - public String getState(String key) { - return handler.handleGetState(key, uuid).toStringUtf8(); - } - - /** - * Puts the given state into a ledger, automatically wrapping it in a ByteString - * @param key reference key - * @param value value to be put - */ - public void putState(String key, String value) { - handler.handlePutState(key, ByteString.copyFromUtf8(value), uuid); - } - - /** - * Deletes the state of the given key from the ledger - * @param key key of the state to be deleted - */ - public void delState(String key) { - handler.handleDeleteState(key, uuid); - } - - /** - * Given a start key and end key, this method returns a map of items with value converted to UTF-8 string. - * @param startKey - * @param endKey - * @return - */ - public Map rangeQueryState(String startKey, String endKey) { - Map retMap = new HashMap<>(); - for (Map.Entry item: rangeQueryRawState(startKey, endKey).entrySet()) { - retMap.put(item.getKey(), item.getValue().toStringUtf8()); - } - return retMap; - } - - /** - * This method is same as rangeQueryState, except it returns value in ByteString, useful in cases where - * serialized object can be retrieved. - * @param startKey - * @param endKey + /** + * + * @param startKey + * @param endKey + * @param limit * @return */ - public Map rangeQueryRawState(String startKey, String endKey) { - Map map = new HashMap<>(); - for (Chaincode.RangeQueryStateKeyValue mapping : handler.handleRangeQueryState( - startKey, endKey, uuid).getKeysAndValuesList()) { - map.put(mapping.getKey(), mapping.getValue()); - } - return map; - } - - /** - * - * @param chaincodeName - * @param function - * @param args - * @return - */ - public String invokeChaincode(String chaincodeName, String function, List args) { - return handler.handleInvokeChaincode(chaincodeName, function, args, uuid).toStringUtf8(); - } - - /** - * - * @param chaincodeName - * @param function - * @param args - * @return - */ - public String queryChaincode(String chaincodeName, String function, List args) { - return handler.handleQueryChaincode(chaincodeName, function, args, uuid).toStringUtf8(); - } - - //------RAW CALLS------ - - /** - * - * @param key - * @return - */ - public ByteString getRawState(String key) { - return handler.handleGetState(key, uuid); - } - - /** - * - * @param key - * @param value - */ - public void putRawState(String key, ByteString value) { - handler.handlePutState(key, value, uuid); - } - - /** - * - * @param startKey - * @param endKey - * @param limit - * @return - */ // public RangeQueryStateResponse rangeQueryRawState(String startKey, String endKey, int limit) { // return handler.handleRangeQueryState(startKey, endKey, limit, uuid); // } - /** - * - * @param chaincodeName - * @param function - * @param args - * @return - */ - public ByteString queryRawChaincode(String chaincodeName, String function, List args) { - return handler.handleQueryChaincode(chaincodeName, function, args, uuid); - } - - /** - * Invokes the provided chaincode with the given function and arguments, and returns the - * raw ByteString value that invocation generated. - * @param chaincodeName The name of the chaincode to invoke - * @param function the function parameter to pass to the chaincode - * @param args the arguments to be provided in the chaincode call - * @return the value returned by the chaincode call - */ - public ByteString invokeRawChaincode(String chaincodeName, String function, List args) { - return handler.handleInvokeChaincode(chaincodeName, function, args, uuid); - } - - -// //TODO Table calls - public void createTable(String name) { - -// if (getTable(name) != null) -// throw new RuntimeException("CreateTable operation failed. Table %s already exists."); - -// if err != ErrTableNotFound { -// return fmt.Errorf("CreateTable operation failed. %s", err) -// } -// -// if columnDefinitions == nil || len(columnDefinitions) == 0 { -// return errors.New("Invalid column definitions. Tables must contain at least one column.") -// } -// -// hasKey := false -// nameMap := make(map[string]bool) -// for i, definition := range columnDefinitions { -// -// // Check name -// if definition == nil { -// return fmt.Errorf("Column definition %d is invalid. Definition must not be nil.", i) -// } -// if len(definition.Name) == 0 { -// return fmt.Errorf("Column definition %d is invalid. Name must be 1 or more characters.", i) -// } -// if _, exists := nameMap[definition.Name]; exists { -// return fmt.Errorf("Invalid table. Table contains duplicate column name '%s'.", definition.Name) -// } -// nameMap[definition.Name] = true -// -// // Check type -// switch definition.Type { -// case ColumnDefinition_STRING: -// case ColumnDefinition_INT32: -// case ColumnDefinition_INT64: -// case ColumnDefinition_UINT32: -// case ColumnDefinition_UINT64: -// case ColumnDefinition_BYTES: -// case ColumnDefinition_BOOL: -// default: -// return fmt.Errorf("Column definition %s does not have a valid type.", definition.Name) -// } -// -// if definition.Key { -// hasKey = true -// } -// } -// -// if !hasKey { -// return errors.New("Inavlid table. One or more columns must be a key.") -// } -// -// table := &Table{name, columnDefinitions} -// tableBytes, err := proto.Marshal(table) -// if err != nil { -// return fmt.Errorf("Error marshalling table: %s", err) -// } -// tableNameKey, err := getTableNameKey(name) -// if err != nil { -// return fmt.Errorf("Error creating table key: %s", err) -// } -// try { -// stub.PutState(tableNameKey, tableBytes) -// } catch (Exception e) { -// throw new RuntimeException("Error inserting table in state: " + e.getMessage()); -// } -// return; - } -// -// // GetTable returns the table for the specified table name or ErrTableNotFound -// // if the table does not exist. -// func (stub *ChaincodeStub) GetTable(tableName string) (*Table, error) { -// return stub.getTable(tableName) -// } -// -// // DeleteTable deletes and entire table and all associated row -// func (stub *ChaincodeStub) DeleteTable(tableName string) error { -// tableNameKey, err := getTableNameKey(tableName) -// if err != nil { -// return err -// } -// -// // Delete rows -// iter, err := stub.RangeQueryState(tableNameKey+"1", tableNameKey+":") -// if err != nil { -// return fmt.Errorf("Error deleting table: %s", err) -// } -// defer iter.Close() -// for iter.HasNext() { -// key, _, err := iter.Next() -// if err != nil { -// return fmt.Errorf("Error deleting table: %s", err) -// } -// err = stub.DelState(key) -// if err != nil { -// return fmt.Errorf("Error deleting table: %s", err) -// } -// } -// -// return stub.DelState(tableNameKey) -// } -// -// // InsertRow inserts a new row into the specified table. -// // Returns - -// // true and no error if the row is successfully inserted. -// // false and no error if a row already exists for the given key. -// // false and a TableNotFoundError if the specified table name does not exist. -// // false and an error if there is an unexpected error condition. -// func (stub *ChaincodeStub) InsertRow(tableName string, row Row) (bool, error) { -// return stub.insertRowInternal(tableName, row, false) -// } -// -// // ReplaceRow updates the row in the specified table. -// // Returns - -// // true and no error if the row is successfully updated. -// // false and no error if a row does not exist the given key. -// // flase and a TableNotFoundError if the specified table name does not exist. -// // false and an error if there is an unexpected error condition. -// func (stub *ChaincodeStub) ReplaceRow(tableName string, row Row) (bool, error) { -// return stub.insertRowInternal(tableName, row, true) -// } -// -// // GetRow fetches a row from the specified table for the given key. -// func (stub *ChaincodeStub) GetRow(tableName string, key []Column) (Row, error) { -// -// var row Row -// -// keyString, err := buildKeyString(tableName, key) -// if err != nil { -// return row, err -// } -// -// rowBytes, err := stub.GetState(keyString) -// if err != nil { -// return row, fmt.Errorf("Error fetching row from DB: %s", err) -// } -// -// err = proto.Unmarshal(rowBytes, &row) -// if err != nil { -// return row, fmt.Errorf("Error unmarshalling row: %s", err) -// } -// -// return row, nil -// -// } -// -// // GetRows returns multiple rows based on a partial key. For example, given table -// // | A | B | C | D | -// // where A, C and D are keys, GetRows can be called with [A, C] to return -// // all rows that have A, C and any value for D as their key. GetRows could -// // also be called with A only to return all rows that have A and any value -// // for C and D as their key. -// func (stub *ChaincodeStub) GetRows(tableName string, key []Column) (<-chan Row, error) { -// -// keyString, err := buildKeyString(tableName, key) -// if err != nil { -// return nil, err -// } -// -// iter, err := stub.RangeQueryState(keyString+"1", keyString+":") -// if err != nil { -// return nil, fmt.Errorf("Error fetching rows: %s", err) -// } -// defer iter.Close() -// -// rows := make(chan Row) -// -// go func() { -// for iter.HasNext() { -// _, rowBytes, err := iter.Next() -// if err != nil { -// close(rows) -// } -// -// var row Row -// err = proto.Unmarshal(rowBytes, &row) -// if err != nil { -// close(rows) -// } -// -// rows <- row -// -// } -// close(rows) -// }() -// -// return rows, nil -// -// } -// -// // DeleteRow deletes the row for the given key from the specified table. -// func (stub *ChaincodeStub) DeleteRow(tableName string, key []Column) error { -// -// keyString, err := buildKeyString(tableName, key) -// if err != nil { -// return err -// } -// -// err = stub.DelState(keyString) -// if err != nil { -// return fmt.Errorf("DeleteRow operation error. Error deleting row: %s", err) -// } -// -// return nil -// } -// -// // VerifySignature ... -// func (stub *ChaincodeStub) VerifySignature(certificate, signature, message []byte) (bool, error) { -// // Instantiate a new SignatureVerifier -// sv := ecdsa.NewX509ECDSASignatureVerifier() -// -// // Verify the signature -// return sv.Verify(certificate, signature, message) -// } -// -// // GetCallerCertificate returns caller certificate -// func (stub *ChaincodeStub) GetCallerCertificate() ([]byte, error) { -// return stub.securityContext.CallerCert, nil -// } -// -// // GetCallerMetadata returns caller metadata -// func (stub *ChaincodeStub) GetCallerMetadata() ([]byte, error) { -// return stub.securityContext.Metadata, nil -// } -// -// // GetBinding returns tx binding -// func (stub *ChaincodeStub) GetBinding() ([]byte, error) { -// return stub.securityContext.Binding, nil -// } -// -// // GetPayload returns tx payload -// func (stub *ChaincodeStub) GetPayload() ([]byte, error) { -// return stub.securityContext.Payload, nil -// } -// -// func (stub *ChaincodeStub) getTable(tableName string) (*Table, error) { -// -// tableName, err := getTableNameKey(tableName) -// if err != nil { -// return nil, err -// } -// -// tableBytes, err := stub.GetState(tableName) -// if tableBytes == nil { -// return nil, ErrTableNotFound -// } -// if err != nil { -// return nil, fmt.Errorf("Error fetching table: %s", err) -// } -// table := &Table{} -// err = proto.Unmarshal(tableBytes, table) -// if err != nil { -// return nil, fmt.Errorf("Error unmarshalling table: %s", err) -// } -// -// return table, nil -// } -// -// func validateTableName(name string) error { -// if len(name) == 0 { -// return errors.New("Inavlid table name. Table name must be 1 or more characters.") -// } -// -// return nil -// } -// -// func getTableNameKey(name string) (string, error) { -// err := validateTableName(name) -// if err != nil { -// return "", err -// } -// -// return strconv.Itoa(len(name)) + name, nil -// } -// -// func buildKeyString(tableName string, keys []Column) (string, error) { -// -// var keyBuffer bytes.Buffer -// -// tableNameKey, err := getTableNameKey(tableName) -// if err != nil { -// return "", err -// } -// -// keyBuffer.WriteString(tableNameKey) -// -// for _, key := range keys { -// -// var keyString string -// switch key.Value.(type) { -// case *Column_String_: -// keyString = key.GetString_() -// case *Column_Int32: -// // b := make([]byte, 4) -// // binary.LittleEndian.PutUint32(b, uint32(key.GetInt32())) -// // keyBuffer.Write(b) -// keyString = strconv.FormatInt(int64(key.GetInt32()), 10) -// case *Column_Int64: -// keyString = strconv.FormatInt(key.GetInt64(), 10) -// case *Column_Uint32: -// keyString = strconv.FormatUint(uint64(key.GetInt32()), 10) -// case *Column_Uint64: -// keyString = strconv.FormatUint(key.GetUint64(), 10) -// case *Column_Bytes: -// keyString = string(key.GetBytes()) -// case *Column_Bool: -// keyString = strconv.FormatBool(key.GetBool()) -// } -// -// keyBuffer.WriteString(strconv.Itoa(len(keyString))) -// keyBuffer.WriteString(keyString) -// } -// -// return keyBuffer.String(), nil -// } -// -// func getKeyAndVerifyRow(table Table, row Row) ([]Column, error) { -// -// var keys []Column -// -// if row.Columns == nil || len(row.Columns) != len(table.ColumnDefinitions) { -// return keys, fmt.Errorf("Table '%s' defines %d columns, but row has %d columns.", -// table.Name, len(table.ColumnDefinitions), len(row.Columns)) -// } -// -// for i, column := range row.Columns { -// -// // Check types -// var expectedType bool -// switch column.Value.(type) { -// case *Column_String_: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_STRING -// case *Column_Int32: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_INT32 -// case *Column_Int64: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_INT64 -// case *Column_Uint32: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_UINT32 -// case *Column_Uint64: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_UINT64 -// case *Column_Bytes: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_BYTES -// case *Column_Bool: -// expectedType = table.ColumnDefinitions[i].Type == ColumnDefinition_BOOL -// default: -// expectedType = false -// } -// if !expectedType { -// return keys, fmt.Errorf("The type for table '%s', column '%s' is '%s', but the column in the row does not match.", -// table.Name, table.ColumnDefinitions[i].Name, table.ColumnDefinitions[i].Type) -// } -// -// if table.ColumnDefinitions[i].Key { -// keys = append(keys, *column) -// } -// -// } -// -// return keys, nil -// } -// -// func (stub *ChaincodeStub) isRowPrsent(tableName string, key []Column) (bool, error) { -// keyString, err := buildKeyString(tableName, key) -// if err != nil { -// return false, err -// } -// rowBytes, err := stub.GetState(keyString) -// if err != nil { -// return false, fmt.Errorf("Error fetching row for key %s: %s", keyString, err) -// } -// if rowBytes != nil { -// return true, nil -// } -// return false, nil -// } -// -// // insertRowInternal inserts a new row into the specified table. -// // Returns - -// // true and no error if the row is successfully inserted. -// // false and no error if a row already exists for the given key. -// // flase and a TableNotFoundError if the specified table name does not exist. -// // false and an error if there is an unexpected error condition. -// func (stub *ChaincodeStub) insertRowInternal(tableName string, row Row, update bool) (bool, error) { -// -// table, err := stub.getTable(tableName) -// if err != nil { -// return false, err -// } -// -// key, err := getKeyAndVerifyRow(*table, row) -// if err != nil { -// return false, err -// } -// -// present, err := stub.isRowPrsent(tableName, key) -// if err != nil { -// return false, err -// } -// if (present && !update) || (!present && update) { -// return false, nil -// } -// -// rowBytes, err := proto.Marshal(&row) -// if err != nil { -// return false, fmt.Errorf("Error marshalling row: %s", err) -// } -// -// keyString, err := buildKeyString(tableName, key) -// if err != nil { -// return false, err -// } -// err = stub.PutState(keyString, rowBytes) -// if err != nil { -// return false, fmt.Errorf("Error inserting row in table %s: %s", tableName, err) -// } -// -// return true, nil + /** + * @param chaincodeName + * @param function + * @param args + * @return + */ + public ByteString queryRawChaincode(String chaincodeName, String function, List args) { + return handler.handleQueryChaincode(chaincodeName, function, args, uuid); + } + + /** + * Invokes the provided chaincode with the given function and arguments, and returns the + * raw ByteString value that invocation generated. + * + * @param chaincodeName The name of the chaincode to invoke + * @param function the function parameter to pass to the chaincode + * @param args the arguments to be provided in the chaincode call + * @return the value returned by the chaincode call + */ + public ByteString invokeRawChaincode(String chaincodeName, String function, List args) { + return handler.handleInvokeChaincode(chaincodeName, function, args, uuid); + } + + public boolean createTable(String tableName, List columnDefinitions) + throws Exception { + if (validateTableName(tableName)) { + logger.debug("Table name %s is valid, continue table creation"); + + if (tableExist(tableName)) { + logger.error("Table with tableName already exist, Create table operation failed"); + return false;//table exist + } else { + if (columnDefinitions != null && columnDefinitions.size() == 0) { + logger.error("Invalid column definitions. Table must contain at least one column"); + return false; + } + Map nameMap = new HashMap<>(); + int idx = 0; + boolean hasKey = false; + logger.debug("Number of columns " + columnDefinitions.size()); + for (TableProto.ColumnDefinition colDef : columnDefinitions) { + logger.debug("Col information - " + colDef.getName()+ "=" + colDef.getType()+ "=" +colDef.isInitialized()); + + if (!colDef.isInitialized() || colDef.getName().length() == 0) { + logger.error("Column definition is invalid for index " + idx); + return false; + } + + if (!nameMap.isEmpty() && nameMap.containsKey(colDef.getName())){ + logger.error("Column already exist for colIdx " + idx + " with name " + colDef.getName()); + return false; + } + nameMap.put(colDef.getName(), true); + switch (colDef.getType()) { + case STRING: + break; + case INT32: + break; + case INT64: + break; + case UINT32: + break; + case UINT64: + break; + case BYTES: + break; + case BOOL: + break; + default: + logger.error("Invalid column type for index " + idx + " given type " + colDef.getType()); +// return false; + + } + + if (colDef.getKey()) hasKey = true; + + idx++; + } + if (!hasKey) { + logger.error("Invalid table. One or more columns must be a key."); + return false; + } + TableProto.Table table = TableProto.Table.newBuilder() + .setName(tableName) + .addAllColumnDefinitions(columnDefinitions) + .build(); + String tableNameKey = getTableNameKey(tableName); + putRawState(tableNameKey, table.toByteString()); + return true; + } + } + return false; + } + public boolean deleteTable(String tableName) { + String tableNameKey = getTableNameKey(tableName); + rangeQueryState(tableNameKey + "1", tableNameKey + ":") + .keySet().forEach(key -> delState(key)); + delState(tableNameKey); + return true; + } + public boolean insertRow(String tableName, TableProto.Row row) throws Exception { + try { + return insertRowInternal(tableName, row, false); + } catch (Exception e) { + logger.error("Error while inserting row on table - " + tableName); + logger.error(e.getMessage()); + + throw e; + } + } + + public boolean replaceRow(String tableName, TableProto.Row row) throws Exception { + try { + return insertRowInternal(tableName, row, true); + } catch (Exception e) { + logger.error("Error while updating row on table - " + tableName); + logger.error(e.getMessage()); + + throw e; + } + } + private List getKeyAndVerifyRow(TableProto.Table table, TableProto.Row row) throws Exception { + List keys = new ArrayList(); + //logger.debug("Entering getKeyAndVerifyRow with tableName -" + table.getName() ); + //logger.debug("Entering getKeyAndVerifyRow with rowcount -" + row.getColumnsCount() ); + if ( !row.isInitialized() || row.getColumnsCount() != table.getColumnDefinitionsCount()){ + logger.error("Table " + table.getName() + " define " + + table.getColumnDefinitionsCount() + " columns but row has " + + row.getColumnsCount() + " columns"); + return keys; + } + int colIdx = 0; + for (TableProto.Column col: row.getColumnsList()) { + boolean expectedType; + switch (col.getValueCase()){ + case STRING: + expectedType = table.getColumnDefinitions(colIdx).getType() + == STRING; + break; + case INT32: + expectedType = table.getColumnDefinitions(colIdx).getType() + == TableProto.ColumnDefinition.Type.INT32; + break; + case INT64: + expectedType = table.getColumnDefinitions(colIdx).getType() + == TableProto.ColumnDefinition.Type.INT64; + break; + case UINT32: + expectedType = table.getColumnDefinitions(colIdx).getType() + == TableProto.ColumnDefinition.Type.UINT32; + break; + case UINT64: + expectedType = table.getColumnDefinitions(colIdx).getType() + == TableProto.ColumnDefinition.Type.UINT64; + break; + case BYTES: + expectedType = table.getColumnDefinitions(colIdx).getType() + == TableProto.ColumnDefinition.Type.BYTES; + break; + case BOOL: + expectedType = table.getColumnDefinitions(colIdx).getType() + == TableProto.ColumnDefinition.Type.BOOL; + break; + default: + expectedType = false; + } + if (!expectedType){ + logger.error("The type for table " + table.getName() + + " column " + table.getColumnDefinitions(colIdx).getName() + " is " + + table.getColumnDefinitions(colIdx).getType() + " but the column in the row does not match" ); + throw new Exception(); + } + if (table.getColumnDefinitions(colIdx).getKey()){ + keys.add(col); + } + + colIdx++; + } + return keys; + } + private boolean isRowPresent(String tableName, List keys){ + String keyString = buildKeyString(tableName, keys); + ByteString rowBytes = getRawState(keyString); + return !rowBytes.isEmpty(); + } + + private String buildKeyString(String tableName, List keys){ + + StringBuffer sb = new StringBuffer(); + String tableNameKey = getTableNameKey(tableName); + + sb.append(tableNameKey); + String keyString=""; + for (TableProto.Column col: keys) { + + switch (col.getValueCase()){ + case STRING: + keyString = col.getString(); + break; + case INT32: + keyString = ""+col.getInt32(); + break; + case INT64: + keyString = ""+col.getInt64(); + break; + case UINT32: + keyString = ""+col.getUint32(); + break; + case UINT64: + keyString = ""+col.getUint64(); + break; + case BYTES: + keyString = col.getBytes().toString(); + break; + case BOOL: + keyString = ""+col.getBool(); + break; + } + + sb.append(keyString.length()); + sb.append(keyString); + + } + return sb.toString(); + } + public TableProto.Row getRow(String tableName, List key) throws InvalidProtocolBufferException { + + String keyString = buildKeyString(tableName, key); + try { + return TableProto.Row.parseFrom(getRawState(keyString)); + } catch (InvalidProtocolBufferException e) { + + logger.error("Error while retrieving row on table -" + tableName); + throw e; + } + } + + public boolean deleteRow(String tableName, List key){ + String keyString = buildKeyString(tableName, key); + delState(keyString); + return true; + } + + private boolean insertRowInternal(String tableName, TableProto.Row row, boolean update) + throws Exception{ + try { + //logger.debug("inside insertRowInternal with tname " + tableName); + TableProto.Table table = getTable(tableName); + //logger.debug("inside insertRowInternal with tableName " + table.getName()); + List keys = getKeyAndVerifyRow(table, row); + Boolean present = isRowPresent(tableName, keys); + if((present && !update) || (!present && update)){ + return false; + } + String keyString = buildKeyString(tableName, keys); + putRawState(keyString, row.toByteString()); + } catch (Exception e) { + logger.error("Unable to insert/update table -" + tableName); + logger.error(e.getMessage()); + throw e; + } + + return true; + } + + private TableProto.Table getTable(String tableName) throws Exception { +logger.info("Inside get tbale"); + String tName = getTableNameKey(tableName); + logger.debug("Table name key for getRawState - " + tName); + ByteString tableBytes = getRawState(tName); + logger.debug("Table after getrawState -" + tableBytes); + return TableProto.Table.parseFrom(tableBytes); + } + + private boolean tableExist(String tableName) throws Exception { + boolean tableExist = false; + //TODO Better way to check table existence ? + if (getTable(tableName).getName().equals(tableName)) { + tableExist = true; + } + return tableExist; + } + + private String getTableNameKey(String name) { + return name.length() + name; + } + + public boolean validateTableName(String name) throws Exception { + boolean validTableName = true; + if (name.length() == 0) { + validTableName = false; + } + return validTableName; + } } diff --git a/core/chaincode/shim/chaincode.pb.go b/core/chaincode/shim/table.pb.go similarity index 99% rename from core/chaincode/shim/chaincode.pb.go rename to core/chaincode/shim/table.pb.go index 36a962bd1ea..5f0a1b96c24 100644 --- a/core/chaincode/shim/chaincode.pb.go +++ b/core/chaincode/shim/table.pb.go @@ -1,12 +1,12 @@ // Code generated by protoc-gen-go. -// source: chaincode.proto +// source: table.proto // DO NOT EDIT! /* Package shim is a generated protocol buffer package. It is generated from these files: - chaincode.proto + table.proto It has these top-level messages: ColumnDefinition diff --git a/core/chaincode/shim/chaincode.proto b/core/chaincode/shim/table.proto similarity index 92% rename from core/chaincode/shim/chaincode.proto rename to core/chaincode/shim/table.proto index d4d530c769d..919731875f1 100644 --- a/core/chaincode/shim/chaincode.proto +++ b/core/chaincode/shim/table.proto @@ -17,7 +17,8 @@ limitations under the License. syntax = "proto3"; package shim; - +option java_package = "org.hyperledger.protos"; +option java_outer_classname = "TableProto"; message ColumnDefinition { string name = 1; enum Type { diff --git a/examples/chaincode/java/TableExample/build.gradle b/examples/chaincode/java/TableExample/build.gradle new file mode 100644 index 00000000000..e0d87bec086 --- /dev/null +++ b/examples/chaincode/java/TableExample/build.gradle @@ -0,0 +1,70 @@ +/* +Copyright DTCC 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +buildscript { + repositories { + mavenLocal() + mavenCentral() + jcenter() + } +} + +plugins { + id "java" + id "eclipse" + id "application" + id "idea" +} + +archivesBaseName = "TableExample" +mainClassName="example.TableExample" + +run { + if (project.hasProperty("appArgs")) { + args = Eval.me(appArgs) + } +} + +sourceSets { + main { + java { + srcDir 'src/main/java' + } + } +} + +repositories { + mavenLocal() + mavenCentral() +} + + +distZip { +// destinationDir = file("${projectDir}") + archiveName='Chaincode.zip' + duplicatesStrategy='exclude' +} + +startScripts{ + applicationName='runChaincode' +} + +dependencies { + compile 'io.grpc:grpc-all:0.13.2' + compile 'commons-cli:commons-cli:1.3.1' + compile project(':core:chaincode:shim:java') + } diff --git a/examples/chaincode/java/TableExample/src/main/java/example/TableExample.java b/examples/chaincode/java/TableExample/src/main/java/example/TableExample.java new file mode 100644 index 00000000000..857e79a6b21 --- /dev/null +++ b/examples/chaincode/java/TableExample/src/main/java/example/TableExample.java @@ -0,0 +1,194 @@ +/* +Copyright DTCC 2016 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package example; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hyperledger.java.shim.ChaincodeBase; +import org.hyperledger.java.shim.ChaincodeStub; +import org.hyperledger.protos.TableProto; + +import java.util.ArrayList; +import java.util.List; + + +public class TableExample extends ChaincodeBase { + private static String tableName = "MyNewTable1"; + private static Log log = LogFactory.getLog(TableExample.class); + @java.lang.Override + public String run(ChaincodeStub stub, String function, String[] args) { + log.info("In run, function:"+function); + switch (function) { + + case "init": + init(stub, function, args); + break; + case "insert": + insertRow(stub, args, false); + break; + case "update": + insertRow(stub, args, true); + break; + case "delete": + delete(stub, args); + break; + default: + log.error("No matching case for function:"+function); + + } + return null; + } + + private void insertRow(ChaincodeStub stub, String[] args, boolean update) { + + int fieldID = 0; + + try { + fieldID = Integer.parseInt(args[0]); + }catch (NumberFormatException e){ + log.error("Illegal field id -" + e.getMessage()); + return; + } + + TableProto.Column col1 = + TableProto.Column.newBuilder() + .setUint32(fieldID).build(); + TableProto.Column col2 = + TableProto.Column.newBuilder() + .setString(args[1]).build(); + List cols = new ArrayList(); + cols.add(col1); + cols.add(col2); + + TableProto.Row row = TableProto.Row.newBuilder() + .addAllColumns(cols) + .build(); + try { + + boolean success = false; + if(update){ + success = stub.replaceRow(tableName,row); + }else + { + success = stub.insertRow(tableName, row); + } + if (success){ + log.info("Row successfully inserted"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public String init(ChaincodeStub stub, String function, String[] args) { + List cols = new ArrayList(); + + cols.add(TableProto.ColumnDefinition.newBuilder() + .setName("ID") + .setKey(true) + .setType(TableProto.ColumnDefinition.Type.UINT32) + .build() + ); + + cols.add(TableProto.ColumnDefinition.newBuilder() + .setName("Name") + .setKey(false) + .setType(TableProto.ColumnDefinition.Type.STRING) + .build() + ); + + + try { + try { + stub.deleteTable(tableName); + } catch (Exception e) { + e.printStackTrace(); + } + stub.createTable(tableName,cols); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private boolean delete(ChaincodeStub stub, String[] args){ + int fieldID = 0; + + try { + fieldID = Integer.parseInt(args[0]); + }catch (NumberFormatException e){ + log.error("Illegal field id -" + e.getMessage()); + return false; + } + + + TableProto.Column queryCol = + TableProto.Column.newBuilder() + .setUint32(fieldID).build(); + List key = new ArrayList<>(); + key.add(queryCol); + return stub.deleteRow(tableName, key); + } + + @java.lang.Override + public String query(ChaincodeStub stub, String function, String[] args) { + log.info("query"); + int fieldID = 0; + + try { + fieldID = Integer.parseInt(args[0]); + }catch (NumberFormatException e){ + log.error("Illegal field id -" + e.getMessage()); + return "ERROR querying "; + } + TableProto.Column queryCol = + TableProto.Column.newBuilder() + .setUint32(fieldID).build(); + List key = new ArrayList<>(); + key.add(queryCol); + switch (function){ + case "get": { + try { + TableProto.Row tableRow = stub.getRow(tableName,key); + if (tableRow.getSerializedSize() > 0) { + return tableRow.getColumns(1).getString(); + }else + { + return "No record found !"; + } + } catch (Exception invalidProtocolBufferException) { + invalidProtocolBufferException.printStackTrace(); + } + } + default: + log.error("No matching case for function:"+function); + return ""; + } + + } + + @java.lang.Override + public String getChaincodeID() { + return "TableExample"; + } + + public static void main(String[] args) throws Exception { + log.info("starting"); + new TableExample().start(args); + } + +} diff --git a/settings.gradle b/settings.gradle index 3743501471f..f25b6013f4d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,4 @@ include ":examples:chaincode:java:MapExample" include ":examples:chaincode:java:LinkExample" include ":examples:chaincode:java:SimpleSample" include ":examples:chaincode:java:RangeExample" +include ":examples:chaincode:java:TableExample"