package com.redhat.lightblue.migrator;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Date;
import java.text.DateFormat;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.File;
import java.io.StringReader;
import java.io.BufferedReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jiff.JsonDiff;
import jiff.JsonDelta;
import jiff.AbstractFieldFilter;
import jcmp.DocCompare;
import jcmp.JsonCompare;
import com.redhat.lightblue.client.LightblueClient;
import com.redhat.lightblue.client.LightblueClientConfiguration;
import com.redhat.lightblue.client.PropertiesLightblueClientConfiguration;
import com.redhat.lightblue.client.util.ClientConstants;
import com.redhat.lightblue.client.http.LightblueHttpClient;
public class Utils {
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
private Utils() {
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
JsonNode node1 = mapper.readTree(new File(args[0]));
JsonNode node2 = mapper.readTree(new File(args[1]));
List<Inconsistency> list = compareDocs(node1, node2, new ArrayList<String>());
System.out.println(list);
}
public static LightblueClient getLightblueClient(String configPath)
throws IOException {
LOGGER.debug("Getting client with config {}", configPath);
LightblueClient cli;
if (configPath == null) {
cli = new LightblueHttpClient();
} else {
try (InputStream is = new FileInputStream(configPath)) {
LightblueClientConfiguration config = PropertiesLightblueClientConfiguration.fromInputStream(is);
cli = new LightblueHttpClient(config);
}
}
return cli;
}
/**
* Build an id-doc map from a list of docs
*/
public static Map<Identity, JsonNode> getDocumentIdMap(List<JsonNode> list, List<String> identityFields) {
Map<Identity, JsonNode> map = new HashMap<>();
if (list != null) {
LOGGER.debug("Getting doc IDs for {} docs, fields={}", list.size(), identityFields);
for (JsonNode node : list) {
Identity id = new Identity(node, identityFields);
LOGGER.debug("ID={}", id);
map.put(id, node);
}
}
return map;
}
/**
*
* @param sourceDocument
* @param destinationDocument
* @return list of inconsistent paths
*/
public static List<Inconsistency> compareDocs(JsonNode sourceDocument, JsonNode destinationDocument, List<String> exclusionPaths) {
List<Inconsistency> ret = new ArrayList<>();
if (fastCompareDocs(sourceDocument, destinationDocument, exclusionPaths)) {
JsonCompare cmp = new JsonCompare();
try {
DocCompare.Difference<JsonNode> diff = cmp.compareNodes(sourceDocument, destinationDocument);
for (DocCompare.Delta<JsonNode> delta : diff.getDelta()) {
String field = delta.getField();
if (!field.endsWith("#") && !isExcluded(exclusionPaths, field)) {
if (!(delta instanceof DocCompare.Move)) {
if (delta instanceof DocCompare.Addition) {
ret.add(new Inconsistency(delta.getField(), null, ((DocCompare.Addition) delta).getAddedNode().toString()));
} else if (delta instanceof DocCompare.Removal) {
ret.add(new Inconsistency(delta.getField(), ((DocCompare.Removal) delta).getRemovedNode().toString(), null));
} else {
JsonNode n1 = ((DocCompare.Modification<JsonNode>) delta).getUnmodifiedNode();
JsonNode n2 = ((DocCompare.Modification<JsonNode>) delta).getModifiedNode();
if (reallyDifferent(n1, n2)) {
ret.add(new Inconsistency(delta.getField(), n1.toString(), n2.toString()));
}
}
}
}
}
} catch (Exception e) {
LOGGER.error("Cannot compare docs:" + e);
throw new RuntimeException(e);
}
}
return ret;
}
/**
* Compare two docs fast if they are the same, excluding exclusions
*
* @return true if documents are different
*/
public static boolean fastCompareDocs(JsonNode sourceDocument, JsonNode destinationDocument, List<String> exclusionPaths) {
try {
JsonDiff diff = new JsonDiff();
diff.setOption(JsonDiff.Option.ARRAY_ORDER_INSIGNIFICANT);
diff.setOption(JsonDiff.Option.RETURN_LEAVES_ONLY);
diff.setFilter(new AbstractFieldFilter() {
public boolean includeField(List<String> fieldName) {
return !fieldName.get(fieldName.size() - 1).endsWith("#");
}
});
List<JsonDelta> list = diff.computeDiff(sourceDocument, destinationDocument);
for (JsonDelta x : list) {
String field = x.getField();
if (!isExcluded(exclusionPaths, field)) {
if (reallyDifferent(x.getNode1(), x.getNode2())) {
return true;
}
}
}
} catch (Exception e) {
LOGGER.error("Cannot compare docs:{}", e, e);
}
return false;
}
/**
* This stupidity is required because data types of the source and dest
* might be different, but they might have the same value. Like, a number
* represented as string in one system could be an int in another. That is
* valid, but JSON comparison fails for that, so we check if the two nodes
* are *really* different, meaning: for anything but dates, check string
* equivalence. For dates, normalize by TZ and check.
*/
private static boolean reallyDifferent(JsonNode source, JsonNode dest) {
if (source == null || source instanceof NullNode) {
if (dest == null || dest instanceof NullNode) {
return false;
} else {
return true;
}
} else if (dest == null || dest instanceof NullNode) {
return true;
} else {
String s1 = source.asText();
String s2 = dest.asText();
if (s1.equals(s2)) {
return false;
}
// They are different strings
// Do they look like dates?
Date d1;
Date d2;
DateFormat fmt = ClientConstants.getDateFormat();
try {
d1 = fmt.parse(s1);
} catch (Exception e) {
return true;
}
try {
d2 = fmt.parse(s2);
} catch (Exception e) {
return true;
}
return d1.getTime() != d2.getTime();
}
}
private static boolean isExcluded(List<String> exclusions, String path) {
if (exclusions != null) {
if (!exclusions.contains(path)) {
for (String x : exclusions) {
if (matches(path, x)) {
return true;
}
}
} else {
return true;
}
}
return false;
}
private static boolean matches(String path, String pattern) {
String[] patternComponents = pattern.split("\\.");
String[] pathComponents = path.split("\\.");
if (patternComponents.length <= pathComponents.length) {
for (int i = 0; i < patternComponents.length; i++) {
if (!(pathComponents[i].equals(patternComponents[i])
|| "*".equals(patternComponents[i]))) {
return false;
}
}
return true;
}
return false;
}
}