X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.jcr%2Fsrc%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=3be8be184b25f269d581d09f2bf541980883143d;hb=aa91054506680d2c2c599bffa911c0933e58d645;hp=c2450e8c7f8f63bc3f04bdc016742764c1a3cb4a;hpb=5330a39edafd14df2e6cdc57aae4e9393ebca75c;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java index c2450e8c7..3be8be184 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java @@ -1,18 +1,3 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.argeo.jcr; import java.io.ByteArrayInputStream; @@ -21,11 +6,19 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.text.DateFormat; import java.text.ParseException; +import java.time.Instant; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -37,6 +30,8 @@ import java.util.Map; import java.util.TreeMap; import javax.jcr.Binary; +import javax.jcr.Credentials; +import javax.jcr.ImportUUIDBehavior; import javax.jcr.NamespaceRegistry; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; @@ -49,6 +44,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.Workspace; +import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeType; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; @@ -61,25 +57,19 @@ import javax.jcr.security.AccessControlPolicyIterator; import javax.jcr.security.Privilege; import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.util.DigestUtils; /** Utility methods to simplify common JCR operations. */ public class JcrUtils { - final private static Log log = LogFactory.getLog(JcrUtils.class); +// final private static Log log = LogFactory.getLog(JcrUtils.class); /** * Not complete yet. See * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local * %20Names */ - public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', - '*', /* - * invalid XML chars : - */ - '<', '>', '&' }; + public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<', + '>', '&' }; /** Prevents instantiation */ private JcrUtils() { @@ -89,8 +79,7 @@ public class JcrUtils { * Queries one single node. * * @return one single node or null if none was found - * @throws ArgeoJcrException - * if more than one node was found + * @throws JcrException if more than one node was found */ public static Node querySingleNode(Query query) { NodeIterator nodeIterator; @@ -98,7 +87,7 @@ public class JcrUtils { QueryResult queryResult = query.execute(); nodeIterator = queryResult.getNodes(); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot execute query " + query, e); + throw new JcrException("Cannot execute query " + query, e); } Node node; if (nodeIterator.hasNext()) @@ -107,7 +96,7 @@ public class JcrUtils { return null; if (nodeIterator.hasNext()) - throw new ArgeoJcrException("Query returned more than one node."); + throw new IllegalArgumentException("Query returned more than one node."); return node; } @@ -116,7 +105,7 @@ public class JcrUtils { if (path.equals("/")) return ""; if (path.charAt(0) != '/') - throw new ArgeoJcrException("Path " + path + " must start with a '/'"); + throw new IllegalArgumentException("Path " + path + " must start with a '/'"); String pathT = path; if (pathT.charAt(pathT.length() - 1) == '/') pathT = pathT.substring(0, pathT.length() - 2); @@ -128,9 +117,9 @@ public class JcrUtils { /** Retrieves the parent path of the provided path */ public static String parentPath(String path) { if (path.equals("/")) - throw new ArgeoJcrException("Root path '/' has no parent path"); + throw new IllegalArgumentException("Root path '/' has no parent path"); if (path.charAt(0) != '/') - throw new ArgeoJcrException("Path " + path + " must start with a '/'"); + throw new IllegalArgumentException("Path " + path + " must start with a '/'"); String pathT = path; if (pathT.charAt(pathT.length() - 1) == '/') pathT = pathT.substring(0, pathT.length() - 2); @@ -159,7 +148,7 @@ public class JcrUtils { path.append(u.getPath()); return path.toString(); } catch (MalformedURLException e) { - throw new ArgeoJcrException("Cannot generate URL path for " + url, e); + throw new IllegalArgumentException("Cannot generate URL path for " + url, e); } } @@ -171,8 +160,10 @@ public class JcrUtils { node.setProperty(Property.JCR_HOST, u.getHost()); node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort())); node.setProperty(Property.JCR_PATH, normalizePath(u.getPath())); - } catch (Exception e) { - throw new ArgeoJcrException("Cannot set URL " + url + " as nt:address properties", e); + } catch (RepositoryException e) { + throw new JcrException("Cannot set URL " + url + " as nt:address properties", e); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e); } } @@ -184,8 +175,10 @@ public class JcrUtils { (int) node.getProperty(Property.JCR_PORT).getLong(), node.getProperty(Property.JCR_PATH).getString()); return u.toString(); - } catch (Exception e) { - throw new ArgeoJcrException("Cannot get URL from nt:address properties of " + node, e); + } catch (RepositoryException e) { + throw new JcrException("Cannot get URL from nt:address properties of " + node, e); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e); } } @@ -239,10 +232,8 @@ public class JcrUtils { /** * The provided data as a path ('/' at the end, not the beginning) * - * @param cal - * the date - * @param addHour - * whether to add hour as well + * @param cal the date + * @param addHour whether to add hour as well */ public static String dateAsPath(Calendar cal, Boolean addHour) { StringBuffer buf = new StringBuffer(14); @@ -284,7 +275,7 @@ public class JcrUtils { calendar.setTime(date); return calendar; } catch (ParseException e) { - throw new ArgeoJcrException("Cannot parse " + value + " with date format " + dateFormat, e); + throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e); } } @@ -292,7 +283,7 @@ public class JcrUtils { /** The last element of a path. */ public static String lastPathElement(String path) { if (path.charAt(path.length() - 1) == '/') - throw new ArgeoJcrException("Path " + path + " cannot end with '/'"); + throw new IllegalArgumentException("Path " + path + " cannot end with '/'"); int index = path.lastIndexOf('/'); if (index < 0) return path; @@ -307,7 +298,7 @@ public class JcrUtils { try { return node.getName(); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot get name from " + node, e); + throw new JcrException("Cannot get name from " + node, e); } } @@ -319,24 +310,50 @@ public class JcrUtils { try { return node.getProperty(propertyName).getString(); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot get name from " + node, e); + throw new JcrException("Cannot get name from " + node, e); } } +// /** +// * Routine that get the child with this name, adding it if it does not already +// * exist +// */ +// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException { +// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType); +// } + /** - * Routine that get the child with this name, adding id it does not already + * Routine that get the child with this name, adding it if it does not already * exist */ - public static Node getOrAdd(Node parent, String childName, String childPrimaryNodeType) throws RepositoryException { - return parent.hasNode(childName) ? parent.getNode(childName) : parent.addNode(childName, childPrimaryNodeType); + public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes) + throws RepositoryException { + Node node; + if (parent.hasNode(name)) { + node = parent.getNode(name); + if (primaryNodeType != null && !node.isNodeType(primaryNodeType)) + throw new IllegalArgumentException("Node " + node + " exists but is of primary node type " + + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType); + for (String mixin : mixinNodeTypes) { + if (!node.isNodeType(mixin)) + node.addMixin(mixin); + } + return node; + } else { + node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name); + for (String mixin : mixinNodeTypes) { + node.addMixin(mixin); + } + return node; + } } /** - * Routine that get the child with this name, adding id it does not already + * Routine that get the child with this name, adding it if it does not already * exist */ - public static Node getOrAdd(Node parent, String childName) throws RepositoryException { - return parent.hasNode(childName) ? parent.getNode(childName) : parent.addNode(childName); + public static Node getOrAdd(Node parent, String name) throws RepositoryException { + return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name); } /** Convert a {@link NodeIterator} to a list of {@link Node} */ @@ -362,7 +379,16 @@ public class JcrUtils { return null; return node.getProperty(propertyName).getString(); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot get property " + propertyName + " of " + node, e); + throw new JcrException("Cannot get property " + propertyName + " of " + node, e); + } + } + + /** Concisely get the path of the given node. */ + public static String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get path of " + node, e); } } @@ -371,7 +397,7 @@ public class JcrUtils { try { return node.getProperty(propertyName).getBoolean(); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot get property " + propertyName + " of " + node, e); + throw new JcrException("Cannot get property " + propertyName + " of " + node, e); } } @@ -380,7 +406,7 @@ public class JcrUtils { try { return getBinaryAsBytes(node.getProperty(propertyName)); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot get property " + propertyName + " of " + node, e); + throw new JcrException("Cannot get property " + propertyName + " of " + node, e); } } @@ -398,8 +424,7 @@ public class JcrUtils { /** * Create sub nodes relative to a parent node * - * @param nodeType - * the type of the leaf node + * @param nodeType the type of the leaf node */ public static Node mkdirs(Node parentNode, String relativePath, String nodeType) { return mkdirs(parentNode, relativePath, nodeType, null); @@ -408,8 +433,7 @@ public class JcrUtils { /** * Create sub nodes relative to a parent node * - * @param nodeType - * the type of the leaf node + * @param nodeType the type of the leaf node */ public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) { List tokens = tokenize(relativePath); @@ -429,24 +453,24 @@ public class JcrUtils { } return currParent; } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e); + throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e); } } /** - * Synchronized and save is performed, to avoid race conditions in - * initializers leading to duplicate nodes. + * Synchronized and save is performed, to avoid race conditions in initializers + * leading to duplicate nodes. */ public synchronized static Node mkdirsSafe(Session session, String path, String type) { try { if (session.hasPendingChanges()) - throw new ArgeoJcrException("Session has pending changes, save them first."); + throw new IllegalStateException("Session has pending changes, save them first."); Node node = mkdirs(session, path, type); session.save(); return node; } catch (RepositoryException e) { discardQuietly(session); - throw new ArgeoJcrException("Cannot safely make directories", e); + throw new JcrException("Cannot safely make directories", e); } } @@ -460,30 +484,28 @@ public class JcrUtils { } /** - * @param type - * the type of the leaf node + * @param type the type of the leaf node */ public static Node mkdirs(Session session, String path, String type) { return mkdirs(session, path, type, null, false); } /** - * Creates the nodes making path, if they don't exist. This is up to the - * caller to save the session. Use with caution since it can create - * duplicate nodes if used concurrently. Requires read access to the root - * node of the workspace. + * Creates the nodes making path, if they don't exist. This is up to the caller + * to save the session. Use with caution since it can create duplicate nodes if + * used concurrently. Requires read access to the root node of the workspace. */ public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType, Boolean versioning) { try { - if (path.equals('/')) + if (path.equals("/")) return session.getRootNode(); if (session.itemExists(path)) { Node node = session.getNode(path); // check type if (type != null && !node.isNodeType(type) && !node.getPath().equals("/")) - throw new ArgeoJcrException("Node " + node + " exists but is of type " + throw new IllegalArgumentException("Node " + node + " exists but is of type " + node.getPrimaryNodeType().getName() + " not of type " + type); // TODO: check versioning return node; @@ -510,8 +532,8 @@ public class JcrUtils { currentNode = currentNode.addNode(part); if (versioning) currentNode.addMixin(NodeType.MIX_VERSIONABLE); - if (log.isTraceEnabled()) - log.debug("Added folder " + part + " as " + current); +// if (log.isTraceEnabled()) +// log.debug("Added folder " + part + " as " + current); } else { currentNode = (Node) session.getItem(current.toString()); } @@ -519,7 +541,7 @@ public class JcrUtils { return currentNode; } catch (RepositoryException e) { discardQuietly(session); - throw new ArgeoJcrException("Cannot mkdirs " + path, e); + throw new JcrException("Cannot mkdirs " + path, e); } finally { } } @@ -580,20 +602,18 @@ public class JcrUtils { // } /** - * Safe and repository implementation independent registration of a - * namespace. + * Safe and repository implementation independent registration of a namespace. */ public static void registerNamespaceSafely(Session session, String prefix, String uri) { try { registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot find namespace registry", e); + throw new JcrException("Cannot find namespace registry", e); } } /** - * Safe and repository implementation independent registration of a - * namespace. + * Safe and repository implementation independent registration of a namespace. */ public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) { try { @@ -602,94 +622,94 @@ public class JcrUtils { if (pref.equals(prefix)) { String registeredUri = nr.getURI(pref); if (!registeredUri.equals(uri)) - throw new ArgeoJcrException("Prefix " + pref + " already registered for URI " + registeredUri - + " which is different from provided URI " + uri); + throw new IllegalArgumentException("Prefix " + pref + " already registered for URI " + + registeredUri + " which is different from provided URI " + uri); else return;// skip } nr.registerNamespace(prefix, uri); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot register namespace " + uri + " under prefix " + prefix, e); + throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e); } } - /** Recursively outputs the contents of the given node. */ - public static void debug(Node node) { - debug(node, log); - } - - /** Recursively outputs the contents of the given node. */ - public static void debug(Node node, Log log) { - try { - // First output the node path - log.debug(node.getPath()); - // Skip the virtual (and large!) jcr:system subtree - if (node.getName().equals("jcr:system")) { - return; - } - - // Then the children nodes (recursive) - NodeIterator it = node.getNodes(); - while (it.hasNext()) { - Node childNode = it.nextNode(); - debug(childNode, log); - } - - // Then output the properties - PropertyIterator properties = node.getProperties(); - // log.debug("Property are : "); - - properties: while (properties.hasNext()) { - Property property = properties.nextProperty(); - if (property.getType() == PropertyType.BINARY) - continue properties;// skip - if (property.getDefinition().isMultiple()) { - // A multi-valued property, print all values - Value[] values = property.getValues(); - for (int i = 0; i < values.length; i++) { - log.debug(property.getPath() + "=" + values[i].getString()); - } - } else { - // A single-valued property - log.debug(property.getPath() + "=" + property.getString()); - } - } - } catch (Exception e) { - log.error("Could not debug " + node, e); - } - - } - - /** Logs the effective access control policies */ - public static void logEffectiveAccessPolicies(Node node) { - try { - logEffectiveAccessPolicies(node.getSession(), node.getPath()); - } catch (RepositoryException e) { - log.error("Cannot log effective access policies of " + node, e); - } - } - - /** Logs the effective access control policies */ - public static void logEffectiveAccessPolicies(Session session, String path) { - if (!log.isDebugEnabled()) - return; - - try { - AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path); - if (effectivePolicies.length > 0) { - for (AccessControlPolicy policy : effectivePolicies) { - if (policy instanceof AccessControlList) { - AccessControlList acl = (AccessControlList) policy; - log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl)); - } - } - } else { - log.debug("No effective access control policy for " + path); - } - } catch (RepositoryException e) { - log.error("Cannot log effective access policies of " + path, e); - } - } +// /** Recursively outputs the contents of the given node. */ +// public static void debug(Node node) { +// debug(node, log); +// } +// +// /** Recursively outputs the contents of the given node. */ +// public static void debug(Node node, Log log) { +// try { +// // First output the node path +// log.debug(node.getPath()); +// // Skip the virtual (and large!) jcr:system subtree +// if (node.getName().equals("jcr:system")) { +// return; +// } +// +// // Then the children nodes (recursive) +// NodeIterator it = node.getNodes(); +// while (it.hasNext()) { +// Node childNode = it.nextNode(); +// debug(childNode, log); +// } +// +// // Then output the properties +// PropertyIterator properties = node.getProperties(); +// // log.debug("Property are : "); +// +// properties: while (properties.hasNext()) { +// Property property = properties.nextProperty(); +// if (property.getType() == PropertyType.BINARY) +// continue properties;// skip +// if (property.getDefinition().isMultiple()) { +// // A multi-valued property, print all values +// Value[] values = property.getValues(); +// for (int i = 0; i < values.length; i++) { +// log.debug(property.getPath() + "=" + values[i].getString()); +// } +// } else { +// // A single-valued property +// log.debug(property.getPath() + "=" + property.getString()); +// } +// } +// } catch (Exception e) { +// log.error("Could not debug " + node, e); +// } +// +// } + +// /** Logs the effective access control policies */ +// public static void logEffectiveAccessPolicies(Node node) { +// try { +// logEffectiveAccessPolicies(node.getSession(), node.getPath()); +// } catch (RepositoryException e) { +// log.error("Cannot log effective access policies of " + node, e); +// } +// } +// +// /** Logs the effective access control policies */ +// public static void logEffectiveAccessPolicies(Session session, String path) { +// if (!log.isDebugEnabled()) +// return; +// +// try { +// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path); +// if (effectivePolicies.length > 0) { +// for (AccessControlPolicy policy : effectivePolicies) { +// if (policy instanceof AccessControlList) { +// AccessControlList acl = (AccessControlList) policy; +// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl)); +// } +// } +// } else { +// log.debug("No effective access control policy for " + path); +// } +// } catch (RepositoryException e) { +// log.error("Cannot log effective access policies of " + path, e); +// } +// } /** Returns a human-readable summary of this access control list. */ public static String accessControlListSummary(AccessControlList acl) { @@ -702,7 +722,34 @@ public class JcrUtils { } return buf.toString(); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot write summary of " + acl, e); + throw new JcrException("Cannot write summary of " + acl, e); + } + } + + /** Copy the whole workspace via a system view XML. */ + public static void copyWorkspaceXml(Session fromSession, Session toSession) { + Workspace fromWorkspace = fromSession.getWorkspace(); + Workspace toWorkspace = toSession.getWorkspace(); + String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML."; + + try (PipedInputStream in = new PipedInputStream(1024 * 1024);) { + new Thread(() -> { + try (PipedOutputStream out = new PipedOutputStream(in)) { + fromSession.exportSystemView("/", out, false, false); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(errorMsg, e); + } catch (RepositoryException e) { + throw new JcrException(errorMsg, e); + } + }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start(); + + toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); + toSession.save(); + } catch (IOException e) { + throw new RuntimeException(errorMsg, e); + } catch (RepositoryException e) { + throw new JcrException(errorMsg, e); } } @@ -710,15 +757,25 @@ public class JcrUtils { * Copies recursively the content of a node to another one. Do NOT copy the * property values of {@link NodeType#MIX_CREATED} and * {@link NodeType#MIX_LAST_MODIFIED}, but update the - * {@link Property#JCR_LAST_MODIFIED} and - * {@link Property#JCR_LAST_MODIFIED_BY} properties if the target node has - * the {@link NodeType#MIX_LAST_MODIFIED} mixin. + * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY} + * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED} + * mixin. */ public static void copy(Node fromNode, Node toNode) { try { if (toNode.getDefinition().isProtected()) return; + // add mixins + for (NodeType mixinType : fromNode.getMixinNodeTypes()) { + try { + toNode.addMixin(mixinType.getName()); + } catch (NoSuchNodeTypeException e) { + // ignore unknown mixins + // TODO log it + } + } + // process properties PropertyIterator pit = fromNode.getProperties(); properties: while (pit.hasNext()) { @@ -743,12 +800,7 @@ public class JcrUtils { // update jcr:lastModified and jcr:lastModifiedBy in toNode in case // they existed, before adding the mixins - updateLastModified(toNode); - - // add mixins - for (NodeType mixinType : fromNode.getMixinNodeTypes()) { - toNode.addMixin(mixinType.getName()); - } + updateLastModified(toNode, true); // process children nodes NodeIterator nit = fromNode.getNodes(); @@ -759,18 +811,25 @@ public class JcrUtils { Node toChild; if (toNode.hasNode(nodeRelPath)) toChild = toNode.getNode(nodeRelPath); - else - toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName()); + else { + try { + toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName()); + } catch (NoSuchNodeTypeException e) { + // ignore unknown primary types + // TODO log it + return; + } + } copy(fromChild, toChild); } } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot copy " + fromNode + " to " + toNode, e); + throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e); } } /** - * Check whether all first-level properties (except jcr:* properties) are - * equal. Skip jcr:* properties + * Check whether all first-level properties (except jcr:* properties) are equal. + * Skip jcr:* properties */ public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) { try { @@ -792,7 +851,7 @@ public class JcrUtils { } return true; } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot check all properties equals of " + reference + " and " + observed, e); + throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e); } } @@ -803,8 +862,8 @@ public class JcrUtils { } /** - * Compare the properties of two nodes. Recursivity to child nodes is not - * yet supported. Skip jcr:* properties. + * Compare the properties of two nodes. Recursivity to child nodes is not yet + * supported. Skip jcr:* properties. */ static void diffPropertiesLevel(Map diffs, String baseRelPath, Node reference, Node observed) { @@ -854,13 +913,12 @@ public class JcrUtils { } } } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot diff " + reference + " and " + observed, e); + throw new JcrException("Cannot diff " + reference + " and " + observed, e); } } /** - * Compare only a restricted list of properties of two nodes. No - * recursivity. + * Compare only a restricted list of properties of two nodes. No recursivity. * */ public static Map diffProperties(Node reference, Node observed, List properties) { @@ -897,7 +955,7 @@ public class JcrUtils { } } } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot diff " + reference + " and " + observed, e); + throw new JcrException("Cannot diff " + reference + " and " + observed, e); } return diffs; } @@ -911,8 +969,8 @@ public class JcrUtils { } /** - * Normalizes a name so that it can be stored in contexts not supporting - * names with ':' (typically databases). Replaces ':' by '_'. + * Normalizes a name so that it can be stored in contexts not supporting names + * with ':' (typically databases). Replaces ':' by '_'. */ public static String normalize(String name) { return name.replace(':', '_'); @@ -954,15 +1012,16 @@ public class JcrUtils { return name; } - /** - * Removes forbidden characters from a path, replacing them with '_' - * - * @deprecated use {@link #replaceInvalidChars(String)} instead - */ - public static String removeForbiddenCharacters(String str) { - return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*', '_'); - - } + // /** + // * Removes forbidden characters from a path, replacing them with '_' + // * + // * @deprecated use {@link #replaceInvalidChars(String)} instead + // */ + // public static String removeForbiddenCharacters(String str) { + // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*', + // '_'); + // + // } /** Cleanly disposes a {@link Binary} even if it is null. */ public static void closeQuietly(Binary binary) { @@ -973,46 +1032,55 @@ public class JcrUtils { /** Retrieve a {@link Binary} as a byte array */ public static byte[] getBinaryAsBytes(Property property) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStream in = null; - Binary binary = null; - try { - binary = property.getBinary(); - in = binary.getStream(); + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + Bin binary = new Bin(property); + InputStream in = binary.getStream()) { IOUtils.copy(in, out); return out.toByteArray(); - } catch (Exception e) { - throw new ArgeoJcrException("Cannot read binary " + property + " as bytes", e); - } finally { - IOUtils.closeQuietly(out); - IOUtils.closeQuietly(in); - closeQuietly(binary); + } catch (RepositoryException e) { + throw new JcrException("Cannot read binary " + property + " as bytes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot read binary " + property + " as bytes", e); } } /** Writes a {@link Binary} from a byte array */ public static void setBinaryAsBytes(Node node, String property, byte[] bytes) { - InputStream in = null; Binary binary = null; - try { - in = new ByteArrayInputStream(bytes); + try (InputStream in = new ByteArrayInputStream(bytes)) { binary = node.getSession().getValueFactory().createBinary(in); node.setProperty(property, binary); - } catch (Exception e) { - throw new ArgeoJcrException("Cannot read binary " + property + " as bytes", e); + } catch (RepositoryException e) { + throw new JcrException("Cannot set binary " + property + " as bytes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot set binary " + property + " as bytes", e); + } finally { + closeQuietly(binary); + } + } + + /** Writes a {@link Binary} from a byte array */ + public static void setBinaryAsBytes(Property prop, byte[] bytes) { + Binary binary = null; + try (InputStream in = new ByteArrayInputStream(bytes)) { + binary = prop.getSession().getValueFactory().createBinary(in); + prop.setValue(binary); + } catch (RepositoryException e) { + throw new JcrException("Cannot set binary " + prop + " as bytes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot set binary " + prop + " as bytes", e); } finally { - IOUtils.closeQuietly(in); closeQuietly(binary); } } /** - * Creates depth from a string (typically a username) by adding levels based - * on its first characters: "aBcD",2 becomes a/aB + * Creates depth from a string (typically a username) by adding levels based on + * its first characters: "aBcD",2 becomes a/aB */ public static String firstCharsToPath(String str, Integer nbrOfChars) { if (str.length() < nbrOfChars) - throw new ArgeoJcrException("String " + str + " length must be greater or equal than " + nbrOfChars); + throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars); StringBuffer path = new StringBuffer(""); StringBuffer curr = new StringBuffer(""); for (int i = 0; i < nbrOfChars; i++) { @@ -1025,8 +1093,8 @@ public class JcrUtils { } /** - * Discards the current changes in the session attached to this node. To be - * used typically in a catch block. + * Discards the current changes in the session attached to this node. To be used + * typically in a catch block. * * @see #discardQuietly(Session) */ @@ -1034,7 +1102,7 @@ public class JcrUtils { try { discardQuietly(node.getSession()); } catch (RepositoryException e) { - log.warn("Cannot quietly discard session of node " + node + ": " + e.getMessage()); + // silent } } @@ -1048,26 +1116,35 @@ public class JcrUtils { if (session != null) session.refresh(false); } catch (RepositoryException e) { - log.warn("Cannot quietly discard session " + session + ": " + e.getMessage()); + // silent } } /** - * Login to a workspace with implicit credentials, creates the workspace - * with these credentials if it does not already exist. + * Login to a workspace with implicit credentials, creates the workspace with + * these credentials if it does not already exist. */ public static Session loginOrCreateWorkspace(Repository repository, String workspaceName) throws RepositoryException { + return loginOrCreateWorkspace(repository, workspaceName, null); + } + + /** + * Login to a workspace with implicit credentials, creates the workspace with + * these credentials if it does not already exist. + */ + public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials) + throws RepositoryException { Session workspaceSession = null; Session defaultSession = null; try { try { - workspaceSession = repository.login(workspaceName); + workspaceSession = repository.login(credentials, workspaceName); } catch (NoSuchWorkspaceException e) { // try to create workspace - defaultSession = repository.login(); + defaultSession = repository.login(credentials); defaultSession.getWorkspace().createWorkspace(workspaceName); - workspaceSession = repository.login(workspaceName); + workspaceSession = repository.login(credentials, workspaceName); } return workspaceSession; } finally { @@ -1075,15 +1152,19 @@ public class JcrUtils { } } - /** Logs out the session, not throwing any exception, even if it is null. */ + /** + * Logs out the session, not throwing any exception, even if it is null. + * {@link Jcr#logout(Session)} should rather be used. + */ public static void logoutQuietly(Session session) { - try { - if (session != null) - if (session.isLive()) - session.logout(); - } catch (Exception e) { - // silent - } + Jcr.logout(session); +// try { +// if (session != null) +// if (session.isLive()) +// session.logout(); +// } catch (Exception e) { +// // silent +// } } /** @@ -1096,7 +1177,7 @@ public class JcrUtils { session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null, nodeType == null ? null : new String[] { nodeType }, true); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot add JCR listener " + listener + " to session " + session, e); + throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e); } } @@ -1112,16 +1193,14 @@ public class JcrUtils { } /** - * Quietly unregisters an {@link EventListener} from the udnerlying - * workspace of this node. + * Quietly unregisters an {@link EventListener} from the udnerlying workspace of + * this node. */ public static void unregisterQuietly(Node node, EventListener eventListener) { try { unregisterQuietly(node.getSession().getWorkspace(), eventListener); } catch (RepositoryException e) { // silent - if (log.isTraceEnabled()) - log.trace("Could not unregister event listener " + eventListener); } } @@ -1133,53 +1212,104 @@ public class JcrUtils { workspace.getObservationManager().removeEventListener(eventListener); } catch (RepositoryException e) { // silent - if (log.isTraceEnabled()) - log.trace("Could not unregister event listener " + eventListener); } } /** - * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it - * updates the {@link Property#JCR_LAST_MODIFIED} property with the current - * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the - * underlying session user id. In Jackrabbit 2.x, - * these properties - * are not automatically updated, hence the need for manual update. The - * session is not saved. + * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards) + * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}. + */ + public static Instant getModified(Node node) { + Calendar calendar = null; + try { + if (node.hasProperty(Property.JCR_LAST_MODIFIED)) + calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate(); + else if (node.hasProperty(Property.JCR_CREATED)) + calendar = node.getProperty(Property.JCR_CREATED).getDate(); + else + throw new IllegalArgumentException("No modification time found in " + node); + return calendar.toInstant(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get modification time for " + node, e); + } + + } + + /** + * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set. + */ + public static Instant getCreated(Node node) { + Calendar calendar = null; + try { + if (node.hasProperty(Property.JCR_CREATED)) + calendar = node.getProperty(Property.JCR_CREATED).getDate(); + else + throw new IllegalArgumentException("No created time found in " + node); + return calendar.toInstant(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get created time for " + node, e); + } + + } + + /** + * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time + * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying + * session user id. */ public static void updateLastModified(Node node) { + updateLastModified(node, false); + } + + /** + * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time + * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying + * session user id. In Jackrabbit 2.x, + * these properties are + * not automatically updated, hence the need for manual update. The session + * is not saved. + */ + public static void updateLastModified(Node node, boolean addMixin) { try { - if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) + if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) node.addMixin(NodeType.MIX_LAST_MODIFIED); node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar()); node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID()); } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot update last modified on " + node, e); + throw new JcrException("Cannot update last modified on " + node, e); } } /** * Update lastModified recursively until this parent. * - * @param node - * the node - * @param untilPath - * the base path, null is equivalent to "/" + * @param node the node + * @param untilPath the base path, null is equivalent to "/" */ public static void updateLastModifiedAndParents(Node node, String untilPath) { + updateLastModifiedAndParents(node, untilPath, true); + } + + /** + * Update lastModified recursively until this parent. + * + * @param node the node + * @param untilPath the base path, null is equivalent to "/" + */ + public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) { try { if (untilPath != null && !node.getPath().startsWith(untilPath)) - throw new ArgeoJcrException(node + " is not under " + untilPath); - updateLastModified(node); + throw new IllegalArgumentException(node + " is not under " + untilPath); + updateLastModified(node, addMixin); if (untilPath == null) { if (!node.getPath().equals("/")) - updateLastModifiedAndParents(node.getParent(), untilPath); + updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); } else { if (!node.getPath().equals(untilPath)) - updateLastModifiedAndParents(node.getParent(), untilPath); + updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); } } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot update lastModified from " + node + " until " + untilPath, e); + throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e); } } @@ -1203,15 +1333,15 @@ public class JcrUtils { if (prop.getDefinition().isMultiple()) sbuf.append("*"); } catch (RepositoryException re) { - throw new ArgeoJcrException("unexpected error while getting property definition as String", re); + throw new JcrException("unexpected error while getting property definition as String", re); } return sbuf.toString(); } /** - * Estimate the sub tree size from current node. Computation is based on the - * Jcr {@link Property#getLength()} method. Note : it is not the exact size - * used on the disk by the current part of the JCR Tree. + * Estimate the sub tree size from current node. Computation is based on the Jcr + * {@link Property#getLength()} method. Note : it is not the exact size used on + * the disk by the current part of the JCR Tree. */ public static long getNodeApproxSize(Node node) { @@ -1234,7 +1364,7 @@ public class JcrUtils { curNodeSize += getNodeApproxSize(ni.nextNode()); return curNodeSize; } catch (RepositoryException re) { - throw new ArgeoJcrException("Unexpected error while recursively determining node size.", re); + throw new JcrException("Unexpected error while recursively determining node size.", re); } } @@ -1254,9 +1384,9 @@ public class JcrUtils { } /** - * Add privileges on a path to a {@link Principal}. The path must already - * exist. Session is saved. Synchronized to prevent concurrent modifications - * of the same node. + * Add privileges on a path to a {@link Principal}. The path must already exist. + * Session is saved. Synchronized to prevent concurrent modifications of the + * same node. */ public synchronized static Boolean addPrivileges(Session session, String path, Principal principal, List privs) throws RepositoryException { @@ -1285,41 +1415,48 @@ public class JcrUtils { Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); acl.addAccessControlEntry(principal, privileges); acm.setPolicy(path, acl); - if (log.isDebugEnabled()) { - StringBuffer privBuf = new StringBuffer(); - for (Privilege priv : privs) - privBuf.append(priv.getName()); - log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" - + session.getWorkspace().getName() + "'"); - } +// if (log.isDebugEnabled()) { +// StringBuffer privBuf = new StringBuffer(); +// for (Privilege priv : privs) +// privBuf.append(priv.getName()); +// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" +// + session.getWorkspace().getName() + "'"); +// } session.refresh(true); session.save(); return true; } - /** Gets access control list for this path, throws exception if not found */ + /** + * Gets the first available access control list for this path, throws exception + * if not found + */ public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path) throws RepositoryException { // search for an access control list AccessControlList acl = null; AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path); - if (policyIterator.hasNext()) { + applicablePolicies: if (policyIterator.hasNext()) { while (policyIterator.hasNext()) { AccessControlPolicy acp = policyIterator.nextAccessControlPolicy(); - if (acp instanceof AccessControlList) + if (acp instanceof AccessControlList) { acl = ((AccessControlList) acp); + break applicablePolicies; + } } } else { AccessControlPolicy[] existingPolicies = acm.getPolicies(path); - for (AccessControlPolicy acp : existingPolicies) { - if (acp instanceof AccessControlList) + existingPolicies: for (AccessControlPolicy acp : existingPolicies) { + if (acp instanceof AccessControlList) { acl = ((AccessControlList) acp); + break existingPolicies; + } } } if (acl != null) return acl; else - throw new ArgeoJcrException("ACL not found at " + path); + throw new IllegalArgumentException("ACL not found at " + path); } /** Clear authorizations for a user at this path */ @@ -1335,6 +1472,8 @@ public class JcrUtils { // the new access control list must be applied otherwise this call: // acl.removeAccessControlEntry(ace); has no effect acm.setPolicy(path, acl); + session.refresh(true); + session.save(); } /* @@ -1351,39 +1490,44 @@ public class JcrUtils { * Copy only nt:folder and nt:file, without their additional types and * properties. * - * @param recursive - * if true copies folders as well, otherwise only first level - * files + * @param recursive if true copies folders as well, otherwise only first level + * files * @return how many files were copied */ - public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor) { + public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) { long count = 0l; - Binary binary = null; - InputStream in = null; + // Binary binary = null; + // InputStream in = null; try { NodeIterator fromChildren = fromNode.getNodes(); - while (fromChildren.hasNext()) { + children: while (fromChildren.hasNext()) { if (monitor != null && monitor.isCanceled()) - throw new ArgeoJcrException("Copy cancelled before it was completed"); + throw new IllegalStateException("Copy cancelled before it was completed"); Node fromChild = fromChildren.nextNode(); String fileName = fromChild.getName(); if (fromChild.isNodeType(NodeType.NT_FILE)) { + if (onlyAdd && toNode.hasNode(fileName)) { + monitor.subTask("Skip existing " + fileName); + continue children; + } + if (monitor != null) monitor.subTask("Copy " + fileName); - binary = fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary(); - in = binary.getStream(); - copyStreamAsFile(toNode, fileName, in); - IOUtils.closeQuietly(in); - closeQuietly(binary); + try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA)); + InputStream in = binary.getStream();) { + copyStreamAsFile(toNode, fileName, in); + } catch (IOException e) { + throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e); + } // save session toNode.getSession().save(); count++; - if (log.isDebugEnabled()) - log.debug("Copied file " + fromChild.getPath()); +// if (log.isDebugEnabled()) +// log.debug("Copied file " + fromChild.getPath()); if (monitor != null) monitor.worked(1); } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) { @@ -1391,29 +1535,29 @@ public class JcrUtils { if (toNode.hasNode(fileName)) { toChildFolder = toNode.getNode(fileName); if (!toChildFolder.isNodeType(NodeType.NT_FOLDER)) - throw new ArgeoJcrException(toChildFolder + " is not of type nt:folder"); + throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder"); } else { toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER); // save session toNode.getSession().save(); } - count = count + copyFiles(fromChild, toChildFolder, recursive, monitor); + count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd); } } return count; } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot copy files between " + fromNode + " and " + toNode); + throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e); } finally { // in case there was an exception - IOUtils.closeQuietly(in); - closeQuietly(binary); + // IOUtils.closeQuietly(in); + // closeQuietly(binary); } } /** - * Iteratively count all file nodes in subtree, inefficient but can be - * useful when query are poorly supported, such as in remoting. + * Iteratively count all file nodes in subtree, inefficient but can be useful + * when query are poorly supported, such as in remoting. */ public static Long countFiles(Node node) { Long localCount = 0l; @@ -1426,45 +1570,42 @@ public class JcrUtils { localCount = localCount + 1; } } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot count all children of " + node); + throw new JcrException("Cannot count all children of " + node, e); } return localCount; } /** - * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session - * is NOT saved. + * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is + * NOT saved. * * @return the created file node */ + @Deprecated public static Node copyFile(Node folderNode, File file) { - InputStream in = null; - try { - in = new FileInputStream(file); + try (InputStream in = new FileInputStream(file)) { return copyStreamAsFile(folderNode, file.getName(), in); } catch (IOException e) { - throw new ArgeoJcrException("Cannot copy file " + file + " under " + folderNode, e); - } finally { - IOUtils.closeQuietly(in); + throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e); } } /** Copy bytes as an nt:file */ public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) { - InputStream in = null; - try { - in = new ByteArrayInputStream(bytes); + // InputStream in = null; + try (InputStream in = new ByteArrayInputStream(bytes)) { + // in = new ByteArrayInputStream(bytes); return copyStreamAsFile(folderNode, fileName, in); - } catch (Exception e) { - throw new ArgeoJcrException("Cannot copy file " + fileName + " under " + folderNode, e); - } finally { - IOUtils.closeQuietly(in); + } catch (IOException e) { + throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e); + // } finally { + // IOUtils.closeQuietly(in); } } /** - * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session - * is NOT saved. + * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is + * NOT saved. * * @return the created file node */ @@ -1476,36 +1617,161 @@ public class JcrUtils { if (folderNode.hasNode(fileName)) { fileNode = folderNode.getNode(fileName); if (!fileNode.isNodeType(NodeType.NT_FILE)) - throw new ArgeoJcrException(fileNode + " is not of type nt:file"); + throw new IllegalArgumentException(fileNode + " is not of type nt:file"); // we assume that the content node is already there contentNode = fileNode.getNode(Node.JCR_CONTENT); } else { fileNode = folderNode.addNode(fileName, NodeType.NT_FILE); - contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); + contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); } binary = contentNode.getSession().getValueFactory().createBinary(in); contentNode.setProperty(Property.JCR_DATA, binary); + updateLastModified(contentNode); return fileNode; - } catch (Exception e) { - throw new ArgeoJcrException("Cannot create file node " + fileName + " under " + folderNode, e); + } catch (RepositoryException e) { + throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e); } finally { closeQuietly(binary); } } - /** Computes the checksum of an nt:file */ - public static String checksumFile(Node fileNode, String algorithm) { - Binary data = null; - InputStream in = null; + /** Read an an nt:file as an {@link InputStream}. */ + public static InputStream getFileAsStream(Node fileNode) throws RepositoryException { + return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream(); + } + + /** + * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this + * file node. + */ + public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException { + Node contentNode = fileNode.getNode(Node.JCR_CONTENT); + if (mimeType != null) + contentNode.setProperty(Property.JCR_MIMETYPE, mimeType); + if (encoding != null) + contentNode.setProperty(Property.JCR_ENCODING, encoding); + // TODO remove properties if args are null? + } + + public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) { try { - data = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary(); - in = data.getStream(); - return DigestUtils.digest(algorithm, in); + Files.createDirectories(targetDir); + for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) { + Node node = nit.nextNode(); + if (node.isNodeType(NodeType.NT_FILE)) { + Path filePath = targetDir.resolve(node.getName()); + try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) { + IOUtils.copy(in, out); + } + } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) { + Path dirPath = targetDir.resolve(node.getName()); + copyFilesToFs(node, dirPath, true); + } + } } catch (RepositoryException e) { - throw new ArgeoJcrException("Cannot checksum file " + fileNode, e); - } finally { - IOUtils.closeQuietly(in); - closeQuietly(data); + throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e); + } catch (IOException e) { + throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e); + } + } + + /** + * Computes the checksum of an nt:file. + * + * @deprecated use separate digest utilities + */ + @Deprecated + public static String checksumFile(Node fileNode, String algorithm) { + try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary() + .getStream()) { + return digest(algorithm, in); + } catch (IOException e) { + throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e); + } catch (RepositoryException e) { + throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e); + } + } + + @Deprecated + private static String digest(String algorithm, InputStream in) { + final Integer byteBufferCapacity = 100 * 1024;// 100 KB + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + byte[] buffer = new byte[byteBufferCapacity]; + int read = 0; + while ((read = in.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + + byte[] checksum = digest.digest(); + String res = encodeHexString(checksum); + return res; + } catch (IOException e) { + throw new RuntimeException("Cannot digest with algorithm " + algorithm, e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); + } + } + + /** + * From + * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to + * -a-hex-string-in-java + */ + @Deprecated + private static String encodeHexString(byte[] bytes) { + final char[] hexArray = "0123456789abcdef".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + /** Export a subtree as a compact XML without namespaces. */ + public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException { + sb.append('<'); + String nodeName = node.getName(); + int colIndex = nodeName.indexOf(':'); + if (colIndex > 0) { + nodeName = nodeName.substring(colIndex + 1); + } + sb.append(nodeName); + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property p = pit.nextProperty(); + // skip multiple properties + if (p.isMultiple()) + continue properties; + String propertyName = p.getName(); + int pcolIndex = propertyName.indexOf(':'); + // skip properties with namespaces + if (pcolIndex > 0) + continue properties; + // skip binaries + if (p.getType() == PropertyType.BINARY) { + continue properties; + // TODO retrieve identifier? + } + sb.append(' '); + sb.append(propertyName); + sb.append('='); + sb.append('\"').append(p.getString()).append('\"'); + } + + if (node.hasNodes()) { + sb.append('>'); + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + toSimpleXml(children.nextNode(), sb); + } + sb.append("'); + } else { + sb.append("/>"); } }