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);
+ }
}