org.hyperledger.fabric.sdk.NetworkConfig Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fabric-sdk-java Show documentation
Show all versions of fabric-sdk-java Show documentation
Java SDK for Hyperledger Fabric. Deprecated as of Fabric v2.5, replaced by org.hyperledger.fabric:fabric-gateway.
/*
* Copyright 2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hyperledger.fabric.sdk;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.json.JsonValue.ValueType;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.hyperledger.fabric.sdk.Channel.PeerOptions;
import org.hyperledger.fabric.sdk.Peer.PeerRole;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.NetworkConfigurationException;
import org.hyperledger.fabric.sdk.identity.X509Enrollment;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.yaml.snakeyaml.Yaml;
import static java.lang.String.format;
import static org.hyperledger.fabric.sdk.helper.Utils.isNullOrEmpty;
/**
* Holds details of network and channel configurations typically loaded from an external config file.
*
* Also contains convenience methods for utilizing the config details,
* including the main {@link HFClient#getChannel(String)} method
*/
public class NetworkConfig {
private final JsonObject jsonConfig;
private OrgInfo clientOrganization;
private Map orderers;
private Map peers;
private Map eventHubs;
/**
* Names of Peers found
*
* @return Collection of peer names found.
*/
public Collection getPeerNames() {
if (peers == null) {
return Collections.EMPTY_SET;
} else {
return new HashSet<>(peers.keySet());
}
}
/**
* Names of Orderers found
*
* @return Collection of peer names found.
*/
public Collection getOrdererNames() {
if (orderers == null) {
return Collections.EMPTY_SET;
} else {
return new HashSet<>(orderers.keySet());
}
}
/**
* Names of EventHubs found
*
* @return Collection of eventhubs names found.
*/
public Collection getEventHubNames() {
if (eventHubs == null) {
return Collections.EMPTY_SET;
} else {
return new HashSet<>(eventHubs.keySet());
}
}
private Properties getNodeProperties(String type, String name, Map nodes) throws InvalidArgumentException {
if (isNullOrEmpty(name)) {
throw new InvalidArgumentException("Parameter name is null or empty.");
}
Node node = nodes.get(name);
if (node == null) {
throw new InvalidArgumentException(format("%s %s not found.", type, name));
}
if (null == node.properties) {
return new Properties();
} else {
return (Properties) node.properties.clone();
}
}
private void setNodeProperties(String type, String name, Map nodes, Properties properties) throws InvalidArgumentException {
if (isNullOrEmpty(name)) {
throw new InvalidArgumentException("Parameter name is null or empty.");
}
if (properties == null) {
throw new InvalidArgumentException("Parameter properties is null.");
}
Node node = nodes.get(name);
if (node == null) {
throw new InvalidArgumentException(format("%S %s not found.", type, name));
}
Properties ourCopyProps = new Properties();
ourCopyProps.putAll(properties);
node.properties = ourCopyProps;
}
/**
* Get properties for a specific peer.
*
* @param name Name of peer to get the properties for.
* @return The peer's properties.
* @throws InvalidArgumentException
*/
public Properties getPeerProperties(String name) throws InvalidArgumentException {
return getNodeProperties("Peer", name, peers);
}
/**
* Get properties for a specific Orderer.
*
* @param name Name of orderer to get the properties for.
* @return The orderer's properties.
* @throws InvalidArgumentException
*/
public Properties getOrdererProperties(String name) throws InvalidArgumentException {
return getNodeProperties("Orderer", name, orderers);
}
/**
* Get properties for a specific eventhub.
*
* @param name Name of eventhub to get the properties for.
* @return The eventhubs's properties.
* @throws InvalidArgumentException
*/
public Properties getEventHubsProperties(String name) throws InvalidArgumentException {
return getNodeProperties("EventHub", name, eventHubs);
}
/**
* Set a specific peer's properties.
*
* @param name The name of the peer's property to set.
* @param properties The properties to set.
* @throws InvalidArgumentException
*/
public void setPeerProperties(String name, Properties properties) throws InvalidArgumentException {
setNodeProperties("Peer", name, peers, properties);
}
/**
* Set a specific orderer's properties.
*
* @param name The name of the orderer's property to set.
* @param properties The properties to set.
* @throws InvalidArgumentException
*/
public void setOrdererProperties(String name, Properties properties) throws InvalidArgumentException {
setNodeProperties("Orderer", name, orderers, properties);
}
/**
* Set a specific eventhub's properties.
*
* @param name The name of the eventhub's property to set.
* @param properties The properties to set.
* @throws InvalidArgumentException
*/
public void setEventHubProperties(String name, Properties properties) throws InvalidArgumentException {
setNodeProperties("EventHub", name, eventHubs, properties);
}
private String getNodeUrl(String type, String name, Map nodes) throws InvalidArgumentException {
if (isNullOrEmpty(name)) {
throw new InvalidArgumentException("Parameter name is null or empty.");
}
Node node = nodes.get(name);
if (node == null) {
throw new InvalidArgumentException(format("%s %s not found.", type, name));
}
return node.getUrl();
}
/**
* Get URL for a specific peer.
*
* @param name Name of peer to get the URL for.
* @return The peer's URL.
* @throws InvalidArgumentException
*/
public String getPeerUrl(String name) throws InvalidArgumentException {
return getNodeUrl("Peer", name, peers);
}
// Organizations, keyed on org name (and not on mspid!)
private Map organizations;
private static final Log logger = LogFactory.getLog(NetworkConfig.class);
private NetworkConfig(JsonObject jsonConfig) throws InvalidArgumentException, NetworkConfigurationException {
this.jsonConfig = jsonConfig;
// Extract the main details
String configName = getJsonValueAsString(jsonConfig.get("name"));
if (configName == null || configName.isEmpty()) {
throw new InvalidArgumentException("Network config must have a name");
}
String configVersion = getJsonValueAsString(jsonConfig.get("version"));
if (configVersion == null || configVersion.isEmpty()) {
throw new InvalidArgumentException("Network config must have a version");
// TODO: Validate the version
}
// Preload and create all peers, orderers, etc
createAllPeers();
createAllOrderers();
Map foundCertificateAuthorities = findCertificateAuthorities();
//createAllCertificateAuthorities();
createAllOrganizations(foundCertificateAuthorities);
// Validate the organization for this client
JsonObject jsonClient = getJsonObject(jsonConfig, "client");
String orgName = jsonClient == null ? null : getJsonValueAsString(jsonClient.get("organization"));
if (orgName == null || orgName.isEmpty()) {
throw new InvalidArgumentException("A client organization must be specified");
}
clientOrganization = getOrganizationInfo(orgName);
if (clientOrganization == null) {
throw new InvalidArgumentException("Client organization " + orgName + " is not defined");
}
}
/**
* Creates a new NetworkConfig instance configured with details supplied in a YAML file.
*
* @param configFile The file containing the network configuration
* @return A new NetworkConfig instance
* @throws InvalidArgumentException
* @throws IOException
*/
public static NetworkConfig fromYamlFile(File configFile) throws InvalidArgumentException, IOException, NetworkConfigurationException {
return fromFile(configFile, false);
}
/**
* Creates a new NetworkConfig instance configured with details supplied in a JSON file.
*
* @param configFile The file containing the network configuration
* @return A new NetworkConfig instance
* @throws InvalidArgumentException
* @throws IOException
*/
public static NetworkConfig fromJsonFile(File configFile) throws InvalidArgumentException, IOException, NetworkConfigurationException {
return fromFile(configFile, true);
}
/**
* Creates a new NetworkConfig instance configured with details supplied in YAML format
*
* @param configStream A stream opened on a YAML document containing network configuration details
* @return A new NetworkConfig instance
* @throws InvalidArgumentException
*/
public static NetworkConfig fromYamlStream(InputStream configStream) throws InvalidArgumentException, NetworkConfigurationException {
logger.trace("NetworkConfig.fromYamlStream...");
// Sanity check
if (configStream == null) {
throw new InvalidArgumentException("configStream must be specified");
}
Yaml yaml = new Yaml();
@SuppressWarnings ("unchecked")
Map map = yaml.load(configStream);
JsonObjectBuilder builder = Json.createObjectBuilder(map);
JsonObject jsonConfig = builder.build();
return fromJsonObject(jsonConfig);
}
/**
* Creates a new NetworkConfig instance configured with details supplied in JSON format
*
* @param configStream A stream opened on a JSON document containing network configuration details
* @return A new NetworkConfig instance
* @throws InvalidArgumentException
*/
public static NetworkConfig fromJsonStream(InputStream configStream) throws InvalidArgumentException, NetworkConfigurationException {
logger.trace("NetworkConfig.fromJsonStream...");
// Sanity check
if (configStream == null) {
throw new InvalidArgumentException("configStream must be specified");
}
// Read the input stream and convert to JSON
try (JsonReader reader = Json.createReader(configStream)) {
JsonObject jsonConfig = (JsonObject) reader.read();
return fromJsonObject(jsonConfig);
}
}
/**
* Creates a new NetworkConfig instance configured with details supplied in a JSON object
*
* @param jsonConfig JSON object containing network configuration details
* @return A new NetworkConfig instance
* @throws InvalidArgumentException
*/
public static NetworkConfig fromJsonObject(JsonObject jsonConfig) throws InvalidArgumentException, NetworkConfigurationException {
// Sanity check
if (jsonConfig == null) {
throw new InvalidArgumentException("jsonConfig must be specified");
}
if (logger.isTraceEnabled()) {
logger.trace(format("NetworkConfig.fromJsonObject: %s", jsonConfig.toString()));
}
return NetworkConfig.load(jsonConfig);
}
// Loads a NetworkConfig object from a Json or Yaml file
private static NetworkConfig fromFile(File configFile, boolean isJson) throws InvalidArgumentException, IOException, NetworkConfigurationException {
// Sanity check
if (configFile == null) {
throw new InvalidArgumentException("configFile must be specified");
}
if (logger.isTraceEnabled()) {
logger.trace(format("NetworkConfig.fromFile: %s isJson = %b", configFile.getAbsolutePath(), isJson));
}
NetworkConfig config;
// Json file
try (InputStream stream = new FileInputStream(configFile)) {
config = isJson ? fromJsonStream(stream) : fromYamlStream(stream);
}
return config;
}
/**
* Returns a new NetworkConfig instance and populates it from the specified JSON object
*
* @param jsonConfig The JSON object containing the config details
* @return A populated NetworkConfig instance
* @throws InvalidArgumentException
*/
private static NetworkConfig load(JsonObject jsonConfig) throws InvalidArgumentException, NetworkConfigurationException {
// Sanity check
if (jsonConfig == null) {
throw new InvalidArgumentException("config must be specified");
}
return new NetworkConfig(jsonConfig);
}
public OrgInfo getClientOrganization() {
return clientOrganization;
}
public OrgInfo getOrganizationInfo(String orgName) {
return organizations.get(orgName);
}
public Collection getOrganizationInfos() {
return Collections.unmodifiableCollection(organizations.values());
}
/**
* Returns the admin user associated with the client organization
*
* @return The admin user details
* @throws NetworkConfigurationException
*/
public UserInfo getPeerAdmin() throws NetworkConfigurationException {
// Get the details from the client organization
return getPeerAdmin(clientOrganization.getName());
}
/**
* Returns the admin user associated with the specified organization
*
* @param orgName The name of the organization
* @return The admin user details
* @throws NetworkConfigurationException
*/
public UserInfo getPeerAdmin(String orgName) throws NetworkConfigurationException {
OrgInfo org = getOrganizationInfo(orgName);
if (org == null) {
throw new NetworkConfigurationException(format("Organization %s is not defined", orgName));
}
return org.getPeerAdmin();
}
/**
* Returns a channel configured using the details in the Network Configuration file
*
* @param client The associated client
* @param channelName The name of the channel
* @return A configured Channel instance
*/
Channel loadChannel(HFClient client, String channelName) throws NetworkConfigurationException {
if (logger.isTraceEnabled()) {
logger.trace(format("NetworkConfig.loadChannel: %s", channelName));
}
Channel channel = null;
JsonObject channels = getJsonObject(jsonConfig, "channels");
if (channels != null) {
JsonObject jsonChannel = getJsonObject(channels, channelName);
if (jsonChannel != null) {
channel = client.getChannel(channelName);
if (channel != null) {
// The channel already exists in the client!
// Note that by rights this should never happen as HFClient.loadChannelFromConfig should have already checked for this!
throw new NetworkConfigurationException(format("Channel %s is already configured in the client!", channelName));
}
channel = reconstructChannel(client, channelName, jsonChannel);
} else {
final Set channelNames = getChannelNames();
if (channelNames.isEmpty()) {
throw new NetworkConfigurationException("Channel configuration has no channels defined.");
}
final StringBuilder sb = new StringBuilder(1000);
channelNames.forEach(s -> {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(s);
});
throw new NetworkConfigurationException(format("Channel %s not found in configuration file. Found channel names: %s ", channelName, sb.toString()));
}
} else {
throw new NetworkConfigurationException("Channel configuration has no channels defined.");
}
return channel;
}
// Creates Node instances representing all the orderers defined in the config file
private void createAllOrderers() throws NetworkConfigurationException {
// Sanity check
if (orderers != null) {
throw new NetworkConfigurationException("INTERNAL ERROR: orderers has already been initialized!");
}
orderers = new HashMap<>();
// orderers is a JSON object containing a nested object for each orderers
JsonObject jsonOrderers = getJsonObject(jsonConfig, "orderers");
if (jsonOrderers != null) {
for (Entry entry : jsonOrderers.entrySet()) {
String ordererName = entry.getKey();
JsonObject jsonOrderer = getJsonValueAsObject(entry.getValue());
if (jsonOrderer == null) {
throw new NetworkConfigurationException(format("Error loading config. Invalid orderer entry: %s", ordererName));
}
Node orderer = createNode(ordererName, jsonOrderer, "url");
if (orderer == null) {
throw new NetworkConfigurationException(format("Error loading config. Invalid orderer entry: %s", ordererName));
}
orderers.put(ordererName, orderer);
}
}
}
// Creates Node instances representing all the peers (and associated event hubs) defined in the config file
private void createAllPeers() throws NetworkConfigurationException {
// Sanity checks
if (peers != null) {
throw new NetworkConfigurationException("INTERNAL ERROR: peers has already been initialized!");
}
if (eventHubs != null) {
throw new NetworkConfigurationException("INTERNAL ERROR: eventHubs has already been initialized!");
}
peers = new HashMap<>();
eventHubs = new HashMap<>();
// peers is a JSON object containing a nested object for each peer
JsonObject jsonPeers = getJsonObject(jsonConfig, "peers");
//out("Peers: " + (jsonPeers == null ? "null" : jsonPeers.toString()));
if (jsonPeers != null) {
for (Entry entry : jsonPeers.entrySet()) {
String peerName = entry.getKey();
JsonObject jsonPeer = getJsonValueAsObject(entry.getValue());
if (jsonPeer == null) {
throw new NetworkConfigurationException(format("Error loading config. Invalid peer entry: %s", peerName));
}
Node peer = createNode(peerName, jsonPeer, "url");
if (peer == null) {
throw new NetworkConfigurationException(format("Error loading config. Invalid peer entry: %s", peerName));
}
peers.put(peerName, peer);
// Also create an event hub with the same name as the peer
Node eventHub = createNode(peerName, jsonPeer, "eventUrl"); // may not be present
if (null != eventHub) {
eventHubs.put(peerName, eventHub);
}
}
}
}
// Produce a map from tag to jsonobject for the CA
private Map findCertificateAuthorities() throws NetworkConfigurationException {
Map ret = new HashMap<>();
JsonObject jsonCertificateAuthorities = getJsonObject(jsonConfig, "certificateAuthorities");
if (null != jsonCertificateAuthorities) {
for (Entry entry : jsonCertificateAuthorities.entrySet()) {
String name = entry.getKey();
JsonObject jsonCA = getJsonValueAsObject(entry.getValue());
if (jsonCA == null) {
throw new NetworkConfigurationException(format("Error loading config. Invalid CA entry: %s", name));
}
ret.put(name, jsonCA);
}
}
return ret;
}
// Creates JsonObjects representing all the Organizations defined in the config file
private void createAllOrganizations(Map foundCertificateAuthorities) throws NetworkConfigurationException {
// Sanity check
if (organizations != null) {
throw new NetworkConfigurationException("INTERNAL ERROR: organizations has already been initialized!");
}
organizations = new HashMap<>();
// organizations is a JSON object containing a nested object for each Org
JsonObject jsonOrganizations = getJsonObject(jsonConfig, "organizations");
if (jsonOrganizations != null) {
for (Entry entry : jsonOrganizations.entrySet()) {
String orgName = entry.getKey();
JsonObject jsonOrg = getJsonValueAsObject(entry.getValue());
if (jsonOrg == null) {
throw new NetworkConfigurationException(format("Error loading config. Invalid Organization entry: %s", orgName));
}
OrgInfo org = createOrg(orgName, jsonOrg, foundCertificateAuthorities);
organizations.put(orgName, org);
}
}
}
// Reconstructs an existing channel
private Channel reconstructChannel(HFClient client, String channelName, JsonObject jsonChannel) throws NetworkConfigurationException {
Channel channel = null;
try {
channel = client.newChannel(channelName);
// orderers is an array of orderer name strings
JsonArray ordererNames = getJsonValueAsArray(jsonChannel.get("orderers"));
boolean foundOrderer = false;
//out("Orderer names: " + (ordererNames == null ? "null" : ordererNames.toString()));
if (ordererNames != null) {
for (JsonValue jsonVal : ordererNames) {
String ordererName = getJsonValueAsString(jsonVal);
Orderer orderer = getOrderer(client, ordererName);
if (orderer == null) {
throw new NetworkConfigurationException(format("Error constructing channel %s. Orderer %s not defined in configuration", channelName, ordererName));
}
channel.addOrderer(orderer);
foundOrderer = true;
}
}
// peers is an object containing a nested object for each peer
JsonObject jsonPeers = getJsonObject(jsonChannel, "peers");
boolean foundPeer = false;
//out("Peers: " + (peers == null ? "null" : peers.toString()));
if (jsonPeers != null) {
for (Entry entry : jsonPeers.entrySet()) {
String peerName = entry.getKey();
if (logger.isTraceEnabled()) {
logger.trace(format("NetworkConfig.reconstructChannel: Processing peer %s", peerName));
}
JsonObject jsonPeer = getJsonValueAsObject(entry.getValue());
if (jsonPeer == null) {
throw new NetworkConfigurationException(format("Error constructing channel %s. Invalid peer entry: %s", channelName, peerName));
}
Peer peer = getPeer(client, peerName);
if (peer == null) {
throw new NetworkConfigurationException(format("Error constructing channel %s. Peer %s not defined in configuration", channelName, peerName));
}
// Set the various roles
PeerOptions peerOptions = PeerOptions.createPeerOptions();
for (PeerRole peerRole : PeerRole.values()) {
setPeerRole(channelName, peerOptions, jsonPeer, peerRole);
}
foundPeer = true;
// Add the event hub associated with this peer
EventHub eventHub = getEventHub(client, peerName);
if (eventHub != null) {
channel.addEventHub(eventHub);
if (peerOptions.peerRoles == null) { // means no roles were found but there is an event hub so define all roles but eventing.
peerOptions.setPeerRoles(EnumSet.of(PeerRole.ENDORSING_PEER, PeerRole.CHAINCODE_QUERY, PeerRole.LEDGER_QUERY));
}
}
channel.addPeer(peer, peerOptions);
}
}
if (!foundPeer) {
// peers is a required field
throw new NetworkConfigurationException(format("Error constructing channel %s. At least one peer must be specified", channelName));
}
} catch (InvalidArgumentException e) {
throw new IllegalArgumentException(e);
}
return channel;
}
private static void setPeerRole(String channelName, PeerOptions peerOptions, JsonObject jsonPeer, PeerRole role) throws NetworkConfigurationException {
String propName = roleNameRemap(role);
JsonValue val = jsonPeer.get(propName);
if (val != null) {
Boolean isSet = getJsonValueAsBoolean(val);
if (isSet == null) {
// This is an invalid boolean value
throw new NetworkConfigurationException(format("Error constructing channel %s. Role %s has invalid boolean value: %s", channelName, propName, val.toString()));
}
if (isSet) {
peerOptions.addPeerRole(role);
}
}
}
private static Map roleNameRemapHash = new HashMap() {
{
put(PeerRole.SERVICE_DISCOVERY, "discover");
}
};
private static String roleNameRemap(PeerRole peerRole) {
String remap = roleNameRemapHash.get(peerRole);
return remap == null ? peerRole.getPropertyName() : remap;
}
// Returns a new Orderer instance for the specified orderer name
private Orderer getOrderer(HFClient client, String ordererName) throws InvalidArgumentException {
Orderer orderer = null;
Node o = orderers.get(ordererName);
if (o != null) {
orderer = client.newOrderer(o.getName(), o.getUrl(), o.getProperties());
}
return orderer;
}
// Creates a new Node instance from a JSON object
private Node createNode(String nodeName, JsonObject jsonNode, String urlPropName) throws NetworkConfigurationException {
// jsonNode.
// if (jsonNode.isNull(urlPropName)) {
// return null;
// }
String url = jsonNode.getString(urlPropName, null);
if (url == null) {
return null;
}
Properties props = extractProperties(jsonNode, "grpcOptions");
if (null != props) {
String value = props.getProperty("grpc.keepalive_time_ms");
if (null != value) {
props.remove("grpc.keepalive_time_ms");
props.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[] {new Long(value), TimeUnit.MILLISECONDS});
}
value = props.getProperty("grpc.keepalive_timeout_ms");
if (null != value) {
props.remove("grpc.keepalive_timeout_ms");
props.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[] {new Long(value), TimeUnit.MILLISECONDS});
}
}
// Extract the pem details
getTLSCerts(nodeName, jsonNode, props);
return new Node(nodeName, url, props);
}
private void getTLSCerts(String nodeName, JsonObject jsonOrderer, Properties props) {
JsonObject jsonTlsCaCerts = getJsonObject(jsonOrderer, "tlsCACerts");
if (jsonTlsCaCerts != null) {
String pemFilename = getJsonValueAsString(jsonTlsCaCerts.get("path"));
String pemBytes = getJsonValueAsString(jsonTlsCaCerts.get("pem"));
if (pemFilename != null) {
// let the sdk handle non existing errors could be they don't exist during parsing but are there later.
props.put("pemFile", pemFilename);
}
if (pemBytes != null) {
props.put("pemBytes", pemBytes.getBytes());
}
}
}
// Creates a new OrgInfo instance from a JSON object
private OrgInfo createOrg(String orgName, JsonObject jsonOrg, Map foundCertificateAuthorities) throws NetworkConfigurationException {
String msgPrefix = format("Organization %s", orgName);
String mspId = getJsonValueAsString(jsonOrg.get("mspid"));
OrgInfo org = new OrgInfo(orgName, mspId);
// Peers
JsonArray jsonPeers = getJsonValueAsArray(jsonOrg.get("peers"));
if (jsonPeers != null) {
for (JsonValue peer : jsonPeers) {
final String peerName = getJsonValueAsString(peer);
if (peerName != null) {
org.addPeerName(peerName);
final Node node = peers.get(peerName);
if (null != node) {
if (null == node.properties) {
node.properties = new Properties();
}
node.properties.put(Peer.PEER_ORGANIZATION_MSPID_PROPERTY, org.getMspId());
} else {
throw new NetworkConfigurationException(format("Organization %s has peer %s listed not found in any channel peer list.", orgName, peerName));
}
}
}
}
// CAs
JsonArray jsonCertificateAuthorities = getJsonValueAsArray(jsonOrg.get("certificateAuthorities"));
if (jsonCertificateAuthorities != null) {
for (JsonValue jsonCA : jsonCertificateAuthorities) {
String caName = getJsonValueAsString(jsonCA);
if (caName != null) {
JsonObject jsonObject = foundCertificateAuthorities.get(caName);
if (jsonObject != null) {
org.addCertificateAuthority(createCA(caName, jsonObject, org));
} else {
throw new NetworkConfigurationException(format("%s: Certificate Authority %s is not defined", msgPrefix, caName));
}
}
}
}
String adminPrivateKeyString = extractPemString(jsonOrg, "adminPrivateKey", msgPrefix);
String signedCert = extractPemString(jsonOrg, "signedCert", msgPrefix);
if (!isNullOrEmpty(adminPrivateKeyString) && !isNullOrEmpty(signedCert)) {
PrivateKey privateKey = null;
try {
privateKey = getPrivateKeyFromString(adminPrivateKeyString);
} catch (IOException ioe) {
throw new NetworkConfigurationException(format("%s: Invalid private key", msgPrefix), ioe);
}
final PrivateKey privateKeyFinal = privateKey;
try {
org.peerAdmin = new UserInfo(CryptoSuite.Factory.getCryptoSuite(), mspId, "PeerAdmin_" + mspId + "_" + orgName, null);
} catch (Exception e) {
throw new NetworkConfigurationException(e.getMessage(), e);
}
org.peerAdmin.setEnrollment(new X509Enrollment(privateKeyFinal, signedCert));
}
return org;
}
private static PrivateKey getPrivateKeyFromString(String data)
throws IOException {
final Reader pemReader = new StringReader(data);
final PrivateKeyInfo pemPair;
try (PEMParser pemParser = new PEMParser(pemReader)) {
pemPair = (PrivateKeyInfo) pemParser.readObject();
}
return new JcaPEMKeyConverter().getPrivateKey(pemPair);
}
// Returns the PEM (as a String) from either a path or a pem field
private static String extractPemString(JsonObject json, String fieldName, String msgPrefix) throws NetworkConfigurationException {
String path = null;
String pemString = null;
JsonObject jsonField = getJsonValueAsObject(json.get(fieldName));
if (jsonField != null) {
path = getJsonValueAsString(jsonField.get("path"));
pemString = getJsonValueAsString(jsonField.get("pem"));
}
if (path != null && pemString != null) {
throw new NetworkConfigurationException(format("%s should not specify both %s path and pem", msgPrefix, fieldName));
}
if (path != null) {
// Determine full pathname and ensure the file exists
File pemFile = new File(path);
String fullPathname = pemFile.getAbsolutePath();
if (!pemFile.exists()) {
throw new NetworkConfigurationException(format("%s: %s file %s does not exist", msgPrefix, fieldName, fullPathname));
}
try (FileInputStream stream = new FileInputStream(pemFile)) {
pemString = IOUtils.toString(stream, "UTF-8");
} catch (IOException ioe) {
throw new NetworkConfigurationException(format("Failed to read file: %s", fullPathname), ioe);
}
}
return pemString;
}
// Creates a new CAInfo instance from a JSON object
private CAInfo createCA(String name, JsonObject jsonCA, OrgInfo org) throws NetworkConfigurationException {
String url = getJsonValueAsString(jsonCA.get("url"));
Properties httpOptions = extractProperties(jsonCA, "httpOptions");
String enrollId = null;
String enrollSecret = null;
List registrars = getJsonValueAsList(jsonCA.get("registrar"));
List regUsers = new LinkedList<>();
if (registrars != null) {
for (JsonObject reg : registrars) {
enrollId = getJsonValueAsString(reg.get("enrollId"));
enrollSecret = getJsonValueAsString(reg.get("enrollSecret"));
try {
regUsers.add(new UserInfo(CryptoSuite.Factory.getCryptoSuite(), org.mspId, enrollId, enrollSecret));
} catch (Exception e) {
throw new NetworkConfigurationException(e.getMessage(), e);
}
}
}
CAInfo caInfo = new CAInfo(name, org.mspId, url, regUsers, httpOptions);
String caName = getJsonValueAsString(jsonCA.get("caName"));
if (caName != null) {
caInfo.setCaName(caName);
}
Properties properties = new Properties();
if (null != httpOptions && "false".equals(httpOptions.getProperty("verify"))) {
properties.setProperty("allowAllHostNames", "true");
}
getTLSCerts(name, jsonCA, properties);
caInfo.setProperties(properties);
return caInfo;
}
// Extracts all defined properties of the specified field and returns a Properties object
private static Properties extractProperties(JsonObject json, String fieldName) {
Properties props = new Properties();
// Extract any other grpc options
JsonObject options = getJsonObject(json, fieldName);
if (options != null) {
for (Entry entry : options.entrySet()) {
String key = entry.getKey();
JsonValue value = entry.getValue();
props.setProperty(key, getJsonValue(value));
}
}
return props;
}
// Returns a new Peer instance for the specified peer name
private Peer getPeer(HFClient client, String peerName) throws InvalidArgumentException {
Peer peer = null;
Node p = peers.get(peerName);
if (p != null) {
peer = client.newPeer(p.getName(), p.getUrl(), p.getProperties());
}
return peer;
}
// Returns a new EventHub instance for the specified name
private EventHub getEventHub(HFClient client, String name) throws InvalidArgumentException {
EventHub ehub = null;
Node e = eventHubs.get(name);
if (e != null) {
ehub = client.newEventHub(e.getName(), e.getUrl(), e.getProperties());
}
return ehub;
}
// Returns the specified JsonValue in a suitable format
// If it's a JsonString - it returns the string
// If it's a number = it returns the string representation of that number
// If it's TRUE or FALSE - it returns "true" and "false" respectively
// If it's anything else it returns null
private static String getJsonValue(JsonValue value) {
String s = null;
if (value != null) {
s = getJsonValueAsString(value);
if (s == null) {
s = getJsonValueAsNumberString(value);
}
if (s == null) {
Boolean b = getJsonValueAsBoolean(value);
if (b != null) {
s = b ? "true" : "false";
}
}
}
return s;
}
// Returns the specified JsonValue as a JsonObject, or null if it's not an object
private static JsonObject getJsonValueAsObject(JsonValue value) {
return (value != null && value.getValueType() == ValueType.OBJECT) ? value.asJsonObject() : null;
}
// Returns the specified JsonValue as a JsonArray, or null if it's not an array
private static JsonArray getJsonValueAsArray(JsonValue value) {
return (value != null && value.getValueType() == ValueType.ARRAY) ? value.asJsonArray() : null;
}
// Returns the specified JsonValue as a List. Allows single or array
private static List getJsonValueAsList(JsonValue value) {
if (value != null) {
if (value.getValueType() == ValueType.ARRAY) {
return value.asJsonArray().getValuesAs(JsonObject.class);
} else if (value.getValueType() == ValueType.OBJECT) {
List ret = new ArrayList<>();
ret.add(value.asJsonObject());
return ret;
}
}
return null;
}
// Returns the specified JsonValue as a String, or null if it's not a string
private static String getJsonValueAsString(JsonValue value) {
return (value != null && value.getValueType() == ValueType.STRING) ? ((JsonString) value).getString() : null;
}
// Returns the specified JsonValue as a String, or null if it's not a string
private static String getJsonValueAsNumberString(JsonValue value) {
return (value != null && value.getValueType() == ValueType.NUMBER) ? value.toString() : null;
}
// Returns the specified JsonValue as a Boolean, or null if it's not a boolean
private static Boolean getJsonValueAsBoolean(JsonValue value) {
if (value != null) {
if (value.getValueType() == ValueType.TRUE) {
return true;
} else if (value.getValueType() == ValueType.FALSE) {
return false;
}
}
return null;
}
// Returns the specified property as a JsonObject
private static JsonObject getJsonObject(JsonObject object, String propName) {
JsonObject obj = null;
JsonValue val = object.get(propName);
if (val != null && val.getValueType() == ValueType.OBJECT) {
obj = val.asJsonObject();
}
return obj;
}
/**
* Get the channel names found.
*
* @return A set of the channel names found in the configuration file or empty set if none found.
*/
public Set getChannelNames() {
Set ret = Collections.EMPTY_SET;
JsonObject channels = getJsonObject(jsonConfig, "channels");
if (channels != null) {
final Set channelNames = channels.keySet();
if (channelNames != null && !channelNames.isEmpty()) {
ret = new HashSet<>(channelNames);
}
}
return ret;
}
// Holds a network "node" (eg. Peer, Orderer, EventHub)
private class Node {
private final String name;
private final String url;
private Properties properties;
Node(String name, String url, Properties properties) {
this.url = url;
this.name = name;
this.properties = properties;
}
private String getName() {
return name;
}
private String getUrl() {
return url;
}
private Properties getProperties() {
return properties;
}
}
/**
* Holds details of a User
*/
public static class UserInfo implements User {
public void setName(String name) {
this.name = name;
}
protected String name;
protected String enrollSecret;
protected String mspid;
private Set roles;
private String account;
private String affiliation;
private Enrollment enrollment;
private CryptoSuite suite;
public void setEnrollSecret(String enrollSecret) {
this.enrollSecret = enrollSecret;
}
public String getMspid() {
return mspid;
}
public void setMspid(String mspid) {
this.mspid = mspid;
}
public void setRoles(Set roles) {
this.roles = roles;
}
public void setAccount(String account) {
this.account = account;
}
public void setAffiliation(String affiliation) {
this.affiliation = affiliation;
}
public void setEnrollment(Enrollment enrollment) {
this.enrollment = enrollment;
}
UserInfo(CryptoSuite suite, String mspid, String name, String enrollSecret) {
this.suite = suite;
this.name = name;
this.enrollSecret = enrollSecret;
this.mspid = mspid;
}
public String getEnrollSecret() {
return enrollSecret;
}
@Override
public String getName() {
return name;
}
@Override
public Set getRoles() {
return roles;
}
@Override
public String getAccount() {
return account;
}
@Override
public String getAffiliation() {
return affiliation;
}
@Override
public Enrollment getEnrollment() {
return enrollment;
}
@Override
public String getMspId() {
return mspid;
}
}
/**
* Holds details of an Organization
*/
public static class OrgInfo {
private final String name;
private final String mspId;
private final List peerNames = new ArrayList<>();
private final List certificateAuthorities = new ArrayList<>();
private UserInfo peerAdmin;
OrgInfo(String orgName, String mspId) {
this.name = orgName;
this.mspId = mspId;
}
private void addPeerName(String peerName) {
peerNames.add(peerName);
}
private void addCertificateAuthority(CAInfo ca) {
certificateAuthorities.add(ca);
}
public String getName() {
return name;
}
public String getMspId() {
return mspId;
}
public List getPeerNames() {
return peerNames;
}
public List getCertificateAuthorities() {
return certificateAuthorities;
}
/**
* Returns the associated admin user
*
* @return The admin user details
*/
public UserInfo getPeerAdmin() {
return peerAdmin;
}
}
/**
* Holds the details of a Certificate Authority
*/
public static class CAInfo {
private final String name;
private final String url;
private final Properties httpOptions;
private final String mspid;
private String caName; // The "optional" caName specified in the config, as opposed to its "config" name
private Properties properties;
private final List registrars;
CAInfo(String name, String mspid, String url, List registrars, Properties httpOptions) {
this.name = name;
this.url = url;
this.httpOptions = httpOptions;
this.registrars = registrars;
this.mspid = mspid;
}
private void setCaName(String caName) {
this.caName = caName;
}
public String getName() {
return name;
}
public String getCAName() {
return caName;
}
public String getUrl() {
return url;
}
public Properties getHttpOptions() {
return httpOptions;
}
void setProperties(Properties properties) {
this.properties = properties;
}
public Properties getProperties() {
return this.properties;
}
public Collection getRegistrars() {
return new LinkedList<>(registrars);
}
}
}