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/09 15:33:47 UTC
svn commit: r646336 - 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 9 06:33:46 2008
New Revision: 646336
URL: http://svn.apache.org/viewvc?rev=646336&view=rev
Log:
JCR-1104 - JSR 283 support
- shareble nodes (work in progress)
- improve share-cycle detection
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.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/ItemManager.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java Wed Apr 9 06:33:46 2008
@@ -310,9 +310,8 @@
// 4. detect share cycle
NodeId srcId = srcState.getNodeId();
NodeId destParentId = destParentState.getNodeId();
- if (destParentId.equals(srcId) ||
- hierMgr.isAncestor(srcId, destParentId)) {
- String msg = "This would create a share cycle.";
+ if (destParentId.equals(srcId) || hierMgr.isAncestor(srcId, destParentId)) {
+ String msg = "Share cycle detected.";
log.debug(msg);
throw new RepositoryException(msg);
}
@@ -546,6 +545,14 @@
// subscript in name element
String msg = safeGetJCRPath(destPath)
+ ": invalid destination path (subscript in name element is not allowed)";
+ log.debug(msg);
+ throw new RepositoryException(msg);
+ }
+
+ HierarchyManagerImpl hierMgr = (HierarchyManagerImpl) this.hierMgr;
+ if (hierMgr.isShareAncestor(target.getNodeId(), destParent.getNodeId())) {
+ String msg = safeGetJCRPath(destPath)
+ + ": invalid destination path (share cycle detected)";
log.debug(msg);
throw new RepositoryException(msg);
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java Wed Apr 9 06:33:46 2008
@@ -93,18 +93,18 @@
* @throws RepositoryException
*/
Name getName(ItemId id) throws ItemNotFoundException, RepositoryException;
-
+
/**
* Returns the name of the specified item, with the given parent id. If the
* given item is not shareable, this is identical to {@link #getName(ItemId)}.
- *
+ *
* @param id node id
* @param parentId parent node id
* @return name
* @throws ItemNotFoundException
* @throws RepositoryException
*/
- Name getName(NodeId id, NodeId parentId)
+ Name getName(NodeId id, NodeId parentId)
throws ItemNotFoundException, RepositoryException;
/**
@@ -154,5 +154,52 @@
* @throws RepositoryException if another error occurs
*/
boolean isAncestor(NodeId nodeId, ItemId itemId)
+ throws ItemNotFoundException, RepositoryException;
+
+ //------------------------------------------- operation with shareable nodes
+
+ /**
+ * Determines whether the node with the specified <code>ancestor</code>
+ * is a share ancestor of the item denoted by the given <code>descendant</code>.
+ * This is <code>true</code> for two nodes <code>A</code>, <code>B</code>
+ * if either:
+ * <ul>
+ * <li><code>A</code> is a (proper) ancestor of <code>B</code></li>
+ * <li>there is a non-empty sequence of nodes <code>N<sub>1</sub></code>,...
+ * ,<code>N<sub>k</sub></code> such that <code>A</code>=
+ * <code>N<sub>1</sub></code> and <code>B</code>=<code>N<sub>k</sub></code>
+ * and <code>N<sub>i</sub></code> is the parent or a share-parent of
+ * <code>N<sub>i+1</sub></code> (for every <code>i</code> in <code>1</code>
+ * ...<code>k-1</code>.</li>
+ * </ul>
+ *
+ * @param nodeId node id
+ * @param itemId item id
+ * @return <code>true</code> if the node denoted by <code>ancestor</code>
+ * is a share ancestor of the item denoted by <code>descendant</code>,
+ * <code>false</code> otherwise
+ * @throws ItemNotFoundException if any of the specified id's does not
+ * denote an existing item.
+ * @throws RepositoryException if another error occurs
+ */
+ boolean isShareAncestor(NodeId ancestor, NodeId descendant)
+ throws ItemNotFoundException, RepositoryException;
+
+ /**
+ * Returns the depth of the specified share-descendant relative to the given
+ * share-ancestor. If <code>ancestor</code> and <code>descendant</code>
+ * denote the same item, <code>0</code> is returned. If <code>ancestor</code>
+ * does not denote an share-ancestor <code>-1</code> is returned.
+ *
+ * @param ancestor ancestor id
+ * @param descendant descendant id
+ * @return the relative depth; <code>-1</code> if <code>ancestor</code> does
+ * not denote a share-ancestor of the item denoted by <code>descendant</code>
+ * (or itself).
+ * @throws ItemNotFoundException if either of the specified id's does not
+ * denote an existing item.
+ * @throws RepositoryException if another error occurs
+ */
+ int getShareRelativeDepth(NodeId ancestorId, ItemId descendantId)
throws ItemNotFoundException, RepositoryException;
}
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=646336&r1=646335&r2=646336&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 9 06:33:46 2008
@@ -16,6 +16,11 @@
*/
package org.apache.jackrabbit.core;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.ItemStateManager;
@@ -216,6 +221,33 @@
}
/**
+ * Return all parents of a node. A shareable node has possibly more than
+ * one parent.
+ *
+ * @param state item state
+ * @return set of parent <code>NodeId</code>s. If state has no parent,
+ * array has length <code>0</code>.
+ */
+ protected Set getParentIds(ItemState state) {
+ if (state.isNode()) {
+ // if this is a node, quickly check whether it is shareable and
+ // whether it contains more than one parent
+ NodeState ns = (NodeState) state;
+ Set s = ns.getSharedSet();
+ if (s.size() > 1) {
+ return s;
+ }
+ }
+ NodeId parentId = getParentId(state);
+ if (parentId != null) {
+ LinkedHashSet s = new LinkedHashSet();
+ s.add(parentId);
+ return s;
+ }
+ return Collections.EMPTY_SET;
+ }
+
+ /**
* Returns the <code>ChildNodeEntry</code> of <code>parent</code> with the
* specified <code>uuid</code> or <code>null</code> if there's no such entry.
* <p/>
@@ -453,7 +485,7 @@
throws ItemNotFoundException, RepositoryException {
NodeState parentState;
-
+
try {
parentState = (NodeState) getItemState(parentId);
} catch (NoSuchItemStateException nsis) {
@@ -475,7 +507,7 @@
}
return entry.getName();
}
-
+
/**
* {@inheritDoc}
*/
@@ -574,5 +606,93 @@
throw new RepositoryException(msg, ise);
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isShareAncestor(NodeId ancestor, NodeId descendant)
+ throws ItemNotFoundException, RepositoryException {
+ if (ancestor.equals(descendant)) {
+ // can't be ancestor of self
+ return false;
+ }
+ try {
+ ItemState state = getItemState(descendant);
+ Set parentIds = getParentIds(state);
+ while (parentIds.size() > 0) {
+ if (parentIds.contains(ancestor)) {
+ return true;
+ }
+ Set grandparentIds = new LinkedHashSet();
+ Iterator iter = parentIds.iterator();
+ while (iter.hasNext()) {
+ NodeId parentId = (NodeId) iter.next();
+ grandparentIds.addAll(getParentIds(getItemState(parentId)));
+ }
+ parentIds = grandparentIds;
+ }
+ // not an ancestor
+ return false;
+ } catch (NoSuchItemStateException nsise) {
+ String msg = "failed to determine degree of relationship of "
+ + ancestor + " and " + descendant;
+ log.debug(msg);
+ throw new ItemNotFoundException(msg, nsise);
+ } catch (ItemStateException ise) {
+ String msg = "failed to determine degree of relationship of "
+ + ancestor + " and " + descendant;
+ log.debug(msg);
+ throw new RepositoryException(msg, ise);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getShareRelativeDepth(NodeId ancestor, ItemId descendant)
+ throws ItemNotFoundException, RepositoryException {
+
+ if (ancestor.equals(descendant)) {
+ return 0;
+ }
+ int depth = 1;
+ try {
+ ItemState state = getItemState(descendant);
+ if (state.hasOverlayedState()) {
+ state = state.getOverlayedState();
+ }
+ Set parentIds = getParentIds(state);
+ while (parentIds.size() > 0) {
+ if (parentIds.contains(ancestor)) {
+ return depth;
+ }
+ depth++;
+ Set grandparentIds = new LinkedHashSet();
+ Iterator iter = parentIds.iterator();
+ while (iter.hasNext()) {
+ NodeId parentId = (NodeId) iter.next();
+ state = getItemState(parentId);
+ if (state.hasOverlayedState()) {
+ state = state.getOverlayedState();
+ }
+ grandparentIds.addAll(getParentIds(state));
+ }
+ parentIds = grandparentIds;
+ }
+ // not an ancestor
+ return -1;
+ } catch (NoSuchItemStateException nsise) {
+ String msg = "failed to determine degree of relationship of "
+ + ancestor + " and " + descendant;
+ log.debug(msg);
+ throw new ItemNotFoundException(msg, nsise);
+ } catch (ItemStateException ise) {
+ String msg = "failed to determine degree of relationship of "
+ + ancestor + " and " + descendant;
+ log.debug(msg);
+ throw new RepositoryException(msg, ise);
+ }
+ }
+
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java Wed Apr 9 06:33:46 2008
@@ -434,7 +434,7 @@
node = (NodeImpl) getItem(id);
if (!node.getParentId().equals(parentId)) {
// verify that parent actually appears in the shared set
- if (!node.hasSharedParent(parentId)) {
+ if (!node.hasShareParent(parentId)) {
String msg = "Node with id '" + id
+ "' does not have shared parent with id: " + parentId;
throw new ItemNotFoundException(msg);
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java Wed Apr 9 06:33:46 2008
@@ -2043,7 +2043,7 @@
// (5) do clone operation
NodeId parentId = getNodeId();
- src.addShare(parentId);
+ src.addShareParent(parentId);
// (6) modify the state of 'this', i.e. the parent node
NodeId srcId = src.getNodeId();
@@ -3181,18 +3181,19 @@
* <code>false</code> otherwise.
* @see NodeState#isShareable()
*/
- protected boolean isShareable() {
+ boolean isShareable() {
return ((NodeState) state).isShareable();
}
/**
* Helper method, returning the parent id this node is attached to. If this
* node is shareable, it returns the primary parent id (which remains
- * fixed). Otherwise returns the underlying state's parent id.
+ * fixed since shareable nodes are not moveable). Otherwise returns the
+ * underlying state's parent id.
*
* @return parent id
*/
- protected NodeId getParentId() {
+ NodeId getParentId() {
if (primaryParentId != null) {
return primaryParentId;
}
@@ -3201,27 +3202,27 @@
/**
* Helper method, returning a flag indicating whether this node has
- * the given shared parent.
+ * the given share-parent.
*
* @param parentId parent id
* @return <code>true</code> if the node has the given shared parent;
* <code>false</code> otherwise.
*/
- protected boolean hasSharedParent(NodeId parentId) {
+ boolean hasShareParent(NodeId parentId) {
return ((NodeState) state).containsShare(parentId);
}
/**
- * Add a parent to the shared set. This method checks first, whether:
+ * Add a share-parent to this node. This method checks, whether:
* <ul>
* <li>this node is shareable</li>
- * <li>adding this parent would create a share cycle</li>
- * <li>whether this parent is already contained in the shared set</li>
+ * <li>adding the given would create a share cycle</li>
+ * <li>the given parent is already a share-parent</li>
* </ul>
* @param parentId parent to add to the shared set
* @throws RepositoryException if an error occurs
*/
- protected void addShare(NodeId parentId) throws RepositoryException {
+ void addShareParent(NodeId parentId) throws RepositoryException {
// verify that we're shareable
if (!isShareable()) {
String msg = "Node at " + safeGetJCRPath() + " is not shareable.";
@@ -3293,8 +3294,10 @@
/**
* Invoked when another node in the same shared set has replaced the
* node state.
+ *
+ * @param state state that is now stored as <code>NodeImpl</code>'s state
*/
- protected void stateReplaced(NodeState state) {
+ void stateReplaced(NodeState state) {
this.state = state;
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java Wed Apr 9 06:33:46 2008
@@ -1556,6 +1556,13 @@
log.debug(msg);
throw new RepositoryException(msg, e);
}
+
+ if (hierMgr.isShareAncestor(targetNode.getNodeId(), destParentNode.getNodeId())) {
+ String msg = destAbsPath + ": invalid destination path (share cycle detected)";
+ log.debug(msg);
+ throw new RepositoryException(msg);
+ }
+
int ind = destName.getIndex();
if (ind > 0) {
// subscript in name element
@@ -1645,7 +1652,7 @@
log.debug(msg);
throw new UnsupportedRepositoryOperationException(msg);
}
-
+
// do move:
// 1. remove child node entry from old parent
NodeState srcParentState =
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java Wed Apr 9 06:33:46 2008
@@ -19,6 +19,7 @@
import org.apache.commons.collections.iterators.IteratorChain;
import org.apache.jackrabbit.core.CachingHierarchyManager;
import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.HierarchyManagerImpl;
import org.apache.jackrabbit.core.ItemId;
import org.apache.jackrabbit.core.NodeId;
import org.apache.jackrabbit.core.PropertyId;
@@ -377,7 +378,7 @@
* Returns an iterator over those transient item state instances that are
* direct or indirect descendants of the item state with the given
* <code>parentId</code>. The transient item state instance with the given
- * <code>parentId</code> itself (if there is such) will not be included.
+ * <code>parentId</code> itself (if there is such) not be included.
* <p/>
* The instances are returned in depth-first tree traversal order.
*
@@ -407,7 +408,7 @@
// determine relative depth: > 0 means it's a descendant
int depth;
try {
- depth = hierMgr.getRelativeDepth(parentId, state.getId());
+ depth = hierMgr.getShareRelativeDepth(parentId, state.getId());
} catch (ItemNotFoundException infe) {
/**
* one of the parents of the specified item has been
@@ -520,7 +521,8 @@
while (iter.hasNext()) {
ItemState state = (ItemState) iter.next();
// determine relative depth: > 0 means it's a descendant
- int depth = zombieHierMgr.getRelativeDepth(parentId, state.getId());
+ //int depth = zombieHierMgr.getRelativeDepth(parentId, state.getId());
+ int depth = zombieHierMgr.getShareRelativeDepth(parentId, state.getId());
if (depth < 1) {
// not a descendant
continue;
Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java?rev=646336&r1=646335&r2=646336&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ShareableNodeTest.java Wed Apr 9 06:33:46 2008
@@ -298,14 +298,14 @@
a2.getNode("b2").remove();
a2.save();
- // verify shareable set contains one element only
+ // verify shared set contains one element only
Node[] shared = getSharedSet(b1);
assertEquals(1, shared.length);
// restore version
a2.restore(v, false);
- // verify shareable set contains two elements again
+ // verify shared set contains again two elements
shared = getSharedSet(b1);
assertEquals(2, shared.length);
}
@@ -496,18 +496,27 @@
workspace.copy(s.getPath(), testRootNode.getPath() + "/d");
// verify source contains shared set with 2 entries
- Node[] shared = getSharedSet(b1);
- assertEquals(2, shared.length);
+ Node[] shared1 = getSharedSet(b1);
+ assertEquals(2, shared1.length);
// verify destination contains shared set with 2 entries
- shared = getSharedSet(testRootNode.getNode("d/a1/b1"));
- assertEquals(2, shared.length);
+ Node[] shared2 = getSharedSet(testRootNode.getNode("d/a1/b1"));
+ assertEquals(2, shared2.length);
+
+ // verify elements in source shared set and destination shared set
+ // don't have the same UUID
+ String srcUUID = shared1[0].getUUID();
+ String destUUID = shared2[0].getUUID();
+ assertFalse(
+ "Source and destination of a copy must not have the same UUID",
+ srcUUID.equals(destUUID));
}
/**
- * Verify that a share cycle is detected (6.13.13).
+ * Verify that a share cycle is detected (6.13.13) when a shareable node
+ * is cloned.
*/
- public void testShareCycle() throws Exception {
+ public void testDetectShareCycleOnClone() throws Exception {
// setup parent nodes and first child
Node a1 = testRootNode.addNode("a1");
Node b1 = a1.addNode("b1");
@@ -523,7 +532,79 @@
// clone underneath b1: this must fail
workspace.clone(workspace.getName(), b1.getPath(),
b1.getPath() + "/c", false);
- fail("Cloning should create a share cycle.");
+ fail("Share cycle not detected on clone.");
+ } catch (RepositoryException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that a share cycle is detected (6.13.13) when a node is moved.
+ */
+ public void testDetectShareCycleOnMove() throws Exception {
+ // setup parent nodes and first child
+ Node a1 = testRootNode.addNode("a1");
+ Node a2 = testRootNode.addNode("a2");
+ Node b1 = a1.addNode("b1");
+ testRootNode.save();
+
+ // add mixin
+ b1.addMixin("mix:shareable");
+ b1.save();
+
+ // clone
+ Workspace workspace = b1.getSession().getWorkspace();
+ workspace.clone(workspace.getName(), b1.getPath(),
+ a2.getPath() + "/b2", false);
+
+ // add child node
+ Node c = b1.addNode("c");
+ b1.save();
+
+ Node[] shared = getSharedSet(b1);
+ assertEquals(2, shared.length);
+
+ // move node
+ try {
+ workspace.move(testRootNode.getPath() + "/a2", c.getPath() + "/d");
+ fail("Share cycle not detected on move.");
+ } catch (RepositoryException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Verify that a share cycle is detected (6.13.13) when a node is
+ * transiently moved.
+ */
+ public void testDetectShareCycleOnTransientMove() throws Exception {
+ // setup parent nodes and first child
+ Node a1 = testRootNode.addNode("a1");
+ Node a2 = testRootNode.addNode("a2");
+ Node b1 = a1.addNode("b1");
+ testRootNode.save();
+
+ // add mixin
+ b1.addMixin("mix:shareable");
+ b1.save();
+
+ // clone
+ Session session = b1.getSession();
+ Workspace workspace = session.getWorkspace();
+ workspace.clone(workspace.getName(), b1.getPath(),
+ a2.getPath() + "/b2", false);
+
+ // add child node
+ Node c = b1.addNode("c");
+ b1.save();
+
+ Node[] shared = getSharedSet(b1);
+ assertEquals(2, shared.length);
+
+ // move node
+ try {
+ session.move(testRootNode.getPath() + "/a2", c.getPath());
+ fail("Share cycle not detected on transient move.");
} catch (RepositoryException e) {
// expected
}
@@ -953,9 +1034,10 @@
}
/**
- * Restore a shareable node and remove an existing shareable node (6.13.19)
- * In this case the particular shared node is removed but its descendants
- * continue to exist below the remaining members of the shared set.
+ * Restore a shareable node that automatically removes an existing shareable
+ * node (6.13.19). In this case the particular shared node is removed but
+ * its descendants continue to exist below the remaining members of the
+ * shared set.
*/
public void testRestoreRemoveExisting() throws Exception {
// setup parent nodes and first child
@@ -1037,42 +1119,6 @@
}
/**
- * Clone a mix:shareable node to the same workspace multiple times, remove
- * all parents and save. Exposes an error that occurred when having more
- * than two members in a shared set and parents were removed in the same
- * order they were created.
- */
- public void testCloneMultipleTimes() throws Exception {
- final int count = 10;
- Node[] parents = new Node[count];
-
- // setup parent nodes and first child
- for (int i = 0; i < parents.length; i++) {
- parents[i] = testRootNode.addNode("a" + (i + 1));
- }
- Node b = parents[0].addNode("b");
- testRootNode.save();
-
- // add mixin
- b.addMixin("mix:shareable");
- b.save();
-
- Workspace workspace = b.getSession().getWorkspace();
-
- // clone to all other nodes
- for (int i = 1; i < parents.length; i++) {
- workspace.clone(workspace.getName(), b.getPath(),
- parents[i].getPath() + "/b", false);
- }
-
- // remove all parents and save
- for (int i = 0; i < parents.length; i++) {
- parents[i].remove();
- }
- testRootNode.save();
- }
-
- /**
* Verify that Node.isSame returns <code>true</code> for shareable nodes
* in the same shared set (6.13.21)
*/
@@ -1244,6 +1290,188 @@
} catch (UnsupportedRepositoryOperationException e) {
// expected
}
+ }
+
+ //----------------------------------------------------- implementation tests
+
+ /**
+ * Verify that invoking save() on a share-ancestor will save changes in
+ * all share-descendants.
+ */
+ public void testRemoveDescendantAndSave() throws Exception {
+ // setup parent nodes and first child
+ Node a1 = testRootNode.addNode("a1");
+ Node a2 = testRootNode.addNode("a2");
+ Node b1 = a1.addNode("b1");
+ testRootNode.save();
+
+ // add mixin
+ b1.addMixin("mix:shareable");
+ b1.save();
+
+ // clone
+ Session session = b1.getSession();
+ Workspace workspace = b1.getSession().getWorkspace();
+ workspace.clone(workspace.getName(), b1.getPath(),
+ a2.getPath() + "/b2", false);
+
+ // add child node c to b1
+ Node c = b1.addNode("c");
+ b1.save();
+
+ // remove child node c
+ c.remove();
+
+ // save a2 (having path /testroot/a2): this should save c as well
+ // since one of the paths to c is /testroot/a2/b2/c
+ a2.save();
+ assertFalse("Saving share-ancestor should save share-descendants",
+ session.hasPendingChanges());
+ }
+
+ /**
+ * Verify that invoking save() on a share-ancestor will save changes in
+ * all share-descendants.
+ */
+ public void testRemoveDescendantAndRemoveShareAndSave() throws Exception {
+ // setup parent nodes and first child
+ Node a1 = testRootNode.addNode("a1");
+ Node a2 = testRootNode.addNode("a2");
+ Node b1 = a1.addNode("b1");
+ testRootNode.save();
+
+ // add mixin
+ b1.addMixin("mix:shareable");
+ b1.save();
+
+ // clone
+ Session session = b1.getSession();
+ Workspace workspace = b1.getSession().getWorkspace();
+ workspace.clone(workspace.getName(), b1.getPath(),
+ a2.getPath() + "/b2", false);
+
+ // add child node c to b1
+ Node c = b1.addNode("c");
+ b1.save();
+
+ // remove child node c
+ c.remove();
+
+ // remove share b2 from a2
+ ((NodeImpl) a2.getNode("b2")).removeShare();
+
+ // save a2 (having path /testroot/a2): this should save c as well
+ // since one of the paths to c was /testroot/a2/b2/c
+ a2.save();
+ assertFalse("Saving share-ancestor should save share-descendants",
+ session.hasPendingChanges());
+ }
+
+ /**
+ * Verify that invoking save() on a share-ancestor will save changes in
+ * all share-descendants.
+ */
+ public void testModifyDescendantAndSave() throws Exception {
+ // setup parent nodes and first child
+ Node a1 = testRootNode.addNode("a1");
+ Node a2 = testRootNode.addNode("a2");
+ Node b1 = a1.addNode("b1");
+ testRootNode.save();
+
+ // add mixin
+ b1.addMixin("mix:shareable");
+ b1.save();
+
+ // clone
+ Workspace workspace = b1.getSession().getWorkspace();
+ workspace.clone(workspace.getName(), b1.getPath(),
+ a2.getPath() + "/b2", false);
+
+ // add child node c to b1
+ Node c = b1.addNode("c");
+ b1.save();
+
+ // add child d to c, this modifies c
+ c.addNode("d");
+
+ // save a2 (having path /testroot/a2): this should save c as well
+ // since one of the paths to c is /testroot/a2/b2/c
+ a2.save();
+ assertFalse("Saving share-ancestor should save share-descendants",
+ c.isModified());
+ }
+
+ /**
+ * Verify that invoking save() on a share-ancestor will save changes in
+ * all share-descendants.
+ */
+ public void testModifyDescendantAndRemoveShareAndSave() throws Exception {
+ // setup parent nodes and first child
+ Node a1 = testRootNode.addNode("a1");
+ Node a2 = testRootNode.addNode("a2");
+ Node b1 = a1.addNode("b1");
+ testRootNode.save();
+
+ // add mixin
+ b1.addMixin("mix:shareable");
+ b1.save();
+
+ // clone
+ Workspace workspace = b1.getSession().getWorkspace();
+ workspace.clone(workspace.getName(), b1.getPath(),
+ a2.getPath() + "/b2", false);
+
+ // add child node c to b1
+ Node c = b1.addNode("c");
+ b1.save();
+
+ // add child d to c, this modifies c
+ c.addNode("d");
+
+ // remove share b2 from a2
+ ((NodeImpl) a2.getNode("b2")).removeShare();
+
+ // save a2 (having path /testroot/a2): this should save c as well
+ // since one of the paths to c was /testroot/a2/b2/c
+ a2.save();
+ assertFalse("Saving share-ancestor should save share-descendants",
+ c.isModified());
+ }
+
+ /**
+ * Clone a mix:shareable node to the same workspace multiple times, remove
+ * all parents and save. Exposes an error that occurred when having more
+ * than two members in a shared set and parents were removed in the same
+ * order they were created.
+ */
+ public void testCloneMultipleTimes() throws Exception {
+ final int count = 10;
+ Node[] parents = new Node[count];
+
+ // setup parent nodes and first child
+ for (int i = 0; i < parents.length; i++) {
+ parents[i] = testRootNode.addNode("a" + (i + 1));
+ }
+ Node b = parents[0].addNode("b");
+ testRootNode.save();
+
+ // add mixin
+ b.addMixin("mix:shareable");
+ b.save();
+
+ Workspace workspace = b.getSession().getWorkspace();
+
+ // clone to all other nodes
+ for (int i = 1; i < parents.length; i++) {
+ workspace.clone(workspace.getName(), b.getPath(),
+ parents[i].getPath() + "/b", false);
+ }
+
+ // remove all parents and save
+ for (int i = 0; i < parents.length; i++) {
+ parents[i].remove();
+ }
+ testRootNode.save();
}
//---------------------------------------------------------- utility methods