+
+ /**
+ * 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, <a
+ * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
+ * are not automatically updated</a>, hence the need for manual update. The
+ * session is not saved.
+ */
+ public static void updateLastModified(Node node) {
+ try {
+ if (!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 ArgeoException("Cannot update last modified on " + node,
+ e);
+ }
+ }
+
+ /** Update lastModified recursively until this parent. */
+ public static void updateLastModifiedAndParents(Node node, String untilPath) {
+ try {
+ if (!node.getPath().startsWith(untilPath))
+ throw new ArgeoException(node + " is not under " + untilPath);
+ updateLastModified(node);
+ if (!node.getPath().equals(untilPath))
+ updateLastModifiedAndParents(node.getParent(), untilPath);
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot update lastModified from " + node
+ + " until " + untilPath, e);
+ }
+ }
+
+ /**
+ * Returns a String representing the short version (see <a
+ * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+ * Notation </a> attributes grammar) of the main business attributes of this
+ * property definition
+ *
+ * @param prop
+ */
+ public static String getPropertyDefinitionAsString(Property prop) {
+ StringBuffer sbuf = new StringBuffer();
+ try {
+ if (prop.getDefinition().isAutoCreated())
+ sbuf.append("a");
+ if (prop.getDefinition().isMandatory())
+ sbuf.append("m");
+ if (prop.getDefinition().isProtected())
+ sbuf.append("p");
+ if (prop.getDefinition().isMultiple())
+ sbuf.append("*");
+ } catch (RepositoryException re) {
+ throw new ArgeoException(
+ "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.
+ */
+
+ public static long getNodeApproxSize(Node node) {
+ long curNodeSize = 0;
+ try {
+ PropertyIterator pi = node.getProperties();
+ while (pi.hasNext()) {
+ Property prop = pi.nextProperty();
+ if (prop.isMultiple()) {
+ int nb = prop.getLengths().length;
+ for (int i = 0; i < nb; i++) {
+ curNodeSize += (prop.getLengths()[i] > 0 ? prop
+ .getLengths()[i] : 0);
+ }
+ } else
+ curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
+ }
+
+ NodeIterator ni = node.getNodes();
+ while (ni.hasNext())
+ curNodeSize += getNodeApproxSize(ni.nextNode());
+ return curNodeSize;
+ } catch (RepositoryException re) {
+ throw new ArgeoException(
+ "Unexpected error while recursively determining node size.",
+ re);
+ }
+ }
+
+ /*
+ * SECURITY
+ */
+
+ /**
+ * Convenience method for adding a single privilege to a principal (user or
+ * role), typically jcr:all
+ */
+ 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(
+ privilege));
+ addPrivileges(session, path, new SimplePrincipal(principal), privileges);
+ }
+
+ /**
+ * 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<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);
+
+ 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.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 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()) {
+ while (policyIterator.hasNext()) {
+ AccessControlPolicy acp = policyIterator
+ .nextAccessControlPolicy();
+ if (acp instanceof AccessControlList)
+ acl = ((AccessControlList) acp);
+ }
+ } else {
+ AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+ for (AccessControlPolicy acp : existingPolicies) {
+ if (acp instanceof AccessControlList)
+ acl = ((AccessControlList) acp);
+ }
+ }
+ if (acl != null)
+ return acl;
+ else
+ throw new ArgeoException("ACL not found at " + path);
+ }
+
+ /** Clear authorizations for a user at this path */
+ 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()) {
+ if (ace.getPrincipal().getName().equals(username)) {
+ acl.removeAccessControlEntry(ace);
+ }
+ }
+ }
+
+ /*
+ * 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
+ */
+ 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);
+ }
+ }
+