Commit 97ed71fd authored by Satheesh Kathamuthu's avatar Satheesh Kathamuthu
Browse files

Java shim/chaincode project reorg, separate java docker env

Fixes the following issues
https://github.com/hyperledger/fabric/issues/2146
https://github.com/hyperledger/fabric/issues/2139



 1. Separate java docker image
 2. Added java package info to proto files
 3. Removed duplicate .proto files checked into java shim project, and copy it as part of build process
 4. Modified java unit test to refer to new path
 5. Updated JAVAChaincode documentation to reflect new changes and added instructions for developing new chaincode
 6. Split java chaincode examples from java shim project

Change-Id: I7cbe92449c30700f17949b03f8fc7c06e0c7efb6
Signed-off-by: default avatarSatheesh Kathamuthu <satheesh.ceg@gmail.com>
parent 8ec1147a
......@@ -54,8 +54,10 @@ SUBDIRS:=$(strip $(SUBDIRS))
BASEIMAGE_RELEASE = $(shell cat ./images/base/release)
BASEIMAGE_DEPS = $(shell git ls-files images/base scripts/provision)
JAVASHIM_DEPS = $(shell git ls-files core/chaincode/shim/java)
PROJECT_FILES = $(shell git ls-files)
IMAGES = base src ccenv peer membersrvc
IMAGES = base src ccenv peer membersrvc javaenv
all: peer membersrvc checks
......@@ -135,9 +137,9 @@ build/docker/bin/%: build/image/src/.dummy $(PROJECT_FILES)
build/bin:
mkdir -p $@
# Both peer and peer-image depend on ccenv-image
build/bin/peer: build/image/ccenv/.dummy
build/image/peer/.dummy: build/image/ccenv/.dummy
# Both peer and peer-image depend on ccenv-image and javaenv-image (all docker env images it supports)
build/bin/peer: build/image/ccenv/.dummy build/image/javaenv/.dummy
build/image/peer/.dummy: build/image/ccenv/.dummy build/image/javaenv/.dummy
build/image/peer/.dummy: build/docker/bin/examples/events/block-listener/
build/bin/%: build/image/base/.dummy $(PROJECT_FILES)
......@@ -170,6 +172,20 @@ build/image/ccenv/.dummy: build/image/src/.dummy build/image/ccenv/bin/protoc-ge
docker build -t $(PROJECT_NAME)-ccenv:latest $(@D)
@touch $@
# Special override for java-image
build/image/javaenv/.dummy: Makefile $(JAVASHIM_DEPS)
@echo "Building docker javaenv-image"
@mkdir -p $(@D)
@cat images/javaenv/Dockerfile.in > $(@D)/Dockerfile
# 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
@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
docker build -t $(PROJECT_NAME)-javaenv:latest $(@D)
@touch $@
# Default rule for image creation
build/image/%/.dummy: build/image/src/.dummy build/docker/bin/%
$(eval TARGET = ${patsubst build/image/%/.dummy,%,${@}})
......
......@@ -6,16 +6,32 @@
#
# @chaincodeImagesUpToDate use this if all scenarios chaincode images are up to date, and do NOT require building. BE SURE!!!
#
#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.
#
#
#@chaincodeImagesUpToDate
Feature: SimpleSample Java example
#@doNotDecompose
# @wip
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 "core/chaincode/shim/java" of "JAVA" with ctor "init" to "vp0"
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
......@@ -24,12 +40,12 @@ Feature: SimpleSample Java example
When requesting "/chain" from "vp0"
Then I should get a JSON response with "height" = "2"
When I query chaincode "example2" function name "query" on "vp0":
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'}"
When I invoke chaincode "example2" function name "transfer" on "vp0"
When I invoke chaincode "SimpleSample" function name "transfer" on "vp0"
|arg1|arg2|arg3|
| a | b | 10 |
Then I should have received a transactionID
......@@ -38,12 +54,66 @@ Feature: SimpleSample Java example
When requesting "/chain" from "vp0"
Then I should get a JSON response with "height" = "3"
When I query chaincode "example2" function name "query" on "vp0":
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'}"
When I query chaincode "example2" function name "query" on "vp0":
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'}"
Scenario: java RangeExample 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/RangeExample" of "JAVA" with ctor "init" to "vp0"
||
||
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 I invoke chaincode "RangeExample" function name "put" on "vp0"
|arg1|arg2|
| a | alice |
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 I invoke chaincode "RangeExample" function name "put" on "vp0"
|arg1|arg2|
| b | 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 query chaincode "RangeExample" function name "get" on "vp0":
|arg1|
| a |
Then I should get a JSON response with "result.message" = "alice"
When I query chaincode "RangeExample" function name "get" on "vp0":
|arg1|
| b |
Then I should get a JSON response with "result.message" = "bob"
When I query chaincode "RangeExample" function name "keys" on "vp0":
||
||
Then I should get a JSON response with "result.message" = "[a, b]"
When I invoke chaincode "RangeExample" function name "del" on "vp0"
|arg1|
| b |
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 "RangeExample" function name "keys" on "vp0":
||
||
Then I should get a JSON response with "result.message" = "[a]"
\ No newline at end of file
......@@ -298,10 +298,13 @@ func (chaincodeSupport *ChaincodeSupport) getArgsAndEnv(cID *pb.ChaincodeID, cLa
case pb.ChaincodeSpec_JAVA:
//TODO add security args
args = strings.Split(
fmt.Sprintf("/usr/bin/gradle run -p /root -PappArgs=[\"-a\",\"%s\",\"-i\",\"%s\"]"+
" -x processResources -x classes", chaincodeSupport.peerAddress, cID.Name),
fmt.Sprintf("/root/Chaincode/bin/runChaincode -a %s -i %s",
chaincodeSupport.peerAddress, cID.Name),
" ")
chaincodeLogger.Debugf("Executable is gradle run on chaincode ID %s", cID.Name)
if chaincodeSupport.peerTLS {
args = append(args, " -s")
}
chaincodeLogger.Debugf("Executable is %s", args[0])
default:
return nil, nil, fmt.Errorf("Unknown chaincodeType: %s", cLang)
}
......
......@@ -6,9 +6,9 @@ import (
"strings"
"time"
"github.com/spf13/viper"
cutil "github.com/hyperledger/fabric/core/container/util"
pb "github.com/hyperledger/fabric/protos"
"github.com/spf13/viper"
)
//tw is expected to have the chaincode in it from GenerateHashcode.
......@@ -38,20 +38,30 @@ func writeChaincodePackage(spec *pb.ChaincodeSpec, tw *tar.Writer) error {
}
urlLocation = urlLocation[strings.LastIndex(urlLocation, "/")+1:]
var newRunLine string
var dockerFileContents string
var buf []string
if viper.GetBool("security.enabled") {
//todo
} else {
newRunLine = fmt.Sprintf("COPY %s /root/\n"+
"RUN cd /root/ && gradle build", urlLocation)
buf = append(buf, viper.GetString("chaincode.java.Dockerfile"))
buf = append(buf, "COPY src /root")
buf = append(buf, "RUN gradle -b build.gradle build")
buf = append(buf, "RUN unzip -od /root build/distributions/Chaincode.zip")
}
dockerFileContents = strings.Join(buf, "\n")
dockerFileContents := fmt.Sprintf("%s\n%s", viper.GetString("chaincode.java.Dockerfile"), newRunLine)
dockerFileSize := int64(len([]byte(dockerFileContents)))
//Make headers identical by using zero time
var zeroTime time.Time
tw.WriteHeader(&tar.Header{Name: "Dockerfile", Size: dockerFileSize, ModTime: zeroTime, AccessTime: zeroTime, ChangeTime: zeroTime})
tw.Write([]byte(dockerFileContents))
err := cutil.WriteJavaProjectToPackage(tw, spec.ChaincodeID.Path)
if err != nil {
return fmt.Errorf("Error writing Chaincode package contents: %s", err)
}
return nil
}
......@@ -40,7 +40,7 @@ func TestJava_BuildImage(t *testing.T) {
return
}
chaincodePath := "../../../shim/java"
chaincodePath := "../../../../../examples/chaincode/java/SimpleSample"
//TODO find a better way to launch example java chaincode
spec := &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_JAVA, ChaincodeID: &pb.ChaincodeID{Path: chaincodePath}, CtorMsg: &pb.ChaincodeInput{Args: shim.ToChaincodeArgs("f")}}
if _, err := vm.BuildChaincodeContainer(spec); err != nil {
......
......@@ -24,21 +24,15 @@ buildscript {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.7.6'
}
}
plugins {
id "java"
id "com.google.protobuf" version "0.7.6"
id "eclipse"
id "application"
}
archivesBaseName = 'chaincode'
version = '1.0'
mainClassName = "example.SimpleSample"
run {
if (project.hasProperty("appArgs")) {
args = Eval.me(appArgs)
}
}
sourceSets {
main {
......@@ -82,8 +76,25 @@ protobuf {
}
}
task copyToLib(type: Copy) {
into "$buildDir/libs"
from configurations.runtime
}
task copyProtos(type:Copy){
into "${projectDir}/src/main/proto"
from "${rootDir}/protos"
include '**/chaincodeevent.proto'
include '**/chaincode.proto'
}
tasks['build'].mustRunAfter tasks['copyProtos']
build.dependsOn(copyProtos)
build.finalizedBy(copyToLib)
dependencies {
compile 'com.google.protobuf:protobuf-java:3.0.0-beta-2'
compile 'com.google.protobuf:protobuf-java:3.0.0-beta-2'
compile 'io.grpc:grpc-all:0.13.2'
compile 'commons-cli:commons-cli:1.3.1'
}
#!/bin/bash
#
#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.
#
#
set -e
PARENTDIR=$(pwd)
gradle -q -b ${PARENTDIR}/core/chaincode/shim/java/build.gradle clean
gradle -q -b ${PARENTDIR}/core/chaincode/shim/java/build.gradle build
......@@ -35,11 +35,11 @@ import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
import io.grpc.stub.StreamObserver;
import io.netty.handler.ssl.SslContext;
import protos.Chaincode.ChaincodeID;
import protos.Chaincode.ChaincodeMessage;
import protos.Chaincode.ChaincodeMessage.Type;
import protos.ChaincodeSupportGrpc;
import protos.ChaincodeSupportGrpc.ChaincodeSupportStub;
import org.hyperledger.protos.Chaincode.ChaincodeID;
import org.hyperledger.protos.Chaincode.ChaincodeMessage;
import org.hyperledger.protos.Chaincode.ChaincodeMessage.Type;
import org.hyperledger.protos.ChaincodeSupportGrpc;
import org.hyperledger.protos.ChaincodeSupportGrpc.ChaincodeSupportStub;
public abstract class ChaincodeBase {
......
......@@ -17,6 +17,10 @@ limitations under the License.
package org.hyperledger.java.shim;
import com.google.protobuf.ByteString;
import org.hyperledger.protos.Chaincode;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
......@@ -65,20 +69,34 @@ public class ChaincodeStub {
}
/**
*
* 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
* @param limit
* @return
*/
// public HashMap<String, String> rangeQueryState(String startKey, String endKey, int limit) {
// HashMap<String, String> map = new HashMap<>();
// for (RangeQueryStateKeyValue mapping : handler.handleRangeQueryState(
// startKey, endKey, limit, uuid).getKeysAndValuesList()) {
// map.put(mapping.getKey(), mapping.getValue().toStringUtf8());
// }
// return map;
// }
public Map<String, String> rangeQueryState(String startKey, String endKey) {
Map<String, String> retMap = new HashMap<>();
for (Map.Entry<String, ByteString> 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<String, ByteString> rangeQueryRawState(String startKey, String endKey) {
Map<String, ByteString> map = new HashMap<>();
for (Chaincode.RangeQueryStateKeyValue mapping : handler.handleRangeQueryState(
startKey, endKey, uuid).getKeysAndValuesList()) {
map.put(mapping.getKey(), mapping.getValue());
}
return map;
}
/**
*
......
......@@ -19,30 +19,10 @@ under the License.
package org.hyperledger.java.shim;
import static org.hyperledger.java.fsm.CallbackType.AFTER_EVENT;
import static org.hyperledger.java.fsm.CallbackType.BEFORE_EVENT;
import static org.hyperledger.java.fsm.CallbackType.ENTER_STATE;
import static protos.Chaincode.ChaincodeMessage.Type.COMPLETED;
import static protos.Chaincode.ChaincodeMessage.Type.DEL_STATE;
import static protos.Chaincode.ChaincodeMessage.Type.ERROR;
import static protos.Chaincode.ChaincodeMessage.Type.GET_STATE;
import static protos.Chaincode.ChaincodeMessage.Type.INIT;
import static protos.Chaincode.ChaincodeMessage.Type.INVOKE_CHAINCODE;
import static protos.Chaincode.ChaincodeMessage.Type.INVOKE_QUERY;
import static protos.Chaincode.ChaincodeMessage.Type.PUT_STATE;
import static protos.Chaincode.ChaincodeMessage.Type.QUERY;
import static protos.Chaincode.ChaincodeMessage.Type.QUERY_COMPLETED;
import static protos.Chaincode.ChaincodeMessage.Type.QUERY_ERROR;
import static protos.Chaincode.ChaincodeMessage.Type.READY;
import static protos.Chaincode.ChaincodeMessage.Type.REGISTERED;
import static protos.Chaincode.ChaincodeMessage.Type.RESPONSE;
import static protos.Chaincode.ChaincodeMessage.Type.TRANSACTION;
import java.util.HashMap;
import java.util.List;
import com.google.protobuf.ByteString;
import io.grpc.stub.StreamObserver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.java.fsm.CBDesc;
import org.hyperledger.java.fsm.Event;
import org.hyperledger.java.fsm.EventDesc;
......@@ -50,15 +30,15 @@ import org.hyperledger.java.fsm.FSM;
import org.hyperledger.java.fsm.exceptions.CancelledException;
import org.hyperledger.java.fsm.exceptions.NoTransitionException;
import org.hyperledger.java.helper.Channel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import io.grpc.stub.StreamObserver;
import protos.Chaincode.ChaincodeID;
import protos.Chaincode.ChaincodeInput;
import protos.Chaincode.ChaincodeMessage;
import protos.Chaincode.ChaincodeMessage.Builder;
import protos.Chaincode.ChaincodeSpec;
import protos.Chaincode.PutStateInfo;
import org.hyperledger.protos.Chaincode.*;
import org.hyperledger.protos.Chaincode.ChaincodeMessage.Builder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hyperledger.java.fsm.CallbackType.*;
import static org.hyperledger.protos.Chaincode.ChaincodeMessage.Type.*;
public class Handler {
......@@ -67,8 +47,8 @@ public class Handler {
private StreamObserver<ChaincodeMessage> chatStream;
private ChaincodeBase chaincode;
private HashMap<String, Boolean> isTransaction;
private HashMap<String, Channel<ChaincodeMessage>> responseChannel;
private Map<String, Boolean> isTransaction;
private Map<String, Channel<ChaincodeMessage>> responseChannel;
public Channel<NextStateInfo> nextState;
private FSM fsm;
......@@ -692,80 +672,79 @@ public class Handler {
}
}
// public RangeQueryStateResponse handleRangeQueryState(String startKey, String endKey, int limit, String uuid) {
// // Create the channel on which to communicate the response from validating peer
// Channel<ChaincodeMessage> responseChannel;
// try {
// responseChannel = createChannel(uuid);
// } catch (Exception e) {
// logger.debug(String.format("[%s]Another state request pending for this Uuid."
// + " Cannot process.", shortID(uuid)));
// throw e;
// }
//
// //Defer
// try {
// // Send RANGE_QUERY_STATE message to validator chaincode support
// RangeQueryStateInfo payload = RangeQueryStateInfo.newBuilder()
// .setStartKey(startKey)
// .setEndKey(endKey)
// .setLimit(limit)
// .build();
//
// ChaincodeMessage message = ChaincodeMessage.newBuilder()
// .setType(RANGE_QUERY_STATE)
// .setPayload(payload.toByteString())
// .setTxid(uuid)
// .build();
//
// logger.debug(String.format("[%s]Sending %s", shortID(message), RANGE_QUERY_STATE));
// try {
// serialSend(message);
// } catch (Exception e){
// logger.error(String.format("[%s]error sending %s", shortID(message), RANGE_QUERY_STATE));
// throw new RuntimeException("could not send message");
// }
//
// // Wait on responseChannel for response
// ChaincodeMessage response;
// try {
// response = receiveChannel(responseChannel);
// } catch (Exception e) {
// logger.error(String.format("[%s]Received unexpected message type", uuid));
// throw new RuntimeException("Received unexpected message type");
// }
//
// if (response.getType() == RESPONSE) {
// // Success response
// logger.debug(String.format("[%s]Received %s. Successfully got range",
// shortID(response.getTxid()), RESPONSE));
//
// RangeQueryStateResponse rangeQueryResponse;
// try {
// rangeQueryResponse = RangeQueryStateResponse.parseFrom(response.getPayload());
// } catch (Exception e) {
// logger.error(String.format("[%s]unmarshall error", shortID(response.getTxid())));
// throw new RuntimeException("Error unmarshalling RangeQueryStateResponse.");
// }
//
// return rangeQueryResponse;
// }
//
// if (response.getType() == ERROR) {
// // Error response
// logger.error(String.format("[%s]Received %s",
// shortID(response.getTxid()), ERROR));
// throw new RuntimeException(response.getPayload().toStringUtf8());
// }
//
// // Incorrect chaincode message received
// logger.error(String.format("Incorrect chaincode message %s recieved. Expecting %s or %s",
// response.getType(), RESPONSE, ERROR));
// throw new RuntimeException("Incorrect chaincode message received");
// } finally {
// deleteChannel(uuid);
// }
// }
public RangeQueryStateResponse handleRangeQueryState(String startKey, String endKey, String uuid) {
// Create the channel on which to communicate the response from validating peer
Channel<ChaincodeMessage> responseChannel;
try {
responseChannel = createChannel(uuid);
} catch (Exception e) {
logger.debug(String.format("[%s]Another state request pending for this Uuid."
+ " Cannot process.", shortID(uuid)));
throw e;
}
//Defer
try {
// Send RANGE_QUERY_STATE message to validator chaincode support
RangeQueryState payload = RangeQueryState.newBuilder()
.setStartKey(startKey)
.setEndKey(endKey)
.build();
ChaincodeMessage message = ChaincodeMessage.newBuilder()
.setType(RANGE_QUERY_STATE)
.setPayload(payload.toByteString())
.setTxid(uuid)
.build();
logger.debug(String.format("[%s]Sending %s", shortID(message), RANGE_QUERY_STATE));
try {
serialSend(message);
} catch (Exception e){
logger.error(String.format("[%s]error sending %s", shortID(message), RANGE_QUERY_STATE));
throw new RuntimeException("could not send message");
}
// Wait on responseChannel for response
ChaincodeMessage response;
try {
response = receiveChannel(responseChannel);
} catch (Exception e) {
logger.error(String.format("[%s]Received unexpected message type", uuid));
throw new RuntimeException("Received unexpected message type");
}
if (response.getType() == RESPONSE) {
// Success response
logger.debug(String.format("[%s]Received %s. Successfully got range",
shortID(response.getTxid()), RESPONSE));
RangeQueryStateResponse rangeQueryResponse;
try {
rangeQueryResponse = RangeQueryStateResponse.parseFrom(response.getPayload());
} catch (Exception e) {
logger.error(String.format("[%s]unmarshall error", shortID(response.getTxid())));
throw new RuntimeException("Error unmarshalling RangeQueryStateResponse.");
}
return rangeQueryResponse;
}