You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by dp...@apache.org on 2008/04/23 10:05:10 UTC

svn commit: r650774 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/state/ test/java/org/apache/jackrabbit/core/

Author: dpfister
Date: Wed Apr 23 01:05:08 2008
New Revision: 650774

URL: http://svn.apache.org/viewvc?rev=650774&view=rev
Log:
JCR-1104 - JSR 283 support
- shareble nodes (work in progress)
- simplify HierarchyManager.resolvePath

Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/CachingHierarchyManagerTest.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java?rev=650774&r1=650773&r2=650774&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java Wed Apr 23 01:05:08 2008
@@ -38,9 +38,7 @@
 import javax.jcr.RepositoryException;
 import java.io.PrintStream;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Set;
 
 /**
  * Implementation of a <code>HierarchyManager</code> that caches paths of
@@ -70,11 +68,6 @@
     private final ReferenceMap idCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);
 
     /**
-     * Set of items that were moved
-     */
-    private final Set movedIds = new HashSet();
-
-    /**
      * Cache monitor object
      */
     private final Object cacheMonitor = new Object();
@@ -109,25 +102,52 @@
     }
 
     //-------------------------------------------------< base class overrides >
+
     /**
      * {@inheritDoc}
-     * <p/>
-     * Cache the intermediate item inside our cache.
      */
