You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by re...@apache.org on 2009/05/04 12:36:18 UTC

svn commit: r771263 [2/2] - in /jackrabbit/trunk: jackrabbit-core/ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/ jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/

Added: jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java?rev=771263&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java Mon May  4 10:36:18 2009
@@ -0,0 +1,1449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.test.api;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.version.Version;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Tests features available with shareable nodes.
+ */
+public class ShareableNodeTest extends AbstractJCRTest {
+    
+    protected void setUp() throws Exception {
+        super.setUp();
+        ensureKnowsNodeType(superuser, "mix:shareable");
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    //------------------------------------------------------ specification tests
+
+    /**
+     * Verify that Node.getIndex returns the correct index in a shareable
+     * node (6.13).
+     */
+    public void testGetIndex() throws Exception {
+        
+        // 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(),
+                a2.getPath() + "/b", false);
+
+        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());
+    }
+
+    /**
+     * Verify that Node.getName returns the correct name in a shareable node
+     * (6.13).
+     */
+    public void testGetName() 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);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
+        // verify names of nodes b1/b2 in shared set
+        assertEquals("b1", b1.getName());
+        assertEquals("b2", b2.getName());
+    }
+
+    /**
+     * Verify that Node.getPath returns the correct path in a shareable
+     * node (6.13).
+     */
+    public void testGetPath() 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);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
+        // verify paths of nodes b1/b2 in shared set
+        assertEquals("/testroot/a1/b1", b1.getPath());
+        assertEquals("/testroot/a2/b2", b2.getPath());
+    }
+
+    /**
+     * Verify that the shareable node returned by Node.getNode() has the right
+     * name.
+     */
+    public void testGetNode() 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);
+
+        // a1.getNode("b1") should return b1
+        b1 = a1.getNode("b1");
+        assertEquals("b1", b1.getName());
+
+        // a2.getNode("b2") should return b2
+        Node b2 = a2.getNode("b2");
+        assertEquals("b2", b2.getName());
+    }
+
+    /**
+     * Verify that the shareable nodes returned by Node.getNodes() have
+     * the right name.
+     */
+    public void testGetNodes() 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);
+
+        // a1.getNodes() should return b1
+        Node[] children = toArray(a1.getNodes());
+        assertEquals(1, children.length);
+        assertEquals("b1", children[0].getName());
+
+        // a2.getNodes() should return b2
+        children = toArray(a2.getNodes());
+        assertEquals(1, children.length);
+        assertEquals("b2", children[0].getName());
+    }
+
+    /**
+     * Verify that the shareable nodes returned by Node.getNodes(String) have
+     * the right name.
+     */
+    public void testGetNodesByPattern() 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);
+
+        // a1.getNodes(*) should return b1
+        Node[] children = toArray(a1.getNodes("*"));
+        assertEquals(1, children.length);
+        assertEquals("b1", children[0].getName());
+
+        // a2.getNodes(*) should return b2
+        children = toArray(a2.getNodes("*"));
+        assertEquals(1, children.length);
+        assertEquals("b2", children[0].getName());
+    }
+
+    /**
+     * Check new API Node.getSharedSet() (6.13.1)
+     */
+    public void testGetSharedSet() 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);
+
+        // verify shared set contains 2 items
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+    }
+
+    /**
+     * Add the mix:shareable mixin to a node (6.13.2).
+     */
+    public void testAddMixin() throws Exception {
+        // setup parent node and first child
+        Node a = testRootNode.addNode("a");
+        Node b = a.addNode("b");
+        testRootNode.save();
+
+        b.addMixin("mix:shareable");
+        b.save();
+    }
+
+    /**
+     * Create a shareable node by restoring it (6.13.3).
+     */
+    public void testRestore() 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();
+
+        // make b1 shareable
+        b1.addMixin("mix:shareable");
+        b1.save();
+
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(),
+                a2.getPath() + "/b2", false);
+
+        // make a2 versionable
+        a2.addMixin("mix:versionable");
+        a2.save();
+
+        // check in version and check out again
+        Version v = a2.checkin();
+        a2.checkout();
+
+        // delete b2 and save
+        a2.getNode("b2").remove();
+        a2.save();
+
+        // verify shared set contains one element only
+        Node[] shared = getSharedSet(b1);
+        assertEquals(1, shared.length);
+
+        // restore version
+        a2.restore(v, false);
+
+        // verify shared set contains again two elements
+        shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+    }
+
+
+    /**
+     * Check new API Node.removeShare() (6.13.4).
+     */
+    public void testRemoveShare() 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);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
+        // remove b1 from shared set
+        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));
+    }
+
+    /**
+     * Check new API Node.removeSharedSet() (6.13.4).
+     */
+    public void testRemoveSharedSet() 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);
+
+        // remove shared set
+        b1.removeSharedSet();
+        testRootNode.save();
+
+        // verify neither a1 nor a2 contain any more children
+        assertFalse(a1.hasNodes());
+        assertFalse(a2.hasNodes());
+    }
+
+    /**
+     * Invoke Node.removeSharedSet(), but save 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
+        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);
+
+        // remove shared set
+        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
+        }
+    }
+
+    /**
+     * Verify that shareable nodes in the same shared set have the same
+     * jcr:uuid (6.13.10).
+     */
+    public void testSameUUID() 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);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
+        // verify nodes in a shared set have the same jcr:uuid
+        assertTrue(b1.getUUID().equals(b2.getUUID()));
+    }
+
+    /**
+     * Add a child to a shareable node and verify that another node in the
+     * same shared set has the same child and is modified when the first
+     * one is (6.13.11).
+     */
+    public void testAddChild() 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);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[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", false);
+
+        // copy source tree to destination
+        workspace.copy(s.getPath(), testRootNode.getPath() + "/d");
+
+        // verify source contains shared set with 2 entries
+        Node[] shared1 = getSharedSet(b1);
+        assertEquals(2, shared1.length);
+
+        // verify destination contains shared set with 2 entries
+        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) when a shareable node
+     * is cloned.
+     */
+    public void testDetectShareCycleOnClone() throws Exception {
+        // 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();
+
+        Workspace workspace = b1.getSession().getWorkspace();
+
+        try {
+            // clone underneath b1: this must fail
+            workspace.clone(workspace.getName(), b1.getPath(),
+                    b1.getPath() + "/c", false);
+            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
+        }
+    }
+
+    /**
+     * Verify export and import of a tree containing multiple nodes in the
+     * same shared set (6.13.14). The first serialized node in that shared
+     * set is serialized in the normal fashion (with all of its properties
+     * and children), but any subsequent shared node in that shared set is
+     * serialized as a special node of type <code>nt:share</code>, which
+     * contains only the <code>jcr:uuid</code> property of the shared node
+     * and the <code>jcr:primaryType</code> property indicating the type
+     * <code>nt:share</code>.
+     */
+    public void testImportExportNtShare() throws Exception {
+        // setup parent nodes and first child
+        Node p = testRootNode.addNode("p");
+        Node a1 = p.addNode("a1");
+        Node a2 = p.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);
+
+        // create temp file
+        File tmpFile = File.createTempFile("test", null);
+        tmpFile.deleteOnExit();
+
+        // export system view of /p
+        OutputStream out = new FileOutputStream(tmpFile);
+        try {
+            session.exportSystemView(p.getPath(), out, false, false);
+        } finally {
+            out.close();
+        }
+
+        // delete p and save
+        p.remove();
+        testRootNode.save();
+
+        // and import again underneath test root
+        InputStream in = new FileInputStream(tmpFile);
+        try {
+            workspace.importXML(testRootNode.getPath(), in,
+                    ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+        } finally {
+            in.close();
+        }
+
+        // verify shared set consists of two nodes
+        Node[] shared = getSharedSet(testRootNode.getNode("p/a1/b1"));
+        assertEquals(2, shared.length);
+    }
+
+    /**
+     * Verify system view import via workspace (6.13.14). Export a system view
+     * containing a shareable node and verify, that reimporting underneath
+     * a different parent adds another member to the shared set and does not
+     * duplicate children nodes.
+     */
+    public void testImportSystemViewCollision() throws Exception {
+        // setup parent nodes and first child
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node a3 = testRootNode.addNode("a3");
+        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 c to shareable nodes b1 & b2
+        b1.addNode("c");
+        b1.save();
+
+        // create temp file
+        File tmpFile = File.createTempFile("test", null);
+        tmpFile.deleteOnExit();
+
+        // export system view of /a1/b1
+        OutputStream out = new FileOutputStream(tmpFile);
+        try {
+            session.exportSystemView(b1.getPath(), out, false, false);
+        } finally {
+            out.close();
+        }
+
+        // and import again underneath /a3
+        InputStream in = new FileInputStream(tmpFile);
+        try {
+            workspace.importXML(a3.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+        } finally {
+            in.close();
+        }
+
+        // verify there's another element in the shared set
+        Node[] shared = getSharedSet(b1);
+        assertEquals(3, shared.length);
+
+        // verify child c has not been duplicated
+        Node[] children = toArray(b1.getNodes());
+        assertEquals(1, children.length);
+    }
+
+    /**
+     * Verify document view import via workspace (6.13.14). Export a document
+     * view containing a shareable node and verify, that reimporting
+     * underneath a different parent adds another member to the shared set and
+     * does not duplicate children nodes.
+     */
+    public void testImportDocumentViewCollision() throws Exception {
+        // setup parent nodes and first child
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node a3 = testRootNode.addNode("a3");
+        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 c to shareable nodes b1 & b2
+        b1.addNode("c");
+        b1.save();
+
+        // create temp file
+        File tmpFile = File.createTempFile("test", null);
+        tmpFile.deleteOnExit();
+
+        // export system view of /a1/b1
+        OutputStream out = new FileOutputStream(tmpFile);
+        try {
+            session.exportDocumentView(b1.getPath(), out, false, false);
+        } finally {
+            out.close();
+        }
+
+        // and import again underneath /a3
+        InputStream in = new FileInputStream(tmpFile);
+        try {
+            workspace.importXML(a3.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+        } finally {
+            in.close();
+        }
+
+        // verify there's another element in the shared set
+        Node[] shared = getSharedSet(b1);
+        assertEquals(3, shared.length);
+
+        // verify child c has not been duplicated
+        Node[] children = toArray(b1.getNodes());
+        assertEquals(1, children.length);
+    }
+
+    /**
+     * Verify system view import via session (6.13.14). Export a system view
+     * containing a shareable node and verify, that reimporting underneath
+     * a different parent adds another member to the shared set and does not
+     * duplicate children nodes.
+     */
+    public void testSessionImportSystemViewCollision() throws Exception {
+        // setup parent nodes and first child
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node a3 = testRootNode.addNode("a3");
+        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 c to shareable nodes b1 & b2
+        b1.addNode("c");
+        b1.save();
+
+        // create temp file
+        File tmpFile = File.createTempFile("test", null);
+        tmpFile.deleteOnExit();
+
+        // export system view of /a1/b1
+        OutputStream out = new FileOutputStream(tmpFile);
+        try {
+            session.exportSystemView(b1.getPath(), out, false, false);
+        } finally {
+            out.close();
+        }
+
+        // and import again underneath /a3
+        InputStream in = new FileInputStream(tmpFile);
+        try {
+            session.importXML(a3.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+            session.save();
+        } finally {
+            in.close();
+        }
+
+        // verify there's another element in the shared set
+        Node[] shared = getSharedSet(b1);
+        assertEquals(3, shared.length);
+
+        // verify child c has not been duplicated
+        Node[] children = toArray(b1.getNodes());
+        assertEquals(1, children.length);
+    }
+
+    /**
+     * Verify document view import via session (6.13.14). Export a document
+     * view containing a shareable node and verify, that reimporting
+     * underneath a different parent adds another member to the shared set and
+     * does not duplicate children nodes.
+     */
+    public void testSessionImportDocumentViewCollision() throws Exception {
+        // setup parent nodes and first child
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node a3 = testRootNode.addNode("a3");
+        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 c to shareable nodes b1 & b2
+        b1.addNode("c");
+        b1.save();
+
+        // create temp file
+        File tmpFile = File.createTempFile("test", null);
+        tmpFile.deleteOnExit();
+
+        // export system view of /a1/b1
+        OutputStream out = new FileOutputStream(tmpFile);
+        try {
+            session.exportSystemView(b1.getPath(), out, false, false);
+        } finally {
+            out.close();
+        }
+
+        // and import again underneath /a3
+        InputStream in = new FileInputStream(tmpFile);
+        try {
+            session.importXML(a3.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+            session.save();
+        } finally {
+            in.close();
+        }
+
+        // verify there's another element in the shared set
+        Node[] shared = getSharedSet(b1);
+        assertEquals(3, shared.length);
+
+        // verify child c has not been duplicated
+        Node[] children = toArray(b1.getNodes());
+        assertEquals(1, children.length);
+    }
+
+    /**
+     * Verify 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
+        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");
+        b1.save();
+
+        // add child c
+        Node c = b1.addNode("c");
+        b1.save();
+
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(),
+                a2.getPath() + "/b2", false);
+
+        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);
+        try {
+            assertTrue(b2.isLocked());
+        } finally {
+            b1.unlock();
+        }
+
+        // deep-lock parent -> locks (common) child node
+        a1.lock(true, true);
+        try {
+            assertTrue(c.isLocked());
+        } finally {
+            a1.unlock();
+        }
+    }
+
+    /**
+     * 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
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.save();
+
+        // make b1 shareable
+        b1.addMixin("mix:shareable");
+        b1.save();
+
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(),
+                a2.getPath() + "/b2", false);
+
+        // add child c
+        b1.addNode("c");
+        b1.save();
+
+        // make a2 versionable
+        a2.addMixin("mix:versionable");
+        a2.save();
+
+        // check in version and check out again
+        Version v = a2.checkin();
+        a2.checkout();
+
+        // delete b2 and save
+        a2.getNode("b2").remove();
+        a2.save();
+
+        // verify shareable set contains one elements only
+        Node[] shared = getSharedSet(b1);
+        assertEquals(1, shared.length);
+
+        // restore version and remove existing (i.e. b1)
+        a2.restore(v, true);
+
+        // verify shareable set contains still one element
+        shared = getSharedSet(a2.getNode("b2"));
+        assertEquals(1, shared.length);
+
+        // verify child c still exists
+        Node[] children = toArray(a2.getNode("b2").getNodes());
+        assertEquals(1, children.length);
+    }
+
+    /**
+     * Clone a mix:shareable node to the same workspace (6.13.20). Verify
+     * that cloning without mix:shareable fails.
+     */
+    public void testClone() 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();
+
+        Workspace workspace = b1.getSession().getWorkspace();
+
+        try {
+            // clone (1st attempt, without mix:shareable, should fail)
+            workspace.clone(workspace.getName(), b1.getPath(),
+                    a2.getPath() + "/b2", false);
+            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(),
+                a2.getPath() + "/b2", false);
+    }
+
+    /**
+     * Verify 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
+        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);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+
+        // verify b1 is same as b2 (and vice-versa)
+        assertTrue(b1.isSame(b2));
+        assertTrue(b2.isSame(b1));
+    }
+
+    /**
+     * Remove 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
+        Node a = testRootNode.addNode("a");
+        Node b = a.addNode("b");
+        testRootNode.save();
+
+        // add mixin
+        b.addMixin("mix:shareable");
+        b.save();
+
+        try {
+            // remove mixin
+            b.removeMixin("mix:shareable");
+            b.save();
+            fail("Removing mix:shareable should fail.");
+        } catch (UnsupportedRepositoryOperationException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that a descendant of a shareable node appears once in the
+     * result set (6.13.23)
+     */
+    public void testSearch() 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 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();
+
+        ArrayList list = new ArrayList();
+
+        NodeIterator iter = res.getNodes();
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+        assertEquals(1, list.size());
+        assertTrue(((Node) list.get(0)).isSame(c));
+    }
+
+    //--------------------------------------------------------- limitation tests
+
+    /**
+     * Clone 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
+        Node a = testRootNode.addNode("a");
+        Node b1 = a.addNode("b1");
+        testRootNode.save();
+
+        // add mixin
+        b1.addMixin("mix:shareable");
+        b1.save();
+
+        Workspace workspace = b1.getSession().getWorkspace();
+
+        try {
+            // clone to same parent
+            workspace.clone(workspace.getName(), b1.getPath(),
+                    a.getPath() + "/b2", false);
+            fail("Cloning inside same parent should fail.");
+        } catch (UnsupportedRepositoryOperationException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Move a node in a shared set. This is unsupported in Jackrabbit.
+     */
+    public void testMoveShareableNode() throws Exception {
+        // 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
+        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 move a node in a shared set. This is unsupported in
+     * Jackrabbit.
+     */
+    public void testTransientMoveShareableNode() throws Exception {
+        // 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
+        }
+    }
+
+    //----------------------------------------------------- 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
+        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
+        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();
+    }
+
+    /**
+     * Verify that shared nodes return correct paths.
+     */
+    public void testSharedNodePath() throws Exception {
+       Node a1 = testRootNode.addNode("a1");
+       Node a2 = a1.addNode("a2");
+       Node b1 = a1.addNode("b1");
+       b1.addMixin("mix:shareable");
+       testRootNode.save();
+
+       //now we have a shareable node N with path a1/b1
+
+       Session session = testRootNode.getSession();
+       Workspace workspace = session.getWorkspace();
+       String path = a2.getPath() + "/b2";
+       workspace.clone(workspace.getName(), b1.getPath(), path, false);
+
+       //now we have another shareable node N' in the same shared set as N with path a1/a2/b2
+
+       //using the path a1/a2/b2, we should get the node N' here
+       Item item = session.getItem(path);
+       assertEquals("unexpectedly got the path from another node from the same shared set", path, item.getPath()); 
+    } 
+    
+    //---------------------------------------------------------- utility methods
+
+    /**
+     * Return a shared set as an array of nodes.
+     *
+     * @param n node
+     * @return array of nodes in shared set
+     */
+    private static Node[] getSharedSet(Node n) throws RepositoryException {
+        return toArray(n.getSharedSet());
+    }
+
+    /**
+     * Return an array of nodes given a <code>NodeIterator</code>.
+     *
+     * @param iter node iterator
+     * @return node array
+     */
+    private static Node[] toArray(NodeIterator iter) {
+        ArrayList list = new ArrayList();
+
+        while (iter.hasNext()) {
+            list.add(iter.nextNode());
+        }
+
+        Node[] result = new Node[list.size()];
+        list.toArray(result);
+        return result;
+    }
+}

Propchange: jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TestAll.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TestAll.java?rev=771263&r1=771262&r2=771263&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TestAll.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TestAll.java Mon May  4 10:36:18 2009
@@ -135,6 +135,12 @@
 
         suite.addTestSuite(ValueFactoryTest.class);
 
+        // JCR 2.0
+        
+        //// shareable nodes
+        
+        suite.addTestSuite(ShareableNodeTest.class);
+        
         return suite;
     }
 }
\ No newline at end of file