org.hyperledger.fabric.contract.metadata.MetadataBuilder Maven / Gradle / Ivy
Show all versions of fabric-chaincode-shim Show documentation
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.contract.metadata;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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;
import org.everit.json.schema.loader.SchemaLoader;
import org.hyperledger.fabric.Logger;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.routing.ContractDefinition;
import org.hyperledger.fabric.contract.routing.DataTypeDefinition;
import org.hyperledger.fabric.contract.routing.RoutingRegistry;
import org.hyperledger.fabric.contract.routing.TransactionType;
import org.hyperledger.fabric.contract.routing.TxFunction;
import org.hyperledger.fabric.contract.routing.TypeRegistry;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.hyperledger.fabric.contract.annotation.Info;
/**
* 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
*/
public class MetadataBuilder {
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 contractDefinition 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