]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java
Add dep folder
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jcr / src / main / java / org / argeo / jcr / JcrUtils.java
index 0d889626ec065c2f9d7848a2ac3f7032187a06ba..2176e757cacac3141eb2e7c90a39302e085e0a8b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2012 Mathieu Baudier
+ * 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.
@@ -64,13 +64,14 @@ import org.apache.commons.io.IOUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.ArgeoException;
+import org.argeo.ArgeoMonitor;
 import org.argeo.util.security.DigestUtils;
 import org.argeo.util.security.SimplePrincipal;
 
 /** Utility methods to simplify common JCR operations. */
 public class JcrUtils implements ArgeoJcrConstants {
 
-       private final static Log log = LogFactory.getLog(JcrUtils.class);
+       final private static Log log = LogFactory.getLog(JcrUtils.class);
 
        /**
         * Not complete yet. See
@@ -113,6 +114,20 @@ public class JcrUtils implements ArgeoJcrConstants {
                return node;
        }
 
+       /** Retrieves the node name from the provided path */
+       public static String nodeNameFromPath(String path) {
+               if (path.equals("/"))
+                       return "";
+               if (path.charAt(0) != '/')
+                       throw new ArgeoException("Path " + path + " must start with a '/'");
+               String pathT = path;
+               if (pathT.charAt(pathT.length() - 1) == '/')
+                       pathT = pathT.substring(0, pathT.length() - 2);
+
+               int index = pathT.lastIndexOf('/');
+               return pathT.substring(index + 1);
+       }
+
        /** Retrieves the parent path of the provided path */
        public static String parentPath(String path) {
                if (path.equals("/"))
@@ -180,6 +195,10 @@ public class JcrUtils implements ArgeoJcrConstants {
                }
        }
 
+       /*
+        * PATH UTILITIES
+        */
+
        /** Make sure that: starts with '/', do not end with '/', do not have '//' */
        public static String normalizePath(String path) {
                List<String> tokens = tokenize(path);
@@ -206,6 +225,21 @@ public class JcrUtils implements ArgeoJcrConstants {
                return path.toString();
        }
 
+       /**
+        * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c =>
+        * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
+        */
+       public static String uuidAsPath(String uuid) {
+               StringBuffer path = new StringBuffer(uuid.length());
+               String[] tokens = uuid.split("-");
+               for (int i = 0; i < tokens.length; i++) {
+                       path.append(tokens[i]);
+                       if (i != 0)
+                               path.append('/');
+               }
+               return path.toString();
+       }
+
        /**
         * The provided data as a path ('/' at the end, not the beginning)
         * 
@@ -266,11 +300,34 @@ public class JcrUtils implements ArgeoJcrConstants {
                        throw new ArgeoException("Path " + path + " cannot end with '/'");
                int index = path.lastIndexOf('/');
                if (index < 0)
-                       throw new ArgeoException("Cannot find last path element for "
-                                       + path);
+                       return path;
                return path.substring(index + 1);
        }
 
+       /**
+        * Call {@link Node#getName()} without exceptions (useful in super
+        * constructors).
+        */
+       public static String getNameQuietly(Node node) {
+               try {
+                       return node.getName();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get name from " + node, e);
+               }
+       }
+
+       /**
+        * Call {@link Node#getProperty(String)} without exceptions (useful in super
+        * constructors).
+        */
+       public static String getStringPropertyQuietly(Node node, String propertyName) {
+               try {
+                       return node.getProperty(propertyName).getString();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get name from " + node, e);
+               }
+       }
+
        /**
         * Routine that get the child with this name, adding id it does not already
         * exist
@@ -363,6 +420,48 @@ public class JcrUtils implements ArgeoJcrConstants {
                return mkdirs(session, path, type, null, false);
        }
 
+       /**
+        * Create sub nodes relative to a parent 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);
+       }
+
+       /**
+        * Create sub nodes relative to a parent node
+        * 
+        * @param nodeType
+        *            the type of the leaf node
+        */
+       public static Node mkdirs(Node parentNode, String relativePath,
+                       String nodeType, String intermediaryNodeType) {
+               List<String> tokens = tokenize(relativePath);
+               Node currParent = parentNode;
+               try {
+                       for (int i = 0; i < tokens.size(); i++) {
+                               String name = tokens.get(i);
+                               if (currParent.hasNode(name)) {
+                                       currParent = currParent.getNode(name);
+                               } else {
+                                       if (i != (tokens.size() - 1)) {// intermediary
+                                               currParent = currParent.addNode(name,
+                                                               intermediaryNodeType);
+                                       } else {// leaf
+                                               currParent = currParent.addNode(name, nodeType);
+                                       }
+                               }
+                       }
+                       return currParent;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot mkdirs relative path "
+                                       + relativePath + " from " + parentNode, e);
+               }
+       }
+
        /**
         * Synchronized and save is performed, to avoid race conditions in
         * initializers leading to duplicate nodes.
@@ -386,14 +485,6 @@ public class JcrUtils implements ArgeoJcrConstants {
                return mkdirsSafe(session, path, null);
        }
 
-       /**
-        * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
-        */
-       public static Node mkfolders(Session session, String path) {
-               return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER,
-                               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
@@ -629,6 +720,9 @@ public class JcrUtils implements ArgeoJcrConstants {
         */
        public static void copy(Node fromNode, Node toNode) {
                try {
+                       if (toNode.getDefinition().isProtected())
+                               return;
+
                        // process properties
                        PropertyIterator pit = fromNode.getProperties();
                        properties: while (pit.hasNext()) {
@@ -940,90 +1034,6 @@ public class JcrUtils implements ArgeoJcrConstants {
                }
        }
 
-       /**
-        * 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);
-                       return copyStreamAsFile(folderNode, file.getName(), in);
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot copy file " + file + " under "
-                                       + folderNode, e);
-               } 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);
-                       return copyStreamAsFile(folderNode, fileName, in);
-               } catch (Exception e) {
-                       throw new ArgeoException("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.
-        * 
-        * @return the created file node
-        */
-       public static Node copyStreamAsFile(Node folderNode, String fileName,
-                       InputStream in) {
-               Binary binary = null;
-               try {
-                       Node fileNode;
-                       Node contentNode;
-                       if (folderNode.hasNode(fileName)) {
-                               fileNode = folderNode.getNode(fileName);
-                               // 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);
-                       }
-                       binary = contentNode.getSession().getValueFactory()
-                                       .createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-                       return fileNode;
-               } catch (Exception e) {
-                       throw new ArgeoException("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;
-               try {
-                       data = fileNode.getNode(Node.JCR_CONTENT)
-                                       .getProperty(Property.JCR_DATA).getBinary();
-                       in = data.getStream();
-                       return DigestUtils.digest(algorithm, in);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot checksum file " + fileNode, e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-                       closeQuietly(data);
-               }
-       }
-
        /**
         * Creates depth from a string (typically a username) by adding levels based
         * on its first characters: "aBcD",2 => a/aB
@@ -1116,8 +1126,14 @@ public class JcrUtils implements ArgeoJcrConstants {
                try {
                        session.getWorkspace()
                                        .getObservationManager()
-                                       .addEventListener(listener, eventTypes, basePath, true,
-                                                       null, new String[] { nodeType }, true);
+                                       .addEventListener(
+                                                       listener,
+                                                       eventTypes,
+                                                       basePath,
+                                                       true,
+                                                       null,
+                                                       nodeType == null ? null : new String[] { nodeType },
+                                                       true);
                } catch (RepositoryException e) {
                        throw new ArgeoException("Cannot add JCR listener " + listener
                                        + " to session " + session, e);
@@ -1191,14 +1207,26 @@ public class JcrUtils implements ArgeoJcrConstants {
                }
        }
 
-       /** Update lastModified recursively until this parent. */
+       /**
+        * 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) {
                try {
-                       if (!node.getPath().startsWith(untilPath))
+                       if (untilPath != null && !node.getPath().startsWith(untilPath))
                                throw new ArgeoException(node + " is not under " + untilPath);
                        updateLastModified(node);
-                       if (!node.getPath().equals(untilPath))
-                               updateLastModifiedAndParents(node.getParent(), untilPath);
+                       if (untilPath == null) {
+                               if (!node.getPath().equals("/"))
+                                       updateLastModifiedAndParents(node.getParent(), untilPath);
+                       } else {
+                               if (!node.getPath().equals(untilPath))
+                                       updateLastModifiedAndParents(node.getParent(), untilPath);
+                       }
                } catch (RepositoryException e) {
                        throw new ArgeoException("Cannot update lastModified from " + node
                                        + " until " + untilPath, e);
@@ -1273,7 +1301,7 @@ public class JcrUtils implements ArgeoJcrConstants {
         * Convenience method for adding a single privilege to a principal (user or
         * role), typically jcr:all
         */
-       public static void addPrivilege(Session session, String path,
+       public synchronized static void addPrivilege(Session session, String path,
                        String principal, String privilege) throws RepositoryException {
                List<Privilege> privileges = new ArrayList<Privilege>();
                privileges.add(session.getAccessControlManager().privilegeFromName(
@@ -1283,28 +1311,53 @@ public class JcrUtils implements ArgeoJcrConstants {
 
        /**
         * Add privileges on a path to a {@link Principal}. The path must already
-        * exist. Session is saved.
+        * exist. Session is saved. Synchronized to prevent concurrent modifications
+        * of the same node.
         */
-       public static void addPrivileges(Session session, String path,
-                       Principal principal, List<Privilege> privs)
+       public synchronized static Boolean addPrivileges(Session session,
+                       String path, Principal principal, List<Privilege> privs)
                        throws RepositoryException {
+               // make sure the session is in line with the persisted state
+               session.refresh(false);
                AccessControlManager acm = session.getAccessControlManager();
                AccessControlList acl = getAccessControlList(acm, path);
-               acl.addAccessControlEntry(principal,
-                               privs.toArray(new Privilege[privs.size()]));
+
+               accessControlEntries: for (AccessControlEntry ace : acl
+                               .getAccessControlEntries()) {
+                       Principal currentPrincipal = ace.getPrincipal();
+                       if (currentPrincipal.getName().equals(principal.getName())) {
+                               Privilege[] currentPrivileges = ace.getPrivileges();
+                               if (currentPrivileges.length != privs.size())
+                                       break accessControlEntries;
+                               for (int i = 0; i < currentPrivileges.length; i++) {
+                                       Privilege currP = currentPrivileges[i];
+                                       Privilege p = privs.get(i);
+                                       if (!currP.getName().equals(p.getName())) {
+                                               break accessControlEntries;
+                                       }
+                               }
+                               return false;
+                       }
+               }
+
+               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
-                                       + " on " + path);
+                       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 */
-       public static AccessControlList getAccessControlList(
+       public synchronized static AccessControlList getAccessControlList(
                        AccessControlManager acm, String path) throws RepositoryException {
                // search for an access control list
                AccessControlList acl = null;
@@ -1331,8 +1384,8 @@ public class JcrUtils implements ArgeoJcrConstants {
        }
 
        /** Clear authorizations for a user at this path */
-       public static void clearAccesControList(Session session, String path,
-                       String username) throws RepositoryException {
+       public synchronized static void clearAccessControList(Session session,
+                       String path, String username) throws RepositoryException {
                AccessControlManager acm = session.getAccessControlManager();
                AccessControlList acl = getAccessControlList(acm, path);
                for (AccessControlEntry ace : acl.getAccessControlEntries()) {
@@ -1341,4 +1394,197 @@ public class JcrUtils implements ArgeoJcrConstants {
                        }
                }
        }
+
+       /*
+        * FILES UTILITIES
+        */
+       /**
+        * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
+        */
+       public static Node mkfolders(Session session, String path) {
+               return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER,
+                               false);
+       }
+
+       /**
+        * 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
+        * @return how many files were copied
+        */
+       @SuppressWarnings("resource")
+       public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive,
+                       ArgeoMonitor monitor) {
+               long count = 0l;
+
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       NodeIterator fromChildren = fromNode.getNodes();
+                       while (fromChildren.hasNext()) {
+                               if (monitor != null && monitor.isCanceled())
+                                       throw new ArgeoException(
+                                                       "Copy cancelled before it was completed");
+
+                               Node fromChild = fromChildren.nextNode();
+                               String fileName = fromChild.getName();
+                               if (fromChild.isNodeType(NodeType.NT_FILE)) {
+                                       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);
+
+                                       // save session
+                                       toNode.getSession().save();
+                                       count++;
+
+                                       if (log.isDebugEnabled())
+                                               log.debug("Copied file " + fromChild.getPath());
+                                       if (monitor != null)
+                                               monitor.worked(1);
+                               } else if (fromChild.isNodeType(NodeType.NT_FOLDER)
+                                               && recursive) {
+                                       Node toChildFolder;
+                                       if (toNode.hasNode(fileName)) {
+                                               toChildFolder = toNode.getNode(fileName);
+                                               if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
+                                                       throw new ArgeoException(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);
+                               }
+                       }
+                       return count;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot copy files between " + fromNode
+                                       + " and " + toNode);
+               } finally {
+                       // in case there was an exception
+                       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.
+        */
+       public static Long countFiles(Node node) {
+               Long localCount = 0l;
+               try {
+                       for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
+                               Node child = nit.nextNode();
+                               if (child.isNodeType(NodeType.NT_FOLDER))
+                                       localCount = localCount + countFiles(child);
+                               else if (child.isNodeType(NodeType.NT_FILE))
+                                       localCount = localCount + 1;
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot count all children of " + node);
+               }
+               return localCount;
+       }
+
+       /**
+        * 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);
+                       return copyStreamAsFile(folderNode, file.getName(), in);
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot copy file " + file + " under "
+                                       + folderNode, e);
+               } 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);
+                       return copyStreamAsFile(folderNode, fileName, in);
+               } catch (Exception e) {
+                       throw new ArgeoException("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.
+        * 
+        * @return the created file node
+        */
+       public static Node copyStreamAsFile(Node folderNode, String fileName,
+                       InputStream in) {
+               Binary binary = null;
+               try {
+                       Node fileNode;
+                       Node contentNode;
+                       if (folderNode.hasNode(fileName)) {
+                               fileNode = folderNode.getNode(fileName);
+                               if (!fileNode.isNodeType(NodeType.NT_FILE))
+                                       throw new ArgeoException(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);
+                       }
+                       binary = contentNode.getSession().getValueFactory()
+                                       .createBinary(in);
+                       contentNode.setProperty(Property.JCR_DATA, binary);
+                       return fileNode;
+               } catch (Exception e) {
+                       throw new ArgeoException("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;
+               try {
+                       data = fileNode.getNode(Node.JCR_CONTENT)
+                                       .getProperty(Property.JCR_DATA).getBinary();
+                       in = data.getStream();
+                       return DigestUtils.digest(algorithm, in);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot checksum file " + fileNode, e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(data);
+               }
+       }
+
 }