com.yahoo.bard.webservice.util.Utils Maven / Gradle / Ivy
Show all versions of fili-core Show documentation
// Copyright 2016 Yahoo Inc.
// Licensed under the terms of the Apache license. Please see LICENSE.md file distributed with this work for terms.
package com.yahoo.bard.webservice.util;
import com.yahoo.bard.webservice.data.config.metric.MetricInstance;
import com.yahoo.bard.webservice.data.metric.MetricDictionary;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.stream.Collectors;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
/**
* Utils.
*/
public class Utils {
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
/**
* A strategy that deletes a directory.
*/
private static final FileVisitor DELETE_VISITOR = new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
};
/**
* Given a collection of objects which share the same super class, return the subset of objects that share a common
* sub class.
*
* @param set Input set
* @param Input set type
* @param type sub class
* @param sub class type
*
* @return ordered subset of objects that share a common sub class
*/
public static LinkedHashSet getSubsetByType(Collection set, Class type) {
return set.stream()
.filter(type::isInstance)
.map(StreamUtils.uncheckedCast(type))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* Easily turn a few instances of an object into a LinkedHashSet.
*
*
* LinkedHashSet<String> stooges = Utils.asLinkedHashSet("Larry", "Moe", "Curly");
*
*
* @param The element type for the linked hash set
* @param e The array from which the LinkedHashSet will be built
*
* @return a LinkedHashSet view of the specified array
*/
@SafeVarargs
public static LinkedHashSet asLinkedHashSet(E... e) {
return new LinkedHashSet<>(Arrays.asList(e));
}
/**
* Create parent directories if they don't exist in a given file path.
*
* @param path The pathname
*/
public static void createParentDirectories(String path) {
File targetFile = new File(path);
File parent = targetFile.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
throw new IllegalStateException("Couldn't create dir: " + parent);
}
}
/**
* Delete files or directories in the specified path.
*
* @param path The pathname
*/
public static void deleteFiles(String path) {
Path file = Paths.get(path);
// do nothing if there is nothing to delete
if (!Files.exists(file)) {
LOG.trace(String.format("'%s' does not exist. Nothing is deleted", path));
return;
}
try {
Files.walkFileTree(file, DELETE_VISITOR);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
/**
* Helper method to throw an exception when a result is expected as a return value (e.g. in a ternary operator)
*
* @param The type of exception being returned
* @param exception The exception to be thrown
*
* @return is only used for type inference. No object is actually ever returned. An exception is always being thrown
* instead
*/
public static T insteadThrowRuntime(RuntimeException exception) {
throw exception;
}
/**
* Helper method to return request headers as a map of the same type with its keys lower cased.
*
* @param headers The request headers.
* @return The headers with their names lower cased.
*/
public static MultivaluedMap headersToLowerCase(MultivaluedMap headers) {
return headers.entrySet().stream()
.collect(
StreamUtils.toMap(
e -> e.getKey().toLowerCase(Locale.ENGLISH),
Map.Entry::getValue,
MultivaluedHashMap::new
)
);
}
/**
* Given a field name and a tree of json nodes, empty the contents of all the json nodes matching the field name.
* This method is recursive.
*
* @param node The root of the tree of json nodes.
* @param fieldName The name of the node to be omitted.
* @param mapper The object mapper that creates and empty node.
* @deprecated Should avoid this method and instead use {@link #canonicalize(JsonNode, ObjectMapper, boolean)}
* which preserves JSON object ordering that guarantees consistent hash values.
*/
@Deprecated
public static void omitField(JsonNode node, String fieldName, ObjectMapper mapper) {
if (node.has("context")) {
((ObjectNode) node).replace(fieldName, mapper.createObjectNode());
}
for (JsonNode child : node) {
omitField(child, fieldName, mapper);
}
}
/**
* Given a JsonNode, order the ObjectNodes and ArrayNodes recursively and replace context blocks with empty nodes.
*
*
* @param node The root of the tree of json nodes.
* @param mapper The object mapper that creates and empty node.
* @param preserveContext Boolean indicating whether context should be omitted.
*/
public static void canonicalize(JsonNode node, ObjectMapper mapper, boolean preserveContext) {
boolean arrayWithNameObjects = true;
if (node.isObject()) {
canonicalizeObject(node, mapper, preserveContext);
} else if (node.isArray()) {
canonicalizeArray(node, mapper, preserveContext, arrayWithNameObjects);
}
}
/**
* Find the minimum value between two comparable objects.
*
* @param one Item 1
* @param two Item 2
* @param Type of object to compare
*
* @return the minimum of the two objects
*/
@SuppressWarnings("unchecked")
public static T getMinValue(T one, T two) {
return one.compareTo(two) < 1 ? one : two;
}
/**
* Given an ImmutablePair, and a right value, returns a new ImmutablePair with the same left value,
* and the specified right value.
*
* @param pair Immutable Pair instance
* @param right The right value, may be null
* @param Left type of the pair
* @param Right type of the pair
* @param The right value to have new Immutable Pair
*
* @return New instance of Immutable Pair
*/
public static ImmutablePair withRight(ImmutablePair pair, V right) {
return new ImmutablePair<>(pair.getLeft(), right);
}
/**
* Create metrics from instance descriptors and store in the metric dictionary.
*
* @param metricDictionary The dictionary to store metrics in
* @param metrics The list of metric descriptors
*/
public static void addToMetricDictionary(MetricDictionary metricDictionary, List metrics) {
metrics.stream().map(MetricInstance::make).forEach(metricDictionary::add);
}
/**
* Given a JsonObjectNode, order the ObjectNode entries on the key recursively and
* replace context blocks with empty nodes.
*
* This method is recursive.
*
* @param node The root of the tree of json nodes.
* @param mapper The object mapper that creates and empty node.
* @param preserveContext Boolean indicating whether context should be omitted.
*/
public static void canonicalizeObject(JsonNode node, ObjectMapper mapper, boolean preserveContext) {
ObjectNode objectNode = ((ObjectNode) node);
if (objectNode.has("context") && !preserveContext) {
objectNode.replace("context", mapper.createObjectNode());
}
Iterator> iterator = objectNode.fields();
// collect and sort the entries
TreeMap fieldMap = new TreeMap<>();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
fieldMap.put(entry.getKey(), entry.getValue());
// canonicalize all child nodes
canonicalize(entry.getValue(), mapper, preserveContext);
}
// remove the existing entries
objectNode.removeAll();
// replace the entries in sorted order
objectNode.setAll(fieldMap);
}
/**
* Given a JsonArrayNode, store the ArrayNode entries in order and recursively canonicalize JsonNodes.
*
* This method is recursive.
*
* @param node The root of the tree of json nodes.
* @param mapper The object mapper that creates and empty node.
* @param preserveContext Boolean indicating whether context should be omitted.
* @param arrayWithNameObjects Boolean indicating whether all objects within the ArrayNode have a name field.
*/
public static void canonicalizeArray(
JsonNode node,
ObjectMapper mapper,
boolean preserveContext,
boolean arrayWithNameObjects
) {
ArrayNode arrayNode = ((ArrayNode) node);
Iterator iterator = arrayNode.elements();
// collect and store the entries in order
LinkedHashSet fieldSet = new LinkedHashSet<>();
while (iterator.hasNext()) {
JsonNode entry = iterator.next();
fieldSet.add(entry);
if (entry.isObject() && entry.get("name") == null) {
arrayWithNameObjects = false;
}
// canonicalize all child nodes
canonicalize(entry, mapper, preserveContext);
}
// if all objects in the array contain name field, then order on its value
if (arrayWithNameObjects) {
List arrayValues = new ArrayList<>();
for (int i = 0; i < arrayNode.size(); i++) {
arrayValues.add(arrayNode.get(i));
}
Collections.sort(arrayValues, new Comparator() {
private static final String KEY_NAME = "name";
@Override
public int compare(JsonNode a, JsonNode b) {
String valA = new String();
String valB = new String();
try {
valA = mapper.writeValueAsString(a.get(KEY_NAME));
valB = mapper.writeValueAsString(b.get(KEY_NAME));
} catch (JsonProcessingException e) {
LOG.warn(String.format("Error processing json for cache key. %s", e.getMessage()), e);
}
return valA.compareTo(valB);
}
});
// remove the existing entries
arrayNode.removeAll();
// replace the entries in order
arrayValues.stream().forEach(val -> arrayNode.add(val));
} else {
// remove the existing entries
arrayNode.removeAll();
// replace the entries in order
arrayNode.addAll(fieldSet);
}
}
}