Index: oak-core/src/test/java/org/apache/jackrabbit/oak/core/RootImplTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport org.apache.jackrabbit.oak.Oak;\nimport org.apache.jackrabbit.oak.api.CommitFailedException;\nimport org.apache.jackrabbit.oak.api.ContentSession;\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Root;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.Tree.Status;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.assertNotSame;\nimport static org.junit.Assert.assertNull;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\n\npublic class RootImplTest {\n\n private ContentSession session;\n\n @Before\n public void setUp() throws CommitFailedException {\n session = new Oak().createContentSession();\n\n // Add test content\n Root root = session.getLatestRoot();\n Tree tree = root.getTree(\"/\");\n tree.setProperty(\"a\", 1);\n tree.setProperty(\"b\", 2);\n tree.setProperty(\"c\", 3);\n Tree x = tree.addChild(\"x\");\n x.addChild(\"xx\");\n x.setProperty(\"xa\", \"value\");\n tree.addChild(\"y\");\n tree.addChild(\"z\");\n root.commit();\n }\n\n @After\n public void tearDown() {\n session = null;\n }\n\n @Test\n public void getTree() {\n Root root = session.getLatestRoot();\n\n List validPaths = new ArrayList();\n validPaths.add(\"/\");\n validPaths.add(\"/x\");\n validPaths.add(\"/x/xx\");\n validPaths.add(\"/y\");\n validPaths.add(\"/z\");\n\n for (String treePath : validPaths) {\n Tree tree = root.getTree(treePath);\n assertNotNull(tree);\n assertEquals(treePath, tree.getPath());\n }\n\n List invalidPaths = new ArrayList();\n invalidPaths.add(\"/any\");\n invalidPaths.add(\"/x/any\");\n\n for (String treePath : invalidPaths) {\n assertNull(root.getTree(treePath));\n }\n }\n\n @Test\n public void move() throws CommitFailedException {\n Root root = session.getLatestRoot();\n Tree tree = root.getTree(\"/\");\n\n Tree y = tree.getChild(\"y\");\n\n assertTrue(tree.hasChild(\"x\"));\n root.move(\"/x\", \"/y/xx\");\n assertFalse(tree.hasChild(\"x\"));\n assertTrue(y.hasChild(\"xx\"));\n \n root.commit();\n tree = root.getTree(\"/\");\n\n assertFalse(tree.hasChild(\"x\"));\n assertTrue(tree.hasChild(\"y\"));\n assertTrue(tree.getChild(\"y\").hasChild(\"xx\"));\n }\n\n @Test\n public void moveNew() throws CommitFailedException {\n Root root = session.getLatestRoot();\n Tree tree = root.getTree(\"/\");\n\n Tree t = tree.addChild(\"new\");\n\n root.move(\"/new\", \"/y/new\");\n assertEquals(Status.DISCONNECTED, t.getStatus());\n\n assertNull(tree.getChild(\"new\"));\n }\n\n /**\n * Regression test for OAK-208\n */\n @Test\n public void removeMoved() throws CommitFailedException {\n Root root = session.getLatestRoot();\n Tree r = root.getTree(\"/\");\n r.addChild(\"a\");\n r.addChild(\"b\");\n\n root.move(\"/a\", \"/b/c\");\n assertFalse(r.hasChild(\"a\"));\n assertTrue(r.hasChild(\"b\"));\n\n r.getChild(\"b\").remove();\n assertFalse(r.hasChild(\"a\"));\n assertFalse(r.hasChild(\"b\"));\n\n root.commit();\n assertFalse(r.hasChild(\"a\"));\n assertFalse(r.hasChild(\"b\"));\n }\n\n @Test\n public void rename() throws CommitFailedException {\n Root root = session.getLatestRoot();\n Tree tree = root.getTree(\"/\");\n\n assertTrue(tree.hasChild(\"x\"));\n root.move(\"/x\", \"/xx\");\n assertFalse(tree.hasChild(\"x\"));\n assertTrue(tree.hasChild(\"xx\"));\n \n root.commit();\n tree = root.getTree(\"/\");\n\n assertFalse(tree.hasChild(\"x\"));\n assertTrue(tree.hasChild(\"xx\"));\n }\n\n @Test\n public void copy() throws CommitFailedException {\n Root root = session.getLatestRoot();\n Tree tree = root.getTree(\"/\");\n\n Tree y = tree.getChild(\"y\");\n\n assertTrue(tree.hasChild(\"x\"));\n root.copy(\"/x\", \"/y/xx\");\n assertTrue(tree.hasChild(\"x\"));\n assertTrue(y.hasChild(\"xx\"));\n \n root.commit();\n tree = root.getTree(\"/\");\n\n assertTrue(tree.hasChild(\"x\"));\n assertTrue(tree.hasChild(\"y\"));\n assertTrue(tree.getChild(\"y\").hasChild(\"xx\"));\n }\n\n @Test\n public void deepCopy() throws CommitFailedException {\n Root root = session.getLatestRoot();\n Tree tree = root.getTree(\"/\");\n\n Tree y = tree.getChild(\"y\");\n\n root.getTree(\"/x\").addChild(\"x1\");\n root.copy(\"/x\", \"/y/xx\");\n assertTrue(y.hasChild(\"xx\"));\n assertTrue(y.getChild(\"xx\").hasChild(\"x1\"));\n\n root.commit();\n tree = root.getTree(\"/\");\n\n assertTrue(tree.hasChild(\"x\"));\n assertTrue(tree.hasChild(\"y\"));\n assertTrue(tree.getChild(\"y\").hasChild(\"xx\"));\n assertTrue(tree.getChild(\"y\").getChild(\"xx\").hasChild(\"x1\"));\n\n Tree x = tree.getChild(\"x\");\n Tree xx = tree.getChild(\"y\").getChild(\"xx\");\n checkEqual(x, xx);\n }\n\n @Test\n public void rebase() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.rebase();\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n\n Tree one = root2.getTree(\"/one\");\n one.getChild(\"two\").remove();\n one.addChild(\"four\");\n root2.commit();\n\n root1.rebase();\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithAddNode() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.getTree(\"/\").addChild(\"child\");\n root1.rebase();\n\n root2.getTree(\"/\").addChild(\"child\");\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithRemoveNode() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.getTree(\"/\").getChild(\"x\").remove();\n root1.rebase();\n\n root2.getTree(\"/\").getChild(\"x\").remove();\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithAddProperty() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.getTree(\"/\").setProperty(\"new\", 42);\n root1.rebase();\n\n root2.getTree(\"/\").setProperty(\"new\", 42);\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithRemoveProperty() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.getTree(\"/\").removeProperty(\"a\");\n root1.rebase();\n\n root2.getTree(\"/\").removeProperty(\"a\");\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithSetProperty() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.getTree(\"/\").setProperty(\"a\", 42);\n root1.rebase();\n\n root2.getTree(\"/\").setProperty(\"a\", 42);\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithMove() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.move(\"/x\", \"/y/x-moved\");\n root1.rebase();\n\n root2.move(\"/x\", \"/y/x-moved\");\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void rebaseWithCopy() throws CommitFailedException {\n Root root1 = session.getLatestRoot();\n Root root2 = session.getLatestRoot();\n\n checkEqual(root1.getTree(\"/\"), root2.getTree(\"/\"));\n\n root2.getTree(\"/\").addChild(\"one\").addChild(\"two\").addChild(\"three\")\n .setProperty(\"p1\", \"V1\");\n root2.commit();\n\n root1.copy(\"/x\", \"/y/x-copied\");\n root1.rebase();\n\n root2.copy(\"/x\", \"/y/x-copied\");\n checkEqual(root1.getTree(\"/\"), (root2.getTree(\"/\")));\n }\n\n @Test\n public void testGetLatest() throws Exception {\n RootImpl root = (RootImpl) session.getLatestRoot();\n Root root2 = root.getLatest();\n assertNotSame(root, root2);\n\n session.close();\n try {\n root.getLatest();\n fail();\n } catch (IllegalStateException e) {\n // success\n }\n\n try {\n ((RootImpl) root2).checkLive();\n fail();\n } catch (IllegalStateException e) {\n // success\n }\n }\n\n private static void checkEqual(Tree tree1, Tree tree2) {\n assertEquals(tree1.getChildrenCount(), tree2.getChildrenCount());\n assertEquals(tree1.getPropertyCount(), tree2.getPropertyCount());\n\n for (PropertyState property1 : tree1.getProperties()) {\n assertEquals(property1, tree2.getProperty(property1.getName()));\n }\n\n for (Tree child1 : tree1.getChildren()) {\n checkEqual(child1, tree2.getChild(child1.getName()));\n }\n }\n}\n =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/core/RootImplTest.java (revision b418a59160c07ef5c27c7d403e87cb8e711d88f6) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/core/RootImplTest.java (revision ) @@ -27,7 +27,6 @@ import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.Tree; -import org.apache.jackrabbit.oak.api.Tree.Status; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -99,11 +98,13 @@ Tree tree = root.getTree("/"); Tree y = tree.getChild("y"); + Tree x = tree.getChild("x"); + assertNotNull(x); - assertTrue(tree.hasChild("x")); root.move("/x", "/y/xx"); assertFalse(tree.hasChild("x")); assertTrue(y.hasChild("xx")); + assertEquals("/y/xx", x.getPath()); root.commit(); tree = root.getTree("/"); @@ -121,7 +122,7 @@ Tree t = tree.addChild("new"); root.move("/new", "/y/new"); - assertEquals(Status.DISCONNECTED, t.getStatus()); + assertEquals("/y/new", t.getPath()); assertNull(tree.getChild("new")); } @@ -153,11 +154,13 @@ public void rename() throws CommitFailedException { Root root = session.getLatestRoot(); Tree tree = root.getTree("/"); + Tree x = tree.getChild("x"); + assertNotNull(x); - assertTrue(tree.hasChild("x")); root.move("/x", "/xx"); assertFalse(tree.hasChild("x")); assertTrue(tree.hasChild("xx")); + assertEquals("/xx", x.getPath()); root.commit(); tree = root.getTree("/"); Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/MoveTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.apache.jackrabbit.oak.jcr;\n\nimport javax.jcr.InvalidItemStateException;\nimport javax.jcr.Node;\n\nimport org.apache.jackrabbit.JcrConstants;\nimport org.apache.jackrabbit.test.AbstractJCRTest;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\n/**\n * MoveTest... TODO\n */\npublic class MoveTest extends AbstractJCRTest {\n\n private void move(String src, String dest, boolean save) throws Exception {\n superuser.move(src, dest);\n if (save) {\n superuser.save();\n }\n }\n\n @Test\n public void testRename() throws Exception {\n Node node1 = testRootNode.addNode(nodeName1);\n superuser.save();\n\n String sourcePath = node1.getPath();\n move(sourcePath, testRoot + '/' + nodeName2, true);\n\n try {\n node1.getPath();\n fail();\n } catch (InvalidItemStateException expected) {}\n\n testRootNode.addNode(nodeName1);\n assertEquals(sourcePath, node1.getPath());\n }\n\n @Test\n public void testRenameNewNode() throws Exception {\n Node node1 = testRootNode.addNode(nodeName1);\n\n String sourcePath = node1.getPath();\n move(sourcePath, testRoot + '/' + nodeName2, false);\n\n try {\n node1.getPath();\n fail();\n } catch (InvalidItemStateException expected) {}\n\n testRootNode.addNode(nodeName1);\n superuser.save();\n assertEquals(sourcePath, node1.getPath());\n }\n\n @Test\n public void testMove() throws Exception {\n Node node1 = testRootNode.addNode(nodeName1);\n Node node2 = testRootNode.addNode(nodeName2);\n superuser.save();\n\n String sourcePath = node1.getPath();\n move(sourcePath, node2.getPath() + '/' + nodeName1, true);\n\n try {\n node1.getPath();\n fail();\n } catch (InvalidItemStateException expected) {}\n\n testRootNode.addNode(nodeName1);\n assertEquals(sourcePath, node1.getPath());\n }\n\n @Ignore(\"OAK-606\")\n @Test\n public void testMoveReferenceable() throws Exception {\n Node node1 = testRootNode.addNode(nodeName1);\n node1.addMixin(JcrConstants.MIX_REFERENCEABLE);\n Node node2 = testRootNode.addNode(nodeName2);\n superuser.save();\n\n String destPath = node2.getPath() + '/' + nodeName1;\n move(node1.getPath(), destPath, true);\n\n assertEquals(destPath, node1.getPath());\n }\n\n @Test\n public void testMoveNewNode() throws Exception {\n Node node1 = testRootNode.addNode(nodeName1);\n Node node2 = testRootNode.addNode(nodeName2);\n\n String sourcePath = node1.getPath();\n move(sourcePath, node2.getPath() + '/' + nodeName1, false);\n\n try {\n node1.getPath();\n fail();\n } catch (InvalidItemStateException expected) {}\n\n testRootNode.addNode(nodeName1);\n superuser.save();\n assertEquals(sourcePath, node1.getPath());\n }\n\n @Ignore(\"OAK-607\")\n @Test\n public void testMoveNewReferenceable() throws Exception {\n Node node1 = testRootNode.addNode(nodeName1);\n node1.addMixin(JcrConstants.MIX_REFERENCEABLE);\n assertTrue(node1.isNodeType(JcrConstants.MIX_REFERENCEABLE));\n Node node2 = testRootNode.addNode(nodeName2);\n\n String destPath = node2.getPath() + '/' + nodeName1;\n move(node1.getPath(), destPath, false);\n\n assertEquals(destPath, node1.getPath());\n\n superuser.save();\n assertEquals(destPath, node1.getPath());\n }\n} =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/MoveTest.java (revision b418a59160c07ef5c27c7d403e87cb8e711d88f6) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/MoveTest.java (revision ) @@ -16,7 +16,6 @@ */ package org.apache.jackrabbit.oak.jcr; -import javax.jcr.InvalidItemStateException; import javax.jcr.Node; import org.apache.jackrabbit.JcrConstants; @@ -41,33 +40,20 @@ Node node1 = testRootNode.addNode(nodeName1); superuser.save(); - String sourcePath = node1.getPath(); - move(sourcePath, testRoot + '/' + nodeName2, true); + String destPath = testRoot + '/' + nodeName2; + move(node1.getPath(), destPath, true); - try { - node1.getPath(); - fail(); - } catch (InvalidItemStateException expected) {} - - testRootNode.addNode(nodeName1); - assertEquals(sourcePath, node1.getPath()); + assertEquals(destPath, node1.getPath()); } @Test public void testRenameNewNode() throws Exception { Node node1 = testRootNode.addNode(nodeName1); - String sourcePath = node1.getPath(); - move(sourcePath, testRoot + '/' + nodeName2, false); + String destPath = testRoot + '/' + nodeName2; + move(node1.getPath(), destPath, false); - try { - node1.getPath(); - fail(); - } catch (InvalidItemStateException expected) {} - - testRootNode.addNode(nodeName1); - superuser.save(); - assertEquals(sourcePath, node1.getPath()); + assertEquals(destPath, node1.getPath()); } @Test @@ -76,16 +62,10 @@ Node node2 = testRootNode.addNode(nodeName2); superuser.save(); - String sourcePath = node1.getPath(); - move(sourcePath, node2.getPath() + '/' + nodeName1, true); + String destPath = node2.getPath() + '/' + nodeName1; + move(node1.getPath(), destPath, true); - try { - node1.getPath(); - fail(); - } catch (InvalidItemStateException expected) {} - - testRootNode.addNode(nodeName1); - assertEquals(sourcePath, node1.getPath()); + assertEquals(destPath, node1.getPath()); } @Ignore("OAK-606") @@ -107,17 +87,10 @@ Node node1 = testRootNode.addNode(nodeName1); Node node2 = testRootNode.addNode(nodeName2); - String sourcePath = node1.getPath(); - move(sourcePath, node2.getPath() + '/' + nodeName1, false); + String destPath = node2.getPath() + '/' + nodeName1; + move(node1.getPath(), destPath, false); - try { - node1.getPath(); - fail(); - } catch (InvalidItemStateException expected) {} - - testRootNode.addNode(nodeName1); - superuser.save(); - assertEquals(sourcePath, node1.getPath()); + assertEquals(destPath, node1.getPath()); } @Ignore("OAK-607") \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.AccessController;\nimport java.security.PrivilegedAction;\nimport java.util.ArrayList;\nimport java.util.List;\nimport javax.annotation.Nonnull;\nimport javax.security.auth.Subject;\n\nimport org.apache.jackrabbit.oak.Oak;\nimport org.apache.jackrabbit.oak.api.Blob;\nimport org.apache.jackrabbit.oak.api.BlobFactory;\nimport org.apache.jackrabbit.oak.api.CommitFailedException;\nimport org.apache.jackrabbit.oak.api.QueryEngine;\nimport org.apache.jackrabbit.oak.api.Root;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\nimport org.apache.jackrabbit.oak.commons.PathUtils;\nimport org.apache.jackrabbit.oak.plugins.index.diffindex.UUIDDiffIndexProviderWrapper;\nimport org.apache.jackrabbit.oak.query.QueryEngineImpl;\nimport org.apache.jackrabbit.oak.security.authentication.SystemSubject;\nimport org.apache.jackrabbit.oak.spi.commit.CommitHook;\nimport org.apache.jackrabbit.oak.spi.commit.CompositeHook;\nimport org.apache.jackrabbit.oak.spi.commit.EmptyHook;\nimport org.apache.jackrabbit.oak.spi.observation.ChangeExtractor;\nimport org.apache.jackrabbit.oak.spi.query.CompositeQueryIndexProvider;\nimport org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;\nimport org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;\nimport org.apache.jackrabbit.oak.spi.security.SecurityConfiguration;\nimport org.apache.jackrabbit.oak.spi.security.SecurityProvider;\nimport org.apache.jackrabbit.oak.spi.security.authorization.PermissionProvider;\nimport org.apache.jackrabbit.oak.spi.state.NodeBuilder;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\nimport org.apache.jackrabbit.oak.spi.state.NodeStateDiff;\nimport org.apache.jackrabbit.oak.spi.state.NodeStore;\nimport org.apache.jackrabbit.oak.spi.state.NodeStoreBranch;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static org.apache.jackrabbit.oak.commons.PathUtils.getName;\nimport static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;\n\npublic class RootImpl implements Root {\n\n /**\n * Number of {@link #updated} calls for which changes are kept in memory.\n */\n private static final int PURGE_LIMIT = 100;\n\n /**\n * The underlying store to which this root belongs\n */\n private final NodeStore store;\n\n private final String workspaceName;\n\n private final CommitHook hook;\n\n private final Subject subject;\n\n private final SecurityProvider securityProvider;\n\n private final QueryIndexProvider indexProvider;\n\n /**\n * Current branch this root operates on\n */\n private NodeStoreBranch branch;\n\n /**\n * Current root {@code Tree}\n */\n private TreeImpl rootTree;\n\n /**\n * Number of {@link #updated} occurred so since the last\n * purge.\n */\n private int modCount;\n\n private PermissionProvider permissionProvider;\n\n /**\n * New instance bases on a given {@link NodeStore} and a workspace\n *\n * @param store node store\n * @param hook the commit hook\n * @param workspaceName name of the workspace\n * @param subject the subject.\n * @param securityProvider the security configuration.\n * @param indexProvider the query index provider.\n */\n public RootImpl(NodeStore store,\n CommitHook hook,\n String workspaceName,\n Subject subject,\n SecurityProvider securityProvider,\n QueryIndexProvider indexProvider) {\n this.store = checkNotNull(store);\n this.workspaceName = checkNotNull(workspaceName);\n this.hook = checkNotNull(hook);\n this.subject = checkNotNull(subject);\n this.securityProvider = checkNotNull(securityProvider);\n this.indexProvider = indexProvider;\n refresh();\n }\n\n // TODO: review if this constructor really makes sense and cannot be replaced.\n public RootImpl(NodeStore store) {\n this.store = checkNotNull(store);\n // FIXME: define proper default or pass workspace name with the constructor\n this.workspaceName = Oak.DEFAULT_WORKSPACE_NAME;\n this.hook = EmptyHook.INSTANCE;\n this.subject = SystemSubject.INSTANCE;\n this.securityProvider = new OpenSecurityProvider();\n this.indexProvider = new CompositeQueryIndexProvider();\n refresh();\n }\n\n /**\n * Oak level variant of {@link org.apache.jackrabbit.oak.api.ContentSession#getLatestRoot()}\n * to be used when no {@code ContentSession} is available.\n *\n * @return A new Root instance.\n * @see org.apache.jackrabbit.oak.api.ContentSession#getLatestRoot()\n */\n public Root getLatest() {\n checkLive();\n RootImpl root = new RootImpl(store, hook, workspaceName, subject, securityProvider, getIndexProvider()) {\n @Override\n protected void checkLive() {\n RootImpl.this.checkLive();\n }\n };\n return root;\n }\n\n /**\n * Called whenever a method on this instance or on any {@code Tree} instance\n * obtained from this {@code Root} is called. This default implementation\n * does nothing. Sub classes may override this method and throw an exception\n * indicating that this {@code Root} instance is not live anymore (e.g. because\n * the session has been logged out already).\n */\n protected void checkLive() {\n\n }\n\n //---------------------------------------------------------------< Root >---\n @Override\n public boolean move(String sourcePath, String destPath) {\n checkLive();\n TreeImpl source = rootTree.getTree(sourcePath);\n if (source == null) {\n return false;\n }\n TreeImpl destParent = rootTree.getTree(getParentPath(destPath));\n if (destParent == null) {\n return false;\n }\n\n String destName = getName(destPath);\n if (destParent.hasChild(destName)) {\n return false;\n }\n\n purgePendingChanges();\n source.moveTo(destParent, destName);\n boolean success = branch.move(sourcePath, destPath);\n reset();\n if (success) {\n getTree(getParentPath(sourcePath)).updateChildOrder();\n getTree(getParentPath(destPath)).updateChildOrder();\n }\n return success;\n }\n\n @Override\n public boolean copy(String sourcePath, String destPath) {\n checkLive();\n purgePendingChanges();\n boolean success = branch.copy(sourcePath, destPath);\n reset();\n if (success) {\n getTree(getParentPath(destPath)).updateChildOrder();\n }\n return success;\n }\n\n @Override\n public TreeImpl getTree(String path) {\n checkLive();\n return rootTree.getTree(path);\n }\n\n @Override\n public TreeLocation getLocation(String path) {\n checkLive();\n checkArgument(PathUtils.isAbsolute(path), \"Not an absolute path: \" + path);\n return rootTree.getLocation().getChild(path.substring(1));\n }\n\n @Override\n public void rebase() {\n checkLive();\n if (!store.getRoot().equals(rootTree.getBaseState())) {\n purgePendingChanges();\n branch.rebase();\n rootTree = TreeImpl.createRoot(this);\n permissionProvider = null;\n }\n }\n\n @Override\n public final void refresh() {\n checkLive();\n branch = store.branch();\n rootTree = TreeImpl.createRoot(this);\n modCount = 0;\n if (permissionProvider != null) {\n permissionProvider.refresh();\n }\n }\n\n @Override\n public void commit() throws CommitFailedException {\n checkLive();\n rebase();\n purgePendingChanges();\n CommitFailedException exception = Subject.doAs(\n getCombinedSubject(), new PrivilegedAction() {\n @Override\n public CommitFailedException run() {\n try {\n branch.merge(getCommitHook());\n return null;\n } catch (CommitFailedException e) {\n return e;\n }\n }\n });\n if (exception != null) {\n throw exception;\n }\n refresh();\n }\n\n /**\n * Combine the globally defined commit hook(s) with the hooks and\n * validators defined by the various security related configurations.\n *\n * @return A commit hook combining repository global commit hook(s) with\n * the pluggable hooks defined with the security modules.\n */\n private CommitHook getCommitHook() {\n List commitHooks = new ArrayList();\n commitHooks.add(hook);\n List securityHooks = new ArrayList();\n for (SecurityConfiguration sc : securityProvider.getSecurityConfigurations()) {\n CommitHook validators = sc.getValidators().getCommitHook(workspaceName);\n if (validators != EmptyHook.INSTANCE) {\n commitHooks.add(validators);\n }\n CommitHook ch = sc.getSecurityHooks().getCommitHook(workspaceName);\n if (ch != EmptyHook.INSTANCE) {\n securityHooks.add(ch);\n }\n }\n commitHooks.addAll(securityHooks);\n return CompositeHook.compose(commitHooks);\n }\n\n // TODO: find a better solution for passing in additional principals\n private Subject getCombinedSubject() {\n Subject accSubject = Subject.getSubject(AccessController.getContext());\n if (accSubject == null) {\n return subject;\n } else {\n Subject combinedSubject = new Subject(false,\n subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials());\n combinedSubject.getPrincipals().addAll(accSubject.getPrincipals());\n combinedSubject.getPrivateCredentials().addAll(accSubject.getPrivateCredentials());\n combinedSubject.getPublicCredentials().addAll((accSubject.getPublicCredentials()));\n return combinedSubject;\n }\n }\n\n @Override\n public boolean hasPendingChanges() {\n checkLive();\n return !getBaseState().equals(rootTree.getNodeState());\n }\n\n @Nonnull\n public ChangeExtractor getChangeExtractor() {\n checkLive();\n return new ChangeExtractor() {\n private NodeState baseLine = store.getRoot();\n\n @Override\n public void getChanges(NodeStateDiff diff) {\n NodeState head = store.getRoot();\n head.compareAgainstBaseState(baseLine, diff);\n baseLine = head;\n }\n };\n }\n\n @Override\n public QueryEngine getQueryEngine() {\n checkLive();\n return new QueryEngineImpl(getIndexProvider()) {\n\n @Override\n protected NodeState getRootState() {\n return rootTree.getNodeState();\n }\n\n @Override\n protected Tree getRootTree() {\n return rootTree;\n }\n\n };\n }\n\n @Nonnull\n @Override\n public BlobFactory getBlobFactory() {\n checkLive();\n\n return new BlobFactory() {\n @Override\n public Blob createBlob(InputStream inputStream) throws IOException {\n checkLive();\n return store.createBlob(inputStream);\n }\n };\n }\n\n @Nonnull\n private QueryIndexProvider getIndexProvider() {\n if (hasPendingChanges()) {\n return new UUIDDiffIndexProviderWrapper(indexProvider,\n getBaseState(), rootTree.getNodeState());\n }\n return indexProvider;\n }\n\n //-----------------------------------------------------------< internal >---\n\n /**\n * Returns the node state from which the current branch was created.\n *\n * @return base node state\n */\n @Nonnull\n NodeState getBaseState() {\n return branch.getBase();\n }\n\n @Nonnull\n NodeBuilder createRootBuilder() {\n return branch.getHead().builder();\n }\n\n // TODO better way to determine purge limit. See OAK-175\n void updated() {\n if (++modCount > PURGE_LIMIT) {\n modCount = 0;\n purgePendingChanges();\n }\n }\n\n @Nonnull\n PermissionProvider getPermissionProvider() {\n if (permissionProvider == null) {\n permissionProvider = createPermissionProvider();\n }\n return permissionProvider;\n }\n\n //------------------------------------------------------------< private >---\n\n /**\n * Purge all pending changes to the underlying {@link NodeStoreBranch}.\n */\n private void purgePendingChanges() {\n branch.setRoot(rootTree.getNodeState());\n reset();\n }\n\n /**\n * Reset the root builder to the branch's current root state\n */\n private void reset() {\n rootTree.getNodeBuilder().reset(branch.getHead());\n }\n\n private PermissionProvider createPermissionProvider() {\n return securityProvider.getAccessControlConfiguration().getPermissionProvider(this, subject.getPrincipals());\n }\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision b418a59160c07ef5c27c7d403e87cb8e711d88f6) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision ) @@ -87,6 +87,9 @@ */ private NodeStoreBranch branch; + /** Sentinel for the next move operation to take place on the this root */ + private Move lastMove; + /** * Current root {@code Tree} */ @@ -192,6 +195,8 @@ getTree(getParentPath(sourcePath)).updateChildOrder(); getTree(getParentPath(destPath)).updateChildOrder(); } + + lastMove = lastMove.setMove(sourcePath, destParent, destName); return success; } @@ -226,7 +231,8 @@ if (!store.getRoot().equals(rootTree.getBaseState())) { purgePendingChanges(); branch.rebase(); - rootTree = TreeImpl.createRoot(this); + lastMove = new Move(); + rootTree = TreeImpl.createRoot(this, lastMove); permissionProvider = null; } } @@ -235,7 +241,8 @@ public final void refresh() { checkLive(); branch = store.branch(); - rootTree = TreeImpl.createRoot(this); + lastMove = new Move(); + rootTree = TreeImpl.createRoot(this, lastMove); modCount = 0; if (permissionProvider != null) { permissionProvider.refresh(); @@ -419,5 +426,58 @@ private PermissionProvider createPermissionProvider() { return securityProvider.getAccessControlConfiguration().getPermissionProvider(this, subject.getPrincipals()); + } + + //------------------------------------------------------------< MoveRecord >--- + + /** + * Instances of this class record move operations which took place on this root. + * They form a singly linked list where each move instance points to the next one. + * The last entry in the list is always an empty slot to be filled in by calling + * {@code setMove()}. This fills the slot with the source and destination of the move + * and links this move to the next one which will be the new empty slot. + * + * Moves can be applied to {@code TreeImpl} instances by calling {@code apply()}, + * which will execute all moves in the list on the passed tree instance + */ + class Move { + + /** source path */ + private String source; + + /** Parent tree of the destination */ + private TreeImpl destParent; + + /** Name at the destination */ + private String destName; + + /** Pointer to the next move. {@code null} if this is the last, empty slot */ + private Move next; + + /** + * Set this move to the given source and destination. Creates a new empty slot, + * sets this as the next move and returns it. + */ + Move setMove(String source, TreeImpl destParent, String destName) { + this.source = source; + this.destParent = destParent; + this.destName = destName; + return next = new Move(); + } + + /** + * Apply this and all subsequent moves to the passed tree instance. + */ + Move apply(TreeImpl tree) { + Move move = this; + while (move.next != null) { + if (move.source.equals(tree.getPathInternal())) { + tree.moveTo(move.destParent, move.destName); + } + move = move.next; + } + return move; + } + } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.Set;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\nimport com.google.common.base.Function;\nimport com.google.common.base.Predicate;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Sets;\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.plugins.memory.MemoryPropertyBuilder;\nimport org.apache.jackrabbit.oak.plugins.memory.MultiStringPropertyState;\nimport org.apache.jackrabbit.oak.spi.state.NodeBuilder;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\nimport org.apache.jackrabbit.oak.spi.state.NodeStateUtils;\nimport org.apache.jackrabbit.oak.spi.state.PropertyBuilder;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static org.apache.jackrabbit.oak.commons.PathUtils.elements;\n\npublic class TreeImpl implements Tree {\n\n /**\n * Internal and hidden property that contains the child order\n */\n public static final String OAK_CHILD_ORDER = \":childOrder\";\n\n /**\n * Underlying {@code Root} of this {@code Tree} instance\n */\n private final RootImpl root;\n\n /**\n * The {@code NodeBuilder} for the underlying node state\n */\n private final NodeBuilder nodeBuilder;\n\n /**\n * Parent of this tree. Null for the root.\n */\n private TreeImpl parent;\n\n /**\n * Name of this tree\n */\n private String name;\n\n private TreeImpl(RootImpl root) {\n this.root = checkNotNull(root);\n this.name = \"\";\n this.nodeBuilder = root.createRootBuilder();\n }\n\n private TreeImpl(RootImpl root, TreeImpl parent, String name) {\n this.root = checkNotNull(root);\n this.parent = checkNotNull(parent);\n this.name = checkNotNull(name);\n this.nodeBuilder = parent.getNodeBuilder().child(name);\n }\n\n @Nonnull\n static TreeImpl createRoot(final RootImpl root) {\n return new TreeImpl(root) {\n @Override\n protected NodeState getBaseState() {\n return root.getBaseState();\n }\n };\n }\n\n @Override\n public String getName() {\n root.checkLive();\n return name;\n }\n\n @Override\n public boolean isRoot() {\n root.checkLive();\n return parent == null;\n }\n\n @Override\n public String getPath() {\n root.checkLive();\n if (isRoot()) {\n // shortcut\n return \"/\";\n }\n\n StringBuilder sb = new StringBuilder();\n buildPath(sb);\n return sb.toString();\n }\n\n @Override\n public Tree getParent() {\n root.checkLive();\n if (parent != null && canRead(parent)) {\n return parent;\n } else {\n return null;\n }\n }\n\n @Override\n public PropertyState getProperty(String name) {\n root.checkLive();\n PropertyState property = internalGetProperty(name);\n if (canRead(property)) {\n return property;\n } else {\n return null;\n }\n }\n\n @Override\n public Status getPropertyStatus(String name) {\n // TODO: see OAK-212\n root.checkLive();\n Status nodeStatus = getStatus();\n if (nodeStatus == Status.NEW) {\n return (hasProperty(name)) ? Status.NEW : null;\n } else if (nodeStatus == Status.DISCONNECTED) {\n return Status.DISCONNECTED;\n } else {\n PropertyState head = internalGetProperty(name);\n if (head != null && !canRead(head)) {\n // no permission to read status information for existing property\n return null;\n }\n\n NodeState parentBase = getBaseState();\n PropertyState base = parentBase == null ? null : parentBase.getProperty(name);\n if (head == null) {\n return (base == null) ? null : Status.DISCONNECTED;\n } else {\n if (base == null) {\n return Status.NEW;\n } else if (head.equals(base)) {\n return Status.EXISTING;\n } else {\n return Status.MODIFIED;\n }\n }\n }\n }\n\n @Override\n public boolean hasProperty(String name) {\n root.checkLive();\n return getProperty(name) != null;\n }\n\n @Override\n public long getPropertyCount() {\n root.checkLive();\n return Iterables.size(getProperties());\n }\n\n @Override\n public Iterable getProperties() {\n root.checkLive();\n return Iterables.filter(nodeBuilder.getProperties(),\n new Predicate() {\n @Override\n public boolean apply(PropertyState propertyState) {\n return canRead(propertyState);\n }\n });\n }\n\n @Override\n public TreeImpl getChild(@Nonnull String name) {\n checkNotNull(name);\n root.checkLive();\n TreeImpl child = internalGetChild(name);\n if (child != null && canRead(child)) {\n return child;\n } else {\n return null;\n }\n }\n\n private boolean isDisconnected() {\n if (isRoot()) {\n return false;\n }\n if (parent.nodeBuilder == null) {\n return false;\n }\n if (!parent.nodeBuilder.isConnected()) {\n return true;\n }\n return !nodeBuilder.isConnected();\n }\n\n @Override\n public Status getStatus() {\n root.checkLive();\n\n if (isDisconnected()) {\n return Status.DISCONNECTED;\n }\n\n if (nodeBuilder.isNew()) {\n return Status.NEW;\n } else if (nodeBuilder.isModified()) {\n return Status.MODIFIED;\n } else {\n return Status.EXISTING;\n }\n }\n\n @Override\n public boolean hasChild(@Nonnull String name) {\n return getChild(name) != null;\n }\n\n @Override\n public long getChildrenCount() {\n // TODO: make sure cnt respects access control\n root.checkLive();\n return nodeBuilder.getChildNodeCount();\n }\n\n @Override\n public Iterable getChildren() {\n root.checkLive();\n Iterable childNames;\n if (hasOrderableChildren()) {\n childNames = getOrderedChildNames();\n } else {\n childNames = nodeBuilder.getChildNodeNames();\n }\n return Iterables.filter(Iterables.transform(\n childNames,\n new Function() {\n @Override\n public Tree apply(String input) {\n return new TreeImpl(root, TreeImpl.this, input);\n }\n }),\n new Predicate() {\n @Override\n public boolean apply(Tree tree) {\n return tree != null && canRead(tree);\n }\n });\n }\n\n @Override\n public Tree addChild(String name) {\n root.checkLive();\n if (!hasChild(name)) {\n nodeBuilder.child(name);\n if (hasOrderableChildren()) {\n nodeBuilder.setProperty(\n MemoryPropertyBuilder.copy(Type.STRING, internalGetProperty(OAK_CHILD_ORDER))\n .addValue(name)\n .getPropertyState());\n }\n root.updated();\n }\n\n TreeImpl child = new TreeImpl(root, this, name);\n\n // Make sure to allocate the node builder for new nodes in order to correctly\n // track removes and moves. See OAK-621\n return child;\n }\n\n @Override\n public void setOrderableChildren(boolean enable) {\n root.checkLive();\n if (enable) {\n ensureChildOrderProperty();\n } else {\n nodeBuilder.removeProperty(OAK_CHILD_ORDER);\n }\n }\n\n @Override\n public boolean remove() {\n root.checkLive();\n if (isDisconnected()) {\n throw new IllegalStateException(\"Cannot remove a disconnected tree\");\n }\n\n if (!isRoot() && parent.hasChild(name)) {\n NodeBuilder parentBuilder = parent.nodeBuilder;\n parentBuilder.removeNode(name);\n if (parent.hasOrderableChildren()) {\n parentBuilder.setProperty(\n MemoryPropertyBuilder.copy(Type.STRING, parent.internalGetProperty(OAK_CHILD_ORDER))\n .removeValue(name)\n .getPropertyState()\n );\n }\n root.updated();\n return true;\n } else {\n return false;\n }\n }\n\n @Override\n public boolean orderBefore(final String name) {\n root.checkLive();\n if (isRoot()) {\n // root does not have siblings\n return false;\n }\n if (name != null && !parent.hasChild(name)) {\n // so such sibling or not accessible\n return false;\n }\n // perform the reorder\n parent.ensureChildOrderProperty();\n // all siblings but not this one\n Iterable filtered = Iterables.filter(\n parent.getOrderedChildNames(),\n new Predicate() {\n @Override\n public boolean apply(@Nullable String input) {\n return !TreeImpl.this.getName().equals(input);\n }\n });\n // create head and tail\n Iterable head;\n Iterable tail;\n if (name == null) {\n head = filtered;\n tail = Collections.emptyList();\n } else {\n int idx = Iterables.indexOf(filtered, new Predicate() {\n @Override\n public boolean apply(@Nullable String input) {\n return name.equals(input);\n }\n });\n head = Iterables.limit(filtered, idx);\n tail = Iterables.skip(filtered, idx);\n }\n // concatenate head, this name and tail\n parent.nodeBuilder.setProperty(MultiStringPropertyState.stringProperty(OAK_CHILD_ORDER, Iterables.concat(head, Collections.singleton(getName()), tail))\n );\n root.updated();\n return true;\n }\n\n @Override\n public void setProperty(PropertyState property) {\n root.checkLive();\n nodeBuilder.setProperty(property);\n root.updated();\n }\n\n @Override\n public void setProperty(String name, T value) {\n root.checkLive();\n nodeBuilder.setProperty(name, value);\n root.updated();\n }\n\n @Override\n public void setProperty(String name, T value, Type type) {\n root.checkLive();\n nodeBuilder.setProperty(name, value, type);\n root.updated();\n }\n\n @Override\n public void removeProperty(String name) {\n root.checkLive();\n nodeBuilder.removeProperty(name);\n root.updated();\n }\n\n @Override\n public TreeLocation getLocation() {\n root.checkLive();\n return new NodeLocation(this);\n }\n\n //----------------------------------------------------------< protected >---\n\n @CheckForNull\n protected NodeState getBaseState() {\n if (isDisconnected()) {\n throw new IllegalStateException(\"Cannot get the base state of a disconnected tree\");\n }\n\n NodeState parentBaseState = parent.getBaseState();\n return parentBaseState == null\n ? null\n : parentBaseState.getChildNode(name);\n }\n\n //-----------------------------------------------------------< internal >---\n\n @Nonnull\n NodeBuilder getNodeBuilder() {\n return nodeBuilder;\n }\n\n /**\n * Move this tree to the parent at {@code destParent} with the new name\n * {@code destName}.\n *\n * @param destParent new parent for this tree\n * @param destName new name for this tree\n */\n void moveTo(TreeImpl destParent, String destName) {\n if (isDisconnected()) {\n throw new IllegalStateException(\"Cannot move a disconnected tree\");\n }\n\n name = destName;\n parent = destParent;\n }\n\n @Nonnull\n NodeState getNodeState() {\n return nodeBuilder.getNodeState();\n }\n\n /**\n * Get a tree for the tree identified by {@code path}.\n *\n * @param path the path to the child\n * @return a {@link Tree} instance for the child at {@code path} or\n * {@code null} if no such tree exits or if the tree is not accessible.\n */\n @CheckForNull\n TreeImpl getTree(String path) {\n checkArgument(path.startsWith(\"/\"));\n TreeImpl child = this;\n for (String name : elements(path)) {\n child = child.internalGetChild(name);\n if (child == null) {\n return null;\n }\n }\n return (canRead(child)) ? child : null;\n }\n\n /**\n * Update the child order with children that have been removed or added.\n * Added children are appended to the end of the {@link #OAK_CHILD_ORDER}\n * property.\n */\n void updateChildOrder() {\n if (!hasOrderableChildren()) {\n return;\n }\n Set names = Sets.newLinkedHashSet();\n for (String name : getOrderedChildNames()) {\n if (nodeBuilder.hasChildNode(name)) {\n names.add(name);\n }\n }\n for (String name : nodeBuilder.getChildNodeNames()) {\n names.add(name);\n }\n PropertyBuilder builder = MemoryPropertyBuilder.array(\n Type.STRING, OAK_CHILD_ORDER);\n builder.setValues(names);\n nodeBuilder.setProperty(builder.getPropertyState());\n }\n\n //------------------------------------------------------------< private >---\n\n private TreeImpl internalGetChild(String childName) {\n return nodeBuilder.hasChildNode(childName)\n ? new TreeImpl(root, this, childName)\n : null;\n }\n\n private PropertyState internalGetProperty(String propertyName) {\n return nodeBuilder.getProperty(propertyName);\n }\n\n private void buildPath(StringBuilder sb) {\n if (!isRoot()) {\n parent.buildPath(sb);\n sb.append('/').append(name);\n }\n }\n\n private boolean canRead(Tree tree) {\n // FIXME: access control eval must have full access to the tree\n // FIXME: special handling for access control item and version content\n return root.getPermissionProvider().canRead(tree);\n }\n\n private boolean canRead(PropertyState property) {\n // FIXME: access control eval must have full access to the tree/property\n // FIXME: special handling for access control item and version content\n return (property != null)\n && root.getPermissionProvider().canRead(this, property)\n && !NodeStateUtils.isHidden(property.getName());\n }\n\n /**\n * @return {@code true} if this tree has orderable children;\n * {@code false} otherwise.\n */\n private boolean hasOrderableChildren() {\n return internalGetProperty(OAK_CHILD_ORDER) != null;\n }\n\n /**\n * Returns the ordered child names. This method must only be called when\n * this tree {@link #hasOrderableChildren()}.\n *\n * @return the ordered child names.\n */\n private Iterable getOrderedChildNames() {\n assert hasOrderableChildren();\n return new Iterable() {\n @Override\n public Iterator iterator() {\n return new Iterator() {\n final PropertyState childOrder = internalGetProperty(OAK_CHILD_ORDER);\n int index = 0;\n\n @Override\n public boolean hasNext() {\n return index < childOrder.count();\n }\n\n @Override\n public String next() {\n return childOrder.getValue(Type.STRING, index++);\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n };\n }\n };\n }\n\n /**\n * Ensures that the {@link #OAK_CHILD_ORDER} exists. This method will create\n * the property if it doesn't exist and initialize the value with the names\n * of the children as returned by {@link NodeBuilder#getChildNodeNames()}.\n */\n private void ensureChildOrderProperty() {\n PropertyState childOrder = nodeBuilder.getProperty(OAK_CHILD_ORDER);\n if (childOrder == null) {\n nodeBuilder.setProperty(\n MultiStringPropertyState.stringProperty(OAK_CHILD_ORDER, nodeBuilder.getChildNodeNames()));\n }\n }\n\n //-------------------------------------------------------< TreeLocation >---\n\n private final class NodeLocation extends AbstractNodeLocation {\n\n private NodeLocation(TreeImpl tree) {\n super(tree);\n }\n\n @Override\n protected NodeLocation createNodeLocation(TreeImpl tree) {\n return new NodeLocation(tree);\n }\n\n @Override\n protected TreeLocation createPropertyLocation(AbstractNodeLocation parentLocation, String name) {\n return new PropertyLocation(parentLocation, name);\n }\n\n @Override\n protected TreeImpl getParentTree() {\n return tree.parent;\n }\n\n @Override\n protected TreeImpl getChildTree(String name) {\n return tree.internalGetChild(name);\n }\n\n @Override\n protected PropertyState getPropertyState(String name) {\n return tree.internalGetProperty(name);\n }\n\n @Override\n protected boolean canRead(TreeImpl tree) {\n return TreeImpl.this.canRead(tree);\n }\n }\n\n private final class PropertyLocation extends AbstractPropertyLocation {\n\n private PropertyLocation(AbstractNodeLocation parentLocation, String name) {\n super(parentLocation, name);\n }\n\n @Override\n protected boolean canRead(PropertyState property) {\n return TreeImpl.this.canRead(property);\n }\n\n }\n\n}\n\n\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java (revision b418a59160c07ef5c27c7d403e87cb8e711d88f6) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java (revision ) @@ -34,6 +34,7 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.TreeLocation; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.core.RootImpl.Move; import org.apache.jackrabbit.oak.plugins.memory.MemoryPropertyBuilder; import org.apache.jackrabbit.oak.plugins.memory.MultiStringPropertyState; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; @@ -72,22 +73,27 @@ */ private String name; - private TreeImpl(RootImpl root) { + /** Pointer into the list of pending moves */ + private Move pendingMoves; + + private TreeImpl(RootImpl root, Move pendingMoves) { this.root = checkNotNull(root); this.name = ""; this.nodeBuilder = root.createRootBuilder(); + this.pendingMoves = checkNotNull(pendingMoves); } - private TreeImpl(RootImpl root, TreeImpl parent, String name) { + private TreeImpl(RootImpl root, TreeImpl parent, String name, Move pendingMoves) { this.root = checkNotNull(root); this.parent = checkNotNull(parent); this.name = checkNotNull(name); this.nodeBuilder = parent.getNodeBuilder().child(name); + this.pendingMoves = checkNotNull(pendingMoves); } @Nonnull - static TreeImpl createRoot(final RootImpl root) { - return new TreeImpl(root) { + static TreeImpl createRoot(final RootImpl root, Move pendingMoves) { + return new TreeImpl(root, pendingMoves) { @Override protected NodeState getBaseState() { return root.getBaseState(); @@ -97,32 +103,25 @@ @Override public String getName() { - root.checkLive(); + enter(); return name; } @Override public boolean isRoot() { - root.checkLive(); + enter(); return parent == null; } @Override public String getPath() { - root.checkLive(); - if (isRoot()) { - // shortcut - return "/"; + enter(); + return getPathInternal(); - } + } - StringBuilder sb = new StringBuilder(); - buildPath(sb); - return sb.toString(); - } - @Override public Tree getParent() { - root.checkLive(); + enter(); if (parent != null && canRead(parent)) { return parent; } else { @@ -132,7 +131,7 @@ @Override public PropertyState getProperty(String name) { - root.checkLive(); + enter(); PropertyState property = internalGetProperty(name); if (canRead(property)) { return property; @@ -144,7 +143,7 @@ @Override public Status getPropertyStatus(String name) { // TODO: see OAK-212 - root.checkLive(); + enter(); Status nodeStatus = getStatus(); if (nodeStatus == Status.NEW) { return (hasProperty(name)) ? Status.NEW : null; @@ -175,19 +174,19 @@ @Override public boolean hasProperty(String name) { - root.checkLive(); + enter(); return getProperty(name) != null; } @Override public long getPropertyCount() { - root.checkLive(); + enter(); return Iterables.size(getProperties()); } @Override public Iterable getProperties() { - root.checkLive(); + enter(); return Iterables.filter(nodeBuilder.getProperties(), new Predicate() { @Override @@ -200,7 +199,7 @@ @Override public TreeImpl getChild(@Nonnull String name) { checkNotNull(name); - root.checkLive(); + enter(); TreeImpl child = internalGetChild(name); if (child != null && canRead(child)) { return child; @@ -224,7 +223,7 @@ @Override public Status getStatus() { - root.checkLive(); + enter(); if (isDisconnected()) { return Status.DISCONNECTED; @@ -247,13 +246,13 @@ @Override public long getChildrenCount() { // TODO: make sure cnt respects access control - root.checkLive(); + enter(); return nodeBuilder.getChildNodeCount(); } @Override public Iterable getChildren() { - root.checkLive(); + enter(); Iterable childNames; if (hasOrderableChildren()) { childNames = getOrderedChildNames(); @@ -265,7 +264,7 @@ new Function() { @Override public Tree apply(String input) { - return new TreeImpl(root, TreeImpl.this, input); + return new TreeImpl(root, TreeImpl.this, input, pendingMoves); } }), new Predicate() { @@ -278,7 +277,7 @@ @Override public Tree addChild(String name) { - root.checkLive(); + enter(); if (!hasChild(name)) { nodeBuilder.child(name); if (hasOrderableChildren()) { @@ -290,7 +289,7 @@ root.updated(); } - TreeImpl child = new TreeImpl(root, this, name); + TreeImpl child = new TreeImpl(root, this, name, pendingMoves); // Make sure to allocate the node builder for new nodes in order to correctly // track removes and moves. See OAK-621 @@ -299,7 +298,7 @@ @Override public void setOrderableChildren(boolean enable) { - root.checkLive(); + enter(); if (enable) { ensureChildOrderProperty(); } else { @@ -309,7 +308,7 @@ @Override public boolean remove() { - root.checkLive(); + enter(); if (isDisconnected()) { throw new IllegalStateException("Cannot remove a disconnected tree"); } @@ -333,7 +332,7 @@ @Override public boolean orderBefore(final String name) { - root.checkLive(); + enter(); if (isRoot()) { // root does not have siblings return false; @@ -378,35 +377,35 @@ @Override public void setProperty(PropertyState property) { - root.checkLive(); + enter(); nodeBuilder.setProperty(property); root.updated(); } @Override public void setProperty(String name, T value) { - root.checkLive(); + enter(); nodeBuilder.setProperty(name, value); root.updated(); } @Override public void setProperty(String name, T value, Type type) { - root.checkLive(); + enter(); nodeBuilder.setProperty(name, value, type); root.updated(); } @Override public void removeProperty(String name) { - root.checkLive(); + enter(); nodeBuilder.removeProperty(name); root.updated(); } @Override public TreeLocation getLocation() { - root.checkLive(); + enter(); return new NodeLocation(this); } @@ -439,10 +438,6 @@ * @param destName new name for this tree */ void moveTo(TreeImpl destParent, String destName) { - if (isDisconnected()) { - throw new IllegalStateException("Cannot move a disconnected tree"); - } - name = destName; parent = destParent; } @@ -496,12 +491,35 @@ nodeBuilder.setProperty(builder.getPropertyState()); } + String getPathInternal() { + if (parent == null) { + return "/"; + } + + StringBuilder sb = new StringBuilder(); + buildPath(sb); + return sb.toString(); + } + //------------------------------------------------------------< private >--- + private void enter() { + root.checkLive(); + applyPendingMoves(); + } + + private void applyPendingMoves() { + if (parent != null) { + parent.applyPendingMoves(); + } + + pendingMoves = pendingMoves.apply(this); + } + private TreeImpl internalGetChild(String childName) { return nodeBuilder.hasChildNode(childName) - ? new TreeImpl(root, this, childName) + ? new TreeImpl(root, this, childName, pendingMoves) - : null; + : null; } private PropertyState internalGetProperty(String propertyName) { @@ -509,7 +527,7 @@ } private void buildPath(StringBuilder sb) { - if (!isRoot()) { + if (parent != null) { parent.buildPath(sb); sb.append('/').append(name); } Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/CompatibilityIssuesTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.apache.jackrabbit.oak.jcr;\n\nimport javax.jcr.InvalidItemStateException;\nimport javax.jcr.Node;\nimport javax.jcr.RepositoryException;\nimport javax.jcr.Session;\n\nimport org.apache.jackrabbit.oak.Oak;\nimport org.apache.jackrabbit.oak.api.CommitFailedException;\nimport org.apache.jackrabbit.oak.api.ContentSession;\nimport org.apache.jackrabbit.oak.api.Root;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertFalse;\nimport static org.junit.Assert.assertTrue;\nimport static org.junit.Assert.fail;\n\n/**\n * This class contains test cases which demonstrate changes in behaviour wrt. to Jackrabbit 2.\n * See OAK-14: Identify and document changes in behaviour wrt. Jackrabbit 2\n */\npublic class CompatibilityIssuesTest extends AbstractRepositoryTest {\n\n /**\n * Trans-session isolation differs from Jackrabbit 2. Snapshot isolation can\n * result in write skew as this test demonstrates: the check method enforces\n * an application logic constraint which says that the sum of the properties\n * p1 and p2 must not be negative. While session1 and session2 each enforce\n * this constraint before saving, the constraint might not hold globally as\n * can be seen in session3.\n *\n * @see \n * Transactional model of the Microkernel based Jackrabbit prototype\n */\n @Test\n public void sessionIsolation() throws RepositoryException {\n Session session0 = createAdminSession();\n Session session1 = null;\n Session session2 = null;\n try {\n Node testNode = session0.getNode(\"/\").addNode(\"testNode\");\n testNode.setProperty(\"p1\", 1);\n testNode.setProperty(\"p2\", 1);\n session0.save();\n check(getAdminSession());\n\n session1 = createAdminSession();\n session2 = createAdminSession();\n session1.getNode(\"/testNode\").setProperty(\"p1\", -1);\n check(session1);\n session1.save();\n\n session2.getNode(\"/testNode\").setProperty(\"p2\", -1);\n check(session2); // Throws on JR2, not on Oak\n session2.save();\n\n Session session3 = createAnonymousSession();\n try {\n check(session3); // Throws on Oak\n fail();\n } catch (AssertionError e) {\n // expected\n } finally {\n session3.logout();\n }\n\n } finally {\n session0.logout();\n if (session1 != null) {\n session1.logout();\n }\n if (session2 != null) {\n session2.logout();\n }\n }\n }\n\n private static void check(Session session) throws RepositoryException {\n if (session.getNode(\"/testNode\").getProperty(\"p1\").getLong() +\n session.getNode(\"/testNode\").getProperty(\"p2\").getLong() < 0) {\n fail(\"p1 + p2 < 0\");\n }\n }\n\n @Test\n public void move() throws RepositoryException {\n Session session = getAdminSession();\n\n Node node = session.getNode(\"/\");\n node.addNode(\"source\").addNode(\"node\");\n node.addNode(\"target\");\n session.save();\n\n session.refresh(true);\n Node sourceNode = session.getNode(\"/source/node\");\n session.move(\"/source/node\", \"/target/moved\");\n // assertEquals(\"/target/moved\", sourceNode.getPath()); // passes on JR2, fails on Oak\n try {\n sourceNode.getPath();\n }\n catch (InvalidItemStateException expected) {\n // sourceNode is stale\n }\n }\n\n @Test\n public void move2() throws CommitFailedException {\n ContentSession session = new Oak().createContentSession();\n Root root = session.getLatestRoot();\n root.getTree(\"/\").addChild(\"x\");\n root.getTree(\"/\").addChild(\"y\");\n root.commit();\n\n Tree r = root.getTree(\"/\");\n Tree x = r.getChild(\"x\");\n Tree y = r.getChild(\"y\");\n\n assertFalse(y.hasChild(\"x\"));\n assertEquals(\"\", x.getParent().getName());\n root.move(\"/x\", \"/y/x\");\n assertTrue(y.hasChild(\"x\"));\n // assertEquals(\"y\", x.getParent().getName()); // passed on JR2, fails on Oak\n assertEquals(\"\", x.getParent().getName()); // fails on JR2, passes on Oak\n }\n\n}\n =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/CompatibilityIssuesTest.java (revision b418a59160c07ef5c27c7d403e87cb8e711d88f6) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/CompatibilityIssuesTest.java (revision ) @@ -21,16 +21,8 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; -import org.apache.jackrabbit.oak.Oak; -import org.apache.jackrabbit.oak.api.CommitFailedException; -import org.apache.jackrabbit.oak.api.ContentSession; -import org.apache.jackrabbit.oak.api.Root; -import org.apache.jackrabbit.oak.api.Tree; import org.junit.Test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** @@ -119,26 +111,6 @@ catch (InvalidItemStateException expected) { // sourceNode is stale } - } - - @Test - public void move2() throws CommitFailedException { - ContentSession session = new Oak().createContentSession(); - Root root = session.getLatestRoot(); - root.getTree("/").addChild("x"); - root.getTree("/").addChild("y"); - root.commit(); - - Tree r = root.getTree("/"); - Tree x = r.getChild("x"); - Tree y = r.getChild("y"); - - assertFalse(y.hasChild("x")); - assertEquals("", x.getParent().getName()); - root.move("/x", "/y/x"); - assertTrue(y.hasChild("x")); - // assertEquals("y", x.getParent().getName()); // passed on JR2, fails on Oak - assertEquals("", x.getParent().getName()); // fails on JR2, passes on Oak } } Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/MoveRemoveTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>package org.apache.jackrabbit.oak.jcr;\n\nimport javax.jcr.InvalidItemStateException;\nimport javax.jcr.Node;\nimport javax.jcr.RepositoryException;\nimport javax.jcr.Session;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.fail;\n\npublic class MoveRemoveTest extends AbstractRepositoryTest {\n\n @Test\n public void removeExistingNode() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"new\");\n session.save();\n\n Node n = session.getNode(\"/new\");\n n.remove();\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"new\");\n assertEquals(\"/new\", n.getPath());\n }\n\n @Test\n public void removeNewNode() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"new\");\n\n Node n = session.getNode(\"/new\");\n n.remove();\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"new\");\n assertEquals(\"/new\", n.getPath());\n }\n\n @Test\n public void removeExistingNodeRefresh() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"new\");\n session.save();\n\n Session session2 = createAdminSession();\n Node n2 = session2.getNode(\"/new\");\n\n Node n = session.getNode(\"/new\");\n n.remove();\n session.save();\n\n assertEquals(\"/new\", n2.getPath());\n\n session2.refresh(true);\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"new\");\n session.save();\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session2.refresh(true);\n assertEquals(\"/new\", n2.getPath());\n\n session2.logout();\n }\n\n @Test\n public void removeExistingNodeParent() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n session.save();\n\n Node n = session.getNode(\"/parent/new\");\n n.getParent().remove();\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n assertEquals(\"/parent/new\", n.getPath());\n }\n\n @Test\n public void removeNewNodeParent() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n\n Node n = session.getNode(\"/parent/new\");\n n.getParent().remove();\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n assertEquals(\"/parent/new\", n.getPath());\n }\n\n @Test\n public void removeExistingNodeRefreshParent() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n session.save();\n\n Session session2 = createAdminSession();\n Node n2 = session2.getNode(\"/parent/new\");\n\n Node n = session.getNode(\"/parent/new\");\n n.getParent().remove();\n session.save();\n\n assertEquals(\"/parent/new\", n2.getPath());\n\n session2.refresh(true);\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n session.save();\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session2.refresh(true);\n assertEquals(\"/parent/new\", n2.getPath());\n\n session2.logout();\n }\n\n @Test\n public void moveExistingNode() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"new\");\n session.save();\n\n Node n = session.getNode(\"/new\");\n session.move(\"/new\", \"/moved\");\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"new\");\n assertEquals(\"/new\", n.getPath());\n }\n\n @Test\n public void moveNewNode() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"new\");\n\n Node n = session.getNode(\"/new\");\n session.move(\"/new\", \"/moved\");\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"new\");\n assertEquals(\"/new\", n.getPath());\n }\n\n @Test\n public void moveExistingNodeRefresh() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"new\");\n session.save();\n\n Session session2 = createAdminSession();\n Node n2 = session2.getNode(\"/new\");\n\n Node n = session.getNode(\"/new\");\n session.move(\"/new\", \"/moved\");\n session.save();\n\n assertEquals(\"/new\", n2.getPath());\n\n session2.refresh(true);\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"new\");\n session.save();\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session2.refresh(true);\n assertEquals(\"/new\", n2.getPath());\n\n session2.logout();\n }\n\n @Test\n public void moveExistingParent() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n session.save();\n\n Node n = session.getNode(\"/parent/new\");\n session.move(\"/parent\", \"/moved\");\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n assertEquals(\"/parent/new\", n.getPath());\n }\n\n @Test\n public void moveNewNodeParent() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n\n Node n = session.getNode(\"/parent/new\");\n session.move(\"/parent\", \"/moved\");\n\n try {\n n.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n assertEquals(\"/parent/new\", n.getPath());\n }\n\n @Test\n public void moveExistingNodeRefreshParent() throws RepositoryException {\n Session session = getAdminSession();\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n session.save();\n\n Session session2 = createAdminSession();\n Node n2 = session2.getNode(\"/parent/new\");\n\n Node n = session.getNode(\"/parent/new\");\n session.move(\"/parent\", \"/moved\");\n session.save();\n\n assertEquals(\"/parent/new\", n2.getPath());\n\n session2.refresh(true);\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session.getRootNode().addNode(\"parent\").addNode(\"new\");\n session.save();\n try {\n n2.getPath();\n fail();\n } catch (InvalidItemStateException e) {}\n\n session2.refresh(true);\n assertEquals(\"/parent/new\", n2.getPath());\n\n session2.logout();\n }\n}\n =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/MoveRemoveTest.java (revision b418a59160c07ef5c27c7d403e87cb8e711d88f6) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/MoveRemoveTest.java (revision ) @@ -159,13 +159,7 @@ Node n = session.getNode("/new"); session.move("/new", "/moved"); - try { - n.getPath(); - fail(); - } catch (InvalidItemStateException e) {} - - session.getRootNode().addNode("new"); - assertEquals("/new", n.getPath()); + assertEquals("/moved", n.getPath()); } @Test @@ -176,13 +170,7 @@ Node n = session.getNode("/new"); session.move("/new", "/moved"); - try { - n.getPath(); - fail(); - } catch (InvalidItemStateException e) {} - - session.getRootNode().addNode("new"); - assertEquals("/new", n.getPath()); + assertEquals("/moved", n.getPath()); } @Test @@ -228,13 +216,7 @@ Node n = session.getNode("/parent/new"); session.move("/parent", "/moved"); - try { - n.getPath(); - fail(); - } catch (InvalidItemStateException e) {} - - session.getRootNode().addNode("parent").addNode("new"); - assertEquals("/parent/new", n.getPath()); + assertEquals("/moved/new", n.getPath()); } @Test @@ -245,13 +227,7 @@ Node n = session.getNode("/parent/new"); session.move("/parent", "/moved"); - try { - n.getPath(); - fail(); - } catch (InvalidItemStateException e) {} - - session.getRootNode().addNode("parent").addNode("new"); - assertEquals("/parent/new", n.getPath()); + assertEquals("/moved/new", n.getPath()); } @Test