/*
- * 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.
/** 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
}
}
+ /*
+ * 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);
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)
*
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
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.
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
}
}
- /**
- * 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
- */
- 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);
- JcrUtils.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);
- JcrUtils.closeQuietly(binary);
- }
- }
-
/**
* Check whether all first-level properties (except jcr:* properties) are
* equal. Skip jcr:* properties
}
}
- /**
- * 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);
- }
- }
-
/**
* Creates depth from a string (typically a username) by adding levels based
* on its first characters: "aBcD",2 => a/aB
}
}
- /** 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);
* 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(
/**
* 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;
}
/** Clear authorizations for a user at this path */
- public static void clearAccessControList(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()) {
}
}
}
+
+ /*
+ * 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);
+ }
+ }
+
}