X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.jcr%2Fsrc%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=331164cf2b442dec485bb7a71ebb0d7a0c50dd65;hb=25e4528153640a2e211e217468f8f5aa01607cf0;hp=97a65a819fe5b6394610d641c008e6acf0adc768;hpb=26759d712f0571f3eb67959fb744f18f24d6bf17;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 97a65a819..331164cf2 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.MessageDigest; import java.security.Principal; import java.text.DateFormat; import java.text.ParseException; @@ -63,7 +64,6 @@ 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 { @@ -75,11 +75,8 @@ public class JcrUtils { * 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 +86,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 ArgeoJcrException if more than one node was found */ public static Node querySingleNode(Query query) { NodeIterator nodeIterator; @@ -146,7 +142,7 @@ public class JcrUtils { /** * Creates a deep path based on a URL: - * http://subdomain.example.com/to/content?args => + * http://subdomain.example.com/to/content?args becomes * com/example/subdomain/to/content */ public static String urlAsPath(String url) { @@ -208,7 +204,7 @@ public class JcrUtils { /** * Creates a path from a FQDN, inverting the order of the component: - * www.argeo.org => org.argeo.www + * www.argeo.org becomes org.argeo.www */ public static String hostAsPath(String host) { StringBuffer path = new StringBuffer(host.length()); @@ -222,7 +218,7 @@ public class JcrUtils { } /** - * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c => + * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning */ public static String uuidAsPath(String uuid) { @@ -239,10 +235,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); @@ -366,6 +360,15 @@ public class JcrUtils { } } + /** Concisely get the path of the given node. */ + public static String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new ArgeoJcrException("Cannot get path of " + node, e); + } + } + /** Concisely get the boolean value of a property */ public static Boolean check(Node node, String propertyName) { try { @@ -398,8 +401,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 +410,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); @@ -434,8 +435,8 @@ public class JcrUtils { } /** - * 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 { @@ -460,23 +461,21 @@ 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)) { @@ -580,8 +579,7 @@ 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 { @@ -592,8 +590,7 @@ 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(NamespaceRegistry nr, String prefix, String uri) { try { @@ -710,9 +707,9 @@ 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 { @@ -769,8 +766,8 @@ public class JcrUtils { } /** - * 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 { @@ -803,8 +800,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) { @@ -859,8 +856,7 @@ public class JcrUtils { } /** - * 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) { @@ -911,8 +907,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 +950,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,42 +970,44 @@ 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(); + // ByteArrayOutputStream out = new ByteArrayOutputStream(); + // InputStream in = null; + // Binary binary = null; + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + Bin binary = new Bin(property); + InputStream in = binary.getStream()) { + // binary = property.getBinary(); + // 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); + // IOUtils.closeQuietly(out); + // IOUtils.closeQuietly(in); + // closeQuietly(binary); } } /** Writes a {@link Binary} from a byte array */ public static void setBinaryAsBytes(Node node, String property, byte[] bytes) { - InputStream in = null; + // InputStream in = null; Binary binary = null; - try { - in = new ByteArrayInputStream(bytes); + try (InputStream in = new ByteArrayInputStream(bytes)) { + // 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); } finally { - IOUtils.closeQuietly(in); + // IOUtils.closeQuietly(in); closeQuietly(binary); } } /** - * Creates depth from a string (typically a username) by adding levels based - * on its first characters: "aBcD",2 => 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) @@ -1025,8 +1024,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) */ @@ -1053,8 +1052,8 @@ public class JcrUtils { } /** - * 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 { @@ -1075,15 +1074,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 +// } } /** @@ -1112,8 +1115,8 @@ 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 { @@ -1139,13 +1142,13 @@ public class JcrUtils { } /** - * 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. + * 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. */ public static void updateLastModified(Node node) { try { @@ -1161,10 +1164,8 @@ public class JcrUtils { /** * 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) { try { @@ -1209,9 +1210,9 @@ public class JcrUtils { } /** - * 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) { @@ -1254,9 +1255,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 { @@ -1351,32 +1352,37 @@ 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"); 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); + } + // IOUtils.closeQuietly(in); + // closeQuietly(binary); // save session toNode.getSession().save(); @@ -1398,22 +1404,22 @@ public class JcrUtils { // 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) { + } catch (RepositoryException | IOException e) { throw new ArgeoJcrException("Cannot copy files between " + fromNode + " and " + toNode); } 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; @@ -1432,39 +1438,39 @@ public class JcrUtils { } /** - * 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 */ public static Node copyFile(Node folderNode, File file) { - InputStream in = null; - try { - in = new FileInputStream(file); + // InputStream in = null; + try (InputStream in = new FileInputStream(file)) { + // 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); + // } finally { + // IOUtils.closeQuietly(in); } } /** 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); + // } 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 */ @@ -1493,20 +1499,58 @@ public class JcrUtils { } } - /** Computes the checksum of an nt:file */ + /** + * Computes the checksum of an nt:file. + * + * @deprecated use separate digest utilities + */ + @Deprecated public static String checksumFile(Node fileNode, String algorithm) { Binary data = null; - InputStream in = null; - try { - data = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary(); - in = data.getStream(); - return DigestUtils.digest(algorithm, in); - } catch (RepositoryException e) { + try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary() + .getStream()) { + return digest(algorithm, in); + } catch (RepositoryException | IOException e) { throw new ArgeoJcrException("Cannot checksum file " + fileNode, e); } finally { - IOUtils.closeQuietly(in); closeQuietly(data); } } + @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 (Exception e) { + throw new ArgeoJcrException("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); + } + }