diff --git a/fabric-chaincode-example-gradle/build.gradle b/fabric-chaincode-example-gradle/build.gradle index 18c12c30..5fb4ec96 100644 --- a/fabric-chaincode-example-gradle/build.gradle +++ b/fabric-chaincode-example-gradle/build.gradle @@ -15,7 +15,7 @@ repositories { } dependencies { - compile group: 'org.hyperledger.fabric-chaincode-java', name: 'fabric-chaincode-shim', version: '2.0.0-SNAPSHOT' + implementation group: 'org.hyperledger.fabric-chaincode-java', name: 'fabric-chaincode-shim', version: '2.0.0-SNAPSHOT' testCompile group: 'junit', name: 'junit', version: '4.12' } diff --git a/fabric-chaincode-protos/build.gradle b/fabric-chaincode-protos/build.gradle index 4a4d4d89..34196217 100644 --- a/fabric-chaincode-protos/build.gradle +++ b/fabric-chaincode-protos/build.gradle @@ -45,7 +45,7 @@ buildscript { } dependencies { - compile 'com.google.protobuf:protobuf-java:3.5.1' + compile 'com.google.protobuf:protobuf-java:3.7.1' compile 'com.google.protobuf:protobuf-java-util:3.5.1' compile 'io.grpc:grpc-netty-shaded:1.9.0' compile 'io.grpc:grpc-protobuf:1.9.0' diff --git a/fabric-chaincode-shim/build.gradle b/fabric-chaincode-shim/build.gradle index 2c9187bc..26b6918c 100644 --- a/fabric-chaincode-shim/build.gradle +++ b/fabric-chaincode-shim/build.gradle @@ -78,7 +78,8 @@ jacocoTestCoverageVerification { 'org.hyperledger.fabric.contract.routing.impl.ContractDefinitionImpl', 'org.hyperledger.fabric.contract.routing.RoutingRegistry', 'org.hyperledger.fabric.contract.execution.impl.ContractInvocationRequest', - 'org.hyperledger.fabric.contract.routing.TransactionType'] + 'org.hyperledger.fabric.contract.routing.TransactionType', + 'org.hyperledger.fabric.contract.metadata.MetadataBuilder'] limit { minimum = 0.86 } @@ -91,7 +92,8 @@ jacocoTestCoverageVerification { 'org.hyperledger.fabric.contract.execution.impl.ContractInvocationRequest', 'org.hyperledger.fabric.contract.routing.impl.ContractDefinitionImpl', 'org.hyperledger.fabric.contract.routing.RoutingRegistry', - 'org.hyperledger.fabric.shim.impl.Handler'] + 'org.hyperledger.fabric.shim.impl.Handler', + 'org.hyperledger.fabric.contract.metadata.MetadataBuilder'] limit { minimum = 0.71 } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/Logger.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/Logger.java index b77fd94f..e9d2b9e6 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/Logger.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/Logger.java @@ -9,11 +9,19 @@ import java.io.StringWriter; import java.util.function.Supplier; import java.util.logging.Level; +import java.util.logging.LogManager; -public class Logger extends java.util.logging.Logger{ +/** + * Logger class to use throughout the Contract Implementation + * + */ +public class Logger extends java.util.logging.Logger { protected Logger(String name) { super(name, null); + + // ensure that the parent logger is set + this.setParent(java.util.logging.Logger.getLogger("org.hyperledger.fabric")); } public static Logger getLogger(String name) { @@ -29,11 +37,14 @@ public void debug(String msg) { } public static Logger getLogger(Class class1) { - return Logger.getLogger(class1.getName()); + // important to add the logger to the log manager + Logger l = Logger.getLogger(class1.getName()); + LogManager.getLogManager().addLogger(l); + return l; } public void error(String message) { - log(Level.SEVERE,message); + log(Level.SEVERE, message); } public void error(Supplier msgSupplier) { @@ -41,7 +52,8 @@ public void error(Supplier msgSupplier) { } public String formatError(Throwable throwable) { - if (throwable == null) return null; + if (throwable == null) + return null; final StringWriter buffer = new StringWriter(); buffer.append(throwable.getMessage()); throwable.printStackTrace(new PrintWriter(buffer)); diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/Context.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/Context.java index 93ebc90d..99ef2bef 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/Context.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/Context.java @@ -9,7 +9,14 @@ import org.hyperledger.fabric.shim.ChaincodeStub; /** - * Context provides {@link ChaincodeStub} API for handling world state + * + * This context is available to all 'transaction functions' and provides the + * transaction context. + * + * It also provides access to the APIs for the world state. {@see ChaincodeStub} + * + * Applications can implement their own versions if they wish to add + * functionality. */ public interface Context extends ChaincodeStub { } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContextFactory.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContextFactory.java index bf4be589..011ebfca 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContextFactory.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContextFactory.java @@ -1,20 +1,20 @@ /* -Copyright IBM Corp., DTCC All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.contract; -import org.hyperledger.fabric.shim.ChaincodeStub; - import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import org.hyperledger.fabric.shim.ChaincodeStub; + /** - * Factory to create {@link Context} from {@link ChaincodeStub} - * by wrapping stub with dynamic proxy. + * Factory to create {@link Context} from {@link ChaincodeStub} by wrapping stub + * with dynamic proxy. */ public class ContextFactory { private static ContextFactory cf; @@ -27,11 +27,8 @@ static synchronized public ContextFactory getInstance() { } public synchronized Context createContext(final ChaincodeStub stub) { - Context newContext = (Context) Proxy.newProxyInstance( - this.getClass().getClassLoader(), - new Class[]{Context.class}, - new ContextInvocationHandler(stub) - ); + Context newContext = (Context) Proxy.newProxyInstance(this.getClass().getClassLoader(), + new Class[] { Context.class }, new ContextInvocationHandler(stub)); return newContext; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractInterface.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractInterface.java index c51cd27f..cdbc4e60 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractInterface.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractInterface.java @@ -7,7 +7,6 @@ package org.hyperledger.fabric.contract; import org.hyperledger.fabric.shim.ChaincodeStub; -import org.hyperledger.fabric.shim.ResponseUtils; /** * Interface all contracts should implement diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRouter.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRouter.java index 7455a62b..0a27f4dd 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRouter.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRouter.java @@ -14,7 +14,6 @@ import org.hyperledger.fabric.contract.routing.ContractDefinition; import org.hyperledger.fabric.contract.routing.RoutingRegistry; import org.hyperledger.fabric.contract.routing.TxFunction; -import org.hyperledger.fabric.contract.routing.TxFunction.Routing; import org.hyperledger.fabric.contract.routing.TypeRegistry; import org.hyperledger.fabric.contract.routing.impl.RoutingRegistryImpl; import org.hyperledger.fabric.contract.routing.impl.TypeRegistryImpl; @@ -27,114 +26,118 @@ * {@link org.hyperledger.fabric.shim.Chaincode} interface. */ public class ContractRouter extends ChaincodeBase { - private static Logger logger = Logger.getLogger(ContractRouter.class.getName()); - - private RoutingRegistry registry; - private TypeRegistry typeRegistry; - private ExecutionService executor; - - /** - * Take the arguments from the cli, and initiate processing of cli options and - * environment variables. - * - * Create the Contract scanner, and the Execution service - * - * @param args - */ - public ContractRouter(String[] args) { - super.initializeLogging(); - super.processEnvironmentOptions(); - super.processCommandLineOptions(args); - - super.validateOptions(); - registry = new RoutingRegistryImpl(); - typeRegistry = new TypeRegistryImpl(); - executor = ExecutionFactory.getInstance().createExecutionService(); - } - - /** - * Locate all the contracts that are available on the classpath - */ - void findAllContracts() { - registry.findAndSetContracts(this.typeRegistry); - } - - /** - * Start the chaincode container off and running, this will send the initial - * flow back to the peer - * - * @throws Exception - */ - void startRouting() { - try { - super.connectToPeer(); - } catch (Exception e) { - ContractRuntimeException cre = new ContractRuntimeException("Unable to start routing"); - logger.error(()->logger.formatError(cre)); - throw cre; - } - } - - @Override - public Response init(ChaincodeStub stub) { - InvocationRequest request = ExecutionFactory.getInstance().createRequest(stub); - Routing routing = getRouting(request); - - logger.debug(() -> "Got routing:" + routing); - return executor.executeRequest(routing, request, stub); - } - - @Override - public Response invoke(ChaincodeStub stub) { - logger.debug(() -> "Got the invocations:" + stub.getFunction() + " " + stub.getParameters()); - InvocationRequest request = ExecutionFactory.getInstance().createRequest(stub); - Routing routing = getRouting(request); - - logger.debug(() -> "Got routing:" + routing); - return executor.executeRequest(routing, request, stub); - } - - /** - * Given the Invocation Request, return the routing object for this call - * - * @param request - * @return - */ - TxFunction.Routing getRouting(InvocationRequest request) { - //request name is the fully qualified 'name:txname' + private static Logger logger = Logger.getLogger(ContractRouter.class.getName()); + + private RoutingRegistry registry; + private TypeRegistry typeRegistry; + private ExecutionService executor; + + /** + * Take the arguments from the cli, and initiate processing of cli options and + * environment variables. + * + * Create the Contract scanner, and the Execution service + * + * @param args + */ + public ContractRouter(String[] args) { + super.initializeLogging(); + super.processEnvironmentOptions(); + super.processCommandLineOptions(args); + + super.validateOptions(); + logger.debug("ContractRouter"); + registry = new RoutingRegistryImpl(); + typeRegistry = new TypeRegistryImpl(); + executor = ExecutionFactory.getInstance().createExecutionService(typeRegistry); + } + + /** + * Locate all the contracts that are available on the classpath + */ + protected void findAllContracts() { + registry.findAndSetContracts(this.typeRegistry); + } + + /** + * Start the chaincode container off and running, this will send the initial + * flow back to the peer + * + * @throws Exception + */ + void startRouting() { + try { + super.connectToPeer(); + } catch (Exception e) { + ContractRuntimeException cre = new ContractRuntimeException("Unable to start routing"); + logger.error(() -> logger.formatError(cre)); + throw cre; + } + } + + @Override + public Response invoke(ChaincodeStub stub) { + logger.info(() -> "Got invoke routing request"); + if (stub.getStringArgs().size() > 0) { + logger.info(() -> "Got the invoke request for:" + stub.getFunction() + " " + stub.getParameters()); + InvocationRequest request = ExecutionFactory.getInstance().createRequest(stub); + TxFunction txFn = getRouting(request); + + logger.info(() -> "Got routing:" + txFn.getRouting()); + return executor.executeRequest(txFn, request, stub); + } else { + return ResponseUtils.newSuccessResponse(); + } + + } + + @Override + public Response init(ChaincodeStub stub) { + return invoke(stub); + } + + /** + * Given the Invocation Request, return the routing object for this call + * + * @param request + * @return + */ + TxFunction getRouting(InvocationRequest request) { + // request name is the fully qualified 'name:txname' if (registry.containsRoute(request)) { - return registry.getRoute(request); + return registry.getTxFn(request); } else { - ContractDefinition contract = registry.getContract(request.getNamespace()); - return contract.getUnkownRoute(); + logger.debug(() -> "Namespace is " + request); + ContractDefinition contract = registry.getContract(request.getNamespace()); + return contract.getUnkownRoute(); } - } + } - /** - * Main method to start the contract based chaincode - * - */ - public static void main(String[] args) { + /** + * Main method to start the contract based chaincode + * + */ + public static void main(String[] args) { - ContractRouter cfc = new ContractRouter(args); - cfc.findAllContracts(); + ContractRouter cfc = new ContractRouter(args); + cfc.findAllContracts(); - // Create the Metadata ahead of time rather than have to produce every - // time - MetadataBuilder.initialize(cfc.getRoutingRegistry(),cfc.getTypeRegistry()); - logger.info(() -> "Metadata follows:" + MetadataBuilder.debugString()); + // Create the Metadata ahead of time rather than have to produce every + // time + MetadataBuilder.initialize(cfc.getRoutingRegistry(), cfc.getTypeRegistry()); + logger.info(() -> "Metadata follows:" + MetadataBuilder.debugString()); - // commence routing, once this has returned the chaincode and contract api is - // 'open for business' - cfc.startRouting(); + // commence routing, once this has returned the chaincode and contract api is + // 'open for chaining' + cfc.startRouting(); - } + } - private TypeRegistry getTypeRegistry() { - return this.typeRegistry; - } + protected TypeRegistry getTypeRegistry() { + return this.typeRegistry; + } - private RoutingRegistry getRoutingRegistry() { - return this.registry; - } + protected RoutingRegistry getRoutingRegistry() { + return this.registry; + } } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRuntimeException.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRuntimeException.java index d6effc34..6206e7b6 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRuntimeException.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/ContractRuntimeException.java @@ -6,28 +6,30 @@ package org.hyperledger.fabric.contract; /** - * Specific RuntimeException for events that occur within the contract implementation. + * Specific RuntimeException for events that occur in the calling and handling + * of the Contracts. * - * At some future point we may wish to add more diagnostic information into this, for example current tx id + * At some future point we wish to add more diagnostic information into this, + * for example current tx id * */ public class ContractRuntimeException extends RuntimeException { - public ContractRuntimeException(String string) { - super(string); - } + public ContractRuntimeException(String string) { + super(string); + } - public ContractRuntimeException(String string, Throwable cause) { - super(string,cause); - } + public ContractRuntimeException(String string, Throwable cause) { + super(string, cause); + } - public ContractRuntimeException(Throwable cause) { - super(cause); - } + public ContractRuntimeException(Throwable cause) { + super(cause); + } - /** - * Generated serial version id - */ - private static final long serialVersionUID = -884373036398750450L; + /** + * Generated serial version id + */ + private static final long serialVersionUID = -884373036398750450L; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Contract.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Contract.java index b13b945f..49def913 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Contract.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Contract.java @@ -1,25 +1,33 @@ /* -Copyright IBM Corp., DTCC All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.contract.annotation; -import io.swagger.v3.oas.annotations.info.Info; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import static java.lang.annotation.RetentionPolicy.RUNTIME; +import io.swagger.v3.oas.annotations.info.Info; /** - * Class level annotation that identifies this class as being a contract. + * Class level annotation that identifies this class as being a contract. Can + * supply information and an alternative name for the contract rather than the + * classname + * + * The Info object can be supplied to provide additional information about the + * contract; the format of this uses the OpenAPI v3 specification of info + * {@see io.swagger.v3.oas.annotations.info.Info} + * */ @Retention(RUNTIME) @Target(ElementType.TYPE) public @interface Contract { Info info(); - String namespace() default ""; + + String name() default ""; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/DataType.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/DataType.java index b6e06986..cc91780e 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/DataType.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/DataType.java @@ -1,24 +1,24 @@ /* -Copyright IBM Corp., DTCC All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.contract.annotation; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - /** - * Class level annotation indicating this class represents one of the complex types that - * can be returned or passed to the transaction functions + * Class level annotation indicating this class represents one of the complex + * types that can be returned or passed to the transaction functions. + * + * This will appear within the metadata */ @Retention(RUNTIME) @Target(ElementType.TYPE) public @interface DataType { - String regex() default ""; - - String namespace() default ""; + String namespace() default ""; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Property.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Property.java index b3412f48..e3d15155 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Property.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Property.java @@ -1,20 +1,23 @@ /* -Copyright IBM Corp., DTCC All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.contract.annotation; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - /** - * Property level annotation defining a property of the class (identified by {@link @DataType}) + * Property level annotation defining a property of the class (identified by + * {@link @DataType}) Can also be used on the paratemers of transaction + * functions */ @Retention(RUNTIME) -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) public @interface Property { + String[] schema() default {}; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Transaction.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Transaction.java index c27e009a..bdbe1a39 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Transaction.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/annotation/Transaction.java @@ -6,21 +6,26 @@ package org.hyperledger.fabric.contract.annotation; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + /** - * Method level annotation indicating the function to be a callable transaction function + * Method level annotation indicating the function to be a callable transaction + * function */ @Retention(RUNTIME) @Target(METHOD) public @interface Transaction { /** - * true indicates that this function is intended to be called with the 'submit' semantics

- * false indicates that this is intended to be called with the evaluate semantics + * TRUE indicates that this function is intended to be called with the 'submit' + * semantics. + * + * FALSE indicates that this is intended to be called with the evaluate + * semantics + * * @return */ boolean submit() default true; diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionFactory.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionFactory.java index 5bc5b78a..288ee4fb 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionFactory.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionFactory.java @@ -1,14 +1,14 @@ /* -Copyright IBM Corp., DTCC All Rights Reserved. +Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.fabric.contract.execution; -import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.execution.impl.ContractExecutionService; import org.hyperledger.fabric.contract.execution.impl.ContractInvocationRequest; +import org.hyperledger.fabric.contract.routing.TypeRegistry; import org.hyperledger.fabric.shim.ChaincodeStub; public class ExecutionFactory { @@ -26,9 +26,9 @@ public InvocationRequest createRequest(ChaincodeStub context) { return new ContractInvocationRequest(context); } - public ExecutionService createExecutionService() { + public ExecutionService createExecutionService(TypeRegistry typeRegistry) { if (es == null) { - es = new ContractExecutionService(); + es = new ContractExecutionService(typeRegistry); } return es; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionService.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionService.java index 62ddc680..052afde3 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionService.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/ExecutionService.java @@ -16,5 +16,5 @@ */ public interface ExecutionService { - Chaincode.Response executeRequest(TxFunction.Routing rd, InvocationRequest req, ChaincodeStub stub); + Chaincode.Response executeRequest(TxFunction txFn, InvocationRequest req, ChaincodeStub stub); } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializer.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializer.java new file mode 100644 index 00000000..4cf649b8 --- /dev/null +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializer.java @@ -0,0 +1,185 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract.execution; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Map; + +import org.hyperledger.fabric.Logger; +import org.hyperledger.fabric.contract.ContractRuntimeException; +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.DataTypeDefinition; +import org.hyperledger.fabric.contract.routing.PropertyDefinition; +import org.hyperledger.fabric.contract.routing.TypeRegistry; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Used as a the default serialisation for transmission from SDK to Contract + */ +public class JSONTransactionSerializer { + private static Logger logger = Logger.getLogger(JSONTransactionSerializer.class.getName()); + private TypeRegistry typeRegistry; + + /** + * Create a new serialiser and maintain a reference to the TypeRegistry + * + * @param typeRegistry + */ + public JSONTransactionSerializer(TypeRegistry typeRegistry) { + this.typeRegistry = typeRegistry; + } + + /** + * Convert the value supplied to a byte array, according to the TypeSchema + * + * @param value + * @param ts + * @return + */ + public byte[] toBuffer(Object value, TypeSchema ts) { + logger.debug(() -> "Schema to convert is " + ts); + byte[] buffer = null; + if (value != null) { + String type = ts.getType(); + if (type != null) { + switch (type) { + case "array": + JSONArray array = new JSONArray(value); + buffer = array.toString().getBytes(UTF_8); + break; + case "string": + buffer = ((String) value).getBytes(UTF_8); + break; + case "number": + case "integer": + case "boolean": + default: + buffer = (value).toString().getBytes(UTF_8); + } + } else { + JSONObject obj = new JSONObject(value); + buffer = obj.toString().getBytes(UTF_8); + } + } + return buffer; + } + + /** + * Take the byte buffer and return the object as required + * + * @param buffer Byte buffer from the wire + * @param ts TypeSchema representing the type + * + * @return Object created; relies on Java auto-boxing for primitives + * + * @throws InstantiationException + * @throws IllegalAccessException + */ + public Object fromBuffer(byte[] buffer, TypeSchema ts) { + try { + String stringData = new String(buffer, StandardCharsets.UTF_8); + Object value = null; + + value = _convert(stringData, ts); + + return value; + } catch (InstantiationException | IllegalAccessException e) { + ContractRuntimeException cre = new ContractRuntimeException(e); + throw cre; + } + } + + /* + * Internal method to do the conversion + */ + private Object _convert(String stringData, TypeSchema ts) + throws IllegalArgumentException, IllegalAccessException, InstantiationException { + logger.debug(() -> "Schema to convert is " + ts); + String type = ts.getType(); + String format = null; + Object value = null; + if (type == null) { + type = "object"; + String ref = ts.getRef(); + format = ref.substring(ref.lastIndexOf("/") + 1); + } + + if (type.contentEquals("string")) { + value = stringData; + } else if (type.contentEquals("integer")) { + String intFormat = ts.getFormat(); + if (intFormat.contentEquals("int32")) { + value = Integer.parseInt(stringData); + } else { + value = Long.parseLong(stringData); + } + } else if (type.contentEquals("number")) { + String numFormat = ts.getFormat(); + if (numFormat.contentEquals("float")) { + value = Float.parseFloat(stringData); + } else { + value = Double.parseDouble(stringData); + } + } else if (type.contentEquals("boolean")) { + value = Boolean.parseBoolean(stringData); + } else if (type.contentEquals("object")) { + value = createComponentInstance(format, stringData, ts); + } else if (type.contentEquals("array")) { + JSONArray jsonArray = new JSONArray(stringData); + TypeSchema itemSchema = ts.getItems(); + Object[] data = (Object[]) Array.newInstance(itemSchema.getTypeClass(this.typeRegistry), + jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + data[i] = _convert(jsonArray.get(i).toString(), itemSchema); + } + value = data; + + } + return value; + } + + Object createComponentInstance(String format, String jsonString, TypeSchema ts) { + + DataTypeDefinition dtd = this.typeRegistry.getDataType(format); + Object obj; + try { + obj = dtd.getTypeClass().newInstance(); + } catch (InstantiationException | IllegalAccessException e1) { + throw new ContractRuntimeException("Unable to to create new instance of type", e1); + } + + JSONObject json = new JSONObject(jsonString); + // request validation of the type may throw an exception if validation fails + ts.validate(json); + + try { + Map fields = dtd.getProperties(); + for (Iterator iterator = fields.values().iterator(); iterator.hasNext();) { + PropertyDefinition prop = iterator.next(); + + Field f = prop.getField(); + f.setAccessible(true); + Object newValue = _convert(json.get(prop.getName()).toString(), prop.getSchema()); + + f.set(obj, newValue); + + } + return obj; + } catch (SecurityException | IllegalArgumentException | IllegalAccessException | InstantiationException + | JSONException e) { + throw new ContractRuntimeException("Unable to convert JSON to object", e); + } + + } +} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractExecutionService.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractExecutionService.java index a45badae..247833e9 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractExecutionService.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractExecutionService.java @@ -6,24 +6,25 @@ package org.hyperledger.fabric.contract.execution.impl; -import static java.nio.charset.StandardCharsets.UTF_8; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.hyperledger.fabric.Logger; import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.ContractInterface; +import org.hyperledger.fabric.contract.ContractRuntimeException; import org.hyperledger.fabric.contract.annotation.Transaction; import org.hyperledger.fabric.contract.execution.ExecutionService; import org.hyperledger.fabric.contract.execution.InvocationRequest; +import org.hyperledger.fabric.contract.execution.JSONTransactionSerializer; +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.ParameterDefinition; import org.hyperledger.fabric.contract.routing.TxFunction; +import org.hyperledger.fabric.contract.routing.TypeRegistry; import org.hyperledger.fabric.shim.Chaincode; import org.hyperledger.fabric.shim.ChaincodeStub; import org.hyperledger.fabric.shim.ResponseUtils; @@ -34,100 +35,115 @@ public class ContractExecutionService implements ExecutionService { - private static Log logger = LogFactory.getLog(ContractExecutionService.class); - - Map proxies = new HashMap<>(); - Map methodInterceptors = new HashMap<>(); - - @Override - public Chaincode.Response executeRequest(TxFunction.Routing rd, InvocationRequest req, ChaincodeStub stub) { - logger.debug("Routing Request"); - logger.debug(rd); - final ContractInterface contractObject = rd.getContractObject(); - final Class contractClass = rd.getContractClass(); - if (!proxies.containsKey(req.getNamespace())) { - ProxyMethodInterceptor interceptor = createMethodInterceptorForContract(contractClass, contractObject); - methodInterceptors.put(req.getNamespace(), interceptor); - proxies.put(req.getNamespace(), createProxyForContract(contractClass, interceptor)); - } - - Object proxyObject = proxies.get(req.getNamespace()); - ProxyMethodInterceptor interceptor = methodInterceptors.get(req.getNamespace()); - interceptor.setContextForThread(stub); - final List args = convertArgs(req.getArgs(), rd.getMethod().getParameterTypes()); - - Chaincode.Response response; - try { - Object value = rd.getMethod().invoke(proxyObject, args.toArray()); - if (value==null){ - response = ResponseUtils.newSuccessResponse(); - } else { - String str = value.toString(); - response = ResponseUtils.newSuccessResponse(str.getBytes(UTF_8)); - } - } catch (IllegalAccessException e) { - logger.warn("Error during contract method invocation", e); - response = ResponseUtils.newErrorResponse(e); - } catch (InvocationTargetException e) { - logger.warn("Error during contract method invocation", e); - response = ResponseUtils.newErrorResponse(e.getCause()); - } - return response; - } - - private List convertArgs(List stubArgs, Class[] methodParameterTypes) { - List args = new ArrayList<>(); - for (int i = 0; i < methodParameterTypes.length; i++) { - Class param = methodParameterTypes[i]; - if (param.isArray()) { - args.add(stubArgs.get(i)); - } else { - args.add(new String(stubArgs.get(i), StandardCharsets.UTF_8)); - } - } - return args; - } - - private Object createProxyForContract(Class contractClass, ProxyMethodInterceptor interceptor) { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(contractClass); - enhancer.setCallback(interceptor); - return enhancer.create(); - } - - private ProxyMethodInterceptor createMethodInterceptorForContract(final Class contractClass, final ContractInterface contractObject) { - return new ProxyMethodInterceptor(contractClass, contractObject); - } - - private static class ProxyMethodInterceptor implements MethodInterceptor { - Class contractClass; - ContractInterface contractObject; - ThreadLocal context = new ThreadLocal<>(); - public ProxyMethodInterceptor(Class contractClass, ContractInterface contractObject) { - this.contractClass = contractClass; - this.contractObject = contractObject; - } - - public void setContextForThread(ChaincodeStub stub) { - context.set(contractObject.createContext(stub)); - } - - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) - throws Throwable { - if (method.getName().equals("getContext")) { - return context.get(); - } else if (method.getDeclaringClass() != Object.class && method.getDeclaringClass() != ContractInterface.class && - method.getAnnotation(Transaction.class) != null) { - contractObject.beforeTransaction(); - Object result = proxy.invokeSuper(obj, args); - contractObject.afterTransaction(); - return result; - } else { - return proxy.invokeSuper(obj, args); - } - } - - } + private static Logger logger = Logger.getLogger(ContractExecutionService.class.getName()); + + private JSONTransactionSerializer serializer; + + Map proxies = new HashMap<>(); + Map methodInterceptors = new HashMap<>(); + + public ContractExecutionService(TypeRegistry typeRegistry) { + // FUTURE: Permit this to swapped out as per node.js + this.serializer = new JSONTransactionSerializer(typeRegistry); + } + + @Override + public Chaincode.Response executeRequest(TxFunction txFn, InvocationRequest req, ChaincodeStub stub) { + logger.debug(() -> "Routing Request" + txFn); + TxFunction.Routing rd = txFn.getRouting(); + + final ContractInterface contractObject = rd.getContractObject(); + final Class contractClass = rd.getContractClass(); + if (!proxies.containsKey(req.getNamespace())) { + ProxyMethodInterceptor interceptor = createMethodInterceptorForContract(contractClass, contractObject); + methodInterceptors.put(req.getNamespace(), interceptor); + proxies.put(req.getNamespace(), createProxyForContract(contractClass, interceptor)); + } + + Object proxyObject = proxies.get(req.getNamespace()); + ProxyMethodInterceptor interceptor = methodInterceptors.get(req.getNamespace()); + interceptor.setContextForThread(stub); + final List args = convertArgs(req.getArgs(), txFn); + + Chaincode.Response response; + try { + Object value = rd.getMethod().invoke(proxyObject, args.toArray()); + if (value == null) { + response = ResponseUtils.newSuccessResponse(); + } else { + response = ResponseUtils.newSuccessResponse(convertReturn(value, txFn)); + } + } catch (IllegalAccessException e) { + logger.error(() -> "Error during contract method invocation" + e); + response = ResponseUtils.newErrorResponse(e); + } catch (InvocationTargetException e) { + logger.error(() -> "Error during contract method invocation" + e); + response = ResponseUtils.newErrorResponse(e.getCause()); + } + return response; + } + + private byte[] convertReturn(Object obj, TxFunction txFn) { + byte[] buffer; + TypeSchema ts = txFn.getReturnSchema(); + buffer = serializer.toBuffer(obj, ts); + + return buffer; + } + + private List convertArgs(List stubArgs, TxFunction txFn) { + + List schemaParams = txFn.getParamsList(); + List args = new ArrayList<>(); + for (int i = 0; i < schemaParams.size(); i++) { + args.add(serializer.fromBuffer(stubArgs.get(i), schemaParams.get(i).getSchema())); + } + return args; + } + + private Object createProxyForContract(Class contractClass, ProxyMethodInterceptor interceptor) { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(contractClass); + enhancer.setCallback(interceptor); + return enhancer.create(); + } + + private ProxyMethodInterceptor createMethodInterceptorForContract(final Class contractClass, + final ContractInterface contractObject) { + return new ProxyMethodInterceptor(contractClass, contractObject); + } + + private static class ProxyMethodInterceptor implements MethodInterceptor { + // TODO: Check if this is really needed + Class contractClass; + ContractInterface contractObject; + ThreadLocal context = new ThreadLocal<>(); + + public ProxyMethodInterceptor(Class contractClass, ContractInterface contractObject) { + this.contractClass = contractClass; + this.contractObject = contractObject; + } + + public void setContextForThread(ChaincodeStub stub) { + context.set(contractObject.createContext(stub)); + } + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + if (method.getName().equals("getContext")) { + return context.get(); + } else if (method.getDeclaringClass() != Object.class + && method.getDeclaringClass() != ContractInterface.class + && method.getAnnotation(Transaction.class) != null) { + contractObject.beforeTransaction(); + Object result = proxy.invokeSuper(obj, args); + contractObject.afterTransaction(); + return result; + } else { + return proxy.invokeSuper(obj, args); + } + } + + } } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractInvocationRequest.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractInvocationRequest.java index 6956e05a..cb34b805 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractInvocationRequest.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/execution/impl/ContractInvocationRequest.java @@ -5,16 +5,15 @@ */ package org.hyperledger.fabric.contract.execution.impl; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.hyperledger.fabric.contract.Context; import org.hyperledger.fabric.contract.execution.InvocationRequest; import org.hyperledger.fabric.shim.ChaincodeStub; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - public class ContractInvocationRequest implements InvocationRequest { String namespace; String method; diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/MetadataBuilder.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/MetadataBuilder.java index c19602d9..a3ce3248 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/MetadataBuilder.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/MetadataBuilder.java @@ -14,6 +14,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; import org.everit.json.schema.Schema; import org.everit.json.schema.ValidationException; @@ -26,9 +28,6 @@ import org.hyperledger.fabric.contract.routing.TransactionType; import org.hyperledger.fabric.contract.routing.TxFunction; import org.hyperledger.fabric.contract.routing.TypeRegistry; -import org.hyperledger.fabric.contract.routing.impl.DataTypeDefinitionImpl; -import org.hyperledger.fabric.contract.routing.impl.RoutingRegistryImpl; -import org.hyperledger.fabric.contract.routing.impl.TypeRegistryImpl; import org.json.JSONObject; import org.json.JSONTokener; @@ -37,254 +36,211 @@ /** * Builder to assist in production of the metadata * - * This class is used to build up the JSON structure to be returned as the metadata - * It is not a store of information, rather a set of functional data to process to and from - * metadata json to the internal data structure + * This class is used to build up the JSON structure to be returned as the + * metadata It is not a store of information, rather a set of functional data to + * process to and from metadata json to the internal data structure */ public class MetadataBuilder { - private static Logger logger = Logger.getLogger(MetadataBuilder.class); - - // Custom sub-type of Map that helps with the case where if there's no value then do not - // insert the property at all - @SuppressWarnings("serial") - static class MetadataMap extends HashMap { - - V putIfNotNull(K key, V value) { - logger.info(key + " " + value); - if (value != null && !value.toString().isEmpty()) { - return put(key, value); - } else { - return null; - } - } - } - - // Metadata is composed of three primary sections - // each of which is stored in a map - static Map> contractMap = new HashMap<>(); - static Map overallInfoMap = new HashMap(); - static Map componentMap = new HashMap(); - - /** - * Validation method - * - * @throws ValidationException if the metadata is not valid - */ - public static void validate() { - logger.info("Running schema test validation"); - try (InputStream inputStream = MetadataBuilder.class.getClassLoader() - .getResourceAsStream("contract-schema.json")) { - JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream)); - Schema schema = SchemaLoader.load(rawSchema); - schema.validate(metadata()); - - } catch (IOException e) { - throw new RuntimeException(e); - } catch (ValidationException e) { - logger.error(e.getMessage()); - e.getCausingExceptions().stream().map(ValidationException::getMessage).forEach(logger::info); - logger.error(debugString()); - throw e; - } - - } - - /** - * Setup the metadata from the found contracts - */ - public static void initialize(RoutingRegistry registry, TypeRegistry typeRegistry) { - Collection contractDefinitions = registry.getAllDefinitions(); - contractDefinitions.forEach(MetadataBuilder::addContract); - - Collection dataTypes = typeRegistry.getAllDataTypes(); - dataTypes.forEach(MetadataBuilder::addComponent); - - // need to validate that the metadata that has been created is really valid - // it should be as it's been created by code, but this is a valuable double - // check - logger.info("Validating scehma created"); - MetadataBuilder.validate(); - - } - - /** - * Adds a component/ complex data-type - */ - public static void addComponent(DataTypeDefinition datatype) { - - Map component = new HashMap<>(); - - component.put("$id", datatype.getName()); - component.put("type", "object"); - component.put("additionalProperties", false); - component.put("properties", datatype.getProperties()); - componentMap.put(datatype.getSimpleName(), component); - } - - /** - * Adds a new contract to the metadata as represented by the class object - * - * @param contractClass Class of the object to use as a contract - * @return the key that the contract class is referred to in the meteadata - */ - @SuppressWarnings("serial") - public static String addContract(ContractDefinition contractDefinition) { - Class contractClass = contractDefinition.getContractImpl().getClass(); - - Contract annotation = contractDefinition.getAnnotation(); - - Info info = annotation.info(); - HashMap infoMap = new HashMap(); - infoMap.put("title", info.title()); - infoMap.put("description", info.description()); - infoMap.put("termsOfService", info.termsOfService()); - infoMap.put("contact", new MetadataMap() { - { - putIfNotNull("email", info.contact().email()); - putIfNotNull("name", info.contact().name()); - putIfNotNull("url", info.contact().url()); - } - }); - infoMap.put("license", new MetadataMap() { - { - put("name", info.license().name()); - putIfNotNull("url", info.license().url()); - } - }); - infoMap.put("version", info.version()); - - String key = contractClass.getSimpleName(); - HashMap contract = new HashMap(); - contract.put("name", key); - contract.put("transactions", new ArrayList()); - contract.put("info", infoMap); - - contractMap.put(key, contract); - boolean defaultContract = true; - if (defaultContract) { - overallInfoMap.putAll(infoMap); - } - - Collection fns = contractDefinition.getTxFunctions(); - fns.forEach(txFn -> { - MetadataBuilder.addTransaction(txFn, key); - }); - - return key; - } - - /** - * Provide a mapping between the Java Language types and the OpenAPI based types - * @param clz - * @return - */ - public static Map propertySchema(Class clz) { - Map schema = new HashMap(); - String className = clz.getSimpleName(); - switch (className) { - case "String": - schema.put("type", className.toLowerCase()); - break; - case "byte": - schema.put("type", "integer"); - schema.put("format", "int8"); - break; - case "short": - schema.put("type", "integer"); - schema.put("format", "int16"); - break; - case "int": - schema.put("type", "integer"); - schema.put("format", "int32"); - break; - case "long": - schema.put("type", "integer"); - schema.put("format", "int64"); - break; - case "double": - schema.put("type", "number"); - schema.put("format", "double"); - break; - case "float": - schema.put("type", "number"); - schema.put("format", "float"); - break; - case "boolean": - schema.put("type", "boolean"); - break; - default: - return null; - } - - return schema; - } - - /** - * Adds a new transaction function to the metadata for the given contract key - * - * @param method Method object representing the transaction function - * @param contractKey Key of the contract that this function belongs to - */ - public static void addTransaction(TxFunction txFunction, String contractName) { - Map transaction = new HashMap(); - Map returnSchema = propertySchema(txFunction.getReturnType()); - if (returnSchema != null) { - transaction.put("returns", returnSchema); - } - - ArrayList tags = new ArrayList(); - tags.add(txFunction.getType()); - - Map contract = contractMap.get(contractName); - @SuppressWarnings("unchecked") - List txs = (ArrayList) contract.get("transactions"); - - java.lang.reflect.Parameter[] params = txFunction.getParameters(); - ArrayList> paramsList = new ArrayList>(); - - for (java.lang.reflect.Parameter parameter : params) { - HashMap paramMap = new HashMap(); - paramMap.put("name", parameter.getName()); - paramMap.put("schema", propertySchema(parameter.getType())); - paramsList.add(paramMap); - } - - transaction.put("parameters", paramsList); - if (tags.size() != 0) { - transaction.put("tags", tags.toArray()); - transaction.put("name", txFunction.getName()); - txs.add(transaction); - } - } - - /** - * Returns the metadata as a JSON string (compact) - */ - public static String getMetadata() { - return metadata().toString(); - } - - /** - * Returns the metadata as a JSON string (spaced out for humans) - */ - public static String debugString() { - return metadata().toString(3); - } - - /** - * Create a JSONObject representing the schema - * - */ - private static JSONObject metadata() { - HashMap metadata = new HashMap(); - - metadata.put("$schema", "https://fabric-shim.github.io/contract-schema.json"); - metadata.put("info", overallInfoMap); - metadata.put("contracts", contractMap); - metadata.put("components", Collections.singletonMap("schemas", componentMap)); - - JSONObject joMetadata = new JSONObject(metadata); - return joMetadata; - } - + private static Logger logger = Logger.getLogger(MetadataBuilder.class); + + @SuppressWarnings("serial") + static class MetadataMap extends HashMap { + + V putIfNotNull(K key, V value) { + logger.info(key + " " + value); + if (value != null && !value.toString().isEmpty()) { + return put(key, value); + } else { + return null; + } + } + } + + // Metadata is composed of three primary sections + // each of which is stored in a map + static Map> contractMap = new HashMap<>(); + static Map overallInfoMap = new HashMap(); + static Map componentMap = new HashMap(); + + /** + * Validation method + * + * @throws ValidationException if the metadata is not valid + */ + public static void validate() { + logger.info("Running schema test validation"); + try (InputStream inputStream = MetadataBuilder.class.getClassLoader() + .getResourceAsStream("contract-schema.json")) { + JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream)); + Schema schema = SchemaLoader.load(rawSchema); + schema.validate(metadata()); + + } catch (IOException e) { + throw new RuntimeException(e); + } catch (ValidationException e) { + logger.error(e.getMessage()); + e.getCausingExceptions().stream().map(ValidationException::getMessage).forEach(logger::info); + logger.error(debugString()); + throw e; + } + + } + + /** + * Setup the metadata from the found contracts + */ + public static void initialize(RoutingRegistry registry, TypeRegistry typeRegistry) { + Collection contractDefinitions = registry.getAllDefinitions(); + contractDefinitions.forEach(MetadataBuilder::addContract); + + Collection dataTypes = typeRegistry.getAllDataTypes(); + dataTypes.forEach(MetadataBuilder::addComponent); + + // need to validate that the metadata that has been created is really valid + // it should be as it's been created by code, but this is a valuable double + // check + logger.info("Validating scehma created"); + MetadataBuilder.validate(); + + } + + /** + * Adds a component/ complex data-type + */ + public static void addComponent(DataTypeDefinition datatype) { + + Map component = new HashMap<>(); + + component.put("$id", datatype.getName()); + component.put("type", "object"); + component.put("additionalProperties", false); + + Map propertiesMap = datatype.getProperties().entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, e -> (e.getValue().getSchema()))); + component.put("properties", propertiesMap); + + componentMap.put(datatype.getSimpleName(), component); + } + + /** + * Adds a new contract to the metadata as represented by the class object + * + * @param contractClass Class of the object to use as a contract + * @return the key that the contract class is referred to in the meteadata + */ + @SuppressWarnings("serial") + public static String addContract(ContractDefinition contractDefinition) { + + String key = contractDefinition.getName(); + + Contract annotation = contractDefinition.getAnnotation(); + + Info info = annotation.info(); + HashMap infoMap = new HashMap(); + infoMap.put("title", info.title()); + infoMap.put("description", info.description()); + infoMap.put("termsOfService", info.termsOfService()); + infoMap.put("contact", new MetadataMap() { + { + putIfNotNull("email", info.contact().email()); + putIfNotNull("name", info.contact().name()); + putIfNotNull("url", info.contact().url()); + } + }); + infoMap.put("license", new MetadataMap() { + { + put("name", info.license().name()); + putIfNotNull("url", info.license().url()); + } + }); + infoMap.put("version", info.version()); + + HashMap contract = new HashMap(); + contract.put("name", key); + contract.put("transactions", new ArrayList()); + contract.put("info", infoMap); + + contractMap.put(key, contract); + boolean defaultContract = true; + if (defaultContract) { + overallInfoMap.putAll(infoMap); + } + + Collection fns = contractDefinition.getTxFunctions(); + fns.forEach(txFn -> { + MetadataBuilder.addTransaction(txFn, key); + }); + + return key; + } + + /** + * Adds a new transaction function to the metadata for the given contract + * + * @param method Method object representing the transaction function + * @param contractKey Key of the contract that this function belongs to + */ + public static void addTransaction(TxFunction txFunction, String contractName) { + TypeSchema transaction = new TypeSchema(); + TypeSchema returnSchema = txFunction.getReturnSchema(); + if (returnSchema != null) { + transaction.put("returns", returnSchema); + } + + ArrayList tags = new ArrayList(); + tags.add(txFunction.getType()); + + Map contract = contractMap.get(contractName); + @SuppressWarnings("unchecked") + List txs = (ArrayList) contract.get("transactions"); + + ArrayList paramsList = new ArrayList(); + txFunction.getParamsList().forEach(pd -> { + TypeSchema paramMap = pd.getSchema(); + paramMap.put("name", pd.getName()); + paramsList.add(paramMap); + }); + + transaction.put("parameters", paramsList); + + if (tags.size() != 0) { + transaction.put("tags", tags.toArray()); + transaction.put("name", txFunction.getName()); + txs.add(transaction); + } + } + + /** + * Returns the metadata as a JSON string (compact) + */ + public static String getMetadata() { + return metadata().toString(); + } + + /** + * Returns the metadata as a JSON string (spaced out for humans) + */ + public static String debugString() { + return metadata().toString(3); + } + + /** + * Create a JSONObject representing the schema + * + */ + private static JSONObject metadata() { + HashMap metadata = new HashMap(); + + metadata.put("$schema", "https://fabric-shim.github.io/release-1.4/contract-schema.json"); + metadata.put("info", overallInfoMap); + metadata.put("contracts", contractMap); + metadata.put("components", Collections.singletonMap("schemas", componentMap)); + + JSONObject joMetadata = new JSONObject(metadata); + return joMetadata; + } + + public static Map getComponents() { + return componentMap; + } } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/TypeSchema.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/TypeSchema.java new file mode 100644 index 00000000..16f64127 --- /dev/null +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/metadata/TypeSchema.java @@ -0,0 +1,220 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract.metadata; + +import java.lang.reflect.Array; +import java.util.HashMap; +import java.util.Map; + +import org.everit.json.schema.Schema; +import org.everit.json.schema.ValidationException; +import org.everit.json.schema.loader.SchemaLoader; +import org.hyperledger.fabric.Logger; +import org.hyperledger.fabric.contract.ContractRuntimeException; +import org.hyperledger.fabric.contract.routing.TypeRegistry; +import org.json.JSONObject; + +/** + * + * Custom sub-type of Map that helps with the case where if there's no value + * then do not insert the property at all + * + * Does not include the "schema" top level map + * + * @param + * @param + */ +@SuppressWarnings("serial") +public class TypeSchema extends HashMap { + private static Logger logger = Logger.getLogger(TypeSchema.class.getName()); + + public TypeSchema() { + + } + + private Object _putIfNotNull(String key, Object value) { + if (value != null && !value.toString().isEmpty()) { + return put(key, value); + } else { + return null; + } + } + + String putIfNotNull(String key, String value) { + return (String) this._putIfNotNull(key, value); + } + + String[] putIfNotNull(String key, String[] value) { + return (String[]) this._putIfNotNull(key, value); + } + + TypeSchema putIfNotNull(String key, TypeSchema value) { + return (TypeSchema) this._putIfNotNull(key, value); + } + + TypeSchema[] putIfNotNull(String key, TypeSchema[] value) { + return (TypeSchema[]) this._putIfNotNull(key, value); + } + + public String getType() { + if (this.containsKey("schema")) { + Map intermediateMap = (Map) this.get("schema"); + return (String) intermediateMap.get("type"); + } + return (String) this.get("type"); + } + + public TypeSchema getItems() { + if (this.containsKey("schema")) { + Map intermediateMap = (Map) this.get("schema"); + return (TypeSchema) intermediateMap.get("items"); + } + return (TypeSchema) this.get("items"); + } + + public String getRef() { + if (this.containsKey("schema")) { + Map intermediateMap = (Map) this.get("schema"); + return (String) intermediateMap.get("$ref"); + } + return (String) this.get("$ref"); + + } + + public String getFormat() { + if (this.containsKey("schema")) { + Map intermediateMap = (Map) this.get("schema"); + return (String) intermediateMap.get("format"); + } + return (String) this.get("format"); + } + + public Class getTypeClass(TypeRegistry typeRegistry) { + Class clz = null; + String type = getType(); + if (type == null) { + type = "object"; + } + + if (type.contentEquals("string")) { + clz = String.class; + } else if (type.contentEquals("integer")) { + clz = int.class; + } else if (type.contentEquals("boolean")) { + clz = boolean.class; + } else if (type.contentEquals("object")) { + String ref = this.getRef(); + String format = ref.substring(ref.lastIndexOf("/") + 1); + clz = typeRegistry.getDataType(format).getTypeClass(); + } else if (type.contentEquals("array")) { + TypeSchema typdef = this.getItems(); + Class arrayType = typdef.getTypeClass(typeRegistry); + clz = Array.newInstance(arrayType, 0).getClass(); + } + + return clz; + } + + /** + * Provide a mapping between the Java Language types and the OpenAPI based types + * + * @param clz + * @return + */ + public static TypeSchema typeConvert(Class clz) { + TypeSchema returnschema = new TypeSchema(); + String className = clz.getTypeName(); + if (className == "void") { + return null; + } + + TypeSchema schema; + + if (clz.isArray()) { + returnschema.put("type", "array"); + schema = new TypeSchema(); + returnschema.put("items", schema); + className = className.substring(0, className.length() - 2); + } else { + schema = returnschema; + } + + switch (className) { + case "java.lang.String": + schema.put("type", "string"); + break; + case "byte": + case "java.lang.Byte": + schema.put("type", "integer"); + schema.put("format", "int8"); + break; + case "short": + case "java.lang.Short": + schema.put("type", "integer"); + schema.put("format", "int16"); + break; + case "int": + case "java.lang.Integer": + schema.put("type", "integer"); + schema.put("format", "int32"); + break; + case "long": + case "java.lang.Long": + schema.put("type", "integer"); + schema.put("format", "int64"); + break; + case "double": + case "java.lang.Double": + schema.put("type", "number"); + schema.put("format", "double"); + break; + case "float": + case "java.lang.Float": + schema.put("type", "number"); + schema.put("format", "float"); + break; + case "boolean": + case "java.lang.Boolean": + schema.put("type", "boolean"); + break; + default: + + schema.put("$ref", "#/components/schemas/" + className.substring(className.lastIndexOf('.') + 1)); + } + + return returnschema; + } + + public void validate(JSONObject obj) { + // get the components bit of the main metadata + + JSONObject toValidate = new JSONObject(); + toValidate.put("prop", obj); + + JSONObject schemaJSON; + if (this.containsKey("schema")) { + schemaJSON = new JSONObject((Map) this.get("schema")); + } else { + schemaJSON = new JSONObject(this); + } + + JSONObject rawSchema = new JSONObject(); + rawSchema.put("properties", new JSONObject().put("prop", schemaJSON)); + rawSchema.put("components", new JSONObject().put("schemas", MetadataBuilder.getComponents())); + Schema schema = SchemaLoader.load(rawSchema); + try { + schema.validate(toValidate); + } catch (ValidationException e) { + StringBuilder sb = new StringBuilder("Validation Errors::"); + e.getCausingExceptions().stream().map(ValidationException::getMessage).forEach(sb::append); + logger.info(sb.toString()); + throw new ContractRuntimeException(sb.toString(), e); + } + + } + +} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ContractDefinition.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ContractDefinition.java index dd186bd3..422dd41d 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ContractDefinition.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ContractDefinition.java @@ -15,33 +15,70 @@ /** * Definition of the Contract * - * A data structure that represents the contract that will be executed in the chaincode. - * Primarily has + * A data structure that represents the contract that will be executed in the + * chaincode. Primarily has * - * Name - either defined by the Contract annotation or the Class name (can be referred to as Namespace) - * Default - is the default contract (defined by the Default annotation) TxFunctions in this contract do not need the name prefix when invoked - * TxFunctions - the transaction functions defined in this contract + * Name - either defined by the Contract annotation or the Class name (can be + * referred to as Namespace) Default - is the default contract (defined by the + * Default annotation) TxFunctions in this contract do not need the name prefix + * when invoked TxFunctions - the transaction functions defined in this contract * - * Will embedded the ContgractInterface instance, as well as the annotation itself, and the routing for any tx function that is unkown + * Will embedded the ContgractInterface instance, as well as the annotation + * itself, and the routing for any tx function that is unkown * */ public interface ContractDefinition { + /** + * @return the fully qualififed name of the Contract + */ String getName(); + /** + * @return Complete collection of all the transaction functions in this contract + */ Collection getTxFunctions(); + /** + * @return Object reference to the instantiated object that is 'the contract' + */ ContractInterface getContractImpl(); + /** + * @param m The java.lang.reflect object that is the method that is a tx + * function + * @return TxFunction object representing this method + */ TxFunction addTxFunction(Method m); + /** + * + * @return if this is contract is the default one or not + */ boolean isDefault(); + /** + * + * @param method name to returned + * @return TxFunction that represent this requested method + */ TxFunction getTxFunction(String method); + /** + * + * @param method name to be checked + * @return true if this txFunction exists or not + */ boolean hasTxFunction(String method); - TxFunction.Routing getUnkownRoute(); + /** + * @return The TxFunction to be used for this contract in case of unknown + * request + */ + TxFunction getUnkownRoute(); + /** + * @return Underlying raw annotation + */ Contract getAnnotation(); } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/DataTypeDefinition.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/DataTypeDefinition.java index cc251428..d2d97311 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/DataTypeDefinition.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/DataTypeDefinition.java @@ -5,12 +5,15 @@ */ package org.hyperledger.fabric.contract.routing; +import java.util.Map; + public interface DataTypeDefinition { String getName(); - Object getProperties(); + Map getProperties(); String getSimpleName(); + Class getTypeClass(); } \ No newline at end of file diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/Parameter.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/Parameter.java deleted file mode 100644 index 5e64117e..00000000 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/Parameter.java +++ /dev/null @@ -1,13 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ -package org.hyperledger.fabric.contract.routing; - -public interface Parameter { - - Class getType(); - String getSchema(); - -} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ParameterDefinition.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ParameterDefinition.java new file mode 100644 index 00000000..8471e642 --- /dev/null +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/ParameterDefinition.java @@ -0,0 +1,22 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.routing; + +import java.lang.reflect.Parameter; + +import org.hyperledger.fabric.contract.metadata.TypeSchema; + +public interface ParameterDefinition { + + Class getTypeClass(); + + TypeSchema getSchema(); + + Parameter getParameter(); + + String getName(); + +} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/PropertyDefinition.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/PropertyDefinition.java new file mode 100644 index 00000000..cdf9af71 --- /dev/null +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/PropertyDefinition.java @@ -0,0 +1,22 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.routing; + +import java.lang.reflect.Field; + +import org.hyperledger.fabric.contract.metadata.TypeSchema; + +public interface PropertyDefinition { + + Class getTypeClass(); + + TypeSchema getSchema(); + + Field getField(); + + String getName(); + +} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/RoutingRegistry.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/RoutingRegistry.java index af65196f..dfaacd71 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/RoutingRegistry.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/RoutingRegistry.java @@ -35,7 +35,21 @@ public interface RoutingRegistry { */ TxFunction.Routing getRoute(InvocationRequest request); - ContractDefinition getContract(String namespace); + /** + * Get the txFunction that matches the routing request + * + * @param request + * @return + */ + TxFunction getTxFn(InvocationRequest request); + + /** + * Get the contract that matches the supplied name + * + * @param name + * @return + */ + ContractDefinition getContract(String name); /** * Returns all the ContractDefinitions for this registry @@ -44,6 +58,11 @@ public interface RoutingRegistry { */ Collection getAllDefinitions(); + /** + * Locate all the contracts in this chaincode + * + * @param typeRegistry + */ void findAndSetContracts(TypeRegistry typeRegistry); } \ No newline at end of file diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TxFunction.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TxFunction.java index 62e49478..59a363c3 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TxFunction.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TxFunction.java @@ -6,28 +6,37 @@ package org.hyperledger.fabric.contract.routing; import java.lang.reflect.Method; +import java.util.List; import org.hyperledger.fabric.contract.ContractInterface; +import org.hyperledger.fabric.contract.metadata.TypeSchema; public interface TxFunction { - interface Routing { - ContractInterface getContractObject(); + interface Routing { + ContractInterface getContractObject(); - Method getMethod(); + Method getMethod(); - Class getContractClass(); + Class getContractClass(); - } + } - String getName(); + String getName(); Routing getRouting(); - Class getReturnType(); + Class getReturnType(); - java.lang.reflect.Parameter[] getParameters(); + java.lang.reflect.Parameter[] getParameters(); - TransactionType getType(); + TransactionType getType(); + void setReturnSchema(TypeSchema returnSchema); + + TypeSchema getReturnSchema(); + + void setParameterDefinitions(List list); + + List getParamsList(); } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TypeRegistry.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TypeRegistry.java index 437835b6..a7d0bfd8 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TypeRegistry.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/TypeRegistry.java @@ -7,12 +7,14 @@ import java.util.Collection; -import org.hyperledger.fabric.contract.routing.impl.DataTypeDefinitionImpl; - public interface TypeRegistry { + void addDataType(DataTypeDefinition dtd); + void addDataType(Class cl); - Collection getAllDataTypes(); + DataTypeDefinition getDataType(String name); + + Collection getAllDataTypes(); } \ No newline at end of file diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ContractDefinitionImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ContractDefinitionImpl.java index 68f09e29..38030a3d 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ContractDefinitionImpl.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ContractDefinitionImpl.java @@ -21,104 +21,106 @@ /** * Implementation of the ContractDefinition * - * Contains information about the contract, including transaction functions and unknown transaction routing + * Contains information about the contract, including transaction functions and + * unknown transaction routing * */ public class ContractDefinitionImpl implements ContractDefinition { - private static Logger logger = Logger.getLogger(ContractDefinitionImpl.class); - - private Map txFunctions = new HashMap<>(); - private String name; - private boolean isDefault; - private ContractInterface contract; - private Contract contractAnnotation; - private TxFunction unknownTx; - - public ContractDefinitionImpl(Class cl) { - - Contract annotation = cl.getAnnotation(Contract.class); - logger.debug(()->"Class Contract Annodation: "+annotation); - - String annotationName = annotation.namespace(); - if (annotationName == null || annotationName.isEmpty()) { - this.name = cl.getSimpleName(); - } else { - this.name = annotationName; - } - - isDefault = (cl.getAnnotation(Default.class) != null); - contractAnnotation = cl.getAnnotation(Contract.class); - try { - contract = (ContractInterface) cl.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - ContractRuntimeException cre = new ContractRuntimeException("Unable to create instance of contract",e); - logger.error(()->logger.formatError(cre)); - throw cre; - } - - try { - Method m = cl.getMethod("unknownTransaction", new Class[] {}); - unknownTx = new TxFunctionImpl(m,this); - } catch (NoSuchMethodException | SecurityException e) { - ContractRuntimeException cre = new ContractRuntimeException("Failure to find unknownTranction method",e); - logger.severe(()->logger.formatError(cre)); - throw cre; - } - - logger.info(()->"Found class: " + cl.getCanonicalName()); - logger.debug(()->"Namespace: " + this.name); - } - - @Override - public String getName() { - return name; - } - - @Override - public Collection getTxFunctions() { - return txFunctions.values(); - } - - @Override - public ContractInterface getContractImpl() { - return contract; - } - - @Override - public TxFunction addTxFunction(Method m) { - logger.debug(()->"Adding method " + m.getName()); - TxFunction txFn = new TxFunctionImpl(m, this); - txFunctions.put(txFn.getName(), txFn); - return txFn; - } - - @Override - public boolean isDefault() { - return isDefault; - } - - @Override - public TxFunction getTxFunction(String method) { - return txFunctions.get(method); - } - - @Override - public boolean hasTxFunction(String method) { - return txFunctions.containsKey(method); - } - - @Override - public TxFunction.Routing getUnkownRoute() { - return unknownTx.getRouting(); - } - - @Override - public Contract getAnnotation() { - return this.contractAnnotation; - } - - @Override - public String toString() { - return name + ":" + txFunctions + " @" + Integer.toHexString(System.identityHashCode(this)); - } + private static Logger logger = Logger.getLogger(ContractDefinitionImpl.class); + + private Map txFunctions = new HashMap<>(); + private String name; + private boolean isDefault; + private ContractInterface contract; + private Contract contractAnnotation; + private TxFunction unknownTx; + + public ContractDefinitionImpl(Class cl) { + + Contract annotation = cl.getAnnotation(Contract.class); + logger.debug(() -> "Class Contract Annodation: " + annotation); + + String annotationName = annotation.name(); + + if (annotationName == null || annotationName.isEmpty()) { + this.name = cl.getSimpleName(); + } else { + this.name = annotationName; + } + + isDefault = (cl.getAnnotation(Default.class) != null); + contractAnnotation = cl.getAnnotation(Contract.class); + try { + contract = (ContractInterface) cl.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + ContractRuntimeException cre = new ContractRuntimeException("Unable to create instance of contract", e); + logger.error(() -> logger.formatError(cre)); + throw cre; + } + + try { + Method m = cl.getMethod("unknownTransaction", new Class[] {}); + unknownTx = new TxFunctionImpl(m, this); + } catch (NoSuchMethodException | SecurityException e) { + ContractRuntimeException cre = new ContractRuntimeException("Failure to find unknownTranction method", e); + logger.severe(() -> logger.formatError(cre)); + throw cre; + } + + logger.info(() -> "Found class: " + cl.getCanonicalName()); + logger.debug(() -> "Namespace: " + this.name); + } + + @Override + public String getName() { + return name; + } + + @Override + public Collection getTxFunctions() { + return txFunctions.values(); + } + + @Override + public ContractInterface getContractImpl() { + return contract; + } + + @Override + public TxFunction addTxFunction(Method m) { + logger.debug(() -> "Adding method " + m.getName()); + TxFunction txFn = new TxFunctionImpl(m, this); + txFunctions.put(txFn.getName(), txFn); + return txFn; + } + + @Override + public boolean isDefault() { + return isDefault; + } + + @Override + public TxFunction getTxFunction(String method) { + return txFunctions.get(method); + } + + @Override + public boolean hasTxFunction(String method) { + return txFunctions.containsKey(method); + } + + @Override + public TxFunction getUnkownRoute() { + return unknownTx; + } + + @Override + public Contract getAnnotation() { + return this.contractAnnotation; + } + + @Override + public String toString() { + return name + ":" + txFunctions + " @" + Integer.toHexString(System.identityHashCode(this)); + } } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/DataTypeDefinitionImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/DataTypeDefinitionImpl.java index 6555b2e4..c397cbc9 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/DataTypeDefinitionImpl.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/DataTypeDefinitionImpl.java @@ -6,35 +6,75 @@ package org.hyperledger.fabric.contract.routing.impl; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.hyperledger.fabric.contract.annotation.Property; -import org.hyperledger.fabric.contract.metadata.MetadataBuilder; +import org.hyperledger.fabric.contract.metadata.TypeSchema; import org.hyperledger.fabric.contract.routing.DataTypeDefinition; - +import org.hyperledger.fabric.contract.routing.PropertyDefinition; public class DataTypeDefinitionImpl implements DataTypeDefinition { - Map properties = new HashMap<>(); + Map properties = new HashMap<>(); + Map fields = new HashMap<>(); String name; String simpleName; + Class clazz; public DataTypeDefinitionImpl(Class componentClass) { + this.clazz = componentClass; this.name = componentClass.getName(); this.simpleName = componentClass.getSimpleName(); - // given this class extract the property elements - Field[] fields = componentClass.getDeclaredFields(); - - for (Field f : fields) { - Property propAnnotation = f.getAnnotation(Property.class); - if (propAnnotation != null) { - properties.put(f.getName(), MetadataBuilder.propertySchema(f.getType())); - } - } + // given this class extract the property elements + Field[] fields = componentClass.getDeclaredFields(); + + for (Field f : fields) { + Property propAnnotation = f.getAnnotation(Property.class); + if (propAnnotation != null) { + TypeSchema ts = TypeSchema.typeConvert(f.getType()); + + // array of strings, "a","b","c","d" to become map of {a:b}, {c:d} + String[] userSupplied = propAnnotation.schema(); + for (int i = 0; i < userSupplied.length; i += 2) { + String userKey = userSupplied[i]; + Object userValue; + switch (userKey.toLowerCase()) { + case "title": + case "pattern": + userValue = userSupplied[i + 1]; + break; + case "uniqueitems": + userValue = Boolean.parseBoolean(userSupplied[i + 1]); + break; + case "required": + case "enum": + userValue = Stream.of(userSupplied[i + 1].split(",")).map(String::trim).toArray(String[]::new); + break; + default: + userValue = Integer.parseInt(userSupplied[i + 1]); + break; + } + ts.put(userKey, userValue); + } + + PropertyDefinition propDef = new PropertyDefinitionImpl(f.getName(), f.getClass(), ts, f); + this.properties.put(f.getName(), propDef); + } + } + + } + + public Class getTypeClass() { + return this.clazz; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.hyperledger.fabric.contract.routing.DataTypeDefinition#getName() */ @Override @@ -42,16 +82,22 @@ public String getName() { return this.name; } - /* (non-Javadoc) - * @see org.hyperledger.fabric.contract.routing.DataTypeDefinition#getProperties() + /* + * (non-Javadoc) + * + * @see + * org.hyperledger.fabric.contract.routing.DataTypeDefinition#getProperties() */ @Override - public Object getProperties() { + public Map getProperties() { return properties; } - /* (non-Javadoc) - * @see org.hyperledger.fabric.contract.routing.DataTypeDefinition#getSimpleName() + /* + * (non-Javadoc) + * + * @see + * org.hyperledger.fabric.contract.routing.DataTypeDefinition#getSimpleName() */ @Override public String getSimpleName() { diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ParameterDefinitionImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ParameterDefinitionImpl.java new file mode 100644 index 00000000..9a4069d6 --- /dev/null +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/ParameterDefinitionImpl.java @@ -0,0 +1,50 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract.routing.impl; + +import java.lang.reflect.Parameter; + +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.ParameterDefinition; + +public class ParameterDefinitionImpl implements ParameterDefinition { + + private Class typeClass; + private TypeSchema schema; + private Parameter parameter; + private String name; + + public ParameterDefinitionImpl(String name, Class typeClass, TypeSchema schema, Parameter p) { + this.typeClass = typeClass; + this.schema = schema; + this.parameter = p; + this.name =name; + } + + @Override + public Class getTypeClass() { + return this.typeClass; + } + + @Override + public TypeSchema getSchema() { + return this.schema; + } + + @Override + public Parameter getParameter() { + return this.parameter; + } + + @Override + public String getName() { + return this.name; + } + public String toString() { + return this.name+"-"+this.typeClass+"-"+this.schema+"-"+this.parameter; + } +} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/PropertyDefinitionImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/PropertyDefinitionImpl.java new file mode 100644 index 00000000..faff96ed --- /dev/null +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/PropertyDefinitionImpl.java @@ -0,0 +1,48 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract.routing.impl; + +import java.lang.reflect.Field; + +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.PropertyDefinition; + +public class PropertyDefinitionImpl implements PropertyDefinition { + + private Class typeClass; + private TypeSchema schema; + private Field field; + private String name; + + public PropertyDefinitionImpl(String name, Class typeClass, TypeSchema schema, Field f) { + this.typeClass = typeClass; + this.schema = schema; + this.field = f; + this.name =name; + } + + @Override + public Class getTypeClass() { + return this.typeClass; + } + + @Override + public TypeSchema getSchema() { + return this.schema; + } + + @Override + public Field getField() { + return this.field; + } + + @Override + public String getName() { + return this.name; + } + +} diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/RoutingRegistryImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/RoutingRegistryImpl.java index 7025932e..e9bba617 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/RoutingRegistryImpl.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/RoutingRegistryImpl.java @@ -59,6 +59,7 @@ public ContractDefinition addNewContract(Class clz) { contracts.put(InvocationRequest.DEFAULT_NAMESPACE, contract); } + logger.debug(()->"Put new contract in under name "+contract.getName()); return contract; } @@ -86,6 +87,13 @@ public TxFunction.Routing getRoute(InvocationRequest request) { return txFunction.getRouting(); } + @Override + public TxFunction getTxFn(InvocationRequest request) { + TxFunction txFunction = contracts.get(request.getNamespace()).getTxFunction(request.getMethod()); + return txFunction; + } + + /* (non-Javadoc) * @see org.hyperledger.fabric.contract.routing.RoutingRegistry#getContract(java.lang.String) */ diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TxFunctionImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TxFunctionImpl.java index de4675ce..05d8cb2e 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TxFunctionImpl.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TxFunctionImpl.java @@ -6,66 +6,69 @@ package org.hyperledger.fabric.contract.routing.impl; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import org.hyperledger.fabric.Logger; import org.hyperledger.fabric.contract.ContractInterface; +import org.hyperledger.fabric.contract.annotation.Property; import org.hyperledger.fabric.contract.annotation.Transaction; +import org.hyperledger.fabric.contract.metadata.TypeSchema; import org.hyperledger.fabric.contract.routing.ContractDefinition; +import org.hyperledger.fabric.contract.routing.ParameterDefinition; import org.hyperledger.fabric.contract.routing.TransactionType; import org.hyperledger.fabric.contract.routing.TxFunction; public class TxFunctionImpl implements TxFunction { - private static Logger logger = Logger.getLogger(TxFunctionImpl.class); - - private Method method; - private ContractDefinition contract; - private TransactionType type; - private Routing routing; - - public class RoutingImpl implements Routing { - ContractInterface contract; - Method method; - Class clazz; - - - public RoutingImpl(Method method, ContractInterface contract) { - this.method = method; - this.contract = contract; - clazz = contract.getClass(); - } - - @Override - public ContractInterface getContractObject() { - return contract; - } - - @Override - public Method getMethod() { - return method; - } - - @Override - public Class getContractClass() { - return clazz; - } - - @Override - public String toString() { - return method.getName()+":"+clazz.getCanonicalName()+":"+contract.getClass().getCanonicalName(); - } - } - - /** - * New TxFunction Definition Impl - * - * @param m Reflect method object - * @param contract ContractDefinition this is part of - */ - public TxFunctionImpl(Method m, ContractDefinition contract) { + private static Logger logger = Logger.getLogger(TxFunctionImpl.class); + + private Method method; + private TransactionType type; + private Routing routing; + private TypeSchema returnSchema; + private List paramsList = new ArrayList<>(); + + public class RoutingImpl implements Routing { + ContractInterface contract; + Method method; + Class clazz; + + public RoutingImpl(Method method, ContractInterface contract) { + this.method = method; + this.contract = contract; + clazz = contract.getClass(); + } - this.method = m; - this.contract = contract; + @Override + public ContractInterface getContractObject() { + return contract; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Class getContractClass() { + return clazz; + } + + @Override + public String toString() { + return method.getName() + ":" + clazz.getCanonicalName() + ":" + contract.getClass().getCanonicalName(); + } + } + + /** + * New TxFunction Definition Impl + * + * @param m Reflect method object + * @param contract ContractDefinition this is part of + */ + public TxFunctionImpl(Method m, ContractDefinition contract) { + this.method = m; if (m.getAnnotation(Transaction.class) != null) { logger.debug("Found Transaction method: " + m.getName()); if (m.getAnnotation(Transaction.class).submit()) { @@ -76,41 +79,86 @@ public TxFunctionImpl(Method m, ContractDefinition contract) { } - this.routing = new RoutingImpl(m,contract.getContractImpl()); - - } - - @Override - public String getName() { - return this.method.getName(); - } - - @Override - public Routing getRouting() { - return this.routing; - } - - @Override - public Class getReturnType() { - return method.getReturnType(); - } + this.routing = new RoutingImpl(m, contract.getContractImpl()); + // set the return schema + this.returnSchema = TypeSchema.typeConvert(m.getReturnType()); - @Override - public java.lang.reflect.Parameter[] getParameters() { - return method.getParameters(); - } - - @Override - public TransactionType getType() { - return this.type; - } - - @Override - public String toString() { - return this.method.getName() + " @" + Integer.toHexString(System.identityHashCode(this)); - } + // parameter processing + java.lang.reflect.Parameter[] params = method.getParameters(); + for (java.lang.reflect.Parameter parameter : params) { + TypeSchema paramMap = new TypeSchema(); + TypeSchema schema = TypeSchema.typeConvert(parameter.getType()); + Property annotation = parameter.getAnnotation(org.hyperledger.fabric.contract.annotation.Property.class); + if (annotation != null) { + String[] userSupplied = annotation.schema(); + for (int i = 0; i < userSupplied.length; i += 2) { + schema.put(userSupplied[i], userSupplied[i + 1]); + } + } + paramMap.put("name", parameter.getName()); + paramMap.put("schema", schema); + ParameterDefinition pd = new ParameterDefinitionImpl(parameter.getName(), parameter.getClass(), paramMap, + parameter); + paramsList.add(pd); + } + } + + @Override + public String getName() { + return this.method.getName(); + } + + @Override + public Routing getRouting() { + return this.routing; + } + + @Override + public Class getReturnType() { + return method.getReturnType(); + } + + @Override + public java.lang.reflect.Parameter[] getParameters() { + return method.getParameters(); + } + + @Override + public TransactionType getType() { + return this.type; + } + + @Override + public String toString() { + return this.method.getName() + " @" + Integer.toHexString(System.identityHashCode(this)); + } + + @Override + public void setReturnSchema(TypeSchema returnSchema) { + this.returnSchema = returnSchema; + } + + @Override + public List getParamsList() { + return paramsList; + } + + public void setParamsList(ArrayList paramsList) { + this.paramsList = paramsList; + } + + @Override + public TypeSchema getReturnSchema() { + return returnSchema; + } + + @Override + public void setParameterDefinitions(List list) { + this.paramsList = list; + + } } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TypeRegistryImpl.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TypeRegistryImpl.java index 171f28e4..606b9fec 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TypeRegistryImpl.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/routing/impl/TypeRegistryImpl.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.Map; +import org.hyperledger.fabric.contract.routing.DataTypeDefinition; import org.hyperledger.fabric.contract.routing.TypeRegistry; /** @@ -19,7 +20,7 @@ */ public class TypeRegistryImpl implements TypeRegistry { - private Map components = new HashMap<>(); + private Map components = new HashMap<>(); /* (non-Javadoc) * @see org.hyperledger.fabric.contract.routing.TypeRegistry#addDataType(java.lang.Class) @@ -27,15 +28,25 @@ public class TypeRegistryImpl implements TypeRegistry { @Override public void addDataType(Class cl) { DataTypeDefinitionImpl type = new DataTypeDefinitionImpl(cl); - components.put(type.getName(), type); + components.put(type.getSimpleName(), type); } /* (non-Javadoc) * @see org.hyperledger.fabric.contract.routing.TypeRegistry#getAllDataTypes() */ @Override - public Collection getAllDataTypes() { + public Collection getAllDataTypes() { return components.values(); } + @Override + public void addDataType(DataTypeDefinition type) { + components.put(type.getName(), type); + } + + @Override + public DataTypeDefinition getDataType(String name) { + return this.components.get(name); + } + } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/systemcontract/SystemContract.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/systemcontract/SystemContract.java index 2994430a..8273f28d 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/systemcontract/SystemContract.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/contract/systemcontract/SystemContract.java @@ -12,15 +12,15 @@ import io.swagger.v3.oas.annotations.info.Info; -@Contract(namespace = "org.hyperledger.fabric.SystemContract", info = @Info(title = "Fabric System Contract", description = "Provides information about the contracts within this container")) +@Contract(name = "org.hyperledger.fabric", info = @Info(title = "Fabric System Contract", description = "Provides information about the contracts within this container")) public class SystemContract implements ContractInterface { public SystemContract() { } - @Transaction(submit=false) - public String getMetadata() { + @Transaction(submit = false) + public String GetMetadata() { String jsonmetadata = MetadataBuilder.getMetadata(); return jsonmetadata; } diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeBase.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeBase.java index f4e0f781..b1664050 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeBase.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeBase.java @@ -12,13 +12,14 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.security.Security; import java.util.Base64; -import java.util.logging.ConsoleHandler; +import java.util.Collections; +import java.util.List; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; @@ -128,6 +129,7 @@ public synchronized String format(LogRecord record) { rootLogger.info("Updated all handlers the format"); // set logging level of chaincode logger Level chaincodeLogLevel = mapLevel(System.getenv(CORE_CHAINCODE_LOGGING_LEVEL)); + Package chaincodePackage = this.getClass().getPackage(); if (chaincodePackage != null) { Logger.getLogger(chaincodePackage.getName()).setLevel(chaincodeLogLevel); @@ -140,10 +142,18 @@ public synchronized String format(LogRecord record) { // set logging level of shim logger Level shimLogLevel = mapLevel(System.getenv(CORE_CHAINCODE_LOGGING_SHIM)); Logger.getLogger(ChaincodeBase.class.getPackage().getName()).setLevel(shimLogLevel); - Logger.getLogger(ContractRouter.class.getPackage().getName()).setLevel(shimLogLevel); + Logger.getLogger(ContractRouter.class.getPackage().getName()).setLevel(chaincodeLogLevel); + + List loggers = Collections.list(LogManager.getLogManager().getLoggerNames()); + loggers.forEach(x -> { + Logger l = LogManager.getLogManager().getLogger((String) x); + //TODO: err what is the code supposed to do? + }); + } private Level mapLevel(String level) { + if (level != null) { switch (level.toUpperCase().trim()) { case "CRITICAL": diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java index 7a04f70d..28156410 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/ChaincodeStub.java @@ -21,7 +21,9 @@ public interface ChaincodeStub { - /** + + + /** * Returns the arguments corresponding to the call to * {@link Chaincode#init(ChaincodeStub)} or * {@link Chaincode#invoke(ChaincodeStub)}, each argument represented as byte array. diff --git a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/helper/Channel.java b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/helper/Channel.java index c44220db..b91aa795 100644 --- a/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/helper/Channel.java +++ b/fabric-chaincode-shim/src/main/java/org/hyperledger/fabric/shim/helper/Channel.java @@ -10,6 +10,7 @@ import java.util.HashSet; import java.util.concurrent.LinkedBlockingQueue; +@SuppressWarnings("serial") public class Channel extends LinkedBlockingQueue implements Closeable { private boolean closed = false; diff --git a/fabric-chaincode-shim/src/test/java/contract/SampleContract.java b/fabric-chaincode-shim/src/test/java/contract/SampleContract.java index aa57d425..785986b7 100644 --- a/fabric-chaincode-shim/src/test/java/contract/SampleContract.java +++ b/fabric-chaincode-shim/src/test/java/contract/SampleContract.java @@ -16,12 +16,7 @@ import io.swagger.v3.oas.annotations.info.Contact; import io.swagger.v3.oas.annotations.info.Info; -@Contract( - namespace = "samplecontract", - info = @Info( - contact = @Contact( email = "fred@example.com" ) - ) -) +@Contract(name = "samplecontract", info = @Info(contact = @Contact(email = "fred@example.com"))) @Default() public class SampleContract implements ContractInterface { static public int beforeInvoked = 0; @@ -32,7 +27,7 @@ public class SampleContract implements ContractInterface { @Transaction public String t3() { - throw new RuntimeException("T3 fail!"); + throw new RuntimeException("T3 fail!"); } @Transaction @@ -61,6 +56,7 @@ public void beforeTransaction() { public void afterTransaction() { afterInvoked++; } + private void doSomeWork() { doWorkInvoked++; } diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ChaincodeStubNaiveImpl.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ChaincodeStubNaiveImpl.java index 5f4d2bb6..f8879892 100644 --- a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ChaincodeStubNaiveImpl.java +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ChaincodeStubNaiveImpl.java @@ -40,6 +40,17 @@ public class ChaincodeStubNaiveImpl implements ChaincodeStub { resp = new Chaincode.Response(404, "Wrong cc name", new byte[]{}); } + ChaincodeStubNaiveImpl(List args) { + this.args = args; + state = new HashMap<>(); + state.put("a", ByteString.copyFrom("asdf", StandardCharsets.UTF_8)); + + argsAsByte = null; + + resp = new Chaincode.Response(404, "Wrong cc name", new byte[]{}); + } + + @Override public List getArgs() { if (argsAsByte == null) { diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ContractRouterTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ContractRouterTest.java index 76f52a02..cb1bcab4 100644 --- a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ContractRouterTest.java +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/ContractRouterTest.java @@ -6,7 +6,6 @@ package org.hyperledger.fabric.contract; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -26,14 +25,14 @@ import org.junit.rules.ExpectedException; import contract.SampleContract; + public class ContractRouterTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @Test public void testCreateAndScan() { - ContractRouter r = new ContractRouter(new String[]{"-a", "127.0.0.1:7052", "-i", "testId"}); + ContractRouter r = new ContractRouter(new String[] { "-a", "127.0.0.1:7052", "-i", "testId" }); r.findAllContracts(); ChaincodeStub s = new ChaincodeStubNaiveImpl(); @@ -43,19 +42,15 @@ public void testCreateAndScan() { args.add("asdf"); ((ChaincodeStubNaiveImpl) s).setStringArgs(args); InvocationRequest request = ExecutionFactory.getInstance().createRequest(s); - assertThat(request.getNamespace(), is(equalTo(SampleContract.class.getAnnotation(Contract.class).namespace()))); + assertThat(request.getNamespace(), is(equalTo(SampleContract.class.getAnnotation(Contract.class).name()))); assertThat(request.getMethod(), is(equalTo("t1"))); assertThat(request.getRequestName(), is(equalTo("samplecontract:t1"))); assertThat(request.getArgs(), is(contains(s.getArgs().get(1)))); - org.hyperledger.fabric.contract.routing.TxFunction.Routing routing = r.getRouting(request); - assertThat(routing.getContractClass().getName(), is(equalTo(SampleContract.class.getName()))); - assertThat(routing.getMethod().getName(), is(equalTo("t1"))); - } @Test public void testInit() { - ContractRouter r = new ContractRouter(new String[]{"-a", "127.0.0.1:7052", "-i", "testId"}); + ContractRouter r = new ContractRouter(new String[] { "-a", "127.0.0.1:7052", "-i", "testId" }); r.findAllContracts(); ChaincodeStub s = new ChaincodeStubNaiveImpl(); @@ -81,7 +76,7 @@ public void testInit() { @Test public void testInvokeTxnThatExists() { - ContractRouter r = new ContractRouter(new String[]{"-a", "127.0.0.1:7052", "-i", "testId"}); + ContractRouter r = new ContractRouter(new String[] { "-a", "127.0.0.1:7052", "-i", "testId" }); r.findAllContracts(); ChaincodeStub s = new ChaincodeStubNaiveImpl(); @@ -107,7 +102,7 @@ public void testInvokeTxnThatExists() { @Test public void testInvokeTxnThatDoesNotExist() { - ContractRouter r = new ContractRouter(new String[]{"-a", "127.0.0.1:7052", "-i", "testId"}); + ContractRouter r = new ContractRouter(new String[] { "-a", "127.0.0.1:7052", "-i", "testId" }); r.findAllContracts(); ChaincodeStub s = new ChaincodeStubNaiveImpl(); @@ -133,7 +128,7 @@ public void testInvokeTxnThatDoesNotExist() { @Test public void testInvokeTxnThatThrowsAnException() { - ContractRouter r = new ContractRouter(new String[]{"-a", "127.0.0.1:7052", "-i", "testId"}); + ContractRouter r = new ContractRouter(new String[] { "-a", "127.0.0.1:7052", "-i", "testId" }); r.findAllContracts(); ChaincodeStub s = new ChaincodeStubNaiveImpl(); @@ -157,8 +152,8 @@ public void testInvokeTxnThatThrowsAnException() { @Test public void exceptions() { - ContractRuntimeException cre1 = new ContractRuntimeException("failure"); - ContractRuntimeException cre2 = new ContractRuntimeException("another failure",cre1); - ContractRuntimeException cre3 = new ContractRuntimeException(new Exception("cause")); + ContractRuntimeException cre1 = new ContractRuntimeException("failure"); + ContractRuntimeException cre2 = new ContractRuntimeException("another failure", cre1); + ContractRuntimeException cre3 = new ContractRuntimeException(new Exception("cause")); } } diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType.java new file mode 100644 index 00000000..f515e8a6 --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType.java @@ -0,0 +1,30 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract; + +import org.hyperledger.fabric.contract.annotation.DataType; +import org.hyperledger.fabric.contract.annotation.Property; + +@DataType +public class MyType { + + @Property() + private String value; + + public MyType setValue(String value) { + this.value = value; + return this; + } + + public String getValue() { + return this.value; + } + + public String toString() { + return "++++ MyType: " + value; + } +} \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType2.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType2.java new file mode 100644 index 00000000..c8100f0d --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/MyType2.java @@ -0,0 +1,38 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract; + +import org.hyperledger.fabric.contract.annotation.DataType; +import org.hyperledger.fabric.contract.annotation.Property; + +@DataType +public class MyType2 { + + @Property() + private String value; + + @Property(schema = {"title","MrProperty", + "Pattern","[a-z]", + "uniqueItems","false", + "required","true,false", + "enum","a,bee,cee,dee", + "minimum","42"}) + private String constrainedValue; + + public MyType2 setValue(String value) { + this.value = value; + return this; + } + + public String getValue() { + return this.value; + } + + public String toString() { + return "++++ MyType: " + value; + } +} \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializerTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializerTest.java new file mode 100644 index 00000000..82a57f10 --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/execution/JSONTransactionSerializerTest.java @@ -0,0 +1,128 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package org.hyperledger.fabric.contract.execution; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.nio.charset.StandardCharsets; + +import org.hyperledger.fabric.contract.MyType; +import org.hyperledger.fabric.contract.metadata.MetadataBuilder; +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.TypeRegistry; +import org.hyperledger.fabric.contract.routing.impl.TypeRegistryImpl; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class JSONTransactionSerializerTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void toBuffer() { + TypeRegistry tr = new TypeRegistryImpl(); + JSONTransactionSerializer serializer = new JSONTransactionSerializer(tr); + + byte[] bytes = serializer.toBuffer("hello world", TypeSchema.typeConvert(String.class)); + assertThat(new String(bytes, StandardCharsets.UTF_8), equalTo("hello world")); + + bytes = serializer.toBuffer(42, TypeSchema.typeConvert(Integer.class)); + assertThat(new String(bytes, StandardCharsets.UTF_8), equalTo("42")); + + bytes = serializer.toBuffer(true, TypeSchema.typeConvert(Boolean.class)); + assertThat(new String(bytes, StandardCharsets.UTF_8), equalTo("true")); + + bytes = serializer.toBuffer(new MyType(), TypeSchema.typeConvert(MyType.class)); + assertThat(new String(bytes, StandardCharsets.UTF_8), equalTo("{}")); + + bytes = serializer.toBuffer(new MyType().setValue("Hello"), TypeSchema.typeConvert(MyType.class)); + assertThat(new String(bytes, StandardCharsets.UTF_8), equalTo("{\"value\":\"Hello\"}")); + + MyType array[] = new MyType[2]; + array[0] = new MyType().setValue("hello"); + array[1] = new MyType().setValue("world"); + bytes = serializer.toBuffer(array, TypeSchema.typeConvert(MyType[].class)); + + byte[] buffer = "[{\"value\":\"hello\"},{\"value\":\"world\"}]".getBytes(StandardCharsets.UTF_8); + + System.out.println(new String(buffer,StandardCharsets.UTF_8)); + System.out.println(new String(bytes,StandardCharsets.UTF_8)); + assertThat(bytes, equalTo(buffer)); + } + + @Test + public void fromBufferObject() { + byte[] buffer = "[{\"value\":\"hello\"},{\"value\":\"world\"}]".getBytes(StandardCharsets.UTF_8); + + TypeRegistry tr = new TypeRegistryImpl(); + tr.addDataType(MyType.class); + + MetadataBuilder.addComponent(tr.getDataType("MyType")); + + JSONTransactionSerializer serializer = new JSONTransactionSerializer(tr); + TypeSchema ts = TypeSchema.typeConvert(MyType[].class); + MyType[] o = (MyType[]) serializer.fromBuffer(buffer, ts); + assertThat(o[0].toString(),equalTo("++++ MyType: hello")); + assertThat(o[1].toString(),equalTo("++++ MyType: world")); + + } + + @Test + public void toBufferPrimitive() { + TypeRegistry tr = new TypeRegistryImpl(); + JSONTransactionSerializer serializer = new JSONTransactionSerializer(tr); + + TypeSchema ts; + Object value; + byte[] buffer; + + ts = TypeSchema.typeConvert(boolean.class); + value = false; + buffer =serializer.toBuffer(value, ts); + assertThat(buffer,equalTo(new byte[] {102, 97, 108, 115, 101})); + assertThat(serializer.fromBuffer(buffer, ts),equalTo(false)); + + ts = TypeSchema.typeConvert(int.class); + value = 1; + buffer =serializer.toBuffer(value, ts); + assertThat(buffer,equalTo(new byte[] {49})); + assertThat(serializer.fromBuffer(buffer, ts),equalTo(1)); + + ts = TypeSchema.typeConvert(long.class); + value = 9192631770l; + buffer =serializer.toBuffer(value, ts); + assertThat(buffer,equalTo(new byte[] {57, 49, 57, 50, 54, 51, 49, 55, 55, 48})); + assertThat(serializer.fromBuffer(buffer, ts),equalTo(9192631770l)); + + ts = TypeSchema.typeConvert(float.class); + float f = 3.1415927F; + buffer =serializer.toBuffer(f, ts); + assertThat(buffer,equalTo(new byte[] {51, 46, 49, 52, 49, 53, 57, 50, 55})); + assertThat(serializer.fromBuffer(buffer, ts),equalTo(3.1415927F)); + + ts = TypeSchema.typeConvert(double.class); + double d = 2.7182818284590452353602874713527; + buffer =serializer.toBuffer(d, ts); + assertThat(buffer,equalTo(new byte[] {50, 46, 55, 49, 56, 50, 56, 49, 56, 50, 56, 52, 53, 57, 48, 52, 53})); + assertThat(serializer.fromBuffer(buffer, ts),equalTo(2.7182818284590452353602874713527)); + } + + @Test + public void fromBufferErrors() { + TypeRegistry tr = new TypeRegistryImpl(); + tr.addDataType(MyType.class); + MetadataBuilder.addComponent(tr.getDataType("MyType")); + JSONTransactionSerializer serializer = new JSONTransactionSerializer(tr); + TypeSchema ts = TypeSchema.typeConvert(MyType[].class); + serializer.toBuffer(null, ts); + } + + + +} diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/MetadataBuilderTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/MetadataBuilderTest.java index fa5abd11..914691a4 100644 --- a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/MetadataBuilderTest.java +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/MetadataBuilderTest.java @@ -43,50 +43,13 @@ public void beforeEach() { MetadataBuilder.overallInfoMap = new HashMap(); } - @Test - public void propertySchema() { - - Map retval = MetadataBuilder.propertySchema(String.class); - assertThat(retval, hasEntry("type", "string")); - - retval = MetadataBuilder.propertySchema(byte.class); - assertThat(retval, hasEntry("type", "integer")); - assertThat(retval, hasEntry("format", "int8")); - - retval = MetadataBuilder.propertySchema(short.class); - assertThat(retval, hasEntry("type", "integer")); - assertThat(retval, hasEntry("format", "int16")); - - retval = MetadataBuilder.propertySchema(int.class); - assertThat(retval, hasEntry("type", "integer")); - assertThat(retval, hasEntry("format", "int32")); - - retval = MetadataBuilder.propertySchema(long.class); - assertThat(retval, hasEntry("type", "integer")); - assertThat(retval, hasEntry("format", "int64")); - - retval = MetadataBuilder.propertySchema(double.class); - assertThat(retval, hasEntry("type", "number")); - assertThat(retval, hasEntry("format", "double")); - - retval = MetadataBuilder.propertySchema(float.class); - assertThat(retval, hasEntry("type", "number")); - assertThat(retval, hasEntry("format", "float")); - - retval = MetadataBuilder.propertySchema(boolean.class); - assertThat(retval, hasEntry("type", "boolean")); - - retval = MetadataBuilder.propertySchema(Exception.class); - assertNull(retval); - } - @Test public void systemContract() { // access the system contract to extract the metadata SystemContract system = new SystemContract(); - String metadatacompressed = system.getMetadata(); - System.out.println(metadatacompressed); + String metadatacompressed = system.GetMetadata(); + } } \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/TypeSchemaTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/TypeSchemaTest.java new file mode 100644 index 00000000..d8b1b3bc --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/metadata/TypeSchemaTest.java @@ -0,0 +1,199 @@ + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.metadata; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import org.hyperledger.fabric.contract.annotation.DataType; +import org.hyperledger.fabric.contract.routing.DataTypeDefinition; +import org.hyperledger.fabric.contract.routing.TypeRegistry; +import org.hyperledger.fabric.contract.routing.impl.DataTypeDefinitionImpl; +import org.hyperledger.fabric.contract.routing.impl.TypeRegistryImpl; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class TypeSchemaTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void beforeEach() { + } + + @Test + public void putIfNotNull() { + TypeSchema ts = new TypeSchema(); + + ts.putIfNotNull("Key", "value"); + String nullstr = null; + ts.putIfNotNull("Key", nullstr); + + assertThat(ts.get("Key"), equalTo("value")); + ts.putIfNotNull("Key", ""); + + assertThat(ts.get("Key"), equalTo("value")); + } + + @Test + public void getType() { + TypeSchema ts = new TypeSchema(); + ts.put("type", "MyType"); + assertThat(ts.getType(), equalTo("MyType")); + + TypeSchema wrapper = new TypeSchema(); + wrapper.put("schema", ts); + assertThat(wrapper.getType(), equalTo("MyType")); + } + + @Test + public void getFormat() { + TypeSchema ts = new TypeSchema(); + ts.put("format", "MyFormat"); + assertThat(ts.getFormat(), equalTo("MyFormat")); + + TypeSchema wrapper = new TypeSchema(); + wrapper.put("schema", ts); + assertThat(wrapper.getFormat(), equalTo("MyFormat")); + } + + @Test + public void getRef() { + TypeSchema ts = new TypeSchema(); + ts.put("$ref", "#/ref/to/MyType"); + assertThat(ts.getRef(), equalTo("#/ref/to/MyType")); + + TypeSchema wrapper = new TypeSchema(); + wrapper.put("schema", ts); + assertThat(wrapper.getRef(), equalTo("#/ref/to/MyType")); + } + + @Test + public void getItems() { + TypeSchema ts1 = new TypeSchema(); + + TypeSchema ts = new TypeSchema(); + ts.put("items", ts1); + assertThat(ts.getItems(), equalTo(ts1)); + + TypeSchema wrapper = new TypeSchema(); + wrapper.put("schema", ts); + assertThat(wrapper.getItems(), equalTo(ts1)); + } + + @DataType + class MyType { + } + + @Test + public void getTypeClass() { + TypeSchema ts = new TypeSchema(); + + ts.put("type", "string"); + TypeRegistry mockRegistry = new TypeRegistryImpl(); + assertThat(ts.getTypeClass(mockRegistry), equalTo(String.class)); + + ts.put("type", "integer"); + assertThat(ts.getTypeClass(mockRegistry), equalTo(int.class)); + + ts.put("type", "boolean"); + assertThat(ts.getTypeClass(mockRegistry), equalTo(boolean.class)); + + ts.put("type", null); + ts.put("$ref", "#/ref/to/MyType"); + + mockRegistry.addDataType(MyType.class); + assertThat(ts.getTypeClass(mockRegistry), equalTo(MyType.class)); + + TypeSchema array = new TypeSchema(); + array.put("type", "array"); + array.put("items", ts); + assertThat(array.getTypeClass(mockRegistry), equalTo(MyType[].class)); + + } + + @Test + public void TypeConvertPrimitives() { + TypeSchema rts; + + String[] array = new String[] {}; + rts = TypeSchema.typeConvert(array.getClass()); + assertThat(rts.getType(), equalTo("array")); + + rts = TypeSchema.typeConvert(int.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(long.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(float.class); + assertThat(rts.getType(), equalTo("number")); + + rts = TypeSchema.typeConvert(double.class); + assertThat(rts.getType(), equalTo("number")); + + rts = TypeSchema.typeConvert(byte.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(short.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(boolean.class); + assertThat(rts.getType(), equalTo("boolean")); + + } + + @Test + public void TypeConvertObjects() { + TypeSchema rts; + rts = TypeSchema.typeConvert(String.class); + assertThat(rts.getType(), equalTo("string")); + + String[] array = new String[] {}; + rts = TypeSchema.typeConvert(array.getClass()); + assertThat(rts.getType(), equalTo("array")); + + rts = TypeSchema.typeConvert(Integer.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(Long.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(Float.class); + assertThat(rts.getType(), equalTo("number")); + + rts = TypeSchema.typeConvert(Double.class); + assertThat(rts.getType(), equalTo("number")); + + rts = TypeSchema.typeConvert(Byte.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(Short.class); + assertThat(rts.getType(), equalTo("integer")); + + rts = TypeSchema.typeConvert(Boolean.class); + assertThat(rts.getType(), equalTo("boolean")); + + rts = TypeSchema.typeConvert(MyType.class); + assertThat(rts.getRef(), equalTo("#/components/schemas/TypeSchemaTest$MyType")); + } + + @Test + public void validate() { + + TypeSchema ts = TypeSchema.typeConvert(org.hyperledger.fabric.contract.MyType.class); + DataTypeDefinition dtd = new DataTypeDefinitionImpl(org.hyperledger.fabric.contract.MyType.class); + + MetadataBuilder.addComponent(dtd); + JSONObject json = new JSONObject(); + ts.validate(json); + + } +} diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ContractDefinitionTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ContractDefinitionTest.java index 071590b8..9f741230 100644 --- a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ContractDefinitionTest.java +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ContractDefinitionTest.java @@ -9,13 +9,9 @@ import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertThat; -import java.lang.reflect.Method; -import java.lang.reflect.ReflectPermission; import java.security.Permission; -import org.hyperledger.fabric.contract.ContractInterface; import org.hyperledger.fabric.contract.annotation.Contract; -import org.hyperledger.fabric.contract.annotation.Transaction; import org.hyperledger.fabric.contract.routing.impl.ContractDefinitionImpl; import org.junit.Before; import org.junit.Rule; @@ -26,73 +22,71 @@ import io.swagger.v3.oas.annotations.info.Info; public class ContractDefinitionTest { - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Before - public void beforeEach() { - } - - @Test - public void constructor() throws NoSuchMethodException, SecurityException { - - ContractDefinition cf = new ContractDefinitionImpl(SampleContract.class); - assertThat(cf.toString(), startsWith("samplecontract:")); - } - - @Contract(namespace="",info = @Info()) - public class FailureTestObject { - - } - - @Test - public void constructorFailure() throws NoSuchMethodException, SecurityException { - try { - ContractDefinition cf = new ContractDefinitionImpl(FailureTestObject.class); - } catch (Exception e) { - assertThat(e.getMessage(), equalTo("Unable to create instance of contract")); - } - } - - public boolean fail; - public int step = 1; - - @Test - public void unkownRoute() { - - - SecurityManager tmp = new SecurityManager() { - int count=0; - - @Override - public void checkPackageAccess(String pkg) { - - if (pkg.startsWith("org.hyperledger.fabric.contract")){ - if (count>=step) { - throw new SecurityException("Sorry I can't do that"); - } - count++; - } - super.checkPackageAccess(pkg); - } - - @Override - public void checkPermission(Permission perm) { - return; - } - }; - - - try { - ContractDefinition cf = new ContractDefinitionImpl(SampleContract.class); - System.setSecurityManager(tmp); - this.fail = true; - - cf.getUnkownRoute(); - } catch (Exception e) { - assertThat(e.getMessage(), equalTo("Failure to find unknownTranction method")); - } finally { - System.setSecurityManager(null); - } - } + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void beforeEach() { + } + + @Test + public void constructor() throws NoSuchMethodException, SecurityException { + + ContractDefinition cf = new ContractDefinitionImpl(SampleContract.class); + assertThat(cf.toString(), startsWith("samplecontract:")); + } + + @Contract(name = "", info = @Info()) + public class FailureTestObject { + + } + + @Test + public void constructorFailure() throws NoSuchMethodException, SecurityException { + try { + ContractDefinition cf = new ContractDefinitionImpl(FailureTestObject.class); + } catch (Exception e) { + assertThat(e.getMessage(), equalTo("Unable to create instance of contract")); + } + } + + public boolean fail; + public int step = 1; + + @Test + public void unkownRoute() { + + SecurityManager tmp = new SecurityManager() { + int count = 0; + + @Override + public void checkPackageAccess(String pkg) { + + if (pkg.startsWith("org.hyperledger.fabric.contract")) { + if (count >= step) { + throw new SecurityException("Sorry I can't do that"); + } + count++; + } + super.checkPackageAccess(pkg); + } + + @Override + public void checkPermission(Permission perm) { + return; + } + }; + + try { + ContractDefinition cf = new ContractDefinitionImpl(SampleContract.class); + System.setSecurityManager(tmp); + this.fail = true; + + cf.getUnkownRoute(); + } catch (Exception e) { + assertThat(e.getMessage(), equalTo("Failure to find unknownTranction method")); + } finally { + System.setSecurityManager(null); + } + } } \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/DataTypeDefinitionTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/DataTypeDefinitionTest.java new file mode 100644 index 00000000..279d38fc --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/DataTypeDefinitionTest.java @@ -0,0 +1,57 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.routing; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.junit.Assert.assertThat; + +import java.util.Map; + +import org.hyperledger.fabric.contract.MyType2; +import org.hyperledger.fabric.contract.routing.impl.DataTypeDefinitionImpl; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class DataTypeDefinitionTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void beforeEach() { + } + + @Test + public void constructor() { + DataTypeDefinitionImpl dtd = new DataTypeDefinitionImpl(MyType2.class); + assertThat(dtd.getTypeClass(), equalTo(MyType2.class)); + assertThat(dtd.getName(), equalTo("org.hyperledger.fabric.contract.MyType2")); + assertThat(dtd.getSimpleName(), equalTo("MyType2")); + + Map properties = dtd.getProperties(); + assertThat(properties.size(), equalTo(2)); + assertThat(properties, hasKey("value")); + assertThat(properties, hasKey("constrainedValue")); + + PropertyDefinition pd = properties.get("constrainedValue"); + Map ts = pd.getSchema(); + + assertThat(ts, hasEntry("title", "MrProperty")); + assertThat(ts, hasEntry("Pattern", "[a-z]")); + assertThat(ts, hasEntry("uniqueItems", false)); + assertThat(ts, hasEntry("required", new String[] {"true","false"})); + assertThat(ts, hasEntry("enum", new String[] {"a","bee","cee","dee"})); + assertThat(ts, hasEntry("minimum", 42)); + + + } + + + +} \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ParameterDefinitionTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ParameterDefinitionTest.java new file mode 100644 index 00000000..7813bbe8 --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/ParameterDefinitionTest.java @@ -0,0 +1,37 @@ + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.routing; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.lang.reflect.Parameter; + +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.impl.ParameterDefinitionImpl; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class ParameterDefinitionTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void beforeEach() { + } + + @Test + public void constructor() throws NoSuchMethodException, SecurityException { + Parameter params[] = String.class.getMethod("concat", String.class).getParameters(); + ParameterDefinition pd = new ParameterDefinitionImpl("test",String.class, new TypeSchema(),params[0]); + assertThat(pd.toString(),equalTo("test-class java.lang.String-{}-java.lang.String arg0")); + assertThat(pd.getTypeClass(), equalTo(String.class)); + assertThat(pd.getParameter(), equalTo(params[0])); + } +} \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/PropertyDefinitionTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/PropertyDefinitionTest.java new file mode 100644 index 00000000..8f6d678d --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/PropertyDefinitionTest.java @@ -0,0 +1,40 @@ + +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.routing; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.lang.reflect.Field; + +import org.hyperledger.fabric.contract.metadata.TypeSchema; +import org.hyperledger.fabric.contract.routing.impl.PropertyDefinitionImpl; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class PropertyDefinitionTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void beforeEach() { + } + + @Test + public void constructor() throws NoSuchMethodException, SecurityException { + Field props[] = String.class.getFields(); + TypeSchema ts = new TypeSchema(); + PropertyDefinition pd = new PropertyDefinitionImpl("test", String.class, ts, props[0]); + + assertThat(pd.getTypeClass(), equalTo(String.class)); + assertThat(pd.getField(), equalTo(props[0])); + assertThat(pd.getSchema(), equalTo(ts)); + assertThat(pd.getName(), equalTo("test")); + }; +} \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TxFunctionTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TxFunctionTest.java index 34b00740..d7bf2d95 100644 --- a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TxFunctionTest.java +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TxFunctionTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.fabric.contract.ContractInterface; +import org.hyperledger.fabric.contract.annotation.Property; import org.hyperledger.fabric.contract.annotation.Transaction; import org.hyperledger.fabric.contract.routing.impl.TxFunctionImpl; import org.junit.Before; @@ -33,7 +34,7 @@ public void testMethod1() { } @Transaction() - public void testMethod2() { + public void testMethod2(@Property(schema= {"a","b"}) int arg) { } } @@ -53,4 +54,16 @@ public void constructor() throws NoSuchMethodException, SecurityException { assertThat(txfn.toString(),startsWith("testMethod1")); } + + @Test + public void property() throws NoSuchMethodException, SecurityException { + TestObject test = new TestObject(); + ContractDefinition cd = mock(ContractDefinition.class); + when(cd.getContractImpl()).thenReturn(test); + TxFunction txfn = new TxFunctionImpl(test.getClass().getMethod("testMethod2", new Class[] {int.class}), cd ); + String name = txfn.getName(); + assertEquals(name, "testMethod2"); + + assertThat(txfn.toString(),startsWith("testMethod2")); + } } \ No newline at end of file diff --git a/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TypeRegistryTest.java b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TypeRegistryTest.java new file mode 100644 index 00000000..455a90ed --- /dev/null +++ b/fabric-chaincode-shim/src/test/java/org/hyperledger/fabric/contract/routing/TypeRegistryTest.java @@ -0,0 +1,59 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package org.hyperledger.fabric.contract.routing; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +import java.util.Collection; + +import org.hyperledger.fabric.contract.routing.impl.DataTypeDefinitionImpl; +import org.hyperledger.fabric.contract.routing.impl.TypeRegistryImpl; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class TypeRegistryTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void beforeEach() { + } + + @Test + public void addDataType() { + TypeRegistryImpl tr = new TypeRegistryImpl(); + tr.addDataType(String.class); + + DataTypeDefinition drt = tr.getDataType("String"); + assertThat(drt.getName(), equalTo("java.lang.String")); + } + + @Test + public void addDataTypeDefinition() { + DataTypeDefinitionImpl dtd = new DataTypeDefinitionImpl(String.class); + TypeRegistryImpl tr = new TypeRegistryImpl(); + tr.addDataType(dtd); + + DataTypeDefinition drt = tr.getDataType("java.lang.String"); + assertThat(drt.getName(), equalTo("java.lang.String")); + } + + @Test + public void getAllDataTypes() { + + TypeRegistryImpl tr = new TypeRegistryImpl(); + tr.addDataType(String.class); + tr.addDataType(Integer.class); + tr.addDataType(Float.class); + + Collection c = tr.getAllDataTypes(); + assertThat(c.size(), equalTo(3)); + } + +}