Return-Path: Delivered-To: apmail-jackrabbit-commits-archive@www.apache.org Received: (qmail 83551 invoked from network); 9 Apr 2008 13:34:14 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 9 Apr 2008 13:34:14 -0000 Received: (qmail 65834 invoked by uid 500); 9 Apr 2008 13:34:15 -0000 Delivered-To: apmail-jackrabbit-commits-archive@jackrabbit.apache.org Received: (qmail 65807 invoked by uid 500); 9 Apr 2008 13:34:15 -0000 Mailing-List: contact commits-help@jackrabbit.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@jackrabbit.apache.org Delivered-To: mailing list commits@jackrabbit.apache.org Received: (qmail 65797 invoked by uid 99); 9 Apr 2008 13:34:15 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 09 Apr 2008 06:34:15 -0700 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 09 Apr 2008 13:33:40 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 8272C1A9838; Wed, 9 Apr 2008 06:33:52 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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/ Date: Wed, 09 Apr 2008 13:33:47 -0000 To: commits@jackrabbit.apache.org From: dpfister@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20080409133352.8272C1A9838@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 ancestor + * is a share ancestor of the item denoted by the given descendant. + * This is true for two nodes A, B + * if either: + *
    + *
  • A is a (proper) ancestor of B
  • + *
  • there is a non-empty sequence of nodes N1,... + * ,Nk such that A= + * N1 and B=Nk + * and Ni is the parent or a share-parent of + * Ni+1 (for every i in 1 + * ...k-1.
  • + *
+ * + * @param nodeId node id + * @param itemId item id + * @return true if the node denoted by ancestor + * is a share ancestor of the item denoted by descendant, + * false 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 ancestor and descendant + * denote the same item, 0 is returned. If ancestor + * does not denote an share-ancestor -1 is returned. + * + * @param ancestor ancestor id + * @param descendant descendant id + * @return the relative depth; -1 if ancestor does + * not denote a share-ancestor of the item denoted by descendant + * (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 NodeIds. If state has no parent, + * array has length 0. + */ + 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 ChildNodeEntry of parent with the * specified uuid or null if there's no such entry. *

@@ -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 @@ * false 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 true if the node has the given shared parent; * false 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: *

    *
  • this node is shareable
  • - *
  • adding this parent would create a share cycle
  • - *
  • whether this parent is already contained in the shared set
  • + *
  • adding the given would create a share cycle
  • + *
  • the given parent is already a share-parent
  • *
* @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 NodeImpl'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 * parentId. The transient item state instance with the given - * parentId itself (if there is such) will not be included. + * parentId itself (if there is such) not be included. *

* 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 true 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