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/03/28 12:28:58 UTC

svn commit: r642204 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/BatchedItemOperations.java main/java/org/apache/jackrabbit/core/ItemImpl.java test/java/org/apache/jackrabbit/core/ShareableNodeTest.java

Author: dpfister
Date: Fri Mar 28 04:28:50 2008
New Revision: 642204

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

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/ItemImpl.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=642204&r1=642203&r2=642204&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 Fri Mar 28 04:28:50 2008
@@ -1676,6 +1676,7 @@
             EffectiveNodeType ent = getEffectiveNodeType(srcState);
             boolean referenceable = ent.includesNodeType(NameConstants.MIX_REFERENCEABLE);
             boolean versionable = ent.includesNodeType(NameConstants.MIX_VERSIONABLE);
+            boolean shareable = ent.includesNodeType(NameConstants.MIX_SHAREABLE);
             switch (flag) {
                 case COPY:
                     // always create new uuid
@@ -1736,6 +1737,10 @@
             // copy node state
             newState.setMixinTypeNames(srcState.getMixinTypeNames());
             newState.setDefinitionId(srcState.getDefinitionId());
+            if (shareable) {
+                // initialize shared set
+                newState.addShare(destParentId);
+            }
             // copy child nodes
             Iterator iter = srcState.getChildNodeEntries().iterator();
             while (iter.hasNext()) {
@@ -1753,6 +1758,32 @@
                  *
                  * todo FIXME delegate to 'node type instance handler'
                  */
+
+                /**
+                 * If child is shareble and its UUID has already been remapped,
+                 * then simply add a reference to the state with that remapped
+                 * UUID instead of copying the whole subtree.
+                 */
+                if (srcChildState.isShareable()) {
+                    UUID uuid = refTracker.getMappedUUID(srcChildState.getNodeId().getUUID());
+                    if (uuid != null) {
+                        NodeId mappedId = new NodeId(uuid);
+                        if (stateMgr.hasItemState(mappedId)) {
+                            NodeState destState = (NodeState) stateMgr.getItemState(mappedId);
+                            if (!destState.isShareable()) {
+                                String msg = "Remapped child is not shareable.";
+                                throw new ItemStateException(msg);
+                            }
+                            if (!destState.addShare(id)) {
+                                String msg = "Unable to add share to node: " + id;
+                                throw new ItemStateException(msg);
+                            }
+                            stateMgr.store(destState);
+                            newState.addChildNodeEntry(entry.getName(), mappedId);
+                            continue;
+                        }
+                    }
+                }
 
                 // recursive copying of child node
                 NodeState newChildState = copyNodeState(srcChildState, srcChildPath,

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java?rev=642204&r1=642203&r2=642204&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemImpl.java Fri Mar 28 04:28:50 2008
@@ -1177,7 +1177,7 @@
                         NodeId newParentId = nodeState.getParentId();
                         if (oldParentId != null) {
                             if (newParentId == null) {
-                                // node has been removed, add old parent
+                                // node has been removed, add old parents
                                 // to dependencies
                                 if (overlayedState.isShareable()) {
                                     dependentIDs.addAll(overlayedState.getSharedSet());

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=642204&r1=642203&r2=642204&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 Fri Mar 28 04:28:50 2008
@@ -35,243 +35,232 @@
 import org.apache.jackrabbit.test.AbstractJCRTest;
 
 /**
- * Tests features available with shareable nodes. 
+ * Tests features available with shareable nodes.
  */
 public class ShareableNodeTest extends AbstractJCRTest {
 
     //------------------------------------------------------ specification tests
-    
+
     /**
-     * Verifies that Node.getIndex returns the correct index in a shareable 
+     * Verifies that Node.getIndex returns the correct index in a shareable
      * node (6.13).
      */
     public void testGetIndex() throws Exception {
-        // setup parent nodes and first child 
+        // setup parent nodes and first child
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
         a2.addNode("b");
         testRootNode.save();
-        
+
         // add mixin
         b1.addMixin("mix:shareable");
         b1.save();
-        
+
         // clone
         Workspace workspace = b1.getSession().getWorkspace();
-        workspace.clone(workspace.getName(), b1.getPath(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b", true);
 
-        ArrayList list = new ArrayList();
-
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
-        assertEquals(b1.getIndex(), 1);
-        assertEquals(b2.getIndex(), 2);
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
+        // verify indices of nodes b1/b2 in shared set
+        assertEquals(1, b1.getIndex());
+        assertEquals(2, b2.getIndex());
     }
-    
+
     /**
      * Verifies that Node.getName returns the correct name in a shareable node
      * (6.13).
      */
     public void testGetName() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
-        ArrayList list = new ArrayList();
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
-        assertEquals(b1.getName(), "b1");
-        assertEquals(b2.getName(), "b2");
+        // verify names of nodes b1/b2 in shared set
+        assertEquals("b1", b1.getName());
+        assertEquals("b2", b2.getName());
     }
-    
+
     /**
-     * Verifies that Node.getPath returns the correct path in a shareable 
+     * Verifies that Node.getPath returns the correct path in a shareable
      * node (6.13).
      */
     public void testGetPath() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
-        ArrayList list = new ArrayList();
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
-        assertEquals(b1.getPath(), "/testroot/a1/b1");
-        assertEquals(b2.getPath(), "/testroot/a2/b2");
+        // verify paths of nodes b1/b2 in shared set
+        assertEquals("/testroot/a1/b1", b1.getPath());
+        assertEquals("/testroot/a2/b2", b2.getPath());
     }
 
     /**
      * Checks new API Node.getSharedSet() (6.13.1)
      */
     public void testIterateSharedSet() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
-        
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        int items = 0;
-        while (iter.hasNext()) {
-            iter.nextNode();
-            items++;
-        }
-        assertEquals(items, 2);
+
+        // verify shared set contains 2 items
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
     }
 
     /**
      * Adds the mix:shareable mixin to a node (6.13.2).
      */
     public void testAddMixin() throws Exception {
-        // setup parent node and first child 
+        // setup parent node and first child
         Node a = testRootNode.addNode("a");
         Node b = a.addNode("b");
         testRootNode.save();
-        
+
         b.addMixin("mix:shareable");
         b.save();
     }
-    
+
     /**
      * Checks new API Node.removeShare() (6.13.4).
      */
     public void testRemoveShare() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
-        ArrayList list = new ArrayList();
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
-        assertTrue(b1.isSame(b2));
-        assertTrue(b2.isSame(b1));
-        
+        // remove b1 from shared set
         ((NodeImpl) b1).removeShare();
         a1.save();
+
+        // verify shared set of b2 contains only 1 item, namely b2 itself
+        shared = getSharedSet(b2);
+        assertEquals(1, shared.length);
+        assertTrue(shared[0].isSame(b2));
     }
 
     /**
      * Checks new API Node.removeSharedSet() (6.13.4).
      */
     public void testRemoveSharedSet() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
+        // remove shared set
         ((NodeImpl) b1).removeSharedSet();
         testRootNode.save();
+
+        // verify neither a1 nor a2 contain any more children
+        assertFalse(a1.hasNodes());
+        assertFalse(a2.hasNodes());
     }
-    
+
     /**
      * Invokes Node.removeSharedSet(), but saves only one of the parent nodes
      * of the shared set. This doesn't need to be supported according to the
      * specification (6.13.4).
      */
     public void testRemoveSharedSetSaveOneParentOnly() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
+        // remove shared set
         ((NodeImpl) b1).removeSharedSet();
-        
+
         try {
+            // save only one of the parents, should fail
             a1.save();
             fail("Removing a shared set requires saving all parents.");
         } catch (ConstraintViolationException e) {
-            // expected 
+            // expected
         }
     }
 
@@ -280,31 +269,27 @@
      * jcr:uuid (6.13.10).
      */
     public void testSameUUID() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
-        ArrayList list = new ArrayList();
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
+        // verify nodes in a shared set have the same jcr:uuid
         assertTrue(b1.getUUID().equals(b2.getUUID()));
     }
 
@@ -314,55 +299,87 @@
      * one is (6.13.11).
      */
     public void testAddChild() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
-        ArrayList list = new ArrayList();
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
-        
+        // add node to b1, verify b2 is modified as well and contains that child
         b1.addNode("c");
         assertTrue(b2.isModified());
         assertTrue(b2.hasNode("c"));
         b1.save();
     }
-    
+
+    /**
+     * Copy a subtree that contains shareable nodes. Verify that the nodes
+     * newly created are not in the shared set that existed before the copy,
+     * but if two nodes in the source of a copy are in the same shared set, then
+     * the two corresponding nodes in the destination of the copy must also be
+     * in the same shared set (6.13.12).
+     */
+    public void testCopy() throws Exception {
+        // setup parent node and first child
+        Node s = testRootNode.addNode("s");
+        Node a1 = s.addNode("a1");
+        Node a2 = s.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", true);
+
+        // copy source tree to destination
+        workspace.copy(s.getPath(), testRootNode.getPath() + "/d");
+
+        // verify source contains shared set with 2 entries
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+
+        // verify destination contains shared set with 2 entries
+        shared = getSharedSet(testRootNode.getNode("d/a1/b1"));
+        assertEquals(2, shared.length);
+    }
+
     /**
      * Verify that a share cycle is detected (6.13.13).
      */
     public void testShareCycle() throws Exception {
-        // setup parent nodes and first child 
+        // setup parent nodes and first child
         Node a1 = testRootNode.addNode("a1");
         Node b1 = a1.addNode("b1");
         testRootNode.save();
-        
+
         // add mixin
         b1.addMixin("mix:shareable");
         b1.save();
-        
-        // clone underneath b1: this must fail
+
         Workspace workspace = b1.getSession().getWorkspace();
-        
+
         try {
-            workspace.clone(workspace.getName(), b1.getPath(), 
+            // clone underneath b1: this must fail
+            workspace.clone(workspace.getName(), b1.getPath(),
                     b1.getPath() + "/c", true);
             fail("Cloning should create a share cycle.");
         } catch (RepositoryException e) {
@@ -374,95 +391,101 @@
      * Verifies that observation events are sent only once (6.13.15).
      */
     public void testObservation() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
+        // event listener that counts events received
         class EventCounter implements SynchronousEventListener {
-            
+
             private int count;
-            
+
             public void onEvent(EventIterator events) {
                 while (events.hasNext()) {
                     events.nextEvent();
                     count++;
                 }
             }
-            
+
             public int getEventCount() {
                 return count;
             }
-            
+
             public void resetCount() {
                 count = 0;
             }
         };
-        
+
         EventCounter el = new EventCounter();
-        ObservationManager om = superuser.getWorkspace().getObservationManager(); 
+        ObservationManager om = superuser.getWorkspace().getObservationManager();
 
-        om.addEventListener(el, Event.NODE_ADDED, testRootNode.getPath(), 
+        // add node underneath shared set: verify it generates one event only
+        om.addEventListener(el, Event.NODE_ADDED, testRootNode.getPath(),
                 true, null, null, false);
         b1.addNode("c");
         b1.save();
         superuser.getWorkspace().getObservationManager().removeEventListener(el);
-        assertEquals(el.getEventCount(), 1);
-        
+        assertEquals(1, el.getEventCount());
+
+        // remove node underneath shared set: verify it generates one event only
         el.resetCount();
-        om.addEventListener(el, Event.NODE_REMOVED, testRootNode.getPath(), 
+        om.addEventListener(el, Event.NODE_REMOVED, testRootNode.getPath(),
                 true, null, null, false);
         b1.getNode("c").remove();
         b1.save();
         superuser.getWorkspace().getObservationManager().removeEventListener(el);
-        assertEquals(el.getEventCount(), 1);
+        assertEquals(1, el.getEventCount());
 
+        // add property underneath shared set: verify it generates one event only
         el.resetCount();
-        om.addEventListener(el, Event.PROPERTY_ADDED, testRootNode.getPath(), 
+        om.addEventListener(el, Event.PROPERTY_ADDED, testRootNode.getPath(),
                 true, null, null, false);
         b1.setProperty("c", "1");
         b1.save();
         superuser.getWorkspace().getObservationManager().removeEventListener(el);
-        assertEquals(el.getEventCount(), 1);
+        assertEquals(1, el.getEventCount());
 
+        // modify property underneath shared set: verify it generates one event only
         el.resetCount();
-        om.addEventListener(el, Event.PROPERTY_CHANGED, testRootNode.getPath(), 
+        om.addEventListener(el, Event.PROPERTY_CHANGED, testRootNode.getPath(),
                 true, null, null, false);
         b1.setProperty("c", "2");
         b1.save();
         superuser.getWorkspace().getObservationManager().removeEventListener(el);
-        assertEquals(el.getEventCount(), 1);
+        assertEquals(1, el.getEventCount());
 
+        // remove property underneath shared set: verify it generates one event only
         el.resetCount();
-        om.addEventListener(el, Event.PROPERTY_REMOVED, testRootNode.getPath(), 
+        om.addEventListener(el, Event.PROPERTY_REMOVED, testRootNode.getPath(),
                 true, null, null, false);
         b1.getProperty("c").remove();
         b1.save();
         superuser.getWorkspace().getObservationManager().removeEventListener(el);
-        assertEquals(el.getEventCount(), 1);
+        assertEquals(1, el.getEventCount());
     }
-    
+
     /**
      * Verifies that a lock applies to all nodes in a shared set (6.13.16).
      */
     public void testLock() throws Exception {
-        // setup parent nodes and first child 
+        // setup parent nodes and first child
         Node a1 = testRootNode.addNode("a1");
         a1.addMixin("mix:lockable");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
         testRootNode.save();
-        
+
         // add mixin
         b1.addMixin("mix:shareable");
         b1.addMixin("mix:lockable");
@@ -471,114 +494,111 @@
         // add child c
         Node c = b1.addNode("c");
         b1.save();
-        
+
         // clone
         Workspace workspace = b1.getSession().getWorkspace();
-        workspace.clone(workspace.getName(), b1.getPath(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
-        
-        ArrayList list = new ArrayList();
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
-        
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
         // lock shareable node -> all nodes in shared set are locked
         b1.lock(false, true);
-        assertTrue(b2.isLocked());
-        b1.unlock();
-        
+        try {
+            assertTrue(b2.isLocked());
+        } finally {
+            b1.unlock();
+        }
+
         // deep-lock parent -> locks (common) child node
         a1.lock(true, true);
-        assertTrue(c.isLocked());
-        a1.unlock();
+        try {
+            assertTrue(c.isLocked());
+        } finally {
+            a1.unlock();
+        }
     }
-    
+
     /**
      * Clones a mix:shareable node to the same workspace (6.13.20). Verifies
      * that cloning without mix:shareable fails.
      */
     public void testClone() throws Exception {
-        // setup parent nodes and first child 
+        // setup parent nodes and first child
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
         testRootNode.save();
-        
-        // clone (1st attempt, without mix:shareable)
+
         Workspace workspace = b1.getSession().getWorkspace();
+
         try {
-            workspace.clone(workspace.getName(), b1.getPath(), 
+            // clone (1st attempt, without mix:shareable, should fail)
+            workspace.clone(workspace.getName(), b1.getPath(),
                     a2.getPath() + "/b2", true);
             fail("Cloning a node into the same workspace should fail.");
         } catch (RepositoryException e) {
             // expected
         }
-        
+
         // add mixin
         b1.addMixin("mix:shareable");
         b1.save();
-        
+
         // clone (2nd attempt, with mix:shareable)
-        workspace.clone(workspace.getName(), b1.getPath(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
     }
-    
+
     /**
      * Verifies that Node.isSame returns <code>true</code> for shareable nodes
      * in the same shared set (6.13.21)
      */
     public void testIsSame() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
 
-        ArrayList list = new ArrayList();
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
 
-        NodeIterator iter = ((NodeImpl) b1).getSharedSet();
-        while (iter.hasNext()) {
-            list.add(iter.nextNode());
-        }
-        
-        assertEquals(list.size(), 2);
-        b1 = (Node) list.get(0);
-        Node b2 = (Node) list.get(1);
+        // verify b1 is same as b2 (and vice-versa)
         assertTrue(b1.isSame(b2));
         assertTrue(b2.isSame(b1));
     }
-    
+
     /**
      * Removes mix:shareable from a shareable node. This is unsupported in
      * Jackrabbit (6.13.22).
      */
     public void testRemoveMixin() throws Exception {
-        // setup parent node and first child 
+        // setup parent node and first child
         Node a = testRootNode.addNode("a");
         Node b = a.addNode("b");
         testRootNode.save();
-        
+
         // add mixin
         b.addMixin("mix:shareable");
         b.save();
-        
-        // remove mixin
+
         try {
+            // remove mixin
             b.removeMixin("mix:shareable");
             b.save();
             fail("Removing mix:shareable should fail.");
@@ -592,26 +612,26 @@
      * result set (6.13.23)
      */
     public void testSearch() throws Exception {
-        // setup parent nodes and first child 
+        // 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(), 
+        workspace.clone(workspace.getName(), b1.getPath(),
                 a2.getPath() + "/b2", true);
-        
+
         // add new referenceable child
         Node c = b1.addNode("c");
         c.addMixin(mixReferenceable);
         b1.save();
-        
+
         String sql = "SELECT * FROM nt:unstructured WHERE jcr:uuid = '"+c.getUUID()+"'";
         QueryResult res = workspace.getQueryManager().createQuery(sql, Query.SQL).execute();
 
@@ -621,31 +641,31 @@
         while (iter.hasNext()) {
             list.add(iter.nextNode());
         }
-        assertEquals(list.size(), 1);
-        assertTrue(((NodeImpl) list.get(0)).isSame(c));
+        assertEquals(1, list.size());
+        assertTrue(((Node) list.get(0)).isSame(c));
     }
 
     //--------------------------------------------------------- limitation tests
-    
+
     /**
      * Clones a mix:shareable node to the same workspace, with the same
      * parent. This is unsupported in Jackrabbit.
      */
     public void testCloneToSameParent() throws Exception {
-        // setup parent nodes and first child 
+        // setup parent nodes and first child
         Node a = testRootNode.addNode("a");
         Node b1 = a.addNode("b1");
         testRootNode.save();
-        
+
         // add mixin
         b1.addMixin("mix:shareable");
         b1.save();
-        
-        // clone
+
         Workspace workspace = b1.getSession().getWorkspace();
-        
+
         try {
-            workspace.clone(workspace.getName(), b1.getPath(), 
+            // clone to same parent
+            workspace.clone(workspace.getName(), b1.getPath(),
                     a.getPath() + "/b2", true);
             fail("Cloning inside same parent should fail.");
         } catch (UnsupportedRepositoryOperationException e) {
@@ -657,12 +677,12 @@
      * Moves a node in a shared set. This is unsupported in Jackrabbit.
      */
     public void testMoveShareableNode() throws Exception {
-        // setup parent nodes and first childs 
+        // setup parent nodes and first childs
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b = a1.addNode("b");
         testRootNode.save();
-        
+
         // add mixin
         b.addMixin("mix:shareable");
         b.save();
@@ -671,37 +691,60 @@
         Workspace workspace = b.getSession().getWorkspace();
 
         try {
+            // move shareable node
             workspace.move(b.getPath(), a2.getPath() + "/b");
             fail("Moving a mix:shareable should fail.");
         } catch (UnsupportedRepositoryOperationException e) {
             // expected
         }
     }
-    
+
     /**
-     * Transiently moves a node in a shared set. This is unsupported in 
+     * Transiently moves a node in a shared set. This is unsupported in
      * Jackrabbit.
      */
     public void testTransientMoveShareableNode() throws Exception {
-        // setup parent nodes and first childs 
+        // setup parent nodes and first childs
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b = a1.addNode("b");
         testRootNode.save();
-        
+
         // add mixin
         b.addMixin("mix:shareable");
         b.save();
 
         // move
         Session session = superuser;
-        
+
         try {
+            // move shareable node
             session.move(b.getPath(), a2.getPath() + "/b");
             session.save();
             fail("Moving a mix:shareable should fail.");
         } catch (UnsupportedRepositoryOperationException e) {
             // expected
         }
+    }
+
+    //---------------------------------------------------------- utility methods
+
+    /**
+     * Return a shared set as an array of nodes.
+     *
+     * @param n node
+     * @return array of nodes in shared set
+     */
+    private Node[] getSharedSet(Node n) throws RepositoryException {
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = ((NodeImpl) n).getSharedSet();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+
+        Node[] result = new Node[list.size()];
+        list.toArray(result);
+        return result;
     }
 }