-    protected void beforeResolvePath(Path path, ItemState state, int next) {
+    protected ItemId resolvePath(Path path, int typesAllowed)
+            throws RepositoryException {
 
-        if (state.isNode() && !isCached(state.getId())) {
-            try {
-                PathBuilder builder = new PathBuilder();
-                Path.Element[] elements = path.getElements();
-                for (int i = 0; i < next; i++) {
-                    builder.addLast(elements[i]);
-                }
-                Path parentPath = builder.getPath();
-                cache(((NodeState) state).getNodeId(), parentPath);
-            } catch (MalformedPathException mpe) {
-                log.warn("Failed to build path of " + state.getId(), mpe);
+        Path pathToNode = path;
+        if ((typesAllowed & RETURN_NODE) == 0) {
+            // if we must not return a node, pass parent path
+            // (since we only cache nodes)
+            pathToNode = path.getAncestor(1);
+        }
+
+        PathMap.Element element = map(pathToNode);
+        if (element == null) {
+            // not even intermediate match: call base class
+            return super.resolvePath(path, typesAllowed);
+        }
+
+        LRUEntry entry = (LRUEntry) element.get();
+        if (element.hasPath(path)) {
+            // exact match: return answer
+            synchronized (cacheMonitor) {
+                entry.touch();
             }
+            return entry.getId();
+        }
+        Path.Element[] elements = path.getElements();
+        try {
+            return resolvePath(elements, element.getDepth() + 1, entry.getId(), typesAllowed);
+        } catch (ItemStateException e) {
+            String msg = "failed to retrieve state of intermediary node";
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void pathResolved(ItemId id, PathBuilder builder)
+            throws MalformedPathException {
+
+        if (id.denotesNode() && !isCached(id)) {
+            cache((NodeId) id, builder.getPath());
         }
     }
 
@@ -171,82 +191,6 @@
     }
 
     //-----------------------------------------------------< HierarchyManager >
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * Check the path indicated inside our cache first.
-     */
-    public ItemId resolvePath(Path path) throws RepositoryException {
-        // Run base class shortcut and sanity checks first
-        if (path.denotesRoot()) {
-            return rootNodeId;
-        } else if (!path.isCanonical()) {
-            String msg = "path is not canonical";
-            log.debug(msg);
-            throw new RepositoryException(msg);
-        }
-
-        ItemId id;
-        PathMap.Element element = map(path);
-        if (element == null) {
-            id = super.resolvePath(path);
-        } else {
-            LRUEntry entry = (LRUEntry) element.get();
-            if (element.hasPath(path)) {
-                synchronized (cacheMonitor) {
-                    entry.touch();
-                }
-                return entry.getId();
-            }
-            // first try to resolve node path, then property path
-            id = super.resolvePath(path, entry.getId(), element.getDepth() + 1, true);
-            if (id == null) {
-                id = super.resolvePath(path, entry.getId(), element.getDepth() + 1, false);
-            }
-        }
-
-        if (id != null && id.denotesNode() && !isCached(id)) {
-            // cache result
-            cache((NodeId) id, path);
-        }
-
-        return id;
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * Check the path indicated inside our cache first.
-     */
-    public NodeId resolveNodePath(Path path) throws RepositoryException {
-        ItemId id = resolvePath(path);
-        return id != null && id.denotesNode() ? (NodeId) id : null;
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p/>
-     * Check the path indicated inside our cache first.
-     */
-    public PropertyId resolvePropertyPath(Path path) throws RepositoryException {
-        // Run base class shortcut and sanity checks first
-        if (path.denotesRoot()) {
-            return null;
-        } else if (!path.isCanonical()) {
-            String msg = "path is not canonical";
-            log.debug(msg);
-            throw new RepositoryException(msg);
-        }
-
-        // check cache for parent path
-        PathMap.Element element = map(path.getAncestor(1));
-        if (element == null) {
-            return super.resolvePropertyPath(path);
-        } else {
-            LRUEntry entry = (LRUEntry) element.get();
-            return (PropertyId) super.resolvePath(path, entry.getId(), element.getDepth() + 1, false);
-        }
-    }
 
     /**
      * {@inheritDoc}
@@ -340,7 +284,9 @@
     /**
      * {@inheritDoc}
      *
-     * Evict moved or renamed items from the cache.
+     * If path information is cached for <code>modified</code>, this iterates
+     * over all child nodes in the path map, evicting the ones that do not
+     * (longer) exist in the underlying <code>NodeState</code>.
      */
     public void nodeModified(NodeState modified) {
         synchronized (cacheMonitor) {
@@ -405,7 +351,7 @@
             if (idCache.containsKey(state.getNodeId())) {
                 try {
                     Path path = PathFactoryImpl.getInstance().create(getPath(state.getNodeId()), name, index, true);
-                    insert(path, id);
+                    nodeAdded(state, path, id);
                 } catch (PathNotFoundException e) {
                     log.warn("Unable to get path of node " + state.getNodeId()
                             + ", event ignored.");
@@ -413,6 +359,8 @@
                     log.warn("Unable to create path of " + id, e);
                 } catch (ItemNotFoundException e) {
                     log.warn("Unable to find item " + state.getNodeId(), e);
+                } catch (ItemStateException e) {
+                    log.warn("Unable to find item " + id, e);
                 } catch (RepositoryException e) {
                     log.warn("Unable to get path of " + state.getNodeId(), e);
                 }
@@ -489,7 +437,7 @@
             if (idCache.containsKey(state.getNodeId())) {
                 try {
                     Path path = PathFactoryImpl.getInstance().create(getPath(state.getNodeId()), name, index, true);
-                    remove(path, id);
+                    nodeRemoved(state, path, id);
                 } catch (PathNotFoundException e) {
                     log.warn("Unable to get path of node " + state.getNodeId()
                             + ", event ignored.");
@@ -557,7 +505,6 @@
             if (idCache.get(id) != null) {
                 return;
             }
-
             if (idCache.size() >= upperLimit) {
                 /**
                  * Remove least recently used item. Scans the LRU list from head to tail
@@ -593,7 +540,7 @@
      * @return <code>true</code> if the item is already cached;
      *         <code>false</code> otherwise
      */
-    private boolean isCached(ItemId id) {
+    boolean isCached(ItemId id) {
         synchronized (cacheMonitor) {
             return idCache.get(id) != null;
         }
@@ -617,6 +564,11 @@
 
     /**
      * Remove item from cache. Index of same name sibling items are shifted!
+     * If <code>removeFromPathCache</code> is <code>true</code>, the path map
+     * element associated with <code>entry</code> is deleted recursively and
+     * every associated element is removed.
+     * If <code>removeFromPathCache</code> is <code>false</code>, only the
+     * LRU entry is removed from the cache.
      *
      * @param entry               LRU entry
      * @param removeFromPathCache whether to remove from path cache
@@ -689,22 +641,30 @@
     }
 
     /**
-     * Insert a node into the cache. This will automatically shift
-     * all indexes of sibling nodes having index greater or equal.
+     * Invoked when a notification about a child node addition has been received.
      *
-     * @param path child path
-     * @param id   node id
+     * @param state node state
+     * @param path  node path
+     * @param id    node id
      *
      * @throws PathNotFoundException if the path was not found
      */
-    private void insert(Path path, ItemId id) throws PathNotFoundException {
+    private void nodeAdded(NodeState state, Path path, NodeId id)
+            throws PathNotFoundException, ItemStateException {
+
         synchronized (cacheMonitor) {
             PathMap.Element element = null;
 
             LRUEntry entry = (LRUEntry) idCache.get(id);
             if (entry != null) {
                 element = entry.getElement();
-                element.remove();
+
+                NodeState child = (NodeState) getItemState(id);
+                if (!child.isShareable()) {
+                    element.remove();
+                } else {
+                    element = null;
+                }
             }
 
             PathMap.Element parent = pathCache.map(path.getAncestor(1), true);
@@ -713,30 +673,36 @@
             }
             if (element != null) {
                 pathCache.put(path, element);
-
-                /* Remember this as a move */
-                movedIds.add(id);
             }
         }
     }
 
     /**
-     * Remove an item from the cache in order to shift the indexes
-     * of items following this item.
+     * Invoked when a notification about a child node removal has been received.
      *
-     * @param path child path
-     * @param id   node id
+     * @param state node state
+     * @param path  node path
+     * @param id    node id
      *
      * @throws PathNotFoundException if the path was not found
      */
-    private void remove(Path path, ItemId id) throws PathNotFoundException {
+    private void nodeRemoved(NodeState state, Path path, NodeId id)
+            throws PathNotFoundException {
+
         synchronized (cacheMonitor) {
-            /* If we remembered this as a move, ignore this event */
-            if (movedIds.remove(id)) {
-                return;
-            }
             PathMap.Element parent = pathCache.map(path.getAncestor(1), true);
             if (parent != null) {
+                // with SNS, this might evict a child that is NOT the one
+                // having <code>id</code>, check first whether item has
+                // the id passed as argument
+                PathMap.Element child = parent.getDescendant(PathFactoryImpl.getInstance().create(
+                        new Path.Element[] { path.getNameElement() }), true);
+                if (child != null) {
+                    LRUEntry entry = (LRUEntry) child.get();
+                    if (entry != null && !entry.getId().equals(id)) {
+                        return;
+                    }
+                }
                 PathMap.Element element = parent.remove(path.getNameElement());
                 if (element != null) {
                     remove(element);

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java?rev=650774&r1=650773&r2=650774&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java Wed Apr 23 01:05:08 2008
@@ -58,6 +58,13 @@
     protected final ItemStateManager provider;
 
     /**
+     * Flags describing what items to return in {@link #resolvePath(Path, int)}.
+     */
+    static final int RETURN_NODE = 1;
+    static final int RETURN_PROPERTY = 2;
+    static final int RETURN_ANY = (RETURN_NODE | RETURN_PROPERTY);
+
+    /**
      * Path resolver for outputting user-friendly paths in error messages.
      */
     protected final PathResolver resolver;
@@ -111,69 +118,63 @@
     }
 
     //-------------------------------------------------------< implementation >
+
     /**
-     * Recursively invoked method that resolves a path into an item id.
+     * Internal implementation that iteratively resolves a path into an item.
      *
-     * @param path  full path of item to resolve
-     * @param state intermediate state
-     * @param next  next path element index to resolve
-     * @param denotesNode flag indicating whether <code>path</code> refers to a
-     *                    node (<code>true</code>) or a property (<code>false</code>)
-     * @return the id of the item denoted by <code>path</code> or
-     *         <code>null</code> if no item exists at <code>path</code>.
-     * @throws ItemStateException if an error occurred
-     */
-    private ItemId resolvePath(Path path, ItemState state, int next,
-                                 boolean denotesNode)
-            throws ItemStateException {
+     * @param elements path elements
+     * @param next index of next item in <code>elements</code> to inspect
+     * @param id id of item at path <code>elements[0]</code>..<code>elements[next - 1]</code>
+     * @param typesAllowed one of <code>RETURN_ANY</code>, <code>RETURN_NODE</code>
+     *                     or <code>RETURN_PROPERTY</code>
+     * @return id or <code>null</code>
+     * @throws ItemStateException if an intermediate item state is not found
+     * @throws MalformedPathException if building an intermediate path fails
+     */
+    protected ItemId resolvePath(Path.Element[] elements, int next,
+                                 ItemId id, int typesAllowed)
+            throws ItemStateException, MalformedPathException {
 
-        // allow subclasses to process intermediate state
-        beforeResolvePath(path, state, next);
-
-        Path.Element[] elements = path.getElements();
-        if (elements.length == next) {
-            return state.getId();
+        PathBuilder builder = new PathBuilder();
+        for (int i = 0; i < next; i++) {
+            builder.addLast(elements[i]);
         }
-        Path.Element elem = elements[next];
-
-        Name name = elem.getName();
-        int index = elem.getIndex();
-        if (index == 0) {
-            index = 1;
-        }
-
-        NodeState parentState = (NodeState) state;
-        if (next == elements.length - 1) {
-            // last path element
-            if (denotesNode) {
-                if (parentState.hasChildNodeEntry(name, index)) {
-                    // child node
-                    NodeState.ChildNodeEntry nodeEntry =
-                            getChildNodeEntry(parentState, name, index);
-                    return nodeEntry.getId();
+        for (int i = next; i < elements.length; i++) {
+            Path.Element elem = elements[i];
+            NodeId parentId = (NodeId) id;
+            id = null;
+
+            Name name = elem.getName();
+            int index = elem.getIndex();
+            if (index == 0) {
+                index = 1;
+            }
+            int typeExpected = typesAllowed;
+            if (i < elements.length - 1) {
+                // intermediate items must always be nodes
+                typeExpected = RETURN_NODE;
+            }
+            NodeState parentState = (NodeState) getItemState(parentId);
+            if ((typeExpected & RETURN_NODE) != 0) {
+                NodeState.ChildNodeEntry nodeEntry =
+                        getChildNodeEntry(parentState, name, index);
+                if (nodeEntry != null) {
+                    id = nodeEntry.getId();
                 }
-            } else {
+            }
+            if (id == null && (typeExpected & RETURN_PROPERTY) != 0) {
                 if (parentState.hasPropertyName(name) && (index <= 1)) {
                     // property
-                    return new PropertyId(parentState.getNodeId(), name);
+                    id = new PropertyId(parentState.getNodeId(), name);
                 }
             }
-            // no such item
-            return null;
-        }
-
-        // intermediate path element
-        ItemId childId;
-        if (parentState.hasChildNodeEntry(name, index)) {
-            // child node
-            NodeState.ChildNodeEntry nodeEntry =
-                    getChildNodeEntry(parentState, name, index);
-            childId = nodeEntry.getId();
-            // recurse
-            return resolvePath(path, getItemState(childId), next + 1, denotesNode);
+            if (id == null) {
+                break;
+            }
+            builder.addLast(elements[i]);
+            pathResolved(id, builder);
         }
-        // no such item
-        return null;
+        return id;
     }
 
     //---------------------------------------------------------< overridables >
@@ -291,48 +292,6 @@
     }
 
     /**
-     * Resolve a path into an item id. Recursively invoked method that may be
-     * overridden by some subclass to either return cached responses or add
-     * response to cache.
-     *
-     * @param path full path of item to resolve
-     * @param id   intermediate item id
-     * @param next next path element index to resolve
-     * @param denotesNode flag indicating whether <code>path</code> refers to a
-     *                    node (<code>true</code>) or a property (<code>false</code>)
-     * @return the id of the item denoted by <code>path</code>
-     * @throws RepositoryException if an error occurred
-     */
-    protected ItemId resolvePath(Path path, ItemId id, int next,
-                                 boolean denotesNode)
-            throws RepositoryException {
-
-        try {
-            return resolvePath(path, getItemState(id), next, denotesNode);
-        } catch (NoSuchItemStateException e) {
-            String msg = "failed to retrieve state of intermediary node";
-            log.debug(msg);
-            throw new RepositoryException(msg, e);
-        } catch (ItemStateException e) {
-            String msg = "failed to retrieve state of intermediary node";
-            log.debug(msg);
-            throw new RepositoryException(msg, e);
-        }
-    }
-
-    /**
-     * Called by recursively invoked method {@link #resolvePath(Path, ItemState, int, boolean)};
-     * May be overridden by some subclass to process/cache intermediate state.
-     *
-     * @param path  full path of item to resolve
-     * @param state intermediate state
-     * @param next  next path element index to resolve
-     */
-    protected void beforeResolvePath(Path path, ItemState state, int next) {
-        // do nothing
-    }
-
-    /**
      * Adds the path element of an item id to the path currently being built.
      * Recursively invoked method that may be overridden by some subclass to
      * either return cached responses or add response to cache. On exit,
@@ -387,39 +346,79 @@
         }
     }
 
+    /**
+     * Internal implementation of {@link #resolvePath(Path)} that will either
+     * resolve to a node or a property. Should be overridden by a subclass
+     * that can resolve an intermediate path into an <code>ItemId</code>. This
+     * subclass can then invoke {@link #resolvePath(Path.Element[], int, ItemId, int)}
+     * with a value of <code>next</code> greater than <code>1</code>.
+     *
+     * @param path path to resolve
+     * @param typesAllowed one of <code>RETURN_ANY</code>, <code>RETURN_NODE</code>
+     *                     or <code>RETURN_PROPERTY</code>
+     * @return id or <code>null</code>
+     * @throws RepositoryException if an error occurs
+     */
+    protected ItemId resolvePath(Path path, int typesAllowed)
+            throws RepositoryException {
+
+        Path.Element[] elements = path.getElements();
+        ItemId id = rootNodeId;
+
+        try {
+            return resolvePath(elements, 1, id, typesAllowed);
+        } catch (ItemStateException e) {
+            String msg = "failed to retrieve state of intermediary node";
+            log.debug(msg);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * Called by {@link #resolvePath(Path.Element[], int, ItemId, int)}.
+     * May be overridden by some subclass to process/cache intermediate state.
+     *
+     * @param id      id of resolved item
+     * @param builder path builder containing path resolved
+     * @throws MalformedPathException if the path contained in <code>builder</code>
+     *                                is malformed
+     */
+    protected void pathResolved(ItemId id, PathBuilder builder)
+            throws MalformedPathException {
+
+        // do nothing
+    }
+
     //-----------------------------------------------------< HierarchyManager >
+
     /**
      * {@inheritDoc}
      */
-    public ItemId resolvePath(Path path) throws RepositoryException {
+    public final ItemId resolvePath(Path path) throws RepositoryException {
         // shortcut
         if (path.denotesRoot()) {
             return rootNodeId;
         }
-
         if (!path.isCanonical()) {
             String msg = "path is not canonical";
             log.debug(msg);
             throw new RepositoryException(msg);
         }
-
-        // first try to resolve node path, then property path
-        ItemId id = resolvePath(path, rootNodeId, 1, true);
-        return (id != null) ? id : resolvePath(path, rootNodeId, 1, false);
+        return resolvePath(path, RETURN_ANY);
     }
 
     /**
      * {@inheritDoc}
      */
     public NodeId resolveNodePath(Path path) throws RepositoryException {
-        return (NodeId) resolvePath(path, rootNodeId, 1, true);
+        return (NodeId) resolvePath(path, RETURN_NODE);
     }
 
     /**
      * {@inheritDoc}
      */
     public PropertyId resolvePropertyPath(Path path) throws RepositoryException {
-        return (PropertyId) resolvePath(path, rootNodeId, 1, false);
+        return (PropertyId) resolvePath(path, RETURN_PROPERTY);
     }
 
     /**
@@ -691,6 +690,4 @@
             throw new RepositoryException(msg, ise);
         }
     }
-
 }
-

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java?rev=650774&r1=650773&r2=650774&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemData.java Wed Apr 23 01:05:08 2008
@@ -134,6 +134,15 @@
     }
 
     /**
+     * Return the parent id of this item.
+     *
+     * @return parent id
+     */
+    public NodeId getParentId() {
+        return getState().getParentId();
+    }
+
+    /**
      * {@inheritDoc}
      */
     public String toString() {

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java?rev=650774&r1=650773&r2=650774&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java Wed Apr 23 01:05:08 2008
@@ -466,6 +466,7 @@
             sharedChildNodeEntries = false;
         }
         childNodeEntries.removeAll();
+        notifyNodesReplaced();
     }
 
     /**

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/CachingHierarchyManagerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/CachingHierarchyManagerTest.java?rev=650774&r1=650773&r2=650774&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/CachingHierarchyManagerTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/CachingHierarchyManagerTest.java Wed Apr 23 01:05:08 2008
@@ -16,6 +16,9 @@
  */
 package org.apache.jackrabbit.core;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+
 import org.apache.jackrabbit.core.nodetype.NodeDefId;
 import org.apache.jackrabbit.core.state.ItemState;
 import org.apache.jackrabbit.core.state.ItemStateException;
@@ -24,9 +27,11 @@
 import org.apache.jackrabbit.core.state.NodeReferences;
 import org.apache.jackrabbit.core.state.NodeReferencesId;
 import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.NodeStateListener;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
 import org.apache.jackrabbit.uuid.UUID;
@@ -90,4 +95,237 @@
 
     };
 
+    /**
+     * Clone a node, cache its path and remove it afterwards. Should remove
+     * the cached path as well as no longer resolve the path.
+     */
+    public void testCloneAndRemove() throws Exception {
+        StaticItemStateManager ism = new StaticItemStateManager();
+        cache = new CachingHierarchyManager(ism.getRootNodeId(), ism, null);
+        ism.setContainer(cache);
+        NodeState a1 = ism.addNode(ism.getRoot(), "a1");
+        NodeState a2 = ism.addNode(ism.getRoot(), "a2");
+        NodeState b1 = ism.addNode(a1, "b1");
+        b1.addShare(b1.getParentId());
+        ism.cloneNode(b1, a2, "b2");
+        ItemId id = cache.resolvePath(toPath("{}\t{}a1\t{}b1"));
+        assertEquals(b1.getId(), id);
+        id = cache.resolvePath(toPath("{}\t{}a2\t{}b2"));
+        ism.removeNode(b1);
+        assertNull("Path no longer valid: /a1/b1",
+                cache.resolvePath(toPath("{}\t{}a1\t{}b1")));
+        ism.removeNode((NodeState) ism.getItemState(id));
+    }
+
+    public void testMove() throws Exception {
+        StaticItemStateManager ism = new StaticItemStateManager();
+        cache = new CachingHierarchyManager(ism.getRootNodeId(), ism, null);
+        ism.setContainer(cache);
+        NodeState a1 = ism.addNode(ism.getRoot(), "a1");
+        NodeState a2 = ism.addNode(ism.getRoot(), "a2");
+        NodeState b1 = ism.addNode(a1, "b1");
+        Path path = cache.getPath(b1.getNodeId());
+        assertEquals("{}\t{}a1\t{}b1", path.toString());
+        ism.moveNode(b1, a2, "b2");
+        path = cache.getPath(b1.getNodeId());
+        assertEquals("{}\t{}a2\t{}b2", path.toString());
+    }
+
+    public void testOrderBefore() throws Exception {
+        StaticItemStateManager ism = new StaticItemStateManager();
+        cache = new CachingHierarchyManager(ism.getRootNodeId(), ism, null);
+        ism.setContainer(cache);
+        NodeState a = ism.addNode(ism.getRoot(), "a");
+        NodeState b1 = ism.addNode(a, "b");
+        NodeState b2 = ism.addNode(a, "b");
+        NodeState b3 = ism.addNode(a, "b");
+        Path path = cache.getPath(b1.getNodeId());
+        assertEquals("{}\t{}a\t{}b", path.toString());
+        ism.orderBefore(b2, b1);
+        ism.orderBefore(b1, b3);
+        path = cache.getPath(b1.getNodeId());
+        assertEquals("{}\t{}a\t{}b[2]", path.toString());
+    }
+
+    public void testRemove() throws Exception {
+        StaticItemStateManager ism = new StaticItemStateManager();
+        cache = new CachingHierarchyManager(ism.getRootNodeId(), ism, null);
+        ism.setContainer(cache);
+        NodeState a = ism.addNode(ism.getRoot(), "a");
+        NodeState b = ism.addNode(a, "b");
+        NodeState c = ism.addNode(b, "c");
+        cache.getPath(c.getNodeId());
+        assertTrue(cache.isCached(c.getId()));
+        ism.removeNode(b);
+        assertFalse(cache.isCached(c.getId()));
+    }
+
+    public void testRename() throws Exception {
+        StaticItemStateManager ism = new StaticItemStateManager();
+        cache = new CachingHierarchyManager(ism.getRootNodeId(), ism, null);
+        ism.setContainer(cache);
+        NodeState a1 = ism.addNode(ism.getRoot(), "a1");
+        NodeState b1 = ism.addNode(a1, "b");
+        NodeState b2 = ism.addNode(a1, "b");
+        Path path = cache.getPath(b1.getNodeId());
+        assertEquals("{}\t{}a1\t{}b", path.toString());
+        path = cache.getPath(b2.getNodeId());
+        assertEquals("{}\t{}a1\t{}b[2]", path.toString());
+        ism.renameNode(b1, "b1");
+        assertTrue(cache.isCached(b1.getNodeId()));
+        assertTrue(cache.isCached(b2.getNodeId()));
+        path = cache.getPath(b1.getNodeId());
+        assertEquals("{}\t{}a1\t{}b1", path.toString());
+    }
+
+    static class StaticItemStateManager implements ItemStateManager {
+
+        private final NodeId rootNodeId;
+        private final HashMap states = new HashMap();
+        private long lsbGenerator;
+        private NodeState root;
+        private NodeStateListener listener;
+
+        public StaticItemStateManager() {
+            rootNodeId = new NodeId(nextUUID());
+        }
+
+        public NodeId getRootNodeId() {
+            return rootNodeId;
+        }
+
+        public NodeState getRoot() {
+            if (root == null) {
+                root = new NodeState(rootNodeId, NameConstants.JCR_ROOT,
+                        null, NodeState.STATUS_EXISTING, false);
+                if (listener != null) {
+                    root.setContainer(listener);
+                }
+            }
+            return root;
+        }
+
+        public void setContainer(NodeStateListener listener) {
+            this.listener = listener;
+        }
+
+        public NodeState addNode(NodeState parent, String name) {
+            NodeId id = new NodeId(nextUUID());
+            NodeState child = new NodeState(id, NameConstants.NT_UNSTRUCTURED,
+                    parent.getNodeId(), NodeState.STATUS_EXISTING, false);
+            if (listener != null) {
+                child.setContainer(listener);
+            }
+            states.put(id, child);
+            parent.addChildNodeEntry(toName(name), child.getNodeId());
+            return child;
+        }
+
+        public void cloneNode(NodeState src, NodeState parent, String name) {
+            src.addShare(parent.getNodeId());
+            parent.addChildNodeEntry(toName(name), src.getNodeId());
+        }
+
+        public void moveNode(NodeState child, NodeState newParent, String name)
+                throws ItemStateException {
+
+            NodeState oldParent = (NodeState) getItemState(child.getParentId());
+            NodeState.ChildNodeEntry cne = oldParent.getChildNodeEntry(child.getNodeId());
+            if (cne == null) {
+                throw new ItemStateException(child.getNodeId().toString());
+            }
+            oldParent.removeChildNodeEntry(cne.getName(), cne.getIndex());
+            child.setParentId(newParent.getNodeId());
+            newParent.addChildNodeEntry(toName(name), child.getNodeId());
+        }
+
+        public void orderBefore(NodeState src, NodeState dest)
+                throws ItemStateException {
+
+            NodeState parent = (NodeState) getItemState(src.getParentId());
+
+            ArrayList list = new ArrayList(parent.getChildNodeEntries());
+
+            int srcIndex = -1, destIndex = -1;
+            for (int i = 0; i < list.size(); i++) {
+                NodeState.ChildNodeEntry cne = (NodeState.ChildNodeEntry) list.get(i);
+                if (cne.getId().equals(src.getId())) {
+                    srcIndex = i;
+                } else if (dest != null && cne.getId().equals(dest.getId())) {
+                    destIndex = i;
+                }
+            }
+            if (destIndex == -1) {
+                list.add(list.remove(srcIndex));
+            } else {
+                if (srcIndex < destIndex) {
+                    list.add(destIndex, list.get(srcIndex));
+                    list.remove(srcIndex);
+                } else {
+                    list.add(destIndex, list.remove(srcIndex));
+                }
+            }
+            parent.setChildNodeEntries(list);
+        }
+
+        public void removeNode(NodeState child) throws ItemStateException {
+            NodeState parent = (NodeState) getItemState(child.getParentId());
+            if (child.isShareable()) {
+                if (child.removeShare(parent.getNodeId()) == 0) {
+                    child.setParentId(null);
+                }
+            }
+            parent.removeChildNodeEntry(child.getNodeId());
+        }
+
+        public void renameNode(NodeState child, String newName) throws ItemStateException {
+            NodeState parent = (NodeState) getItemState(child.getParentId());
+            NodeState.ChildNodeEntry cne = parent.getChildNodeEntry(child.getNodeId());
+            if (cne == null) {
+                throw new ItemStateException(child.getNodeId().toString());
+            }
+            parent.renameChildNodeEntry(cne.getName(), cne.getIndex(), toName(newName));
+        }
+
+        private UUID nextUUID() {
+            return new UUID(0, lsbGenerator++);
+        }
+
+        public ItemState getItemState(ItemId id)
+                throws NoSuchItemStateException, ItemStateException {
+
+            if (id.equals(root.getId())) {
+                return root;
+            }
+            ItemState item = (ItemState) states.get(id);
+            if (item == null) {
+                throw new NoSuchItemStateException(id.toString());
+            }
+            return item;
+        }
+
+        public boolean hasItemState(ItemId id) {
+            if (id.equals(root.getId())) {
+                return true;
+            }
+            return states.containsKey(id);
+        }
+
+        public NodeReferences getNodeReferences(NodeReferencesId id)
+                throws NoSuchItemStateException, ItemStateException {
+            return null;
+        }
+
+        public boolean hasNodeReferences(NodeReferencesId id) {
+            return false;
+        }
+    }
+
+    private static Path toPath(String s) {
+        return PathFactoryImpl.getInstance().create(s);
+    }
+
+    private static Name toName(String s) {
+        return NameFactoryImpl.getInstance().create("", s);
+    }
 }