Java tutorial
/* * 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.helper; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.SecureRandom; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Collection; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.TimeZone; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.google.common.io.ByteStreams; import com.google.protobuf.ByteString; import com.google.protobuf.Timestamp; import io.netty.util.internal.StringUtil; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SHA3Digest; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.commons.codec.binary.Hex.encodeHexString; public final class Utils { private static final Log logger = LogFactory.getLog(Utils.class); private static final boolean TRACE_ENABED = logger.isTraceEnabled(); private static final Config config = Config.getConfig(); private static final int MAX_LOG_STRING_LENGTH = config.maxLogStringLength(); /** * Generate parameter hash for the given chaincode path,func and args * * @param path Chaincode path * @param func Chaincode function name * @param args List of arguments * @return hash of path, func and args */ public static String generateParameterHash(String path, String func, List<String> args) { logger.debug(format("GenerateParameterHash : path=%s, func=%s, args=%s", path, func, args)); // Append the arguments StringBuilder param = new StringBuilder(path); param.append(func); args.forEach(param::append); // Compute the hash return Hex.toHexString(hash(param.toString().getBytes(UTF_8), new SHA3Digest())); } /** * Generate hash of a chaincode directory * * @param rootDir Root directory * @param chaincodeDir Channel code directory * @param hash Previous hash (if any) * @return hash of the directory * @throws IOException */ public static String generateDirectoryHash(String rootDir, String chaincodeDir, String hash) throws IOException { // Generate the project directory Path projectPath = null; if (rootDir == null) { projectPath = Paths.get(chaincodeDir); } else { projectPath = Paths.get(rootDir, chaincodeDir); } File dir = projectPath.toFile(); if (!dir.exists() || !dir.isDirectory()) { throw new IOException(format("The chaincode path \"%s\" is invalid", projectPath)); } StringBuilder hashBuilder = new StringBuilder(hash); Files.walk(projectPath).sorted(Comparator.naturalOrder()).filter(Files::isRegularFile).map(Path::toFile) .forEach(file -> { try { byte[] buf = readFile(file); byte[] toHash = Arrays.concatenate(buf, hashBuilder.toString().getBytes(UTF_8)); hashBuilder.setLength(0); hashBuilder.append(Hex.toHexString(hash(toHash, new SHA3Digest()))); } catch (IOException ex) { throw new RuntimeException(format("Error while reading file %s", file.getAbsolutePath()), ex); } }); // If original hash and final hash are the same, it indicates that no new contents were found if (hashBuilder.toString().equals(hash)) { throw new IOException(format("The chaincode directory \"%s\" has no files", projectPath)); } return hashBuilder.toString(); } /** * Compress the contents of given directory using Tar and Gzip to an in-memory byte array. * * @param sourceDirectory the source directory. * @param pathPrefix a path to be prepended to every file name in the .tar.gz output, or {@code null} if no prefix is required. * @param chaincodeMetaInf * @return the compressed directory contents. * @throws IOException */ public static byte[] generateTarGz(File sourceDirectory, String pathPrefix, File chaincodeMetaInf) throws IOException { logger.trace(format("generateTarGz: sourceDirectory: %s, pathPrefix: %s, chaincodeMetaInf: %s", sourceDirectory == null ? "null" : sourceDirectory.getAbsolutePath(), pathPrefix, chaincodeMetaInf == null ? "null" : chaincodeMetaInf.getAbsolutePath())); ByteArrayOutputStream bos = new ByteArrayOutputStream(500000); String sourcePath = sourceDirectory.getAbsolutePath(); TarArchiveOutputStream archiveOutputStream = new TarArchiveOutputStream( new GzipCompressorOutputStream(bos)); archiveOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); try { Collection<File> childrenFiles = org.apache.commons.io.FileUtils.listFiles(sourceDirectory, null, true); ArchiveEntry archiveEntry; FileInputStream fileInputStream; for (File childFile : childrenFiles) { String childPath = childFile.getAbsolutePath(); String relativePath = childPath.substring((sourcePath.length() + 1), childPath.length()); if (pathPrefix != null) { relativePath = Utils.combinePaths(pathPrefix, relativePath); } relativePath = FilenameUtils.separatorsToUnix(relativePath); if (TRACE_ENABED) { logger.trace(format("generateTarGz: Adding '%s' entry from source '%s' to archive.", relativePath, childFile.getAbsolutePath())); } archiveEntry = new TarArchiveEntry(childFile, relativePath); fileInputStream = new FileInputStream(childFile); archiveOutputStream.putArchiveEntry(archiveEntry); try { IOUtils.copy(fileInputStream, archiveOutputStream); } finally { IOUtils.closeQuietly(fileInputStream); archiveOutputStream.closeArchiveEntry(); } } if (null != chaincodeMetaInf) { childrenFiles = org.apache.commons.io.FileUtils.listFiles(chaincodeMetaInf, null, true); final URI metabase = chaincodeMetaInf.toURI(); for (File childFile : childrenFiles) { final String relativePath = Paths .get("META-INF", metabase.relativize(childFile.toURI()).getPath()).toString(); if (TRACE_ENABED) { logger.trace(format("generateTarGz: Adding '%s' entry from source '%s' to archive.", relativePath, childFile.getAbsolutePath())); } archiveEntry = new TarArchiveEntry(childFile, relativePath); fileInputStream = new FileInputStream(childFile); archiveOutputStream.putArchiveEntry(archiveEntry); try { IOUtils.copy(fileInputStream, archiveOutputStream); } finally { IOUtils.closeQuietly(fileInputStream); archiveOutputStream.closeArchiveEntry(); } } } } finally { IOUtils.closeQuietly(archiveOutputStream); } return bos.toByteArray(); } /** * Read the contents a file. * * @param input source file to read. * @return contents of the file. * @throws IOException */ public static byte[] readFile(File input) throws IOException { return Files.readAllBytes(Paths.get(input.getAbsolutePath())); } /** * Generate a v4 UUID * * @return String representation of {@link UUID} */ public static String generateUUID() { return UUID.randomUUID().toString(); } /** * Create a new {@link Timestamp} instance based on the current time * * @return timestamp */ public static Timestamp generateTimestamp() { Instant time = Instant.now(); return Timestamp.newBuilder().setSeconds(time.getEpochSecond()).setNanos(time.getNano()).build(); } /** * Delete a file or directory * * @param file {@link File} representing file or directory * @throws IOException */ public static void deleteFileOrDirectory(File file) throws IOException { if (file.exists()) { if (file.isDirectory()) { Path rootPath = Paths.get(file.getAbsolutePath()); Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS).sorted(Comparator.reverseOrder()) .map(Path::toFile).forEach(File::delete); } else { file.delete(); } } else { throw new RuntimeException("File or directory does not exist"); } } /** * Generate hash of the given input using the given Digest. * * @param input input data. * @param digest the digest to use for hashing * @return hashed data. */ public static byte[] hash(byte[] input, Digest digest) { byte[] retValue = new byte[digest.getDigestSize()]; digest.update(input, 0, input.length); digest.doFinal(retValue, 0); return retValue; } /** * Combine two or more paths * * @param first parent directory path * @param other children * @return combined path */ public static String combinePaths(String first, String... other) { return Paths.get(first, other).toString(); } /** * Read a file from classpath * * @param fileName * @return byte[] data * @throws IOException */ public static byte[] readFileFromClasspath(String fileName) throws IOException { InputStream is = Utils.class.getClassLoader().getResourceAsStream(fileName); byte[] data = ByteStreams.toByteArray(is); try { is.close(); } catch (IOException ex) { } return data; } public static Properties parseGrpcUrl(String url) { if (StringUtil.isNullOrEmpty(url)) { throw new RuntimeException("URL cannot be null or empty"); } Properties props = new Properties(); Pattern p = Pattern.compile("([^:]+)[:]//([^:]+)[:]([0-9]+)", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(url); if (m.matches()) { props.setProperty("protocol", m.group(1)); props.setProperty("host", m.group(2)); props.setProperty("port", m.group(3)); String protocol = props.getProperty("protocol"); if (!"grpc".equals(protocol) && !"grpcs".equals(protocol)) { throw new RuntimeException( format("Invalid protocol expected grpc or grpcs and found %s.", protocol)); } } else { throw new RuntimeException("URL must be of the format protocol://host:port. Found: '" + url + "'"); } // TODO: allow all possible formats of the URL return props; } /** * Check if the strings Grpc url is valid * * @param url * @return Return the exception that indicates the error or null if ok. */ public static Exception checkGrpcUrl(String url) { try { parseGrpcUrl(url); return null; } catch (Exception e) { return e; } } /** * Check if a string is null or empty. * * @param url the string to test. * @return {@code true} if the string is null or empty; otherwise {@code false}. */ public static boolean isNullOrEmpty(String url) { return url == null || url.isEmpty(); } /** * Makes logging strings which can be long or with unprintable characters be logged and trimmed. * * @param string Unsafe string too long * @return returns a string which does not have unprintable characters and trimmed in length. */ public static String logString(final String string) { if (string == null || string.length() == 0) { return string; } String ret = string.replaceAll("[^\\p{Print}]", "?"); ret = ret.substring(0, Math.min(ret.length(), MAX_LOG_STRING_LENGTH)) + (ret.length() > MAX_LOG_STRING_LENGTH ? "..." : ""); return ret; } private static final int NONONCE_LENGTH = 24; private static final SecureRandom RANDOM = new SecureRandom(); public static byte[] generateNonce() { byte[] values = new byte[NONONCE_LENGTH]; RANDOM.nextBytes(values); return values; } public static String toHexString(ByteString byteString) { if (byteString == null) { return null; } return encodeHexString(byteString.toByteArray()); } public static String toHexString(byte[] bytes) { if (bytes == null) { return null; } return encodeHexString(bytes); } /** * Private constructor to prevent instantiation. */ private Utils() { } }