/* * #%L * ACS AEM Commons Bundle * %% * Copyright (C) 2015 Adobe * %% * 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. * #L% */ package com.adobe.acs.commons.analysis.jcrchecksum.impl; import aQute.bnd.annotation.ProviderType; import com.adobe.acs.commons.analysis.jcrchecksum.ChecksumGenerator; import com.adobe.acs.commons.analysis.jcrchecksum.ChecksumGeneratorOptions; import com.adobe.acs.commons.analysis.jcrchecksum.impl.options.DefaultChecksumGeneratorOptions; import org.apache.commons.codec.digest.DigestUtils; import org.apache.sling.commons.json.JSONException; import org.apache.sling.commons.json.io.JSONWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFormatException; import java.io.IOException; import java.util.Calendar; import java.util.HashSet; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; /** * Utility that generates checksums for JCR paths. * The checksum is calculated using a depth first traversal * and calculates an aggregate checksum on the nodes with the specified node types * (via {@link ChecksumGeneratorOptions}). */ @ProviderType public final class JSONGenerator { private static final Logger log = LoggerFactory.getLogger(ChecksumGenerator.class); private JSONGenerator() { // Private cstor for static util } public static void generateJSON(Session session, String path, JSONWriter out) throws RepositoryException, JSONException { Set<String> paths = new HashSet<String>(); paths.add(path); generateJSON(session, paths, new DefaultChecksumGeneratorOptions(), out); } public static void generateJSON(Session session, Set<String> paths, ChecksumGeneratorOptions opts, JSONWriter out) throws RepositoryException, JSONException { Node node = null; if (paths.size() > 1) { out.array(); } for (String path : paths) { out.object(); try { if (session.itemExists(path)) { Item item = session.getItem(path); if (item.isNode()) { node = (Node) item; } } traverseTree(node, opts, out); } catch (PathNotFoundException e) { out.key("ERROR"); out.value("WARN: Path doesn't exist: " + path); } catch (RepositoryException e) { out.key("ERROR"); out.value("Unable to read path: " + e.getMessage()); } finally { out.endObject(); } } if (paths.size() > 1) { out.endArray(); } } private static void traverseTree(Node node, ChecksumGeneratorOptions opts, JSONWriter out) throws JSONException { Set<String> nodeTypes = opts.getIncludedNodeTypes(); Set<String> nodeTypeExcludes = opts.getExcludedNodeTypes(); if (node != null) { String primaryNodeType; try { primaryNodeType = node.getPrimaryNodeType().getName(); NodeIterator nIt; if (nodeTypes.contains(primaryNodeType) && !nodeTypeExcludes.contains(primaryNodeType)) { generateJSON(node, opts, out); } else { nIt = node.getNodes(); while (nIt.hasNext()) { primaryNodeType = node.getPrimaryNodeType().getName(); Node child = nIt.nextNode(); if (nodeTypes.contains(primaryNodeType)) { generateJSON(child, opts, out); } else { traverseTree(child, opts, out); } } } } catch (RepositoryException e) { log.error("Error while traversing tree {}", e.getMessage()); } } } private static void generateJSON(Node node, ChecksumGeneratorOptions opts, JSONWriter out) throws RepositoryException, JSONException { out.key(node.getPath()); out.object(); outputProperties(node, opts, out); outputChildNodes(node, opts, out); out.endObject(); } private static void generateSubnodeJSON(Node node, ChecksumGeneratorOptions opts, JSONWriter out) throws RepositoryException, JSONException { out.key(node.getName()); out.object(); outputProperties(node, opts, out); outputChildNodes(node, opts, out); out.endObject(); } /** * @param node * @param opts * @param out * @throws RepositoryException * @throws JSONException * @throws ValueFormatException */ private static void outputProperties(Node node, ChecksumGeneratorOptions opts, JSONWriter out) throws RepositoryException, JSONException, ValueFormatException { Set<String> excludes = opts.getExcludedProperties(); Set<String> sortValues = opts.getSortedProperties(); SortedMap<String, Property> props = new TreeMap<String, Property>(); PropertyIterator pi = node.getProperties(); // sort the properties by name as the JCR makes no guarantees on property order while (pi.hasNext()) { Property p = pi.nextProperty(); //skip the property if it is in the excludes list if (excludes.contains(p.getName())) { continue; } else { props.put(p.getName(), p); } } pi = null; for (Property p : props.values()) { int type = p.getType(); if (p.isMultiple()) { out.key(p.getName()); // create an array for multi value output out.array(); boolean isSortedValues = sortValues.contains(p.getName()); Value[] values = p.getValues(); TreeMap<String, Value> sortedValueMap = new TreeMap<String, Value>(); for (Value v : values) { type = v.getType(); if (type == PropertyType.BINARY) { if (isSortedValues) { try { java.io.InputStream stream = v.getBinary().getStream(); String ckSum = DigestUtils.shaHex(stream); stream.close(); sortedValueMap.put(ckSum, v); } catch (IOException e) { sortedValueMap.put("ERROR: generating hash for binary of " + p.getPath() + " : " + e.getMessage(), v); } } else { outputPropertyValue(p, v, out); } } else { String val = v.getString(); if (isSortedValues) { sortedValueMap.put(val, v); } else { outputPropertyValue(p, v, out); } } } if (isSortedValues) { for (Value v : sortedValueMap.values()) { outputPropertyValue(p, v, out); } } out.endArray(); // end multi value property output } else { out.key(p.getName()); outputPropertyValue(p, p.getValue(), out); } } // if (nodeTypes.contains(primaryNodeType)) { // out.print(node.getPath()); // out.print("\t"); // out.println(DigestUtils.shaHex(checkSums.toString())); // out.flush(); // } } /** * @param node * @param opts * @param out * @throws RepositoryException * @throws JSONException */ private static void outputChildNodes(Node node, ChecksumGeneratorOptions opts, JSONWriter out) throws RepositoryException, JSONException { Set<String> nodeTypeExcludes = opts.getExcludedNodeTypes(); NodeIterator nIt; nIt = node.getNodes(); TreeMap<String, Node> childSortMap = new TreeMap<String, Node>(); boolean hasOrderedChildren = false; try { hasOrderedChildren = node.getPrimaryNodeType().hasOrderableChildNodes(); } catch (Exception e) { } while (nIt.hasNext()) { Node child = nIt.nextNode(); if (!nodeTypeExcludes .contains(child.getPrimaryNodeType().getName())) { if (hasOrderedChildren) { //output child node if parent is has orderable children out.key(child.getName()); out.object(); generateSubnodeJSON(child, opts, out); out.endObject(); } else { // otherwise put the child nodes into a sorted map // to output them with consistent ordering childSortMap.put(child.getName(), child); } } } // output the non-ordered child nodes in sorted order (lexicographically) for (Node child : childSortMap.values()) { out.key(child.getName()); out.object(); generateSubnodeJSON(child, opts, out); out.endObject(); } } private static void outputPropertyValue(Property p, Value v, JSONWriter out) throws RepositoryException, JSONException { if (v.getType() == PropertyType.STRING) { out.value(v.getString()); } else if (v.getType() == PropertyType.BINARY) { try { java.io.InputStream stream = v.getBinary().getStream(); String ckSum = DigestUtils.shaHex(stream); stream.close(); out.value(ckSum); } catch (IOException e) { out.value("ERROR: calculating hash for binary of " + p.getPath() + " : " + e.getMessage()); } } else if (v.getType() == PropertyType.BOOLEAN) { out.value(v.getBoolean()); } else if (v.getType() == PropertyType.DATE) { Calendar cal = v.getDate(); if (cal != null) { out.object(); out.key("type"); out.value(PropertyType.nameFromValue(v.getType())); out.key("val"); out.value(cal.getTime().toString()); out.endObject(); } } else if (v.getType() == PropertyType.LONG) { out.value(v.getLong()); } else if (v.getType() == PropertyType.DOUBLE) { out.value(v.getDouble()); } else if (v.getType() == PropertyType.DECIMAL) { out.value(v.getDecimal()); } else { out.object(); out.key("type"); out.value(PropertyType.nameFromValue(v.getType())); out.key("val"); out.value(v.getString()); out.endObject(); } } }