You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2009/10/22 19:26:39 UTC

svn commit: r828791 [4/8] - in /jackrabbit/trunk: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ jackrabbi...

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Thu Oct 22 17:26:37 2009
@@ -41,8 +41,8 @@
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
-import javax.jcr.Session;
 import javax.jcr.ItemNotFoundException;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.version.VersionException;
@@ -78,8 +78,8 @@
  * <ul>
  * <li>The names of the hierarchy folders is determined from ID of the
  * authorizable to be created, consisting of the leading N chars where N is
- * the relative depth starting from the node at {@link UserConstants#USERS_PATH}
- * or {@link UserConstants#GROUPS_PATH}.</li>
+ * the relative depth starting from the node at {@link #getUsersPath()}
+ * or {@link #getGroupsPath()}.</li>
  * <li>By default 2 levels (depth == 2) are created.</li>
  * <li>Parent nodes are expected to consist of folder structure only.</li>
  * <li>If the ID contains invalid JCR chars that would prevent the creation of
@@ -222,8 +222,8 @@
     private final String groupsPath;
 
     /**
-     * Flag indicating if {@link #getAuthorizable(String)} should find users or
-     * groups created with Jackrabbit < 2.0.<br>
+     * Flag indicating if {@link #getAuthorizable(String)} should be able to deal
+     * with users or groups created with Jackrabbit < 2.0.<br>
      * As of 2.0 authorizables are created using a defined logic that allows
      * to retrieve them without searching/traversing. If this flag is
      * <code>true</code> this method will try to find authorizables using the
@@ -232,22 +232,11 @@
     private final boolean compatibleJR16;
 
     /**
-     * Flat storing the batch modus.
-     * If this option is turned on changes made through this user manager will
-     * only be persisted upon an explicit call to {@link javax.jcr.Session#save()},
-     * which must be the editing session object. Otherwise (default behavior)
-     * changes are immediately persisted.
-     *
-     * @see #setPersistenceModus(boolean, Session)
-     */
-    boolean batchModus = false;
-
-    /**
      * Create a new <code>UserManager</code> with the default configuration.
      *
-     * @param session
-     * @param adminId
-     * @throws RepositoryException
+     * @param session The editing/reading session.
+     * @param adminId The user ID of the administrator.
+     * @throws RepositoryException If an error occurs.
      */
     public UserManagerImpl(SessionImpl session, String adminId) throws RepositoryException {
         this(session, adminId, null);
@@ -267,10 +256,10 @@
      *
      * See the overall {@link UserManagerImpl introduction} for details.
      *
-     * @param session
-     * @param adminId
-     * @param config
-     * @throws RepositoryException
+     * @param session The editing/reading session.
+     * @param adminId The user ID of the administrator.
+     * @param config The configuration parameters.
+     * @throws RepositoryException If an error occurs.
      */
     public UserManagerImpl(SessionImpl session, String adminId, Properties config) throws RepositoryException {
         this.session = session;
@@ -297,23 +286,7 @@
         authResolver = nr;
         authResolver.setSearchRoots(usersPath, groupsPath);
     }
-
-    /**
-     * @param batched If <code>true</code> changes made through this manager and
-     * to authorizables are only persisted upon explict call to {@link
-     * javax.jcr.Session#save()}.
-     * @throws RepositoryException If the passed <code>editingSession</code> is
-     * not the same this UserManager is writing to.
-     */
-    /* COMMENTED. WORK IN PROGRESS
-    public void setPersistenceModus(boolean batched, Session editingSession) throws RepositoryException {
-        if (editingSession != session) {
-            throw new RepositoryException("Cannot change persistence modus: Session mismatch.");
-        }
-        batchModus = batched;
-    }
-    */
-
+    
     /**
      * Implementation specific methods releaving where users are created within
      * the content.
@@ -344,36 +317,7 @@
         if (id == null || id.length() == 0) {
             throw new IllegalArgumentException("Invalid authorizable name '" + id + "'");
         }
-        Authorizable authorz = null;
-        NodeId nodeId = buildNodeId(id);        
-        NodeImpl n = null;
-        try {
-            n = session.getNodeById(nodeId);
-        } catch (ItemNotFoundException e) {
-            if (compatibleJR16) {
-                // backwards-compatibility with JR < 2.0 user/group structure that doesn't
-                // allow to determine existance of an authorizable from the id directly.
-                // search for it the node belonging to that id
-                n = (NodeImpl) authResolver.findNode(P_USERID, id, NT_REP_USER);
-                if (n == null) {
-                    // no user -> look for group.
-                    // NOTE: JR < 2.0 always returned groupIDs that didn't contain any
-                    // illegal JCR chars. Since Group.getID() now unescapes the node
-                    // name additional escaping is required.
-                    Name nodeName = session.getQName(Text.escapeIllegalJcrChars(id));
-                    n = (NodeImpl) authResolver.findNode(nodeName, NT_REP_GROUP);
-                }
-            } // else: no matching node found -> ignore exception.
-        }
-
-        if (n != null) {
-            if (n.isNodeType(NT_REP_USER)) {
-                authorz = createUser(n);
-            } else if (n.isNodeType(NT_REP_GROUP)) {
-                authorz = createGroup(n);
-            } // else some other node but not an authorizable -> return null.
-        } // else no matching node -> return null.
-        return authorz;
+        return internalGetAuthorizable(id);
     }
 
     /**
@@ -391,34 +335,37 @@
                 }
             }
         } else {
-            // another Principal -> search
-            String name = principal.getName();
+            // another Principal implementation.
+            // a) try short-cut that works in case of ID.equals(principalName) only.
+            // b) execute query in case of pName mismatch or exc. however, query
+            //    requires persisted user nodes (see known issue of UserImporter).
+            String name = principal.getName();           
+            try {
+                Authorizable a = internalGetAuthorizable(name);
+                if (a != null && name.equals(a.getPrincipal().getName())) {
+                    return a;
+                }
+            } catch (RepositoryException e) {
+                // ignore and execute the query.
+            }
+            // shortcut didn't work -> search.
             n = (NodeImpl) authResolver.findNode(P_PRINCIPAL_NAME, name, NT_REP_AUTHORIZABLE);
         }
         // build the corresponding authorizable object
-        if (n != null) {
-            if (n.isNodeType(NT_REP_USER)) {
-               return createUser(n);
-            } else if (n.isNodeType(NT_REP_GROUP)) {
-               return createGroup(n);
-            } else {
-                log.debug("Unexpected user nodetype " + n.getPrimaryNodeType().getName());
-            }
-        }
-        return null;
+        return getAuthorizable(n);
     }
 
     /**
      * @see UserManager#findAuthorizables(String,String)
      */
-    public Iterator findAuthorizables(String propertyName, String value) throws RepositoryException {
+    public Iterator<Authorizable> findAuthorizables(String propertyName, String value) throws RepositoryException {
         return findAuthorizables(propertyName, value, SEARCH_TYPE_AUTHORIZABLE);
     }
 
     /**
      * @see UserManager#findAuthorizables(String,String, int)
      */
-    public Iterator findAuthorizables(String propertyName, String value, int searchType)
+    public Iterator<Authorizable> findAuthorizables(String propertyName, String value, int searchType)
             throws RepositoryException {
         Name name = session.getQName(propertyName);
         Name ntName;
@@ -474,27 +421,22 @@
         if (password == null) {
             throw new IllegalArgumentException("Cannot create user: null password.");
         }
-        if (!isValidPrincipal(principal)) {
-            throw new IllegalArgumentException("Cannot create user: Principal may not be null and must have a valid name.");
-        }
-        if (getAuthorizable(userID) != null) {
+        if (internalGetAuthorizable(userID) != null) {
             throw new AuthorizableExistsException("User for '" + userID + "' already exists");
         }
-        if (hasAuthorizable(principal)) {
-            throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists");
-        }
 
         try {
             NodeImpl userNode = (NodeImpl) nodeCreator.createUserNode(userID, intermediatePath);
-
+            setPrincipal(userNode, principal);
             setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
-            setProperty(userNode, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
-            if (!batchModus) {
+
+            User user = createUser(userNode);
+            if (isAutoSave()) {
                 session.save();
             }
 
             log.debug("User created: " + userID + "; " + userNode.getPath());
-            return createUser(userNode);
+            return user;
         } catch (RepositoryException e) {
             // something went wrong -> revert changes and rethrow
             session.refresh(false);
@@ -514,7 +456,7 @@
 
     /**
      * Create a new <code>Group</code> from the given <code>principal</code>.
-     * It will be created below the this {@link #GROUPS_PATH group path}.<br>
+     * It will be created below the defined {@link #getGroupsPath() group path}.<br>
      * Non-existant elements of the Path will be created as nodes
      * of type {@link #NT_REP_AUTHORIZABLE_FOLDER rep:AuthorizableFolder}.
      * The group ID will be generated from the principal name. If the name
@@ -531,24 +473,20 @@
      */
     public Group createGroup(Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
         if (!isValidPrincipal(principal)) {
-            throw new IllegalArgumentException("Cannot create Group: Principal may not be null and must have a valid name.");
+            throw new IllegalArgumentException("Cannot create group: Principal may not be null and must have a valid name.");
         }
-        if (hasAuthorizable(principal)) {
-            throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists: ");
-        }
-        
         try {
             String groupID = getGroupId(principal.getName());
             NodeImpl groupNode = (NodeImpl) nodeCreator.createGroupNode(groupID, intermediatePath);
+            setPrincipal(groupNode, principal);
 
-            setProperty(groupNode, P_PRINCIPAL_NAME, getValue(principal.getName()));
-            if (!batchModus) {
+            Group group = createGroup(groupNode);
+            if (isAutoSave()) {
                 session.save();
             }
 
             log.debug("Group created: " + groupID + "; " + groupNode.getPath());
-
-            return createGroup(groupNode);
+            return group;
         } catch (RepositoryException e) {
             session.refresh(false);
             log.debug("newInstance new Group failed, revert changes on parent");
@@ -556,46 +494,100 @@
         }
     }
 
-    //--------------------------------------------------------------------------
     /**
+     * Always returns <code>true</code> as by default the autoSave behavior
+     * cannot be altered (see also {@link #autoSave(boolean)}.
      *
-     * @param principal
-     * @return
-     * @throws RepositoryException
+     * @return Always <code>true</code>.
+     * @see org.apache.jackrabbit.api.security.user.UserManager#isAutoSave()
+     */
+    public boolean isAutoSave() {
+        return true;
+    }
+
+    /**
+     * Always throws <code>unsupportedRepositoryOperationException</code> as
+     * modification of the autosave behavior is not supported.
+     *
+     * @see UserManager#autoSave(boolean)
      */
-    private boolean hasAuthorizable(Principal principal) throws RepositoryException {
-        return getAuthorizable(principal) != null;
+    public void autoSave(boolean enable) throws UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedRepositoryOperationException("Cannot change autosave behavior.");
+    }
+
+    //--------------------------------------------------------------------------
+    void setPrincipal(NodeImpl node, Principal principal) throws AuthorizableExistsException, RepositoryException {
+        if (!isValidPrincipal(principal)) {
+            throw new IllegalArgumentException("Cannot create Authorizable: Principal may not be null and must have a valid name.");
+        }
+        if (getAuthorizable(principal) != null) {
+            throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists: ");
+        }
+        if (!node.isNew() || node.hasProperty(P_PRINCIPAL_NAME)) {
+            throw new RepositoryException("rep:principalName can only be set once on a new node.");
+        }
+        setProperty(node, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
     }
 
     void setProtectedProperty(NodeImpl node, Name propName, Value value) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
         setProperty(node, propName, value);
-        if (!batchModus) {
+        if (isAutoSave()) {
             node.save();
         }
     }
 
     void setProtectedProperty(NodeImpl node, Name propName, Value[] values) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
         setProperty(node, propName, values);
-        if (!batchModus) {
+        if (isAutoSave()) {
             node.save();
         }
     }
 
     void setProtectedProperty(NodeImpl node, Name propName, Value[] values, int type) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
         setProperty(node, propName, values, type);
-        if (!batchModus) {
+        if (isAutoSave()) {
             node.save();
         }
     }
 
     void removeProtectedItem(ItemImpl item, Node parent) throws RepositoryException, AccessDeniedException, VersionException {
         removeItem(item);
-        if (!batchModus) {
+        if (isAutoSave()) {
             parent.save();
         }
     }
 
     /**
+     * Implementation specific method used to retrieve a user/group by Node.
+     * <code>Null</code> is returned if
+     * <pre>
+     * - the passed node is <code>null</code>,
+     * - doesn't have the correct node type or
+     * - isn't placed underneith the configured user/group tree.
+     * </pre>
+     *
+     * @param n
+     * @return An authorizable or <code>null</code>.
+     * @throws RepositoryException
+     */
+    Authorizable getAuthorizable(NodeImpl n) throws RepositoryException {
+        Authorizable authorz = null;
+        if (n != null) {
+            String path = n.getPath();
+            if (n.isNodeType(NT_REP_USER) && Text.isDescendant(usersPath, path)) {
+                authorz = createUser(n);
+            } else if (n.isNodeType(NT_REP_GROUP) && Text.isDescendant(groupsPath, path)) {
+                authorz = createGroup(n);
+            } else {
+                /* else some other node type or outside of the valid user/group
+                   hierarchy  -> return null. */
+                log.debug("Unexpected user nodetype " + n.getPrimaryNodeType().getName());
+            }
+        } /* else no matching node -> return null */
+        return authorz;
+    }
+
+    /**
      * Test if a user or group exists that has the given principals name as ID,
      * which might happen if userID != principal-name.
      * In this case: generate another ID for the group to be created.
@@ -607,13 +599,43 @@
     private String getGroupId(String principalName) throws RepositoryException {
         String groupID = principalName;
         int i = 0;
-        while (getAuthorizable(groupID) != null) {
+        while (internalGetAuthorizable(groupID) != null) {
             groupID = principalName + "_" + i;
             i++;
         }
         return groupID;
     }
 
+    /**
+     * @param id The user or group ID.
+     * @return The authorizable with the given <code>id</code> or <code>null</code>.
+     * @throws RepositoryException If an error occurs.
+     */
+    private Authorizable internalGetAuthorizable(String id) throws RepositoryException {
+        NodeId nodeId = buildNodeId(id);
+        NodeImpl n = null;
+        try {
+            n = session.getNodeById(nodeId);
+        } catch (ItemNotFoundException e) {
+            if (compatibleJR16) {
+                // backwards-compatibility with JR < 2.0 user/group structure that doesn't
+                // allow to determine existance of an authorizable from the id directly.
+                // search for it the node belonging to that id
+                n = (NodeImpl) authResolver.findNode(P_USERID, id, NT_REP_USER);
+                if (n == null) {
+                    // no user -> look for group.
+                    // NOTE: JR < 2.0 always returned groupIDs that didn't contain any
+                    // illegal JCR chars. Since Group.getID() now unescapes the node
+                    // name additional escaping is required.
+                    Name nodeName = session.getQName(Text.escapeIllegalJcrChars(id));
+                    n = (NodeImpl) authResolver.findNode(nodeName, NT_REP_GROUP);
+                }
+            } // else: no matching node found -> ignore exception.
+        }
+
+        return getAuthorizable(n);
+    }
+
     private Value getValue(String strValue) throws RepositoryException {
         return session.getValueFactory().createValue(strValue);
     }
@@ -638,7 +660,7 @@
             throw new IllegalArgumentException();
         }
         if (!Text.isDescendant(usersPath, userNode.getPath())) {
-            throw new IllegalArgumentException("User has to be within the User Path");
+            throw new RepositoryException("User has to be within the User Path");
         }
         return doCreateUser(userNode);
     }
@@ -648,7 +670,7 @@
      * return a custom implementation.
      *
      * @param node user node
-     * @return user object
+     * @return the user object
      * @throws RepositoryException if an error occurs
      */
     protected User doCreateUser(NodeImpl node) throws RepositoryException {
@@ -664,27 +686,47 @@
      * @throws RepositoryException
      */
     Group createGroup(NodeImpl groupNode) throws RepositoryException {
-        return GroupImpl.create(groupNode, this);
+        if (groupNode == null || !groupNode.isNodeType(NT_REP_GROUP)) {
+            throw new IllegalArgumentException();
+        }
+        if (!Text.isDescendant(groupsPath, groupNode.getPath())) {
+            throw new RepositoryException("Group has to be within the Group Path");
+        }
+        return doCreateGroup(groupNode);
     }
 
-    private static boolean isValidPrincipal(Principal principal) {
-        return principal != null && principal.getName() != null && principal.getName().length() > 0;
+    /**
+     * Build the group object from the given group node. May be overridden to
+     * return a custom implementation.
+     *
+     * @param node group node
+     * @return A group 
+     * @throws RepositoryException if an error occurs
+     */
+    protected Group doCreateGroup(NodeImpl node) throws RepositoryException {
+        return new GroupImpl(node, this);
     }
 
     /**
+     * Creates a UUID from the given <code>id</code> String that is converted
+     * to lower case before.
      *
-     * @param id
-     * @return
-     * @throws RepositoryException
+     * @param id The user/group id that needs to be converted to a valid NodeId.
+     * @return a new <code>NodeId</code>.
+     * @throws RepositoryException If an error occurs.
      */
-    private static NodeId buildNodeId(String id) throws RepositoryException {
+    private NodeId buildNodeId(String id) throws RepositoryException {
         try {
-            UUID uuid = UUID.nameUUIDFromBytes(id.getBytes("UTF-8"));
+            UUID uuid = UUID.nameUUIDFromBytes(id.toLowerCase().getBytes("UTF-8"));
             return new NodeId(uuid);
         } catch (UnsupportedEncodingException e) {
             throw new RepositoryException("Unexpected error while build ID hash", e);
         }
     }
+    
+    private static boolean isValidPrincipal(Principal principal) {
+        return principal != null && principal.getName() != null && principal.getName().length() > 0;
+    }
 
     //----------------------------------------------------< SessionListener >---
     /**
@@ -704,11 +746,11 @@
         }
     }
 
-    //--------------------------------------------------------------------------
+    //------------------------------------------------------< inner classes >---
     /**
      * Inner class
      */
-    private final class AuthorizableIterator implements Iterator {
+    private final class AuthorizableIterator implements Iterator<Authorizable> {
 
         private final Set<String> served = new HashSet<String>();
 
@@ -731,7 +773,7 @@
         /**
          * @see Iterator#next()
          */
-        public Object next() {
+        public Authorizable next() {
             Authorizable authr = next;
             if (authr == null) {
                 throw new NoSuchElementException();
@@ -753,17 +795,11 @@
                 NodeImpl node = (NodeImpl) authNodeIter.nextNode();
                 try {
                     if (!served.contains(node.getUUID())) {
-                        Authorizable authr;
-                        if (node.isNodeType(NT_REP_USER)) {
-                            authr = createUser(node);
-                        } else if (node.isNodeType(NT_REP_GROUP)) {
-                            authr = createGroup(node);
-                        } else {
-                            log.warn("Ignoring unexpected nodetype: " + node.getPrimaryNodeType().getName());
-                            continue;
-                        }
+                        Authorizable authr = getAuthorizable(node);
                         served.add(node.getUUID());
-                        return authr;
+                        if (authr != null) {
+                            return authr;
+                        }
                     }
                 } catch (RepositoryException e) {
                     log.debug(e.getMessage());
@@ -988,34 +1024,51 @@
             Name ntName = (isGroup) ? NT_REP_GROUP : NT_REP_USER;
             NodeId nid = buildNodeId(id);
 
+            // check if there exists an colliding folder child node.
+            while (((NodeImpl) folder).hasNode(nodeName)) {
+                NodeImpl colliding = ((NodeImpl) folder).getNode(nodeName);
+                if (colliding.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+                    log.warn("Existing folder node collides with user/group to be created. Expanding path: " + colliding.getPath());
+                    folder = colliding;
+                } else {
+                    // should never get here as folder creation above already
+                    // asserts that only rep:authorizable folders exist.
+                    // similarly collisions with existing authorizable have been
+                    // checked.
+                    String msg = "Failed to create authorizable node: Detected conflicting node of unexpected nodetype '" + colliding.getPrimaryNodeType().getName() + "'.";
+                    log.error(msg);
+                    throw new ConstraintViolationException(msg);
+                }
+            }
             return addNode((NodeImpl) folder, nodeName, ntName, nid);
         }
 
         private Node createDefaultFolderNodes(String id, String escapedId,
                                               boolean isGroup, String intermediatePath) throws RepositoryException {
-            NodeImpl folder;
+
             String defaultPath = getDefaultFolderPath(id, isGroup, intermediatePath);
-            if (session.nodeExists(defaultPath)) {
-                folder = (NodeImpl) session.getNode(defaultPath);
-                if (!folder.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
-                    throw new RepositoryException("Invalid intermediate path. Must be of type rep:AuthorizableFolder.");
-                }
-            } else {
-                String[] segmts = defaultPath.split("/");
-                folder = (NodeImpl) session.getRootNode();
 
-                for (String segment : segmts) {
-                    if (segment.length() < 1) {
-                        continue;
-                    }
-                    if (folder.hasNode(segment)) {
-                        folder = (NodeImpl) folder.getNode(segment);
-                        if (!folder.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
-                            throw new RepositoryException("Invalid intermediate path. Must be of type rep:AuthorizableFolder.");
-                        }
-                    } else {
-                        folder = addNode(folder, session.getQName(segment), NT_REP_AUTHORIZABLE_FOLDER);
+            // make sure users/groups are never nested and exclusively created
+            // under a tree of rep:AuthorizableFolder(s) starting at usersPath
+            // or groupsPath, respectively. ancestors of the usersPath/groupsPath
+            // may or may not be rep:AuthorizableFolder(s).
+            // therefore the shortcut Session.getNode(defaultPath) is omitted.
+            String[] segmts = defaultPath.split("/");
+            NodeImpl folder = (NodeImpl) session.getRootNode();
+            String authRoot = (isGroup) ? groupsPath : usersPath;
+
+            for (String segment : segmts) {
+                if (segment.length() < 1) {
+                    continue;
+                }
+                if (folder.hasNode(segment)) {
+                    folder = (NodeImpl) folder.getNode(segment);
+                    if (Text.isDescendantOrEqual(authRoot, folder.getPath()) &&
+                            !folder.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+                        throw new ConstraintViolationException("Invalid intermediate path. Must be of type rep:AuthorizableFolder.");
                     }
+                } else {
+                    folder = addNode(folder, session.getQName(segment), NT_REP_AUTHORIZABLE_FOLDER);
                 }
             }
 
@@ -1099,7 +1152,7 @@
                         // should never get here: some other, unexpected node type
                         String msg = "Failed to create authorizable node: Detected conflict with node of unexpected nodetype '" + n.getPrimaryNodeType().getName() + "'.";
                         log.error(msg);
-                        throw new RepositoryException(msg);
+                        throw new ConstraintViolationException(msg);
                     }
                 } else {
                     // folder doesn't exist nor does another colliding child node.

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java?rev=828791&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java Thu Oct 22 17:26:37 2009
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.core.SessionImpl;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import java.util.Properties;
+
+/**
+ * Derived UserManager implementation that allows to switch between autosaving
+ * and transient change mode.
+ * <p/>
+ * NOTE: This requires that the Session passed to the user manager upon creation
+ * is identical to the Session passed to
+ * {@link org.apache.jackrabbit.core.security.JackrabbitSecurityManager#getUserManager(Session)}.
+ *
+ * @see UserPerWorkspaceUserManager
+ */
+public class UserPerWorkspaceUserManager extends UserManagerImpl {
+
+    private boolean autoSave = true;
+
+    /**
+     * Same as <code>TransientChangeUserManagerImpl(session, adminID, null)</code>.
+     *
+     * @param session
+     * @param adminId
+     * @throws RepositoryException
+     */
+    public UserPerWorkspaceUserManager(SessionImpl session, String adminId) throws RepositoryException {
+        this(session, adminId, null);
+    }
+
+    /**
+     * Creates a UserManager that doesn't implicitly save changes but requires
+     * an explicit call to {@link javax.jcr.Session#save()}.
+     *
+     * @param session
+     * @param adminId
+     * @param config
+     * @throws javax.jcr.RepositoryException
+     */
+    public UserPerWorkspaceUserManager(SessionImpl session, String adminId, Properties config) throws RepositoryException {
+        super(session, adminId, config);
+    }
+
+    //--------------------------------------------------------< UserManager >---
+    /**
+     * @see org.apache.jackrabbit.api.security.user.UserManager#isAutoSave()
+     */
+    @Override
+    public boolean isAutoSave() {
+        return autoSave;
+    }
+
+    /**
+     * @see org.apache.jackrabbit.api.security.user.UserManager#autoSave(boolean)
+     */
+    @Override
+    public void autoSave(boolean enable) throws UnsupportedRepositoryOperationException, RepositoryException {
+        autoSave = enable;
+    }
+}
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/ReferenceChangeTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/ReferenceChangeTracker.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/ReferenceChangeTracker.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/ReferenceChangeTracker.java Thu Oct 22 17:26:37 2009
@@ -22,6 +22,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.List;
 
 /**
  * Simple helper class that can be used to keep track of node id mappings
@@ -91,4 +92,15 @@
     public Iterator<Object> getProcessedReferences() {
         return references.iterator();
     }
+
+    /**
+     * Remove the given references that have already been processed from the
+     * references list.
+     * 
+     * @param processedReferences
+     * @return <code>true</code> if the internal list of references changed.
+     */
+    public boolean removeReferences(List<Object> processedReferences) {
+        return references.removeAll(processedReferences);
+    }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java Thu Oct 22 17:26:37 2009
@@ -41,6 +41,7 @@
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
 import org.apache.jackrabbit.core.security.principal.UnknownPrincipal;
@@ -75,27 +76,44 @@
         ACE_NODETYPES.add(AccessControlConstants.NT_REP_GRANT_ACE);
     }
 
-    private final AccessControlManager acMgr;
     private final Stack<Integer> prevStatus = new Stack<Integer>();
-
+    
+    private AccessControlManager acMgr;
     private int status = STATUS_UNDEFINED;
     private NodeImpl parent = null;
 
     private boolean principalbased = false;
 
+    private boolean initialized = false;
+
     /**
      * the ACL for non-principal based
      */
     private JackrabbitAccessControlList acl = null;
 
-    public AccessControlImporter(JackrabbitSession session, NamePathResolver resolver,
-                                 boolean isWorkspaceImport, int uuidBehavior) throws RepositoryException {
-        super(session, resolver, isWorkspaceImport, uuidBehavior);
-
-        acMgr = session.getAccessControlManager();
+    @Override
+    public boolean init(JackrabbitSession session, NamePathResolver resolver,
+                        boolean isWorkspaceImport, int uuidBehavior,
+                        ReferenceChangeTracker referenceTracker) {
+        if (super.init(session, resolver, isWorkspaceImport, uuidBehavior, referenceTracker)) {
+            if (initialized) {
+                throw new IllegalStateException("Already initialized");
+            }
+            try {
+                acMgr = session.getAccessControlManager();
+                initialized = true;
+            } catch (RepositoryException e) {
+                // initialization failed. ac-import not possible
+            }
+        }
+        return initialized;
     }
 
+    @Override
     public boolean start(NodeImpl protectedParent) throws RepositoryException {
+        if (!initialized) {
+            throw new IllegalStateException("Not initialized");
+        }
         if (isStarted()) {
             // only ok if same parent
             if (!protectedParent.isSame(parent)) {
@@ -116,7 +134,7 @@
                 && protectedParent.isNodeType(AccessControlConstants.NT_REP_ACL)) {
             acl = getACL(protectedParent.getParent().getPath());
             if (acl == null) {
-                log.warn("AccessControlImporter cannot be started: no ACL for {}.", parent.getParent().getPath());
+                log.warn("AccessControlImporter cannot be started: no ACL for {}.", protectedParent.getParent().getPath());
                 return false;
             }
             status = STATUS_ACL;
@@ -155,6 +173,7 @@
         return acl;
     }
 
+    @Override
     public boolean start(NodeState protectedParent) throws IllegalStateException, RepositoryException {
         if (isStarted()) {
             throw new IllegalStateException();
@@ -166,16 +185,17 @@
         return false;
     }
 
+    @Override
     public void end(NodeImpl protectedParent) throws RepositoryException {
         if (!isStarted()) {
             return;
         }
 
         if (!principalbased) {
-            checkStatus(STATUS_ACL, "");
+            checkStatus(STATUS_ACL, "STATUS_ACL expected.");
             acMgr.setPolicy(acl.getPath(), acl);
         } else {
-            checkStatus(STATUS_AC_FOLDER, "");
+            checkStatus(STATUS_AC_FOLDER, "STATUS_AC_FOLDER expected.");
             if (!prevStatus.isEmpty()) {
                 throw new ConstraintViolationException("Incomplete protected item tree: "+ prevStatus.size()+ " calls to 'endChildInfo' missing.");
             }
@@ -183,10 +203,12 @@
         reset();
     }
 
+    @Override
     public void end(NodeState protectedParent) throws IllegalStateException, ConstraintViolationException, RepositoryException {
         // nothing to do. will never get here.
     }
 
+    @Override
     public void startChildInfo(NodeInfo childInfo, List<PropInfo> propInfos) throws RepositoryException {
         if (!isStarted()) {
             return;
@@ -240,9 +262,7 @@
         prevStatus.push(previousStatus);        
     }
 
-    /**
-     * @throws javax.jcr.RepositoryException
-     */
+    @Override
     public void endChildInfo() throws RepositoryException {
         if (!isStarted()) {
             return;
@@ -261,7 +281,6 @@
         status = prevStatus.pop();
     }
 
-
     private boolean isStarted() {
         return status > STATUS_UNDEFINED;
     }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedNodeImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedNodeImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedNodeImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedNodeImporter.java Thu Oct 22 17:26:37 2009
@@ -21,6 +21,7 @@
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
@@ -30,25 +31,24 @@
  */
 public class DefaultProtectedNodeImporter implements ProtectedNodeImporter {
 
-    protected final JackrabbitSession session;
+    protected JackrabbitSession session;
+    protected NamePathResolver resolver;
+    protected boolean isWorkspaceImport;
+    protected int uuidBehavior;
+    protected ReferenceChangeTracker referenceTracker;
 
-    protected final NamePathResolver resolver;
-
-    protected final boolean isWorkspaceImport;
-
-    protected final int uuidBehavior;
+    public DefaultProtectedNodeImporter() {
+    }
 
-    public DefaultProtectedNodeImporter(JackrabbitSession session,
-                                 NamePathResolver resolver,
-                                 boolean isWorkspaceImport,
-                                 int uuidBehavior) {
+    public boolean init(JackrabbitSession session, NamePathResolver resolver, boolean isWorkspaceImport, int uuidBehavior, ReferenceChangeTracker referenceTracker) {
         this.session = session;
         this.resolver = resolver;
         this.isWorkspaceImport = isWorkspaceImport;
         this.uuidBehavior = uuidBehavior;
+        this.referenceTracker = referenceTracker;
+        return true;
     }
 
-
     /**
      * Always returns <code>false</code>.
      *
@@ -98,4 +98,12 @@
      */
     public void endChildInfo() throws RepositoryException {
     }
+
+    /**
+     * Does nothing.
+     *
+     * @see ProtectedNodeImporter#processReferences()
+     */
+    public void processReferences() throws RepositoryException {
+    }
 }
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedPropertyImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedPropertyImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedPropertyImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DefaultProtectedPropertyImporter.java Thu Oct 22 17:26:37 2009
@@ -19,6 +19,7 @@
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
@@ -29,18 +30,28 @@
  */
 public class DefaultProtectedPropertyImporter implements ProtectedPropertyImporter {
 
-    protected final JackrabbitSession session;
+    protected JackrabbitSession session;
 
-    protected final NamePathResolver resolver;
+    protected NamePathResolver resolver;
 
-    protected final boolean isWorkspaceImport;
+    protected boolean isWorkspaceImport;
 
-    public DefaultProtectedPropertyImporter(JackrabbitSession session,
-                                 NamePathResolver resolver,
-                                 boolean isWorkspaceImport) {
+    protected int uuidBehavior;
+
+    protected ReferenceChangeTracker referenceTracker;
+
+    public DefaultProtectedPropertyImporter() {
+    }
+
+    public boolean init(JackrabbitSession session, NamePathResolver resolver,
+                        boolean isWorkspaceImport,
+                        int uuidBehavior, ReferenceChangeTracker referenceTracker) {
         this.session = session;
         this.resolver = resolver;
         this.isWorkspaceImport = isWorkspaceImport;
+        this.uuidBehavior = uuidBehavior;
+        this.referenceTracker = referenceTracker;
+        return true;
     }
 
     /**
@@ -48,7 +59,7 @@
      *
      * @see ProtectedPropertyImporter#handlePropInfo(org.apache.jackrabbit.core.NodeImpl, PropInfo, QPropertyDefinition)
      */
-    public boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo, QPropertyDefinition def) {
+    public boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo, QPropertyDefinition def) throws RepositoryException {
         return false;
     }
 
@@ -60,4 +71,12 @@
     public boolean handlePropInfo(NodeState parent, PropInfo protectedPropInfo, QPropertyDefinition def) throws RepositoryException {
         return false;
     }
+
+    /**
+     * Always returns <code>false</code>.
+     *
+     * @see ProtectedPropertyImporter#processReferences()
+     */
+    public void processReferences() throws RepositoryException {
+    }
 }
\ No newline at end of file

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java Thu Oct 22 17:26:37 2009
@@ -17,7 +17,10 @@
 package org.apache.jackrabbit.core.xml;
 
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.ConstraintViolationException;
@@ -33,12 +36,23 @@
  * with all subsequent child <code>NodeInfo</code>s present below the protected
  * parent until {@link #end(NodeImpl)} is called. The latter resets this importer
  * and makes it available for another protected import.
- *
- * TODO: check if references properties can be handled with the info present
  */
 public interface ProtectedNodeImporter {
 
     /**
+     * 
+     * @param session
+     * @param resolver
+     * @param isWorkspaceImport
+     * @param uuidBehavior
+     * @param referenceTracker
+     * @return
+     */
+    boolean init(JackrabbitSession session, NamePathResolver resolver,
+                 boolean isWorkspaceImport, int uuidBehavior,
+                 ReferenceChangeTracker referenceTracker);
+
+    /**
      * Notifies this importer about the existience of a protected node that
      * has either been created (NEW) or has been found to be existing.
      * This importer implementation is in charge of evaluating the nature of
@@ -149,4 +163,13 @@
      */
     void endChildInfo() throws RepositoryException;
 
+    /**
+     * Post processing protected reference properties underneith a parent
+     * node that has been handled by this importer.
+     * This method is called
+     * from {@link org.apache.jackrabbit.core.xml.Importer#end()}.
+     * 
+     * @throws RepositoryException If an error occurs.
+     */
+    void processReferences() throws RepositoryException;
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedPropertyImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedPropertyImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedPropertyImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedPropertyImporter.java Thu Oct 22 17:26:37 2009
@@ -19,8 +19,11 @@
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.api.JackrabbitSession;
 
 /**
  * <code>ProtectedPropertyImporter</code> is in charge of importing single
@@ -32,6 +35,18 @@
 public interface ProtectedPropertyImporter {
 
     /**
+     * 
+     * @param session
+     * @param resolver
+     * @param isWorkspaceImport
+     * @param uuidBehavior
+     *@param referenceTracker  @return
+     */
+    boolean init(JackrabbitSession session, NamePathResolver resolver,
+                 boolean isWorkspaceImport, int uuidBehavior,
+                 ReferenceChangeTracker referenceTracker);
+
+    /**
      * Handles a single protected property.
      *
      * @param parent The affected parent node.
@@ -42,7 +57,8 @@
      * <code>false</code> otherwise.
      * @throws RepositoryException If an error occurs.
      */
-    boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo, QPropertyDefinition def)
+    boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo,
+                           QPropertyDefinition def)
             throws RepositoryException;
 
     /**
@@ -56,8 +72,17 @@
      * <code>false</code> otherwise.
      * @throws RepositoryException If an error occurs.
      */
-    boolean handlePropInfo(NodeState parent, PropInfo protectedPropInfo, QPropertyDefinition def)
+    boolean handlePropInfo(NodeState parent, PropInfo protectedPropInfo,
+                           QPropertyDefinition def)
             throws RepositoryException;
 
 
+    /**
+     * Post processing protected reference properties. This method is called
+     * from {@link org.apache.jackrabbit.core.xml.Importer#end()}.
+     *
+     * @throws RepositoryException If an error occurs.
+     */
+    void processReferences() throws RepositoryException;
+
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SessionImporter.java Thu Oct 22 17:26:37 2009
@@ -19,6 +19,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Stack;
+import java.util.ArrayList;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.ImportUUIDBehavior;
@@ -34,6 +35,7 @@
 
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.config.ImportConfig;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
@@ -62,15 +64,16 @@
      */
     private final ReferenceChangeTracker refTracker;
 
+    private final List<ProtectedNodeImporter> pnImporters = new ArrayList();
     /**
-     * Importer for protected nodes.
+     * Available importers for protected properties.
      */
-    private final ProtectedNodeImporter pnImporter;
+    private final List<ProtectedPropertyImporter> ppImporters = new ArrayList();
 
     /**
-     * Importer for protected properties.
+     * Currently active importer for protected nodes.
      */
-    private final ProtectedPropertyImporter ppImporter;
+    private ProtectedNodeImporter pnImporter = null;
 
     /**
      * Creates a new <code>SessionImporter</code> instance.
@@ -83,7 +86,7 @@
     public SessionImporter(NodeImpl importTargetNode,
                            SessionImpl session,
                            int uuidBehavior) {
-        this(importTargetNode, session, uuidBehavior, null, null);
+        this(importTargetNode, session, uuidBehavior, null);
     }
 
     /**
@@ -92,27 +95,47 @@
      * @param importTargetNode the target node
      * @param session session
      * @param uuidBehavior the uuid behaviro
-     * @param pnImporter importer for protected nodes
-     * @param ppImporter importer for protected properties
+     * @param config
      */
     public SessionImporter(NodeImpl importTargetNode, SessionImpl session,
-                           int uuidBehavior,
-                           ProtectedNodeImporter pnImporter,
-                           ProtectedPropertyImporter ppImporter) {
+                           int uuidBehavior, ImportConfig config) {
         this.importTargetNode = importTargetNode;
         this.session = session;
         this.uuidBehavior = uuidBehavior;
 
-        this.ppImporter = ppImporter == null
-                ? new DefaultProtectedPropertyImporter(session, session, false)
-                : ppImporter;
-        this.pnImporter = pnImporter == null
-                ? new DefaultProtectedNodeImporter(session, session, false, uuidBehavior)
-                : pnImporter;
         refTracker = new ReferenceChangeTracker();
 
         parents = new Stack<NodeImpl>();
         parents.push(importTargetNode);
+
+        if (config != null) {
+            List<ProtectedNodeImporter> ln = config.getProtectedNodeImporters();
+            for (ProtectedNodeImporter pni : ln) {
+                if (pni.init(session, session, false, uuidBehavior, refTracker)) {
+                    pnImporters.add(pni);
+                }
+            }
+            List<ProtectedPropertyImporter> lp = config.getProtectedPropertyImporters();
+            for (ProtectedPropertyImporter ppi : lp) {
+                if (ppi.init(session, session, false, uuidBehavior, refTracker)) {
+                    ppImporters.add(ppi);
+                }
+            }
+        }
+
+        // missing config -> initialize defaults.
+        if (pnImporters.isEmpty()) {
+            ProtectedNodeImporter def = new DefaultProtectedNodeImporter();
+            if (def.init(session, session, false, uuidBehavior, refTracker)) {
+                pnImporters.add(def);
+            }
+        }
+        if (ppImporters.isEmpty()) {
+            DefaultProtectedPropertyImporter def = new DefaultProtectedPropertyImporter();
+            if (def.init(session, session, false, uuidBehavior, refTracker)) {
+                ppImporters.add(def);
+            }
+        }
     }
 
     /**
@@ -272,7 +295,9 @@
             // parent node was skipped, skip this child node too
             parents.push(null); // push null onto stack for skipped node
             // notify the p-i-importer
-            pnImporter.startChildInfo(nodeInfo, propInfos);
+            if (pnImporter != null) {
+                pnImporter.startChildInfo(nodeInfo, propInfos);
+            }
             return;
         }
 
@@ -284,11 +309,16 @@
             // Notify the ProtectedNodeImporter about the start of a item
             // tree that is protected by this parent. If it potentially is
             // able to deal with it, notify it about the child node.
-            if (pnImporter.start(parent)) {
-                log.debug("Protected node -> delegated to ProtectedPropertyImporter");
-                pnImporter.startChildInfo(nodeInfo, propInfos);
-            } /* else: p-i-Importer isn't able to deal with the protected tree.
-                 skip the tree below the protected parent */
+            for (ProtectedNodeImporter pni : pnImporters) {
+                if (pni.start(parent)) {
+                    log.debug("Protected node -> delegated to ProtectedPropertyImporter");
+                    pnImporter = pni;
+                    pnImporter.startChildInfo(nodeInfo, propInfos);
+                    break;
+                } /* else: p-i-Importer isn't able to deal with the protected tree.
+                     try next. and if none can handle the passed parent the
+                     tree below will be skipped */
+            }
             return;
         }
 
@@ -375,10 +405,14 @@
                 log.debug("Skipping protected property " + pi.getName());
 
                 // notify the ProtectedPropertyImporter.
-                if (ppImporter.handlePropInfo(node, pi, def)) {
-                    // TODO: deal with reference props within the imported tree?                    
-                    log.debug("Protected property -> delegated to ProtectedPropertyImporter");
-                } // else: p-i-Importer isn't able to deal with this property
+                for (ProtectedPropertyImporter ppi : ppImporters) {
+                    if (ppi.handlePropInfo(node, pi, def)) {
+                        log.debug("Protected property -> delegated to ProtectedPropertyImporter");
+                        break;
+                    } /* else: p-i-Importer isn't able to deal with this property.
+                         try next pp-importer */
+
+                }
             } else {
                 // regular property -> create the property
                 createProperty(node, pi, def);
@@ -395,10 +429,16 @@
     public void endNode(NodeInfo nodeInfo) throws RepositoryException {
         NodeImpl parent = parents.pop();
         if (parent == null) {
-            pnImporter.endChildInfo();
+            if (pnImporter != null) {
+                pnImporter.endChildInfo();
+            }
         } else if (parent.getDefinition().isProtected()) {
-            pnImporter.end(parent);
-            // TODO: deal with reference props within the imported tree?
+            if (pnImporter != null) {
+                pnImporter.end(parent);
+                // and reset the pnImporter field waiting for the next protected
+                // parent -> selecting again from available importers
+                pnImporter = null;
+            }
         }
     }
 
@@ -410,9 +450,24 @@
          * adjust references that refer to uuid's which have been mapped to
          * newly generated uuid's on import
          */
-        Iterator iter = refTracker.getProcessedReferences();
+        // 1. let protected property/node importers handle protected ref-properties
+        //    and (protected) properties underneith a protected parent node.
+        for (ProtectedPropertyImporter ppi : ppImporters) {
+            ppi.processReferences();
+        }
+        for (ProtectedNodeImporter pni : pnImporters) {
+            pni.processReferences();
+        }
+
+        // 2. regular non-protected properties.
+        Iterator<Object> iter = refTracker.getProcessedReferences();
         while (iter.hasNext()) {
-            Property prop = (Property) iter.next();
+            Object ref = iter.next();
+            if (!(ref instanceof Property)) {
+                continue;
+            }
+
+            Property prop = (Property) ref;
             // being paranoid...
             if (prop.getType() != PropertyType.REFERENCE
                     && prop.getType() != PropertyType.WEAKREFERENCE) {

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java Thu Oct 22 17:26:37 2009
@@ -35,6 +35,7 @@
 import org.apache.jackrabbit.core.HierarchyManager;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.WorkspaceImpl;
+import org.apache.jackrabbit.core.config.ImportConfig;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
 import org.apache.jackrabbit.core.nodetype.EffectiveNodeType;
@@ -66,7 +67,6 @@
     private final WorkspaceImpl wsp;
     private final SessionImpl session;
     private final InternalVersionManager versionManager;
-    private final NodeTypeRegistry ntReg;
     private final HierarchyManager hierMgr;
     private final BatchedItemOperations itemOps;
 
@@ -107,10 +107,40 @@
                              int uuidBehavior)
             throws PathNotFoundException, ConstraintViolationException,
             VersionException, LockException, RepositoryException {
+        this(parentPath, wsp, ntReg, uuidBehavior, null);
+    }
+
+    /**
+     * Creates a new <code>WorkspaceImporter</code> instance.
+     *
+     * @param parentPath   target path where to add the imported subtree
+     * @param wsp the workspace to operate on
+     * @param ntReg the node type registry
+     * @param uuidBehavior flag that governs how incoming UUIDs are handled
+     * @param config import configuration.
+     * @throws PathNotFoundException        if no node exists at
+     *                                      <code>parentPath</code> or if the
+     *                                      current session is not granted read
+     *                                      access.
+     * @throws ConstraintViolationException if the node at
+     *                                      <code>parentPath</code> is protected
+     * @throws VersionException             if the node at
+     *                                      <code>parentPath</code> is not
+     *                                      checked-out
+     * @throws LockException                if a lock prevents the addition of
+     *                                      the subtree
+     * @throws RepositoryException          if another error occurs
+     */
+    public WorkspaceImporter(Path parentPath,
+                             WorkspaceImpl wsp,
+                             NodeTypeRegistry ntReg,
+                             int uuidBehavior,
+                             ImportConfig config)
+            throws PathNotFoundException, ConstraintViolationException,
+            VersionException, LockException, RepositoryException {
         this.wsp = wsp;
         this.session = (SessionImpl) wsp.getSession();
         this.versionManager = session.getInternalVersionManager();
-        this.ntReg = ntReg;
         this.uuidBehavior = uuidBehavior;
 
         itemOps = new BatchedItemOperations(
@@ -128,6 +158,24 @@
 
         parents = new Stack<NodeState>();
         parents.push(importTarget);
+
+        // TODO: TOBEFIXED importer doesn't yet pass protected items to the configured importers.
+        // for the time being throw exception if an importer is configured that
+        // is expected to work with workspace import.
+        if (config != null) {
+            List<ProtectedNodeImporter> ln = config.getProtectedNodeImporters();
+            for (ProtectedNodeImporter pni : ln) {
+                if (pni.init(session, session, true, uuidBehavior, refTracker)) {
+                    throw new UnsupportedOperationException("Workspace-Import of protected nodes: Not yet implement. ");
+                }
+            }
+            List<ProtectedPropertyImporter> lp = config.getProtectedPropertyImporters();
+            for (ProtectedPropertyImporter ppi : lp) {
+                if (ppi.init(session, session, true, uuidBehavior, refTracker)) {
+                    throw new UnsupportedOperationException("Workspace-Import of protected properties: Not yet implement. ");
+                }
+            }
+        }
     }
 
     /**
@@ -660,7 +708,7 @@
              * adjust references that refer to uuid's which have been mapped to
              * newly gererated uuid's on import
              */
-            Iterator iter = refTracker.getProcessedReferences();
+            Iterator<Object> iter = refTracker.getProcessedReferences();
             while (iter.hasNext()) {
                 PropertyState prop = (PropertyState) iter.next();
                 // being paranoid...

Modified: jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd Thu Oct 22 17:26:37 2009
@@ -586,12 +586,10 @@
 // User Management 
 // -----------------------------------------------------------------------------
 
-[rep:Authorizable] > mix:referenceable
+[rep:Authorizable] > mix:referenceable, nt:hierarchyNode
   abstract
-  + * (rep:Authorizable) = rep:Authorizable protected VERSION
-  + * (rep:AuthorizableFolder) = rep:AuthorizableFolder protected VERSION
+  + * (nt:base) = nt:unstructured VERSION
   - rep:principalName (STRING) protected mandatory
-  - rep:groups (WEAKREFERENCE) protected multiple < 'rep:Group'
   - * (UNDEFINED)
   - * (UNDEFINED) multiple
 
@@ -603,11 +601,12 @@
   - rep:password (STRING) protected mandatory
 
 [rep:Group] > rep:Authorizable
+  - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable'
 
-[rep:AuthorizableFolder] > mix:referenceable
-  + * (rep:Authorizable) = rep:User protected VERSION
-  + * (rep:AuthorizableFolder) = rep:AuthorizableFolder protected VERSION
-
+[rep:AuthorizableFolder] > mix:referenceable, nt:hierarchyNode
+  + * (rep:Authorizable) = rep:User VERSION
+  + * (rep:AuthorizableFolder) = rep:AuthorizableFolder VERSION
+  
 // -----------------------------------------------------------------------------
 // J A C K R A B B I T  R E T E N T I O N  M A N A G E M E N T
 // -----------------------------------------------------------------------------

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AbstractUserTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AbstractUserTest.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AbstractUserTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AbstractUserTest.java Thu Oct 22 17:26:37 2009
@@ -34,7 +34,6 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.Set;
 import java.util.UUID;
 
@@ -45,6 +44,7 @@
 
     protected UserManager userMgr;
 
+    @Override
     protected void setUp() throws Exception {
         super.setUp();
 
@@ -56,6 +56,22 @@
         }
     }
 
+    /**
+     * Conditional save if the userManager expects an extra save() call.
+     * NOTE: only works if changes are made with UserManager affect the passed
+     * session object (i.e. if the Session hold within the UserManager is
+     * the same as the passes Session s.
+     *
+     * @param s
+     * @throws RepositoryException
+     */
+    protected static void save(Session s) throws RepositoryException, NotExecutableException {
+        UserManager umgr = getUserManager(s);
+        if (!umgr.isAutoSave() && s.hasPendingChanges()) {
+            s.save();
+        }
+    }
+
     protected static UserManager getUserManager(Session session) throws RepositoryException, NotExecutableException {
         if (!(session instanceof JackrabbitSession)) {
             throw new NotExecutableException();
@@ -70,7 +86,7 @@
     }
 
     protected static Subject buildSubject(Principal p) {
-        return new Subject(true, Collections.singleton(p), Collections.EMPTY_SET, Collections.EMPTY_SET);
+        return new Subject(true, Collections.singleton(p), Collections.emptySet(), Collections.emptySet());
     }
 
     protected Principal getTestPrincipal() throws RepositoryException {
@@ -79,14 +95,13 @@
     }
 
     protected Principal getTestPrincipal(String name) throws RepositoryException {
-        Principal p = new TestPrincipal(name);
-        return p;
+        return new TestPrincipal(name);
     }
 
     protected String buildPassword(String uid, boolean createDigest) throws IllegalArgumentException {
         if (createDigest) {
             try {
-                StringBuffer password = new StringBuffer();
+                StringBuilder password = new StringBuilder();
                 password.append("{").append(SecurityConstants.DEFAULT_DIGEST).append("}");
                 password.append(Text.digest(SecurityConstants.DEFAULT_DIGEST, uid.getBytes("UTF-8")));
                 return password.toString();
@@ -108,7 +123,7 @@
         return new SimpleCredentials(uID, pw.toCharArray());
     }
 
-    protected static Set getPrincipalSetFromSession(Session session) throws NotExecutableException {
+    protected static Set<Principal> getPrincipalSetFromSession(Session session) throws NotExecutableException {
         if (session instanceof SessionImpl) {
             return ((SessionImpl) session).getSubject().getPrincipals();
         } else {
@@ -123,13 +138,12 @@
             return (User) auth;
         }
         // should never happen. An Session should always have a corresponding User.
-        throw new RepositoryException("Unable to retrieve a User.");
+        throw new NotExecutableException("Unable to retrieve a User.");
     }
 
     protected Group getTestGroup(Session session) throws NotExecutableException, RepositoryException {
-        Set principals = getPrincipalSetFromSession(session);
-        for (Iterator it = principals.iterator(); it.hasNext();) {
-            Authorizable auth = getUserManager(session).getAuthorizable((Principal) it.next());
+        for (Principal principal : getPrincipalSetFromSession(session)) {
+            Authorizable auth = getUserManager(session).getAuthorizable(principal);
             if (auth != null && auth.isGroup()) {
                 return (Group) auth;
             }

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AuthorizableTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AuthorizableTest.java?rev=828791&r1=828790&r2=828791&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AuthorizableTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/AuthorizableTest.java Thu Oct 22 17:26:37 2009
@@ -16,11 +16,11 @@
  */
 package org.apache.jackrabbit.api.security.user;
 
+import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.test.NotExecutableException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.Value;
 import java.security.Principal;
 import java.util.Arrays;
@@ -31,8 +31,6 @@
  */
 public class AuthorizableTest extends AbstractUserTest {
 
-    private static Logger log = LoggerFactory.getLogger(AuthorizableTest.class);
-
     public void testGetId() throws NotExecutableException, RepositoryException {
         User user = getTestUser(superuser);
         assertNotNull(user.getID());
@@ -61,23 +59,26 @@
         Value v = superuser.getValueFactory().createValue("Super User");
         try {
             auth.setProperty(propName, v);
+            save(superuser);
         } catch (RepositoryException e) {
             throw new NotExecutableException("Cannot test 'Authorizable.setProperty'.");
         }
 
         try {
             boolean found = false;
-            for (Iterator it = auth.getPropertyNames(); it.hasNext() && !found;) {
-                found = propName.equals(it.next().toString());
+            for (Iterator<String> it = auth.getPropertyNames(); it.hasNext() && !found;) {
+                found = propName.equals(it.next());
             }
             assertTrue(found);
             assertTrue(auth.hasProperty(propName));
             assertTrue(auth.getProperty(propName).length == 1);
             assertEquals(v, auth.getProperty(propName)[0]);
             assertTrue(auth.removeProperty(propName));
+            save(superuser);
         } finally {
             // try to remove the property again even if previous calls failed.
             auth.removeProperty(propName);
+            save(superuser);
         }
     }
 
@@ -89,22 +90,25 @@
         Value[] v = new Value[] {superuser.getValueFactory().createValue("Super User")};
         try {
             auth.setProperty(propName, v);
+            save(superuser);
         } catch (RepositoryException e) {
             throw new NotExecutableException("Cannot test 'Authorizable.setProperty'.");
         }
 
         try {
             boolean found = false;
-            for (Iterator it = auth.getPropertyNames(); it.hasNext() && !found;) {
-                found = propName.equals(it.next().toString());
+            for (Iterator<String> it = auth.getPropertyNames(); it.hasNext() && !found;) {
+                found = propName.equals(it.next());
             }
             assertTrue(found);
             assertTrue(auth.hasProperty(propName));
             assertEquals(Arrays.asList(v), Arrays.asList(auth.getProperty(propName)));
             assertTrue(auth.removeProperty(propName));
+            save(superuser);
         } finally {
             // try to remove the property again even if previous calls failed.
             auth.removeProperty(propName);
+            save(superuser);
         }
     }
 
@@ -116,19 +120,21 @@
         Value v = superuser.getValueFactory().createValue("Super User");
         try {
             auth.setProperty(propName, v);
+            save(superuser);
         } catch (RepositoryException e) {
             throw new NotExecutableException("Cannot test 'Authorizable.setProperty'.");
         }
 
         try {
-            for (Iterator it = auth.getPropertyNames(); it.hasNext();) {
-                String name = it.next().toString();
+            for (Iterator<String> it = auth.getPropertyNames(); it.hasNext();) {
+                String name = it.next();
                 assertTrue(auth.hasProperty(name));
                 assertNotNull(auth.getProperty(name));
             }
         } finally {
             // try to remove the property again even if previous calls failed.
             auth.removeProperty(propName);
+            save(superuser);
         }
     }
 
@@ -155,12 +161,13 @@
             i++;
         }
         assertFalse(auth.removeProperty(propName));
+        save(superuser);
     }
 
     public void testMemberOf() throws NotExecutableException, RepositoryException {
         Authorizable auth = getTestUser(superuser);
 
-        Iterator it = auth.memberOf();
+        Iterator<Group> it = auth.memberOf();
         while (it.hasNext()) {
             Object group = it.next();
             assertTrue(group instanceof Group);
@@ -170,7 +177,7 @@
     public void testDeclaredMemberOf() throws NotExecutableException, RepositoryException {
         Authorizable auth = getTestUser(superuser);
 
-        Iterator it = auth.declaredMemberOf();
+        Iterator<Group> it = auth.declaredMemberOf();
         while (it.hasNext()) {
             Object group = it.next();
             assertTrue(group instanceof Group);
@@ -179,6 +186,8 @@
 
     /**
      * Removing an authorizable that is still listed as member of a group.
+     * @throws javax.jcr.RepositoryException
+     * @throws org.apache.jackrabbit.test.NotExecutableException
      */
     public void testRemoveListedAuthorizable() throws RepositoryException, NotExecutableException {
         String newUserId = null;
@@ -187,13 +196,16 @@
         try {
             Principal uP = getTestPrincipal();
             User newUser = userMgr.createUser(uP.getName(), uP.getName());
+            save(superuser);
             newUserId = newUser.getID();
 
             newGroup = userMgr.createGroup(getTestPrincipal());
             newGroup.addMember(newUser);
+            save(superuser);
 
             // remove the new user that is still listed as member.
             newUser.remove();
+            save(superuser);
         } finally {
             if (newUserId != null) {
                 Authorizable u = userMgr.getAuthorizable(newUserId);
@@ -207,6 +219,41 @@
             if (newGroup != null) {
                 newGroup.remove();
             }
+            save(superuser);
+        }
+    }
+
+    public void testRecreateUser() throws RepositoryException, NotExecutableException {
+        String id = "bla";
+        Authorizable auth = userMgr.getAuthorizable(id);
+        if (auth == null) {
+            auth = userMgr.createUser(id, id);
+        }
+        auth.remove();
+        save(superuser);
+
+        assertNull(userMgr.getAuthorizable(id));
+
+        // recreate the user using another session.
+        Session s2 = getHelper().getSuperuserSession();
+        User u2 = null;
+        try {
+            UserManager umgr = ((JackrabbitSession) s2).getUserManager();
+            assertNull(umgr.getAuthorizable(id));
+
+            // recreation must succeed
+            u2 = umgr.createUser(id, id);
+            
+            // must be present with both session.
+            assertNotNull(umgr.getAuthorizable(id));
+            assertNotNull(userMgr.getAuthorizable(id));
+
+        } finally {
+            if (u2 != null) {
+                u2.remove();
+                save(s2);
+            }
+            s2.logout();
         }
     }
 }