X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=server%2Fruntime%2Forg.argeo.server.jcr%2Fsrc%2Fmain%2Fjava%2Forg%2Fargeo%2Fjcr%2FJcrUtils.java;h=9f3d761cafd79e8bdb51a7ae481fc9cfb32c847e;hb=e499eba6852f5cc487ade0db65e6f9cfc4c5d45a;hp=66c6a9388f1f6c1df93e79de91beb6e5a117e832;hpb=484dcb1507e4e35cc282e50522ea7eac7e99a7f9;p=lgpl%2Fargeo-commons.git diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java index 66c6a9388..9f3d761ca 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Mathieu Baudier + * Copyright (C) 2007-2012 Mathieu Baudier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.argeo.jcr; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; +import java.security.Principal; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; @@ -28,7 +31,6 @@ import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -36,6 +38,7 @@ import java.util.TreeMap; import javax.jcr.Binary; import javax.jcr.NamespaceRegistry; +import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; @@ -43,7 +46,6 @@ import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.Workspace; @@ -51,15 +53,25 @@ import javax.jcr.nodetype.NodeType; import javax.jcr.observation.EventListener; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; +import javax.jcr.security.AccessControlEntry; +import javax.jcr.security.AccessControlList; +import javax.jcr.security.AccessControlManager; +import javax.jcr.security.AccessControlPolicy; +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.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 @@ -102,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("/")) @@ -260,6 +286,30 @@ public class JcrUtils implements ArgeoJcrConstants { 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 @@ -280,6 +330,54 @@ public class JcrUtils implements ArgeoJcrConstants { .addNode(childName); } + /** Convert a {@link NodeIterator} to a list of {@link Node} */ + public static List nodeIteratorToList(NodeIterator nodeIterator) { + List nodes = new ArrayList(); + while (nodeIterator.hasNext()) { + nodes.add(nodeIterator.nextNode()); + } + return nodes; + } + + /* + * PROPERTIES + */ + + /** + * Concisely get the string value of a property or null if this node doesn't + * have this property + */ + public static String get(Node node, String propertyName) { + try { + if (!node.hasProperty(propertyName)) + return null; + return node.getProperty(propertyName).getString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + + /** Concisely get the boolean value of a property */ + public static Boolean check(Node node, String propertyName) { + try { + return node.getProperty(propertyName).getBoolean(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + + /** Concisely get the bytes array value of a property */ + public static byte[] getBytes(Node node, String propertyName) { + try { + return getBinaryAsBytes(node.getProperty(propertyName)); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + /** Creates the nodes making path, if they don't exist. */ public static Node mkdirs(Session session, String path) { return mkdirs(session, path, null, null, false); @@ -341,8 +439,8 @@ public class JcrUtils implements ArgeoJcrConstants { if (session.itemExists(path)) { Node node = session.getNode(path); // check type - if (type != null - && !type.equals(node.getPrimaryNodeType().getName())) + if (type != null && !node.isNodeType(type) + && !node.getPath().equals("/")) throw new ArgeoException("Node " + node + " exists but is of type " + node.getPrimaryNodeType().getName() @@ -503,6 +601,55 @@ public class JcrUtils implements ArgeoJcrConstants { } + /** 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) { + StringBuffer buf = new StringBuffer(""); + try { + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + buf.append('\t').append(ace.getPrincipal().getName()) + .append('\n'); + for (Privilege priv : ace.getPrivileges()) + buf.append("\t\t").append(priv.getName()).append('\n'); + } + return buf.toString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot write summary of " + acl, e); + } + } + /** * Copies recursively the content of a node to another one. Do NOT copy the * property values of {@link NodeType#MIX_CREATED} and @@ -513,6 +660,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()) { @@ -843,42 +993,6 @@ public class JcrUtils implements ArgeoJcrConstants { return path.toString(); } - /** - * Wraps the call to the repository factory based on parameter - * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it - * and protect against future API changes. - */ - public static Repository getRepositoryByAlias( - RepositoryFactory repositoryFactory, String alias) { - try { - Map parameters = new HashMap(); - parameters.put(JCR_REPOSITORY_ALIAS, alias); - return repositoryFactory.getRepository(parameters); - } catch (RepositoryException e) { - throw new ArgeoException( - "Unexpected exception when trying to retrieve repository with alias " - + alias, e); - } - } - - /** - * Wraps the call to the repository factory based on parameter - * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and - * protect against future API changes. - */ - public static Repository getRepositoryByUri( - RepositoryFactory repositoryFactory, String uri) { - try { - Map parameters = new HashMap(); - parameters.put(JCR_REPOSITORY_URI, uri); - return repositoryFactory.getRepository(parameters); - } catch (RepositoryException e) { - throw new ArgeoException( - "Unexpected exception when trying to retrieve repository with uri " - + uri, e); - } - } - /** * Discards the current changes in the session attached to this node. To be * used typically in a catch block. @@ -909,6 +1023,29 @@ public class JcrUtils implements ArgeoJcrConstants { } } + /** + * 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 { + Session workspaceSession = null; + Session defaultSession = null; + try { + try { + workspaceSession = repository.login(workspaceName); + } catch (NoSuchWorkspaceException e) { + // try to create workspace + defaultSession = repository.login(); + defaultSession.getWorkspace().createWorkspace(workspaceName); + workspaceSession = repository.login(workspaceName); + } + return workspaceSession; + } finally { + logoutQuietly(defaultSession); + } + } + /** Logs out the session, not throwing any exception, even if it is null. */ public static void logoutQuietly(Session session) { try { @@ -920,6 +1057,29 @@ public class JcrUtils implements ArgeoJcrConstants { } } + /** + * Convenient method to add a listener. uuids passed as null, deep=true, + * local=true, only one node type + */ + public static void addListener(Session session, EventListener listener, + int eventTypes, String basePath, String nodeType) { + try { + session.getWorkspace() + .getObservationManager() + .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); + } + } + /** Removes a listener without throwing exception */ public static void removeListenerQuietly(Session session, EventListener listener) { @@ -933,215 +1093,6 @@ public class JcrUtils implements ArgeoJcrConstants { } } - /** Returns the home node of the session user or null if none was found. */ - public static Node getUserHome(Session session) { - String userID = session.getUserID(); - return getUserHome(session, userID); - } - - /** User home path is NOT configurable */ - public static String getUserHomePath(String username) { - String homeBasePath = "/home"; - return homeBasePath + '/' + firstCharsToPath(username, 2) + '/' - + username; - } - - /** - * Returns the home node of the session user or null if none was found. - * - * @param session - * the session to use in order to perform the search, this can be - * a session with a different user ID than the one searched, - * typically when a system or admin session is used. - * @param username - * the username of the user - */ - public static Node getUserHome(Session session, String username) { - try { - String homePath = getUserHomePath(username); - return session.itemExists(homePath) ? session.getNode(homePath) - : null; - // kept for example of QOM queries - // QueryObjectModelFactory qomf = session.getWorkspace() - // .getQueryManager().getQOMFactory(); - // Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, - // "userHome"); - // DynamicOperand userIdDop = qomf.propertyValue("userHome", - // ArgeoNames.ARGEO_USER_ID); - // StaticOperand userIdSop = qomf.literal(session.getValueFactory() - // .createValue(username)); - // Constraint constraint = qomf.comparison(userIdDop, - // QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - // Query query = qomf.createQuery(userHomeSel, constraint, null, - // null); - // Node userHome = JcrUtils.querySingleNode(query); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot find home for user " + username, e); - } - } - - /** - * Creates an Argeo user home, does nothing if it already exists. Session is - * NOT saved. - */ - public static Node createUserHomeIfNeeded(Session session, String username) { - try { - String homePath = getUserHomePath(username); - if (session.itemExists(homePath)) - return session.getNode(homePath); - else { - Node userHome = JcrUtils.mkdirs(session, homePath); - userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); - userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - return userHome; - } - } catch (RepositoryException e) { - discardQuietly(session); - throw new ArgeoException("Cannot create home for " + username - + " in workspace " + session.getWorkspace().getName(), e); - } - } - - /** - * Creates a user profile in the home of this user. Creates the home if - * needed, but throw an exception if a profile already exists. The session - * is not saved and the node is in a checkedOut state (that is, it requires - * a subsequent checkin after saving the session). - */ - public static Node createUserProfile(Session session, String username) { - try { - Node userHome = createUserHomeIfNeeded(session, username); - if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) - throw new ArgeoException( - "There is already a user profile under " + userHome); - Node userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); - userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - userProfile.setProperty(ArgeoNames.ARGEO_ENABLED, true); - userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_EXPIRED, true); - userProfile.setProperty(ArgeoNames.ARGEO_ACCOUNT_NON_LOCKED, true); - userProfile.setProperty(ArgeoNames.ARGEO_CREDENTIALS_NON_EXPIRED, - true); - return userProfile; - } catch (RepositoryException e) { - discardQuietly(session); - throw new ArgeoException("Cannot create home for " + username - + " in workspace " + session.getWorkspace().getName(), e); - } - } - - /** Creates an Argeo user home. */ - // public static Node createUserHome(Session session, String homeBasePath, - // String username) { - // try { - // if (session == null) - // throw new ArgeoException("Session is null"); - // if (session.hasPendingChanges()) - // throw new ArgeoException( - // "Session has pending changes, save them first"); - // - // String homePath = getUserHomePath(username); - // - // if (session.itemExists(homePath)) { - // try { - // throw new ArgeoException( - // "Trying to create a user home that already exists"); - // } catch (Exception e) { - // // we use this workaround to be sure to get the stack trace - // // to identify the sink of the bug. - // log.warn("trying to create an already existing userHome at path:" - // + homePath + ". Stack trace : "); - // e.printStackTrace(); - // } - // } - // - // Node userHome = JcrUtils.mkdirs(session, homePath); - // Node userProfile; - // if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) { - // log.warn("userProfile node already exists for userHome path: " - // + homePath + ". We do not add a new one"); - // } else { - // userProfile = userHome.addNode(ArgeoNames.ARGEO_PROFILE); - // userProfile.addMixin(ArgeoTypes.ARGEO_USER_PROFILE); - // // session.getWorkspace().getVersionManager() - // // .checkout(userProfile.getPath()); - // userProfile.setProperty(ArgeoNames.ARGEO_USER_ID, username); - // session.save(); - // session.getWorkspace().getVersionManager() - // .checkin(userProfile.getPath()); - // // we need to save the profile before adding the user home type - // } - // userHome.addMixin(ArgeoTypes.ARGEO_USER_HOME); - // // see - // // - // http://jackrabbit.510166.n4.nabble.com/Jackrabbit-2-0-beta-6-Problem-adding-a-Mixin-type-with-mandatory-properties-after-setting-propertiesn-td1290332.html - // userHome.setProperty(ArgeoNames.ARGEO_USER_ID, username); - // session.save(); - // return userHome; - // } catch (RepositoryException e) { - // discardQuietly(session); - // throw new ArgeoException("Cannot create home node for user " - // + username, e); - // } - // } - - /** - * Returns user home has path, embedding exceptions. Contrary to - * {@link #getUserHome(Session)}, it never returns null but throws and - * exception if not found. - * - * @deprecated use getUserHome() instead, throwing an exception if it - * returns null - */ - @Deprecated - public static String getUserHomePath(Session session) { - String userID = session.getUserID(); - try { - String homePath = getUserHomePath(userID); - if (session.itemExists(homePath)) - return homePath; - else - throw new ArgeoException("No home registered for " + userID); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot find user home path", e); - } - } - - /** - * @return null if not found * - * @deprecated will soon be removed. Call instead - * getUserHome().getNode(ARGEO_PROFILE) on the security - * workspace. - */ - @Deprecated - public static Node getUserProfile(Session session, String username) { - try { - Node userHome = getUserHome(session, username); - if (userHome == null) - return null; - if (userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) - return userHome.getNode(ArgeoNames.ARGEO_PROFILE); - else - return null; - } catch (RepositoryException e) { - throw new ArgeoException( - "Cannot find profile for user " + username, e); - } - } - - /** - * Get the profile of the user attached to this session. - * - * @deprecated will soon be removed. Call instead - * getUserHome().getNode(ARGEO_PROFILE) on the security - * workspace. - */ - @Deprecated - public static Node getUserProfile(Session session) { - String userID = session.getUserID(); - return getUserProfile(session, userID); - } - /** * Quietly unregisters an {@link EventListener} from the udnerlying * workspace of this node. @@ -1269,4 +1220,277 @@ public class JcrUtils implements ArgeoJcrConstants { 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 privileges = new ArrayList(); + 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 void addPrivileges(Session session, String path, + Principal principal, List 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()])); + 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); + } + session.refresh(true); + session.save(); + } + + /** 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); + } + } + }