You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2009/09/08 18:09:45 UTC

svn commit: r812570 [14/24] - in /jackrabbit/sandbox/JCR-1456: ./ jackrabbit-api/ jackrabbit-api/src/main/appended-resources/ jackrabbit-api/src/main/appended-resources/META-INF/ jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/ jackrabb...

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java Tue Sep  8 16:09:28 2009
@@ -21,17 +21,16 @@
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.core.ItemImpl;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.ProtectedItemModifier;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.SessionListener;
-import org.apache.jackrabbit.core.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.util.Text;
-import org.apache.commons.collections.map.LRUMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -50,29 +49,185 @@
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.Set;
-import java.util.Map;
+import java.util.Properties;
 
 /**
- * UserManagerImpl
+ * Default implementation of the <code>UserManager</code> interface with the
+ * following characteristics:
+ *
+ * <ul>
+ * <li>Users and Groups are stored in the repository as JCR nodes.</li>
+ * <li>Users are created below {@link UserConstants#USERS_PATH},<br>Groups are
+ * created below {@link UserConstants#GROUPS_PATH}.</li>
+ * <li>In order to structure the users and groups tree and void creating a flat
+ * hierarchy, additional hierarchy nodes of type "rep:AuthorizableFolder" are
+ * introduced.</li>
+ * <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>
+ * <li>By default 2 levels (depth == 2) are created.</li>
+ * <li>Searching authorizables by ID always starts looking at that specific
+ * hierarchy level. 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
+ * a Node with that name, the names of authorizable node and the intermediate
+ * hierarchy nodes are {@link Text#escapeIllegalJcrChars(String) escaped}.</li>
+ * <li>Any intermediate path passed to either
+ * {@link #createUser(String, String, Principal, String) createUser} or
+ * {@link #createGroup(Principal, String) createGroup} are ignored. This allows
+ * to directly find authorizables by ID without having to search or traverse
+ * the complete tree.<br>
+ * See also {@link #PARAM_COMPATIBILE_JR16}.
+ * </li>
+ * </ul>
+ * Example: Creating an non-existing authorizable with ID 'aSmith' would result
+ * in the following structure:
+ * 
+ * <pre>
+ * + rep:security            [nt:unstructured]
+ *   + rep:authorizables     [rep:AuthorizableFolder]
+ *     + rep:users           [rep:AuthorizableFolder]
+ *       + a                 [rep:AuthorizableFolder]
+ *         + aS              [rep:AuthorizableFolder]
+ *           + aSmith        [rep:User]
+ * </pre>
+ *
+ * This <code>UserManager</code> is able to handle the following configuration
+ * options:
+ *
+ * <ul>
+ * <li>{@link #PARAM_COMPATIBILE_JR16}: If the param is present and its
+ * value is <code>true</code> looking up authorizables by ID will use the
+ * <code>NodeResolver</code> if not found otherwise.<br>
+ * If the parameter is missing (or false) users and groups created
+ * with a Jackrabbit repository &lt; v2.0 will not be found any more.<br>
+ * By default this option is disabled.</li>
+ * <li>{@link #PARAM_DEFAULT_DEPTH}: Parameter used to change the number of
+ * levels that are used by default store authorizable nodes.<br>The value is
+ * expected to be a positive integer greater than zero. The default
+ * number of levels is 2.
+ * <p/>
+ * <strong>NOTE:</strong> Changing the default depth once users and groups
+ * have been created in the repository will cause inconsistencies, due to
+ * the fact that the resolution of ID to an authorizable relies on the
+ * structure defined by the default depth.<br>
+ * It is recommended to remove all authorizable nodes that will not be
+ * reachable any more, before this config option is changed.
+ * <ul>
+ * <li>If default depth is increased:<br>
+ * All authorizables on levels &lt; default depth are not reachable any more.</li>
+ * <li>If default depth is decreased:<br>
+ * All authorizables on levels &gt; default depth aren't reachable any more
+ * unless the {@link #PARAM_AUTO_EXPAND_TREE} flag is set to <code>true</code>.</li>
+ * </ul>
+ * </li>
+ * <li>{@link #PARAM_AUTO_EXPAND_TREE}: If this parameter is present and its
+ * value is <code>true</code>, the trees containing user and group nodes will
+ * automatically created additional hierarchy levels if the number of nodes
+ * on a given level exceeds the maximal allowed {@link #PARAM_AUTO_EXPAND_SIZE size}.
+ * <br>By default this option is disabled.</li>
+ * <li>{@link #PARAM_AUTO_EXPAND_SIZE}: This parameter only takes effect
+ * if {@link #PARAM_AUTO_EXPAND_TREE} is enabled.<br>The value is expected to be
+ * a positive long greater than zero. The default value is 1000.</li>
+ * </ul>
  */
-public class UserManagerImpl extends ProtectedItemModifier implements UserManager, UserConstants, SessionListener {
+public class UserManagerImpl extends ProtectedItemModifier
+        implements UserManager, UserConstants, SessionListener {
+
+    /**
+     * Flag to enable a minimal backwards compatibility with Jackrabbit &lt;
+     * v2.0<br>
+     * If the param is present and its value is <code>true</code> looking up
+     * authorizables by ID will use the <code>NodeResolver</code> if not found
+     * otherwise.<br>
+     * If the parameter is missing (or false) users and groups created
+     * with a Jackrabbit repository &lt; v2.0 will not be found any more.<br>
+     * By default this option is disabled.
+     */
+    public static final String PARAM_COMPATIBILE_JR16 = "compatibleJR16";
+
+    /**
+     * Parameter used to change the number of levels that are used by default
+     * store authorizable nodes.<br>The default number of levels is 2.
+     * <p/>
+     * <strong>NOTE:</strong> Changing the default depth once users and groups
+     * have been created in the repository will cause inconsistencies, due to
+     * the fact that the resolution of ID to an authorizable relies on the
+     * structure defined by the default depth.<br>
+     * It is recommended to remove all authorizable nodes that will not be
+     * reachable any more, before this config option is changed.
+     * <ul>
+     * <li>If default depth is increased:<br>
+     * All authorizables on levels &lt; default depth are not reachable any more.</li>
+     * <li>If default depth is decreased:<br>
+     * All authorizables on levels &gt; default depth aren't reachable any more
+     * unless the {@link #PARAM_AUTO_EXPAND_TREE} flag is set to <code>true</code>.</li>
+     * </ul>
+     */
+    public static final String PARAM_DEFAULT_DEPTH = "defaultDepth";
+
+    /**
+     * If this parameter is present and its value is <code>true</code>, the trees
+     * containing user and group nodes will automatically created additional
+     * hierarchy levels if the number of nodes on a given level exceeds the
+     * maximal allowed {@link #PARAM_AUTO_EXPAND_SIZE size}.
+     * <br>By default this option is disabled.
+     */
+    public static final String PARAM_AUTO_EXPAND_TREE = "autoExpandTree";
+
+    /**
+     * This parameter only takes effect if {@link #PARAM_AUTO_EXPAND_TREE} is
+     * enabled.<br>The default value is 1000.
+     */
+    public static final String PARAM_AUTO_EXPAND_SIZE = "autoExpandSize";
 
     private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
 
     private final SessionImpl session;
     private final String adminId;
     private final NodeResolver authResolver;
+    private final IdResolver idResolver;
 
     /**
-     * Simple unmanaged map from authorizableID to nodePath (node representing
-     * the authorizable) used limit the number of calls to the
-     * <code>NodeResolver</code> in order to find authorizable nodes by the
-     * authorizable id.
+     * Flag indicating if {@link #getAuthorizable(String)} should find 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
+     * <code>authResolver</code> if not found otherwise.
      */
-    private final Map idPathMap = new LRUMap(1000);
+    private final boolean compatibleJR16;
 
+    /**
+     * Create a new <code>UserManager</code> with the default configuration.
+     *
+     * @param session
+     * @param adminId
+     * @throws RepositoryException
+     */
     public UserManagerImpl(SessionImpl session, String adminId) throws RepositoryException {
-        super();
+        this(session, adminId, null);
+    }
+
+    /**
+     * Create a new <code>UserManager</code> for the given <code>session</code>.
+     * Currently the following configuration options are respected:
+     *
+     * <ul>
+     * <li>{@link #PARAM_COMPATIBILE_JR16}. By default this option is disabled.</li>
+     * <li>{@link #PARAM_DEFAULT_DEPTH}. The default number of levels is 2.</li>
+     * <li>{@link #PARAM_AUTO_EXPAND_TREE}. By default this option is disabled.</li>
+     * <li>{@link #PARAM_AUTO_EXPAND_SIZE}. The default value is 1000.</li>
+     * </ul>
+     *
+     * See the overall {@link UserManagerImpl introduction} for details.
+     *
+     * @param session
+     * @param adminId
+     * @param config
+     * @throws RepositoryException
+     */
+    public UserManagerImpl(SessionImpl session, String adminId, Properties config) throws RepositoryException {
         this.session = session;
         this.adminId = adminId;
 
@@ -80,10 +235,17 @@
         try {
             nr = new IndexNodeResolver(session, session);
         } catch (RepositoryException e) {
-            log.debug("UserManger: no QueryManager available for workspace '" + session.getWorkspace().getName() + "' -> Use traversing node resolver.");
+            log.debug("UserManager: no QueryManager available for workspace '" + session.getWorkspace().getName() + "' -> Use traversing node resolver.");
             nr = new TraversingNodeResolver(session, session);
         }
         authResolver = nr;
+
+        idResolver = new IdResolver(config);
+        boolean compatMode = false;
+        if (config != null && config.containsKey(PARAM_COMPATIBILE_JR16)) {
+            compatMode = Boolean.parseBoolean(config.get(PARAM_COMPATIBILE_JR16).toString());
+        }
+        compatibleJR16 = compatMode;
     }
 
     //--------------------------------------------------------< UserManager >---
@@ -133,7 +295,7 @@
             } else if (n.isNodeType(NT_REP_GROUP)) {
                return createGroup(n);
             } else {
-                log.warn("Unexpected user nodetype " + n.getPrimaryNodeType().getName());
+                log.debug("Unexpected user nodetype " + n.getPrimaryNodeType().getName());
             }
         }
         return null;
