]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java
Improve JCR utilities
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jcr / src / main / java / org / argeo / jcr / JcrUtils.java
index 66c6a9388f1f6c1df93e79de91beb6e5a117e832..e51ac40cce522efb908827153b3b14d04c9e141c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 Mathieu Baudier <mbaudier@argeo.org>
+ * 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.
  * 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;
@@ -36,8 +39,10 @@ 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.PathNotFoundException;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
@@ -46,19 +51,29 @@ import javax.jcr.RepositoryException;
 import javax.jcr.RepositoryFactory;
 import javax.jcr.Session;
 import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
 import javax.jcr.Workspace;
 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 javax.jcr.version.VersionManager;
 
 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.util.security.SimplePrincipal;
 
 /** Utility methods to simplify common JCR operations. */
 public class JcrUtils implements ArgeoJcrConstants {
+
        private final static Log log = LogFactory.getLog(JcrUtils.class);
 
        /**
@@ -280,6 +295,49 @@ public class JcrUtils implements ArgeoJcrConstants {
                                .addNode(childName);
        }
 
+       /** Convert a {@link NodeIterator} to a list of {@link Node} */
+       public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
+               List<Node> nodes = new ArrayList<Node>();
+               while (nodeIterator.hasNext()) {
+                       nodes.add(nodeIterator.nextNode());
+               }
+               return nodes;
+       }
+
+       /*
+        * PROPERTIES
+        */
+
+       /** Concisely get the string value of a property */
+       public static String get(Node node, String propertyName) {
+               try {
+                       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);
@@ -327,6 +385,14 @@ 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
@@ -341,8 +407,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 +569,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
@@ -824,6 +939,73 @@ 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);
+               }
+       }
+
        /**
         * Creates depth from a string (typically a username) by adding levels based
         * on its first characters: "aBcD",2 => a/aB
@@ -909,6 +1091,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 +1125,23 @@ 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, 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) {
@@ -941,7 +1163,7 @@ public class JcrUtils implements ArgeoJcrConstants {
 
        /** User home path is NOT configurable */
        public static String getUserHomePath(String username) {
-               String homeBasePath = "/home";
+               String homeBasePath = DEFAULT_HOME_BASE_PATH;
                return homeBasePath + '/' + firstCharsToPath(username, 2) + '/'
                                + username;
        }
@@ -1025,8 +1247,37 @@ public class JcrUtils implements ArgeoJcrConstants {
                        return userProfile;
                } catch (RepositoryException e) {
                        discardQuietly(session);
-                       throw new ArgeoException("Cannot create home for " + username
-                                       + " in workspace " + session.getWorkspace().getName(), e);
+                       throw new ArgeoException("Cannot create user profile for "
+                                       + username + " in workspace "
+                                       + session.getWorkspace().getName(), e);
+               }
+       }
+
+       /**
+        * Create user profile if needed, the session IS saved.
+        * 
+        * @return the user profile
+        */
+       public static Node createUserProfileIfNeeded(Session securitySession,
+                       String username) {
+               try {
+                       Node userHome = JcrUtils.createUserHomeIfNeeded(securitySession,
+                                       username);
+                       Node userProfile = userHome.hasNode(ArgeoNames.ARGEO_PROFILE) ? userHome
+                                       .getNode(ArgeoNames.ARGEO_PROFILE) : JcrUtils
+                                       .createUserProfile(securitySession, username);
+                       if (securitySession.hasPendingChanges())
+                               securitySession.save();
+                       VersionManager versionManager = securitySession.getWorkspace()
+                                       .getVersionManager();
+                       if (versionManager.isCheckedOut(userProfile.getPath()))
+                               versionManager.checkin(userProfile.getPath());
+                       return userProfile;
+               } catch (RepositoryException e) {
+                       discardQuietly(securitySession);
+                       throw new ArgeoException("Cannot create user profile for "
+                                       + username + " in workspace "
+                                       + securitySession.getWorkspace().getName(), e);
                }
        }
 
@@ -1109,11 +1360,7 @@ public class JcrUtils implements ArgeoJcrConstants {
 
        /**
         * @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);
@@ -1131,12 +1378,7 @@ public class JcrUtils implements ArgeoJcrConstants {
 
        /**
         * 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);
@@ -1269,4 +1511,61 @@ public class JcrUtils implements ArgeoJcrConstants {
                                        re);
                }
        }
+
+       /*
+        * SECURITY
+        */
+
+       /**
+        * Convenience method for adding a single privilege to a principal (user or
+        * role), typically jcr:all
+        */
+       public 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.
+        */
+       public static void addPrivileges(Session session, String path,
+                       Principal principal, List<Privilege> privs)
+                       throws RepositoryException {
+               AccessControlManager acm = session.getAccessControlManager();
+               // 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) {
+                       acl.addAccessControlEntry(principal,
+                                       privs.toArray(new Privilege[privs.size()]));
+                       acm.setPolicy(path, acl);
+                       if (log.isDebugEnabled())
+                               log.debug("Added privileges " + privs + " to " + principal
+                                               + " on " + path);
+               } else {
+                       throw new ArgeoException("Don't know how to apply  privileges "
+                                       + privs + " to " + principal + " on " + path);
+               }
+       }
+
 }