@@ -181,7 +343,6 @@
      * @param userID
      * @param password
      * @see UserManager#createUser(String,String)
-     * @inheritDoc
      */
     public User createUser(String userID, String password) throws RepositoryException {
         return createUser(userID, password, new PrincipalImpl(userID), null);
@@ -192,7 +353,7 @@
      * @param userID
      * @param password
      * @param principal
-     * @param intermediatePath
+     * @param intermediatePath Is always ignored.
      * @return
      * @throws AuthorizableExistsException
      * @throws RepositoryException
@@ -215,53 +376,54 @@
         if (hasAuthorizableOrReferee(principal)) {
             throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists");
         }
+        if (intermediatePath != null) {
+            log.debug("Intermediate path param " + intermediatePath + " is ignored.");
+        }
 
         NodeImpl parent = null;
         try {
-            String parentPath = getParentPath(intermediatePath, getCurrentUserPath());
-            parent = createParentNode(parentPath);
-
-            Name nodeName = session.getQName(Text.escapeIllegalJcrChars(userID));
-            NodeImpl userNode = addNode(parent, nodeName, NT_REP_USER);
+            NodeImpl userNode = (NodeImpl) idResolver.createUserNode(userID);
 
             setProperty(userNode, P_USERID, getValue(userID), true);
             setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
             setProperty(userNode, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
-            parent.save();
+            session.save();
 
             log.debug("User created: " + userID + "; " + userNode.getPath());
             return createUser(userNode);
         } catch (RepositoryException e) {
             // something went wrong -> revert changes and rethrow
-            if (parent != null) {
-                parent.refresh(false);
-                log.debug("Failed to create new User, reverting changes.");
-            }
+            session.refresh(false);
+            log.debug("Failed to create new User, reverting changes.");
             throw e;
         }
     }
 
     /**
-     * Create a new <code>Group</code> with the given <code>groupName</code>.
-     * It will be created below the this UserManager's root Path.<br>
-     * If non-existant elements of the Path will be created as <code>Nodes</code>
-     * of type {@link #NT_REP_AUTHORIZABLE_FOLDER rep:AuthorizableFolder}
-     *
-     * @param principal
-     * @see UserManager#createGroup(Principal);
-     * @inheritDoc
+     * Same as {@link #createGroup(java.security.Principal, String )} where the
+     * intermediate path is <code>null</code>.
+     * @see UserManager#createGroup(Principal)
      */
     public Group createGroup(Principal principal) throws RepositoryException {
         return createGroup(principal, null);
     }
 
     /**
+     * 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>
+     * 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
+     * conflicts with an existing authorizable ID (may happen in cases where
+     * principal name != ID) the principal name is expanded by a suffix;
+     * otherwise the resulting group ID equals the principal name.
      *
      * @param principal
-     * @param intermediatePath
-     * @return
+     * @param intermediatePath Is always ignored.
+     * @return A new group.
      * @throws AuthorizableExistsException
      * @throws RepositoryException
+     * @see UserManager#createGroup(java.security.Principal, String)
      */
     public Group createGroup(Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
         if (!isValidPrincipal(principal)) {
@@ -270,25 +432,24 @@
         if (hasAuthorizableOrReferee(principal)) {
             throw new AuthorizableExistsException("Authorizable for '" + principal.getName() + "' already exists: ");
         }
-
+        if (intermediatePath != null) {
+            log.debug("Intermediate path param " + intermediatePath + " is ignored.");
+        }
+        
         NodeImpl parent = null;
         try {
-            String parentPath = getParentPath(intermediatePath, GROUPS_PATH);
-            parent = createParentNode(parentPath);
-            Name groupID = getGroupId(principal.getName());
+            String groupID = getGroupId(principal.getName());
+            NodeImpl groupNode = (NodeImpl) idResolver.createGroupNode(groupID);
 
-            NodeImpl groupNode = addNode(parent, groupID, NT_REP_GROUP);
             setProperty(groupNode, P_PRINCIPAL_NAME, getValue(principal.getName()));
-            parent.save();
+            session.save();
 
             log.debug("Group created: " + groupID + "; " + groupNode.getPath());
 
             return createGroup(groupNode);
         } catch (RepositoryException e) {
-            if (parent != null) {
-                parent.refresh(false);
-                log.debug("newInstance new Group failed, revert changes on parent");
-            }
+            session.refresh(false);
+            log.debug("newInstance new Group failed, revert changes on parent");
             throw e;
         }
     }
@@ -301,7 +462,7 @@
      * @throws RepositoryException
      */
     boolean hasAuthorizableOrReferee(Principal principal) throws RepositoryException {
-        Set s = new HashSet(2);
+        Set<Name> s = new HashSet<Name>(2);
         s.add(P_PRINCIPAL_NAME);
         s.add(P_REFEREES);
         NodeIterator res = authResolver.findNodes(s, principal.getName(), NT_REP_AUTHORIZABLE, true, 1);
@@ -318,29 +479,34 @@
         node.save();
     }
 
+    void setProtectedProperty(NodeImpl node, Name propName, Value[] values, int type) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
+        setProperty(node, propName, values, type);
+        node.save();
+    }
+
     void removeProtectedItem(ItemImpl item, Node parent) throws RepositoryException, AccessDeniedException, VersionException {
         removeItem(item);
         parent.save();
     }
 
     /**
-     * Escape illegal JCR characters and test if a user exists that has the
-     * principals name as userId, which might happen if userID != principal-name.
+     * 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.
      *
      * @param principalName to be used as hint for the groupid.
      * @return a group id.
      * @throws RepositoryException
      */
-    private Name getGroupId(String principalName) throws RepositoryException {
-        String escHint = Text.escapeIllegalJcrChars(principalName);
+    private String getGroupId(String principalName) throws RepositoryException {
+        String escHint = principalName;
         String groupID = escHint;
         int i = 0;
         while (getAuthorizable(groupID) != null) {
             groupID = escHint + "_" + i;
             i++;
         }
-        return session.getQName(groupID);
+        return groupID;
     }
 
     private Value getValue(String strValue) throws RepositoryException {
@@ -348,10 +514,11 @@
     }
 
     /**
+     * @param userID
      * @return true if the given userID belongs to the administrator user.
      */
     boolean isAdminId(String userID) {
-        return (adminId == null) ? false : adminId.equals(userID);
+        return (adminId != null) && adminId.equals(userID);
     }
 
     /**
@@ -369,7 +536,6 @@
             throw new IllegalArgumentException("User has to be within the User Path");
         }
         User user = doCreateUser(userNode);
-        idPathMap.put(user.getID(), userNode.getPath());
         return user;
     }
 
@@ -395,141 +561,53 @@
      */
     Group createGroup(NodeImpl groupNode) throws RepositoryException {
         Group group = GroupImpl.create(groupNode, this);
-        idPathMap.put(group.getID(), groupNode.getPath());
         return group;
     }
 
     /**
-     * @param userID
+     * Resolve the given <code>userID</code> to an rep:user node in the repository.
+     *
+     * @param userID A valid userID.
      * @return the node associated with the given userID or <code>null</code>.
+     * @throws RepositoryException If an error occurs.
      */
     private NodeImpl getUserNode(String userID) throws RepositoryException {
-        NodeImpl n = null;
-        if (idPathMap.containsKey(userID)) {
-            String path = idPathMap.get(userID).toString();
-            if (session.itemExists(path)) {
-                Item itm = session.getItem(path);
-                // make sure the item really represents the node associated with
-                // the given userID. if not the search below is execute.
-                if (itm.isNode()) {
-                    NodeImpl tmp = (NodeImpl) itm;
-                    if (tmp.isNodeType(NT_REP_USER) && userID.equals(((NodeImpl) itm).getProperty(P_USERID).getString())) {
-                        n = (NodeImpl) itm;
-                    }
-                }
-            }
-        }
-
-        if (n == null) {
-            // clear eventual previous entry
-            idPathMap.remove(userID);
+        NodeImpl n = (NodeImpl) idResolver.findNode(userID, false);
+        if (n == null && compatibleJR16) {
+            // backwards-compatibility with JR < 2.0 user structure that doesn't
+            // allow to determine the auth-path from the id directly.
             // search for it the node belonging to that userID
             n = (NodeImpl) authResolver.findNode(P_USERID, userID, NT_REP_USER);
-        }
+        } // else: no such user -> return null.
         return n;
     }
 
+    /**
+     * Resolve the given <code>groupID</code> to an rep:group node in the repository.
+     *
+     * @param groupID A valid groupID.
+     * @return the node associated with the given userID or <code>null</code>.
+     * @throws RepositoryException If an error occurs.
+     */
     private NodeImpl getGroupNode(String groupID) throws RepositoryException {
-        NodeImpl n = null;
-        if (idPathMap.containsKey(groupID)) {
-            String path = idPathMap.get(groupID).toString();
-            if (session.itemExists(path)) {
-                Item itm = session.getItem(path);
-                // make sure the item really represents the node associated with
-                // the given userID. if not the search below is execute.
-                if (itm.isNode()) {
-                    NodeImpl tmp = (NodeImpl) itm;
-                    if (tmp.isNodeType(NT_REP_GROUP) && groupID.equals(tmp.getName())) {
-                        n = (NodeImpl) itm;
-                    }
-                }
-            }
-        }
-        if (n == null) {
-            // clear eventual previous entry
-            idPathMap.remove(groupID);
-            // search for it the node belonging to that groupID
-            Name nodeName = session.getQName(groupID);
+        NodeImpl n = (NodeImpl) idResolver.findNode(groupID, true);
+        if (n == null && compatibleJR16) {
+            // backwards-compatibility with JR < 2.0 group structure that doesn't
+            // allow to determine the auth-path from the id directly
+            // search for it the node belonging to that groupID.
+            // 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(groupID));
             n = (NodeImpl) authResolver.findNode(nodeName, NT_REP_GROUP);
-        }
+        } // else: no such group -> return null.
         return n;
     }
 
-    /**
-     * @return the path refering to the node associated with the user this
-     * <code>UserManager</code> has been built for.
-     */
-    private String getCurrentUserPath() {
-        // fallback: default user-path
-        String currentUserPath = USERS_PATH;
-        String userId = session.getUserID();
-
-        if (idPathMap.containsKey(userId)) {
-            currentUserPath = idPathMap.get(userId).toString();
-        } else {
-            try {
-                Node n = getUserNode(userId);
-                if (n != null) {
-                    currentUserPath = n.getPath();
-                }
-            } catch (RepositoryException e) {
-                // should never get here
-                log.error("Internal error: unable to build current user path.", e.getMessage());
-            }
-        }
-        return currentUserPath;
-    }
-
     private static boolean isValidPrincipal(Principal principal) {
         return principal != null && principal.getName() != null && principal.getName().length() > 0;
     }
 
-    private static String getParentPath(String hint, String root) {
-        StringBuffer b = new StringBuffer();
-        if (hint == null || !hint.startsWith(root)) {
-            b.append(root);
-        }
-        if (hint != null && hint.length() > 1) {
-            if (!hint.startsWith("/")) {
-                b.append("/");
-            }
-            b.append(hint);
-        }
-        return b.toString();
-    }
-
-    /**
-     * @param path to the authorizable node to be created
-     * @return
-     * @throws RepositoryException
-     */
-    private NodeImpl createParentNode(String path) throws RepositoryException {
-        NodeImpl parent = (NodeImpl) session.getRootNode();
-        String[] elem = path.split("/");
-        for (int i = 0; i < elem.length; i++) {
-            String name = elem[i];
-            if (name.length() < 1) {
-                continue;
-            }
-            Name nName = session.getQName(name);
-            if (!parent.hasNode(nName)) {
-                Name ntName;
-                if (i == 0) {
-                    // rep:security node
-                    ntName = NameConstants.NT_UNSTRUCTURED;
-                } else {
-                    ntName = NT_REP_AUTHORIZABLE_FOLDER;
-                }
-                NodeImpl added = addNode(parent, nName, ntName);
-                parent.save();
-                parent = added;
-            } else {
-                parent = parent.getNode(nName);
-            }
-        }
-        return parent;
-    }
-
     //----------------------------------------------------< SessionListener >---
     /**
      * @see SessionListener#loggingOut(org.apache.jackrabbit.core.SessionImpl)
@@ -542,8 +620,6 @@
      * @see SessionListener#loggedOut(org.apache.jackrabbit.core.SessionImpl)
      */
     public void loggedOut(SessionImpl session) {
-        // clear the map
-        idPathMap.clear();
         // and logout the session unless it is the loggedout session itself.
         if (session != this.session) {
             this.session.logout();
@@ -556,7 +632,7 @@
      */
     private final class AuthorizableIterator implements Iterator {
 
-        private final Set served = new HashSet();
+        private final Set<String> served = new HashSet<String>();
 
         private Authorizable next;
         private NodeIterator authNodeIter;
@@ -621,4 +697,409 @@
             return null;
         }
     }
+
+    //--------------------------------------------------------------------------
+    /**
+     * Inner class creating and finding the JCR nodes corresponding the a given
+     * authorizable ID with the following behavior:
+     * <ul>
+     * <li>Users are created below /rep:security/rep:authorizables/rep:users</li>
+     * <li>Groups are created below /rep:security/rep:authorizables/rep:users</li>
+     * <li>Below each category authorizables are created within a human readable
+     * structure, whose depth is defined by the <code>defaultDepth</code> config
+     * option.<br>
+     * E.g. creating a user node for an ID 'aSmith' would result in the following
+     * structure assuming defaultDepth == 2 is used:
+     * <pre>
+     * + rep:security            [nt:unstructured]
+     *   + rep:authorizables     [rep:AuthorizableFolder]
+     *     + rep:users           [rep:AuthorizableFolder]
+     *       + a                 [rep:AuthorizableFolder]
+     *         + aS              [rep:AuthorizableFolder]
+     * ->        + aSmith        [rep:User]
+     * </pre>
+     * </li>
+     * <li>In case of a user the node name is calculated from the specified UserID
+     * {@link Text#escapeIllegalJcrChars(String) escaping} any illegal JCR chars.
+     * In case of a Group the node name is calculated from the specified principal
+     * name circumventing any conflicts with existing ids and escaping illegal chars.</li>
+     * <li>The names of the intermediate folders are caculated from the leading
+     * chars of the escaped node name.</li>
+     * <li>If the escaped node name is shorter than the <code>defaultDepth</code>
+     * the last char is repeated.<br>
+     * E.g. creating a user node for an ID 'a' would result in the following
+     * structure assuming defaultDepth == 2 is used:
+     * <pre>
+     * + rep:security            [nt:unstructured]
+     *   + rep:authorizables     [rep:AuthorizableFolder]
+     *     + rep:users           [rep:AuthorizableFolder]
+     *       + a                 [rep:AuthorizableFolder]
+     *         + aa              [rep:AuthorizableFolder]
+     * ->        + a             [rep:User]
+     * </pre>
+     * </li>
+     * <li>If the <code>autoExpandTree</code> option is <code>true</code> the
+     * user tree will be automatically expanded using additional levels if
+     * <code>autoExpandSize</code> is exceeded within a given level.</li>
+     * </ul>
+     *
+     * The auto-expansion of the authorizable tree is defined by the following
+     * steps and exceptional cases:
+     * <ul>
+     * <li>As long as <code>autoExpandSize</code> isn't reached authorizable
+     * nodes are created within the structure defined by the
+     * <code>defaultDepth</code>. (see above)</li>
+     * <li>If <code>autoExpandSize</code> is reached additional intermediate
+     * folders will be created.<br>
+     * E.g. creating a user node for an ID 'aSmith1001' would result in the
+     * following structure:
+     * <pre>
+     * + rep:security            [nt:unstructured]
+     *   + rep:authorizables     [rep:AuthorizableFolder]
+     *     + rep:users           [rep:AuthorizableFolder]
+     *       + a                 [rep:AuthorizableFolder]
+     *         + aS              [rep:AuthorizableFolder]
+     *           + aSmith1       [rep:User]
+     *           + aSmith2       [rep:User]
+     *           [...]
+     *           + aSmith1000    [rep:User]
+     * ->        + aSm           [rep:AuthorizableFolder]
+     * ->          + aSmith1001  [rep:User]
+     * </pre>
+     * </li>
+     * <li>Conflicts: In order to prevent any conflicts that would arise from
+     * creating a authorizable node that upon later expansion could conflict
+     * with an authorizable folder, intermediate levels are always created if
+     * the node name equals any of the names reserved for the next level of
+     * folders.<br>
+     * In the example above any attempt to create a user with ID 'aSm' would
+     * result in an intermediate level irrespective if max-size has been
+     * reached or not:
+     * <pre>
+     * + rep:security            [nt:unstructured]
+     *   + rep:authorizables     [rep:AuthorizableFolder]
+     *     + rep:users           [rep:AuthorizableFolder]
+     *       + a                 [rep:AuthorizableFolder]
+     *         + aS              [rep:AuthorizableFolder]
+     * ->        + aSm           [rep:AuthorizableFolder]
+     * ->          + aSm         [rep:User]
+     * </pre>
+     * </li>
+     * <li>Special case: If the name of the authorizable node to be created is
+     * shorter or equal to the length of the folder at level N, the authorizable
+     * node is created even if max-size has been reached before.<br>
+     * An attempt to create the users 'aS' and 'aSm' in a structure containing
+     * tons of 'aSmith' users will therefore result in:
+     * <pre>
+     * + rep:security            [nt:unstructured]
+     *   + rep:authorizables     [rep:AuthorizableFolder]
+     *     + rep:users           [rep:AuthorizableFolder]
+     *       + a                 [rep:AuthorizableFolder]
+     *         + aS              [rep:AuthorizableFolder]
+     *           + aSmith1       [rep:User]
+     *           + aSmith2       [rep:User]
+     *           [...]
+     *           + aSmith1000    [rep:User]
+     * ->        + aS            [rep:User]
+     *           + aSm           [rep:AuthorizableFolder]
+     *             + aSmith1001  [rep:User]
+     * ->          + aSm         [rep:User]
+     * </pre>
+     * </li>
+     * <li>Special case: If <code>autoExpandTree</code> is enabled later on
+     * AND any of the existing authorizable nodes collides with an intermediate
+     * folder to be created the auto-expansion is aborted and the new
+     * authorizable is inserted at the last valid level irrespective of
+     * max-size being reached.
+     * </li>
+     * </ul>
+     *
+     * The configuration options:
+     * <ul>
+     * <li><strong>defaultDepth</strong>:<br>
+     * A positive <code>integer</code> greater than zero defining the depth of
+     * the default structure that is always created.<br>
+     * Default value: 2</li>
+     * <li><strong>autoExpandTree</strong>:<br>
+     * <code>boolean</code> defining if the tree gets automatically expanded
+     * if within a level the maximum number of child nodes is reached.<br>
+     * Default value: <code>false</code></li>
+     * <li><strong>autoExpandSize</strong>:<br>
+     * A positive <code>long</code> greater than zero defining the maximum
+     * number of child nodes that are allowed at a given level.<br>
+     * Default value: 1000<br>
+     * NOTE: that total number of child nodes may still be greater that
+     * autoExpandSize.</li>
+     * </ul>
+     */
+    private class IdResolver {
+
+        private static final String DELIMITER = "/";
+        private static final int DEFAULT_DEPTH = 2;
+        private static final long DEFAULT_SIZE = 1000;
+        
+        private final int defaultDepth;
+        private final boolean autoExpandTree;
+        // best effort max-size of authorizables per folder. there may be
+        // more nodes created if the editing session isn't allowed to see
+        // all child nodes.
+        private final long autoExpandSize;
+
+        private IdResolver(Properties config) {
+            int d = DEFAULT_DEPTH;
+            boolean expand = false;
+            long size = DEFAULT_SIZE;
+
+            if (config != null) {
+                if (config.containsKey(PARAM_DEFAULT_DEPTH)) {
+                    try {
+                        d = Integer.parseInt(config.get(PARAM_DEFAULT_DEPTH).toString());
+                        if (d <= 0) {
+                           log.warn("Invalid defaultDepth '" + d + "' -> using default.");
+                           d = DEFAULT_DEPTH;
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("Unable to parse defaultDepth config parameter -> using default.", e);
+                    }
+                }
+                if (config.containsKey(PARAM_AUTO_EXPAND_TREE)) {
+                    expand = Boolean.parseBoolean(config.get(PARAM_AUTO_EXPAND_TREE).toString());
+                }
+                if (config.containsKey(PARAM_AUTO_EXPAND_SIZE)) {
+                    try {
+                        size = Integer.parseInt(config.get(PARAM_AUTO_EXPAND_SIZE).toString());
+                        if (expand && size <= 0) {
+                            log.warn("Invalid autoExpandSize '" + size + "' -> using default.");
+                            size = DEFAULT_SIZE;
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("Unable to parse autoExpandSize config parameter -> using default.", e);
+                    }
+                }
+            }
+
+            defaultDepth = d;
+            autoExpandTree = expand;
+            autoExpandSize = size;
+        }
+
+        public Node createUserNode(String userID) throws RepositoryException {
+            return createAuthorizableNode(userID, false);
+        }
+
+        public Node createGroupNode(String groupID) throws RepositoryException {
+            return createAuthorizableNode(groupID, true);
+        }
+
+        public Node findNode(String id, boolean isGroup) throws RepositoryException {
+            String defaultFolderPath = getDefaultFolderPath(id, isGroup);
+            String escapedId = Text.escapeIllegalJcrChars(id);
+
+            if (session.nodeExists(defaultFolderPath)) {
+                Node folder = session.getNode(defaultFolderPath);
+                Name expectedNt = (isGroup) ? NT_REP_GROUP : NT_REP_USER;
+
+                // traverse the potentially existing hierarchy looking for the
+                // authorizable node.
+                int segmLength = defaultDepth +1;
+                while (folder != null) {
+                    if (folder.hasNode(escapedId)) {
+                        NodeImpl aNode = (NodeImpl) folder.getNode(escapedId);
+                        if (aNode.isNodeType(expectedNt)) {
+                            // done. found the right auth-node
+                            return aNode;
+                        } else {
+                            folder = aNode;
+                        }
+                    } else {
+                        // no child node with name 'escapedId' -> look for
+                        // additional levels that may exist.
+                        Node parent = folder;
+                        folder = null;
+                        if (id.length() >= segmLength) {
+                            String folderName = Text.escapeIllegalJcrChars(id.substring(0, segmLength));
+                            if (parent.hasNode(folderName)) {
+                                NodeImpl f = (NodeImpl) parent.getNode(folderName);
+                                if (f.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+                                    folder = f;
+                                } // else: matching node isn't an authorizable-folder
+                            } // else: failed to find a suitable next level
+                        } // else: id is shorter than required length at the current level.
+                    }
+                    segmLength++;
+                }
+            } // else: no node at default-path
+
+            // no matching node found -> authorizable doesn't exist.
+            return null;
+        }
+
+        private Node createAuthorizableNode(String id, boolean isGroup) throws RepositoryException {
+            String escapedId = Text.escapeIllegalJcrChars(id);
+
+            // first create the default folder nodes, that are always present.
+            Node folder = createDefaultFolderNodes(id, escapedId, isGroup);
+            // eventually create additional intermediate folders.
+            folder = createIntermediateFolderNodes(id, escapedId, folder);
+
+            // finally create the authorizable node
+            Name nodeName = session.getQName(escapedId);
+            Name ntName = (isGroup) ? NT_REP_GROUP : NT_REP_USER;
+            Node authNode = addNode((NodeImpl) folder, nodeName, ntName);
+
+            return authNode;
+        }
+
+        private Node createDefaultFolderNodes(String id, String escapedId, boolean isGroup) throws RepositoryException {
+            NodeImpl folder;
+            // first create the levels that are always present -> see #getDefaultFolderPath
+            String defaultPath = getDefaultFolderPath(id, isGroup);
+            if (session.nodeExists(defaultPath)) {
+                folder = (NodeImpl) session.getNode(defaultPath);
+            } else {
+                String[] segmts = defaultPath.split("/");
+                folder = (NodeImpl) session.getRootNode();
+                String repSecurity = SECURITY_ROOT_PATH.substring(1);
+
+                for (String segment : segmts) {
+                    if (segment.length() < 1) {
+                        continue;
+                    }
+                    if (folder.hasNode(segment)) {
+                        folder = (NodeImpl) folder.getNode(segment);
+                    } else {
+                        Name ntName;
+                        if (repSecurity.equals(segment)) {
+                            // rep:security node
+                            ntName = NameConstants.NT_UNSTRUCTURED;
+                        } else {
+                            ntName = NT_REP_AUTHORIZABLE_FOLDER;
+                        }
+                        NodeImpl added = addNode(folder, session.getQName(segment), ntName);
+                        folder = added;
+                    }
+                }
+            }
+
+            // validation check if authorizable to be create doesn't conflict.
+            checkAuthorizableNodeExists(escapedId, folder);
+            return folder;
+        }
+
+        private String getDefaultFolderPath(String id, boolean isGroup) {
+            StringBuilder bld = new StringBuilder();
+            if (isGroup) {
+                bld.append(GROUPS_PATH);
+            } else {
+                bld.append(USERS_PATH);
+            }
+            StringBuilder lastSegment = new StringBuilder(defaultDepth);
+            int idLength = id.length();
+            for (int i = 0; i < defaultDepth; i++) {
+                if (idLength > i) {
+                    lastSegment.append(id.charAt(i));
+                } else {
+                    // escapedID is too short -> append the last char again
+                    lastSegment.append(id.charAt(idLength-1));
+                }
+                bld.append(DELIMITER).append(Text.escapeIllegalJcrChars(lastSegment.toString()));
+            }
+            return bld.toString();
+        }
+
+        private Node createIntermediateFolderNodes(String id, String escapedId, Node folder) throws RepositoryException {
+            if (!autoExpandTree) {
+                // additional folders are never created
+                return folder;
+            }
+
+            // additional folders needs be created if
+            // - the maximal size of child nodes is reached
+            // - if the auth-node to be created potentially collides with any
+            //   of the intermediate nodes.
+            int segmLength = defaultDepth +1;
+            int idLength = id.length();
+
+            while (intermediateFolderNeeded(escapedId, folder)) {
+                String folderName = Text.escapeIllegalJcrChars(id.substring(0, segmLength));
+                if (folder.hasNode(folderName)) {
+                    NodeImpl n = (NodeImpl) folder.getNode(folderName);
+                    // validation check: folder must be of type rep:AuthorizableFolder
+                    // and not an authorizable node.
+                    if (n.isNodeType(NT_REP_AUTHORIZABLE_FOLDER)) {
+                        // expected nodetype -> no violation
+                        folder = n;
+                    } else if (n.isNodeType(NT_REP_AUTHORIZABLE)){
+                        /*
+                         an authorizable node has been created before with the
+                         name of the intermediate folder to be created.
+                         this may only occur if the 'autoExpandTree' option has
+                         been enabled later on.
+                         Resolution:
+                         - abort auto-expanding and create the authorizable
+                           at the current level, ignoring that max-size is reached.
+                         - note, that this behavior has been preferred over tmp.
+                           removing and recreating the colliding authorizable node.
+                        */
+                        log.warn("Auto-expanding aborted. An existing authorizable node '" + n.getName() +"'conflicts with intermediate folder to be created.");
+                        break;
+                    } else {
+                        // 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);
+                    }
+                } else {
+                    // folder doesn't exist nor does another colliding child node.
+                    folder = addNode((NodeImpl) folder, session.getQName(folderName), NT_REP_AUTHORIZABLE_FOLDER);
+                }
+                segmLength++;
+            }
+
+            // final validation check if authorizable to be created doesn't conflict.
+            checkAuthorizableNodeExists(escapedId, folder);
+            return folder;
+        }
+
+        private void checkAuthorizableNodeExists(String nodeName, Node folder) throws AuthorizableExistsException, RepositoryException {
+            if (folder.hasNode(nodeName) &&
+                    ((NodeImpl) folder.getNode(nodeName)).isNodeType(NT_REP_AUTHORIZABLE)) {
+                throw new AuthorizableExistsException("Unable to create Group/User: Collision with existing authorizable.");
+            }
+        }
+
+        private boolean intermediateFolderNeeded(String nodeName, Node folder) throws RepositoryException {
+            // don't create additional intermediate folders for ids that are
+            // shorter or equally long as the folder name. In this case the
+            // MAX_SIZE flag is ignored.
+            if (nodeName.length() <= folder.getName().length()) {
+                return false;
+            }
+
+            // test for potential (or existing) collision in which case the
+            // intermediate node is created irrespective of the MAX_SIZE and the
+            // existing number of children.
+            if (nodeName.length() == folder.getName().length()+1) {
+                // max-size may not yet be reached yet on folder but the node to
+                // be created potentially collides with an intermediate folder.
+                // e.g.:
+                // existing folder structure: a/ab
+                // authID to be created     : abt
+                // OR
+                // existing collition that would result from
+                // existing folder structure: a/ab/abt
+                // authID to be create      : abt
+                return true;
+            }
+
+            // last possibility: max-size is reached.
+            if (folder.getNodes().getSize() >= autoExpandSize) {
+                return true;
+            }
+            
+            // no collision and no need to create an additional intermediate
+            // folder due to max-size reached
+            return false;
+        }
+    }
 }

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java Tue Sep  8 16:09:28 2009
@@ -69,8 +69,8 @@
                  * - either session moved node
                  */
 
-                // compare current transient state with externally modified
-                // overlayed state and determine what has been changed by whom
+                // compare current state with externally modified overlayed
+                // state and determine what has been changed by whom
 
                 // child node entries order
                 if (!state.getReorderedChildNodeEntries().isEmpty()) {

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java Tue Sep  8 16:09:28 2009
@@ -93,14 +93,13 @@
      * @param stateMgr the local item state manager
      * @param ntReg node type registry
      */
-    public SessionItemStateManager(NodeId rootNodeId,
+    protected SessionItemStateManager(NodeId rootNodeId,
                                    LocalItemStateManager stateMgr,
                                    NodeTypeRegistry ntReg) {
         transientStore = new ItemStateMap();
         atticStore = new ItemStateMap();
 
         this.stateMgr = stateMgr;
-        stateMgr.addListener(this);
 
         // create hierarchy manager that uses both transient and persistent state
         hierMgr = new CachingHierarchyManager(rootNodeId, this);
@@ -110,6 +109,24 @@
     }
 
     /**
+     * Creates a new <code>SessionItemStateManager</code> instance.
+     *
+     * @param rootNodeId the root node id
+     * @param stateMgr the local item state manager
+     * @param ntReg node type registry
+     * @return the session item state manager.
+     */
+    public static SessionItemStateManager createInstance(
+            NodeId rootNodeId,
+            LocalItemStateManager stateMgr,
+            NodeTypeRegistry ntReg) {
+        SessionItemStateManager mgr = new SessionItemStateManager(
+                rootNodeId, stateMgr, ntReg);
+        stateMgr.addListener(mgr);
+        return mgr;
+    }
+
+    /**
      * Returns the hierarchy manager
      *
      * @return the hierarchy manager
@@ -313,6 +330,9 @@
      * {@inheritDoc}
      */
     public void dispose() {
+        // remove hierarchy manager as listener to avoid
+        // unnecessary work during stateMgr.dispose()
+        removeListener(hierMgr);
         // discard all transient changes
         disposeAllTransientItemStates();
         // dispose our (i.e. 'local') state manager
@@ -393,13 +413,14 @@
         // the depth is used as array index
         List[] la = new List[10];
         try {
+            HierarchyManager atticAware = getAtticAwareHierarchyMgr();
             Iterator iter = transientStore.values().iterator();
             while (iter.hasNext()) {
                 ItemState state = (ItemState) iter.next();
                 // determine relative depth: > 0 means it's a descendant
                 int depth;
                 try {
-                    depth = hierMgr.getShareRelativeDepth(parentId, state.getId());
+                    depth = atticAware.getShareRelativeDepth(parentId, state.getId());
                 } catch (ItemNotFoundException infe) {
                     /**
                      * one of the parents of the specified item has been

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Tue Sep  8 16:09:28 2009
@@ -148,7 +148,7 @@
      * for referential integrity.
      * Should be changed very carefully by experienced developers only.
      *
-     * @see https://issues.apache.org/jira/browse/JCR-954
+     * @see "https://issues.apache.org/jira/browse/JCR-954"
      */
     private boolean checkReferences = true;
 
@@ -202,13 +202,14 @@
         if (!hasNonVirtualItemState(rootNodeId)) {
             createRootNodeState(rootNodeId, ntReg);
         }
+        ensureActivitiesNode();
     }
 
     /**
      * Enables or disables the referential integrity checking, this
      * should be used very carefully by experienced developers only.
      *
-     * @see https://issues.apache.org/jira/browse/JCR-954
+     * @see "https://issues.apache.org/jira/browse/JCR-954"
      * @param checkReferences whether to do referential integrity checks
      */
     public void setCheckReferences(boolean checkReferences) {
@@ -1295,6 +1296,9 @@
         // add child node entry for virtual jcr:versionStorage
         jcrSystemState.addChildNodeEntry(NameConstants.JCR_VERSIONSTORAGE, RepositoryImpl.VERSION_STORAGE_NODE_ID);
 
+        // add child node entry for virtual jcr:activities
+        jcrSystemState.addChildNodeEntry(NameConstants.JCR_ACTIVITIES, RepositoryImpl.ACTIVITIES_NODE_ID);
+
         // add child node entry for virtual jcr:nodeTypes
         jcrSystemState.addChildNodeEntry(NameConstants.JCR_NODETYPES, RepositoryImpl.NODETYPES_NODE_ID);
 
@@ -1312,6 +1316,28 @@
     }
 
     /**
+     * Makes sure child node entry for mandatory jcr:activities exist.
+     * Repositories upgraded from 1.x do not have it.
+     * <p/>
+     * This method assumes that the jcr:system node already exists.
+     *
+     * @throws ItemStateException if an error occurs while reading or writing to
+     *                            the persistence manager.
+     */
+    private void ensureActivitiesNode() throws ItemStateException {
+        NodeState jcrSystemState = (NodeState) getNonVirtualItemState(RepositoryImpl.SYSTEM_ROOT_NODE_ID);
+        if (!jcrSystemState.hasChildNodeEntry(RepositoryImpl.ACTIVITIES_NODE_ID)) {
+            jcrSystemState.addChildNodeEntry(NameConstants.JCR_ACTIVITIES, RepositoryImpl.ACTIVITIES_NODE_ID);
+
+            ChangeLog changeLog = new ChangeLog();
+            changeLog.modified(jcrSystemState);
+
+            persistMgr.store(changeLog);
+            changeLog.persisted();
+        }
+    }
+
+    /**
      * Returns the item state for the given id without considering virtual
      * item state providers.
      */

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/CooperativeFileLock.java Tue Sep  8 16:09:28 2009
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.core.util;
 
-import org.apache.jackrabbit.uuid.UUID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -27,6 +26,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Properties;
+import java.util.UUID;
 
 import javax.jcr.RepositoryException;
 

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java Tue Sep  8 16:09:28 2009
@@ -18,8 +18,6 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Binary;
@@ -39,48 +37,6 @@
 abstract class BLOBFileValue implements Binary {
 
     /**
-     * Returns a String representation of this value.
-     *
-     * @return String representation of this value.
-     * @throws RepositoryException
-     */
-    String getString() throws RepositoryException {
-        // TODO: review again. currently the getString method of the JCR Value is delegated to the QValue.
-        InputStream stream = getStream();
-        try {
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-            byte[] buffer = new byte[8192];
-            int read;
-            while ((read = stream.read(buffer)) > 0) {
-                out.write(buffer, 0, read);
-            }
-            byte[] data = out.toByteArray();
-            return new String(data, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException("UTF-8 not supported on this platform", e);
-        } catch (IOException e) {
-            throw new RepositoryException("conversion from stream to string failed", e);
-        } finally {
-            try {
-                if (stream != null) {
-                    stream.close();
-                }
-            } catch (IOException e) {
-                // ignore
-            }
-        }
-    }
-
-    /**
-     * Frees temporarily allocated resources such as temporary file, buffer, etc.
-     * If this <code>BLOBFileValue</code> is backed by a persistent resource
-     * calling this method will have no effect.
-     *
-     * @see #delete(boolean)
-     */
-    abstract void discard();
-
-    /**
      * Deletes the persistent resource backing this <code>BLOBFileValue</code>.
      *
      * @param pruneEmptyParentDirs if <code>true</code>, empty parent directories
@@ -89,12 +45,18 @@
     abstract void delete(boolean pruneEmptyParentDirs);
 
     /**
-     * Checks if this object is immutable.
-     * Immutable objects can not change and can safely copied.
+     * Returns a copy of this BLOB file value. The returned copy may also be
+     * this object. However an implementation must guarantee that the returned
+     * value has state that is independent from this value. Immutable values
+     * can savely return the same value (this object).
+     * <p/>
+     * Specifically, {@link #dispose()} on the returned value must not have an
+     * effect on this value!
      *
-     * @return true if the object is immutable
+     * @return a value that can be used independently from this value.
+     * @throws RepositoryException if an error occur while copying this value.
      */
-    abstract boolean isImmutable();
+    abstract BLOBFileValue copy() throws RepositoryException;
 
     public abstract boolean equals(Object obj);
 
@@ -112,9 +74,6 @@
     }
 
     //-----------------------------------------------------< javax.jcr.Binary >
-    public abstract long getSize();
-
-    public abstract InputStream getStream() throws RepositoryException;
 
     public int read(byte[] b, long position) throws IOException, RepositoryException {
         InputStream in = getStream();
@@ -125,9 +84,4 @@
             in.close();
         }
     }
-
-    public void dispose() {
-        discard();
-    }
-
 }

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java Tue Sep  8 16:09:28 2009
@@ -57,7 +57,7 @@
         // do nothing
     }
 
-    void discard() {
+    public void dispose() {
         // do nothing
     }
 
@@ -65,8 +65,8 @@
         return identifier;
     }
 
-    boolean isImmutable() {
-        return true;
+    BLOBFileValue copy() throws RepositoryException {
+        return this;
     }
 
     public boolean equals(Object obj) {

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInMemory.java Tue Sep  8 16:09:28 2009
@@ -16,14 +16,12 @@
  */
 package org.apache.jackrabbit.core.value;
 
-import org.apache.jackrabbit.uuid.Constants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.jcr.RepositoryException;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 
 /**
@@ -123,14 +121,14 @@
         // the data will be garbage collected
     }
 
-    void discard() {
+    public void dispose() {
         // do nothing
         // this object could still be referenced
         // the data will be garbage collected
     }
 
-    boolean isImmutable() {
-        return true;
+    BLOBFileValue copy() throws RepositoryException {
+        return this;
     }
 
     public long getSize() {
@@ -141,22 +139,13 @@
         return new ByteArrayInputStream(data);
     }
 
-    String getString() throws RepositoryException {
-        try {
-            return new String(data, "UTF-8");
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException("UTF-8 not supported on this platform", e);
-        }
-    }
-
     public String toString() {
         StringBuilder buff = new StringBuilder(PREFIX.length() + 2 * data.length);
         buff.append(PREFIX);
-        char[] hex = Constants.hexDigits;
         for (int i = 0; i < data.length; i++) {
             int c = data[i] & 0xff;
-            buff.append(hex[c >> 4]);
-            buff.append(hex[c & 0xf]);
+            buff.append(Integer.toHexString(c >> 4));
+            buff.append(Integer.toHexString(c & 0xf));
         }
         return buff.toString();
     }

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInResource.java Tue Sep  8 16:09:28 2009
@@ -92,13 +92,12 @@
 
     }
 
-    void discard() {
+    public void dispose() {
         // this instance is not backed by temporarily allocated resource/buffer
     }
 
-    boolean isImmutable() {
-        // delete will modify the state.
-        return false;
+    BLOBFileValue copy() throws RepositoryException {
+        return BLOBInTempFile.getInstance(getStream(), true);
     }
 
     public long getSize() {

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInTempFile.java Tue Sep  8 16:09:28 2009
@@ -92,8 +92,12 @@
      * @param in the stream
      * @param temp
      */
-    static BLOBInTempFile getInstance(InputStream in, boolean temp) throws RepositoryException {
-        return new BLOBInTempFile(in, temp);
+    static BLOBFileValue getInstance(InputStream in, boolean temp) throws RepositoryException {
+        if (temp) {
+            return new RefCountingBLOBFileValue(new BLOBInTempFile(in, temp));
+        } else {
+            return new BLOBInTempFile(in, temp);
+        }
     }
 
     /**
@@ -101,7 +105,7 @@
      *
      * @param file the file
      */
-    static BLOBInTempFile getInstance(File file, boolean temp) throws IOException {
+    static BLOBInTempFile getInstance(File file, boolean temp) {
         return new BLOBInTempFile(file, temp);
     }
 
@@ -111,15 +115,18 @@
         file = null;
     }
 
-    void discard() {
+    public void dispose() {
         if (temp) {
             delete(true);
         }
     }
 
-    boolean isImmutable() {
-        // discard and delete can modify the state.
-        return false;
+    BLOBFileValue copy() throws RepositoryException {
+        if (temp) {
+            return BLOBInTempFile.getInstance(getStream(), temp);
+        } else {
+            return BLOBInTempFile.getInstance(file, temp);
+        }
     }
 
     public long getSize() {

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java Tue Sep  8 16:09:28 2009
@@ -49,6 +49,7 @@
 import org.apache.jackrabbit.spi.commons.value.AbstractQValueFactory;
 import org.apache.jackrabbit.spi.commons.value.QValueValue;
 import org.apache.jackrabbit.util.ISO8601;
+import org.apache.commons.io.IOUtils;
 
 /**
  * <code>InternalValue</code> represents the internal format of a property value.
@@ -115,7 +116,6 @@
             throws ValueFormatException, RepositoryException {
         switch (value.getType()) {
             case PropertyType.BINARY:
-                InternalValue result;
                 BLOBFileValue blob = null;
                 if (value instanceof BinaryValueImpl) {
                     BinaryValueImpl bin = (BinaryValueImpl) value;
@@ -131,10 +131,24 @@
                     }
                 }
                 if (blob == null) {
-                    blob = getBLOBFileValue(store, value.getBinary().getStream(), true);
+                    Binary b = value.getBinary();
+                    boolean dispose = false;
+                    try {
+                        if (b instanceof BLOBFileValue) {
+                            // use as is
+                            blob = (BLOBFileValue) b;
+                        } else {
+                            // create a copy from the stream
+                            dispose = true;
+                            blob = getBLOBFileValue(store, b.getStream(), true);
+                        }
+                    } finally {
+                        if (dispose) {
+                            b.dispose();
+                        }
+                    }
                 }
-                result = new InternalValue(blob);
-                return result;
+                return new InternalValue(blob);
             case PropertyType.BOOLEAN:
                 return create(value.getBoolean());
             case PropertyType.DATE:
@@ -323,7 +337,7 @@
      * @return the internal value
      * @throws RepositoryException
      */
-    static InternalValue create(InputStream value, DataStore store) throws RepositoryException {
+    public static InternalValue create(InputStream value, DataStore store) throws RepositoryException {
         return new InternalValue(getBLOBFileValue(store, value, false));
     }
 
@@ -334,7 +348,7 @@
      * @throws RepositoryException
      */
     public static InternalValue create(InputStream value) throws RepositoryException {
-        return new InternalValue(getBLOBFileValue(null, value, false));
+        return create(value, null);
     }
 
     /**
@@ -432,21 +446,8 @@
             // wrapped value is immutable (and therefore this instance as well)
             return this;
         }
-        BLOBFileValue v = (BLOBFileValue) val;
-        if (v.isImmutable()) {
-            return this;
-        }
-        // return a copy since the wrapped BLOBFileValue instance is mutable
-        InputStream stream = v.getStream();
-        try {
-            return createTemporary(stream);
-        } finally {
-            try {
-                stream.close();
-            } catch (IOException e) {
-                // ignore
-            }
-        }
+        // return a copy of the wrapped BLOBFileValue
+        return new InternalValue(((BLOBFileValue) val).copy());
     }
 
     /**
@@ -639,7 +640,7 @@
      */
     public long getLength() throws RepositoryException {
         if (PropertyType.BINARY == type) {
-            return ((BLOBFileValue) val).getSize();
+            return ((Binary) val).getSize();
         } else {
             return super.getLength();
         }
@@ -650,7 +651,14 @@
      */
     public String getString() throws RepositoryException {
         if (type == PropertyType.BINARY) {
-            return ((BLOBFileValue) val).getString();
+            InputStream stream = getStream();
+            try {
+                return IOUtils.toString(stream, "UTF-8");
+            } catch (IOException e) {
+                throw new RepositoryException("conversion from stream to string failed", e);
+            } finally {
+                IOUtils.closeQuietly(stream);
+            }
         } else if (type == PropertyType.DATE) {
             return ISO8601.format(((Calendar) val));
         } else {
@@ -663,7 +671,7 @@
      */
     public InputStream getStream() throws RepositoryException {
         if (type == PropertyType.BINARY) {
-            return ((BLOBFileValue) val).getStream();
+            return ((Binary) val).getStream();
         } else {
             try {
                 // convert via string
@@ -679,7 +687,9 @@
      */
     public Binary getBinary() throws RepositoryException {
         if (type == PropertyType.BINARY) {
-            return (BLOBFileValue) val;
+            // return an independent copy that can be disposed without
+            // affecting this value
+            return ((BLOBFileValue) val).copy();
         } else {
             try {
                 // convert via string
@@ -697,7 +707,7 @@
     public void discard() {
         if (type == PropertyType.BINARY) {
             BLOBFileValue bfv = (BLOBFileValue) val;
-            bfv.discard();
+            bfv.dispose();
         } else {
             super.discard();
         }

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java Tue Sep  8 16:09:28 2009
@@ -18,39 +18,27 @@
 
 import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.QValueFactory;
-import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
-import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.value.AbstractQValueFactory;
-import org.apache.jackrabbit.util.ISO8601;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
-import javax.jcr.ValueFormatException;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
 import java.io.FileInputStream;
 import java.util.Calendar;
-import java.math.BigDecimal;
 import java.net.URI;
+import java.math.BigDecimal;
 
 /**
- * <code>InternalValueFactory</code>...
+ * <code>InternalValueFactory</code> implements a {@link QValueFactory} that
+ * creates {@link InternalValue} instances for binary values.
  */
 public final class InternalValueFactory extends AbstractQValueFactory {
 
-    /**
-     * logger instance
-     */
-    private static final Logger log = LoggerFactory.getLogger(InternalValueFactory.class);
-
     private static final QValueFactory INSTANCE = new InternalValueFactory(null);
 
     private final DataStore store;
@@ -63,56 +51,6 @@
         return INSTANCE;
     }
 
-    public QValue create(String value, int type) throws ValueFormatException, RepositoryException {
-        if (value == null) {
-            throw new IllegalArgumentException("Cannot create QValue from null value.");
-        }
-        try {
-            switch (type) {
-                case PropertyType.BOOLEAN:
-                    return InternalValue.create(Boolean.valueOf(value).booleanValue());
-                case PropertyType.DATE: {
-                    Calendar cal = ISO8601.parse(value);
-                    if (cal == null) {
-                        throw new ValueFormatException("not a valid date: " + value);
-                    }
-                    return InternalValue.create(cal);
-                }
-                case PropertyType.DOUBLE:
-                    return InternalValue.create(Double.parseDouble(value));
-                case PropertyType.LONG:
-                    return InternalValue.create(Long.parseLong(value));
-                case PropertyType.DECIMAL:
-                    return InternalValue.create(new BigDecimal(value));
-                case PropertyType.PATH:
-                    return InternalValue.create(PathFactoryImpl.getInstance().create(value));
-                case PropertyType.NAME:
-                    return InternalValue.create(NameFactoryImpl.getInstance().create(value));
-                case PropertyType.STRING:
-                    return InternalValue.create(value);
-                case PropertyType.URI:
-                    return InternalValue.create(URI.create(value));
-                case PropertyType.REFERENCE:
-                    return InternalValue.create(new NodeId(value));
-                case PropertyType.WEAKREFERENCE:
-                    return InternalValue.create(new NodeId(value), true);
-                case PropertyType.BINARY:
-                    return InternalValue.create(value.getBytes("UTF-8"));
-                // default: invalid type specified -> see below.
-            }
-        } catch (NumberFormatException ex) {
-            // given String value cannot be converted to Decimal
-            throw new ValueFormatException(ex);
-        } catch (IllegalArgumentException ex) {
-            // given String value cannot be converted to Long/Double/Path/Name
-            throw new ValueFormatException(ex);
-        } catch (UnsupportedEncodingException ex) {
-            throw new RepositoryException(ex);
-        }
-
-        // invalid type specified:
-        throw new IllegalArgumentException("illegal type " + type);    }
-
     public QValue create(Calendar value) throws RepositoryException {
         return InternalValue.create(value);
     }
@@ -150,11 +88,23 @@
     }
 
     public QValue create(InputStream value) throws RepositoryException, IOException {
-        return InternalValue.create(value, store);
+        if (store == null) {
+            return InternalValue.createTemporary(value);
+        } else {
+            return InternalValue.create(value, store);
+        }
     }
 
     public QValue create(File value) throws RepositoryException, IOException {
         InputStream in = new FileInputStream(value);
         return InternalValue.createTemporary(in);
     }
+
+    protected QValue createReference(String ref, boolean weak) {
+        return InternalValue.create(new NodeId(ref), weak);
+    }
+
+    protected QValue createString(String value) {
+        return InternalValue.create(value);
+    }
 }
\ No newline at end of file

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java Tue Sep  8 16:09:28 2009
@@ -21,6 +21,7 @@
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
 import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.value.BinaryImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,7 +60,7 @@
     public Value createValue(QValue qvalue) {
         if (qvalue instanceof InternalValue && PropertyType.BINARY == qvalue.getType()) {
             try {
-                return new BinaryValueImpl(((InternalValue) qvalue).getBLOBFileValue());
+                return new BinaryValueImpl(((InternalValue) qvalue).getBLOBFileValue().copy());
             } catch (RepositoryException e) {
                 // should not get here
                 log.error(e.getMessage(), e);
@@ -68,6 +69,19 @@
         return super.createValue(qvalue);
     }
 
+    public Binary createBinary(InputStream stream) throws RepositoryException {
+        try {
+            QValue value = getQValueFactory().create(stream);
+            if (value instanceof InternalValue) {
+                return ((InternalValue) value).getBLOBFileValue();
+            } else {
+                return new BinaryImpl(stream);
+            }
+        } catch (IOException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
     public Value createValue(Binary binary) {
         try {
             if (binary instanceof BLOBInDataStore) {
@@ -77,6 +91,8 @@
                     // if the value is already in this data store
                     return new BinaryValueImpl(value.getBLOBFileValue());
                 }
+            } else if (binary instanceof BLOBFileValue) {
+                return new BinaryValueImpl(((BLOBFileValue) binary).copy());
             }
             return createValue(binary.getStream());
         } catch (RepositoryException e) {

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/DateVersionSelector.java Tue Sep  8 16:09:28 2009
@@ -16,12 +16,12 @@
  */
 package org.apache.jackrabbit.core.version;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
 import java.util.Calendar;
 
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Name;
+
 /**
  * This Class implements a version selector that selects a version by creation
  * date. The selected version is the latest that is older or equal than the
@@ -57,7 +57,7 @@
      * Creates a <code>DateVersionSelector</code> that will select the latest
      * version of all those that are older than the given date.
      *
-     * @param date
+     * @param date reference date
      */
     public DateVersionSelector(Calendar date) {
         this.date = date;
@@ -67,8 +67,8 @@
      * Creates a <code>DateVersionSelector</code> that will select the latest
      * version of all those that are older than the given date.
      *
-     * @param date
-     * @param returnLatest
+     * @param date reference date
+     * @param returnLatest if <code>true</code> latest is selected
      */
     public DateVersionSelector(Calendar date, boolean returnLatest) {
         this.date = date;
@@ -87,7 +87,7 @@
     /**
      * Sets the date hint
      *
-     * @param date
+     * @param date reference date
      */
     public void setDate(Calendar date) {
         this.date = date;
@@ -107,22 +107,20 @@
      * Sets the flag, if the latest version should be selected, if no
      * version can be found using the given hint.
      *
-     * @param returnLatest
+     * @param returnLatest the <ocde>returnLatest</code> flag
      */
     public void setReturnLatest(boolean returnLatest) {
         this.returnLatest = returnLatest;
     }
 
     /**
+     * {@inheritDoc}
+     *
      * Selects a version from the given version history using the previously
      * assigned hint in the following order: name, label, date, latest.
-     *
-     * @param versionHistory
-     * @return
-     * @throws RepositoryException
      */
-    public Version select(VersionHistory versionHistory) throws RepositoryException {
-        Version selected = null;
+    public InternalVersion select(InternalVersionHistory versionHistory) throws RepositoryException {
+        InternalVersion selected = null;
         if (date != null) {
             selected = DateVersionSelector.selectByDate(versionHistory, date);
         }
@@ -135,21 +133,20 @@
     /**
      * Selects a version by date.
      *
-     * @param history
-     * @param date
+     * @param history history to select from
+     * @param date reference date
      * @return the latest version that is older than the given date date or
      * <code>null</code>
-     * @throws RepositoryException
+     * @throws RepositoryException if an error occurs
      */
-    public static Version selectByDate(VersionHistory history, Calendar date)
+    public static InternalVersion selectByDate(InternalVersionHistory history, Calendar date)
             throws RepositoryException {
         long time = (date != null) ? date.getTimeInMillis() : Long.MAX_VALUE;
         long latestDate = Long.MIN_VALUE;
-        Version latestVersion = null;
-        VersionIterator iter = history.getAllVersions();
-        while (iter.hasNext()) {
-            Version v = iter.nextVersion();
-            if (v.getPredecessors().length == 0) {
+        InternalVersion latestVersion = null;
+        for (Name name: history.getVersionNames()) {
+            InternalVersion v = history.getVersion(name);
+            if (v.isRootVersion()) {
                 // ignore root version
                 continue;
             }

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivity.java Tue Sep  8 16:09:28 2009
@@ -16,12 +16,8 @@
  */
 package org.apache.jackrabbit.core.version;
 
-import java.util.Map;
-
 import javax.jcr.RepositoryException;
 
-import org.apache.jackrabbit.core.id.NodeId;
-
 /**
  * This interface defines the internal activity.
  */
@@ -33,17 +29,17 @@
      * @return the version
      * @throws RepositoryException if an error occurs
      */
-    public InternalVersion getLatestVersion(InternalVersionHistory history)
+    InternalVersion getLatestVersion(InternalVersionHistory history)
             throws RepositoryException;
 
     /**
      * Returns the changeset of this activity.
      * This is the set of versions that are the latest members of this activity
-     * in their respective version histories. the changeset is a map grouped by
-     * the nodeid of the respective histories.
+     * in their respective version histories.
+     *
      * @return the changeset
      * @throws RepositoryException if an error occurs
      */
-    public Map<NodeId, InternalVersion> getChangeSet() throws RepositoryException;
+    VersionSet getChangeSet() throws RepositoryException;
 
 }
\ No newline at end of file

Modified: jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java?rev=812570&r1=812569&r2=812570&view=diff
==============================================================================
--- jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java (original)
+++ jackrabbit/sandbox/JCR-1456/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalActivityImpl.java Tue Sep  8 16:09:28 2009
@@ -33,7 +33,7 @@
 
 /**
  * Implements a internal representation of an activity node.
- * this is only for the {@link XAVersionManager}.
+ * this is only for the {@link InternalXAVersionManager}.
  */
 class InternalActivityImpl extends InternalVersionItemImpl implements InternalActivity {
 
@@ -43,7 +43,7 @@
      * @param node version history node state
      * @throws RepositoryException if an error occurs
      */
-    public InternalActivityImpl(AbstractVersionManager vMgr, NodeStateEx node)
+    public InternalActivityImpl(InternalVersionManagerBase vMgr, NodeStateEx node)
             throws RepositoryException {
         super(vMgr, node);
     }
@@ -156,7 +156,7 @@
     /**
      * {@inheritDoc}
      */
-    public Map<NodeId, InternalVersion> getChangeSet() throws RepositoryException {
+    public VersionSet getChangeSet() throws RepositoryException {
         Map<NodeId, InternalVersion> changeset = new HashMap<NodeId, InternalVersion>();
         if (node.hasProperty(NameConstants.REP_VERSIONS)) {
             for (InternalValue ref: node.getPropertyValues(NameConstants.REP_VERSIONS)) {
@@ -164,6 +164,6 @@
                 changeset.put(v.getVersionHistory().getId(), v);
             }
         }
-        return changeset;
+        return new VersionSet(changeset);
     }
 }
\ No newline at end of file