From 50c1fd137e93d5eb873c0990aa8b4dc18da61b6f Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Fri, 2 Aug 2024 11:23:18 +0100 Subject: [PATCH 01/13] OAK-11000: Test coverage for common JCR operations related to importing content (work-in-progress) --- .../oak/jcr/ImportOperationsTest.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100755 oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java new file mode 100755 index 00000000000..bd2c1ea240f --- /dev/null +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java @@ -0,0 +1,161 @@ +/* + * 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.oak.jcr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.ReferentialIntegrityException; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.nodetype.NodeType; + +import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; +import org.junit.Before; +import org.junit.Test; + +/** + * Test coverage for common JCR operations related to importing content. + *

+ * Note that the purpose of these tests is not to check conformance with the JCR + * specification, but to observe the actual behavior of the implementation + * (which may be hard to change). + */ +public class ImportOperationsTest extends AbstractRepositoryTest { + + private Session session; + private Node testNode; + private static String TEST_NODE_NAME = "ImportOperationsTest"; + private static String TEST_NODE_NAME_REF = "ImportOperationsTest-Reference"; + + public ImportOperationsTest(NodeStoreFixture fixture) { + super(fixture); + } + + @Before + public void setup() throws RepositoryException { + this.session = getAdminSession(); + this.testNode = session.getRootNode().addNode("import-tests", NodeType.NT_UNSTRUCTURED); + session.save(); + } + + @Test + public void jcrMixinCreatedOnNtUnstructured() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + try { + test.setProperty("jcr:created", false); + session.save(); + test.addMixin(NodeType.MIX_CREATED); + session.save(); + // surprise: setting the mixin succeeds, and the subsequent state of + // the node is inconsistent with the mixin's type definition + assertEquals(false, test.getProperty("jcr:created").getBoolean()); + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + try { + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + // surprise: setting the mixin succeeds, and the subsequent state of + // the node doesn't have an autocreated UUID + assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredAfterSettingMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + try { + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + fail("Setting jcr:uuid after adding mixin:referenceable should fail"); + } catch (ConstraintViolationException ex) { + // expected + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + try { + test.removeMixin(NodeType.MIX_REFERENCEABLE); + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); + Node check = session.getNodeByIdentifier(testUuid); + assertTrue(test.isSame(check)); + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixinButDanglingReference() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); + session.save(); + + try { + test.removeMixin(NodeType.MIX_REFERENCEABLE); + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + fail("Changing jcr:uuid causing a dangling refence should fail"); + } catch (ReferentialIntegrityException ex) { + // expected + } finally { + ref.remove(); + test.remove(); + session.save(); + } + } +} From 4f5715683f6fc3fa61282b920b10db67e526f8c1 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Fri, 2 Aug 2024 15:25:50 +0100 Subject: [PATCH 02/13] OAK-11000: Test coverage for common JCR operations related to importing content (work-in-progress) --- .../oak/jcr/ImportOperationsTest.java | 383 ++++++++++-------- 1 file changed, 222 insertions(+), 161 deletions(-) mode change 100755 => 100644 oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java old mode 100755 new mode 100644 index bd2c1ea240f..f4514eb825e --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java @@ -1,161 +1,222 @@ -/* - * 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.oak.jcr; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.UUID; - -import javax.jcr.Node; -import javax.jcr.PropertyType; -import javax.jcr.ReferentialIntegrityException; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.ConstraintViolationException; -import javax.jcr.nodetype.NodeType; - -import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; -import org.junit.Before; -import org.junit.Test; - -/** - * Test coverage for common JCR operations related to importing content. - *

- * Note that the purpose of these tests is not to check conformance with the JCR - * specification, but to observe the actual behavior of the implementation - * (which may be hard to change). - */ -public class ImportOperationsTest extends AbstractRepositoryTest { - - private Session session; - private Node testNode; - private static String TEST_NODE_NAME = "ImportOperationsTest"; - private static String TEST_NODE_NAME_REF = "ImportOperationsTest-Reference"; - - public ImportOperationsTest(NodeStoreFixture fixture) { - super(fixture); - } - - @Before - public void setup() throws RepositoryException { - this.session = getAdminSession(); - this.testNode = session.getRootNode().addNode("import-tests", NodeType.NT_UNSTRUCTURED); - session.save(); - } - - @Test - public void jcrMixinCreatedOnNtUnstructured() throws RepositoryException { - Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); - try { - test.setProperty("jcr:created", false); - session.save(); - test.addMixin(NodeType.MIX_CREATED); - session.save(); - // surprise: setting the mixin succeeds, and the subsequent state of - // the node is inconsistent with the mixin's type definition - assertEquals(false, test.getProperty("jcr:created").getBoolean()); - } finally { - test.remove(); - session.save(); - } - } - - @Test - public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws RepositoryException { - Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); - try { - String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); - session.save(); - test.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); - // surprise: setting the mixin succeeds, and the subsequent state of - // the node doesn't have an autocreated UUID - assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); - } finally { - test.remove(); - session.save(); - } - } - - @Test - public void jcrMixinReferenceableOnNtUnstructuredAfterSettingMixin() throws RepositoryException { - Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); - test.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); - try { - String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); - session.save(); - fail("Setting jcr:uuid after adding mixin:referenceable should fail"); - } catch (ConstraintViolationException ex) { - // expected - } finally { - test.remove(); - session.save(); - } - } - - @Test - public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixin() throws RepositoryException { - Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); - test.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); - try { - test.removeMixin(NodeType.MIX_REFERENCEABLE); - String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); - session.save(); - test.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); - assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); - Node check = session.getNodeByIdentifier(testUuid); - assertTrue(test.isSame(check)); - } finally { - test.remove(); - session.save(); - } - } - - @Test - public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixinButDanglingReference() throws RepositoryException { - Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); - test.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); - Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); - ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); - session.save(); - - try { - test.removeMixin(NodeType.MIX_REFERENCEABLE); - String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); - session.save(); - fail("Changing jcr:uuid causing a dangling refence should fail"); - } catch (ReferentialIntegrityException ex) { - // expected - } finally { - ref.remove(); - test.remove(); - session.save(); - } - } -} +/* + * 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.oak.jcr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.ReferentialIntegrityException; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.nodetype.NodeType; + +import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; +import org.junit.Before; +import org.junit.Test; + +/** + * Test coverage for common JCR operations related to importing content. + *

+ * Note that the purpose of these tests is not to check conformance with the JCR + * specification, but to observe the actual behavior of the implementation + * (which may be hard to change). + */ +public class ImportOperationsTest extends AbstractRepositoryTest { + + private Session session; + private Node testNode; + private static String TEST_NODE_NAME = "ImportOperationsTest"; + private static String TEST_NODE_NAME_REF = "ImportOperationsTest-Reference"; + private static String TEST_NODE_NAME_TMP = "ImportOperationsTest-Temp"; + + public ImportOperationsTest(NodeStoreFixture fixture) { + super(fixture); + } + + @Before + public void setup() throws RepositoryException { + this.session = getAdminSession(); + this.testNode = session.getRootNode().addNode("import-tests", NodeType.NT_UNSTRUCTURED); + session.save(); + } + + @Test + public void jcrMixinCreatedOnNtUnstructured() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + try { + test.setProperty("jcr:created", false); + session.save(); + test.addMixin(NodeType.MIX_CREATED); + session.save(); + // surprise: setting the mixin succeeds, and the subsequent state of + // the node is inconsistent with the mixin's type definition + assertEquals(false, test.getProperty("jcr:created").getBoolean()); + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + try { + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + // surprise: setting the mixin succeeds, and the subsequent state of + // the node doesn't have an autocreated UUID + assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredAfterSettingMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + try { + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + fail("Setting jcr:uuid after adding mixin:referenceable should fail"); + } catch (ConstraintViolationException ex) { + // expected + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + try { + test.removeMixin(NodeType.MIX_REFERENCEABLE); + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); + Node check = session.getNodeByIdentifier(testUuid); + assertTrue(test.isSame(check)); + } finally { + test.remove(); + session.save(); + } + } + + @Test + public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixinButDanglingReference() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); + session.save(); + + try { + test.removeMixin(NodeType.MIX_REFERENCEABLE); + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + session.save(); + fail("Changing jcr:uuid causing a dangling refence should fail"); + } catch (ReferentialIntegrityException ex) { + // expected + } finally { + ref.remove(); + test.remove(); + session.save(); + } + } + + @Test + public void changeUuidOnReferenceNode() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); + session.save(); + + // temporary node for rewriting the references + Node tmp = testNode.addNode(TEST_NODE_NAME_TMP, NodeType.NT_UNSTRUCTURED); + tmp.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + + try { + // find all existing references to the node for which we want to rewrite the jcr:uuid + Set referrers = getReferrers(test); + + // move existing references to TEST_MODE_NAME to TEST_NODE_NAME_TMP + setReferrersTo(referrers, tmp.getIdentifier()); + session.save(); + + // rewrite jcr:uuid + test.removeMixin(NodeType.MIX_REFERENCEABLE); + String testUuid = UUID.randomUUID().toString(); + test.setProperty("jcr:uuid", testUuid); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + + // restore references + setReferrersTo(referrers, testUuid); + session.save(); + } finally { + tmp.remove(); + ref.remove(); + test.remove(); + session.save(); + } + } + + private static Set getReferrers(Node to) throws RepositoryException { + Set referrers = new HashSet<>(); + PropertyIterator pit = to.getReferences(); + while (pit.hasNext()) { + referrers.add(pit.nextProperty()); + } + return referrers; + } + + private static void setReferrersTo(Set referrers, String identifier) throws RepositoryException { + for (Property p : referrers) { + // add case for multivalued + p.getParent().setProperty(p.getName(), identifier); + } + } +} From 737408ff94296b0a42e53ed25f362024af289c6f Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 5 Aug 2024 10:16:33 +0100 Subject: [PATCH 03/13] OAK-11000:rename to ProtectedPropertyTest --- .../{ImportOperationsTest.java => ProtectedPropertyTest.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/{ImportOperationsTest.java => ProtectedPropertyTest.java} (98%) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java similarity index 98% rename from oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java rename to oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index f4514eb825e..b32178af216 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ImportOperationsTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -47,7 +47,7 @@ * specification, but to observe the actual behavior of the implementation * (which may be hard to change). */ -public class ImportOperationsTest extends AbstractRepositoryTest { +public class ProtectedPropertyTest extends AbstractRepositoryTest { private Session session; private Node testNode; @@ -55,7 +55,7 @@ public class ImportOperationsTest extends AbstractRepositoryTest { private static String TEST_NODE_NAME_REF = "ImportOperationsTest-Reference"; private static String TEST_NODE_NAME_TMP = "ImportOperationsTest-Temp"; - public ImportOperationsTest(NodeStoreFixture fixture) { + public ProtectedPropertyTest(NodeStoreFixture fixture) { super(fixture); } From 0025cf4188946d06a16d893abd216f22ea86c442 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 5 Aug 2024 10:23:36 +0100 Subject: [PATCH 04/13] OAK-11000: fix comment about adding mixin:created behavior (ack kwin) --- .../apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index b32178af216..744aa0a162c 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -74,9 +74,13 @@ public void jcrMixinCreatedOnNtUnstructured() throws RepositoryException { session.save(); test.addMixin(NodeType.MIX_CREATED); session.save(); - // surprise: setting the mixin succeeds, and the subsequent state of - // the node is inconsistent with the mixin's type definition assertEquals(false, test.getProperty("jcr:created").getBoolean()); + // in Oak, existing properties are left as-is (even the property + // type), which means that after adding the mixin:created type, the + // state of the node might be inconsistent with the mixin:created + // type. This may come as a surprise, but is allowed as + // implementation specific behavior, see + // https://developer.adobe.com/experience-manager/reference-materials/spec/jcr/2.0/3_Repository_Model.html#3.7.11.7%20mix:created } finally { test.remove(); session.save(); From 0517fd4c86f31a2fd22de93e8da55612c922c01f Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 5 Aug 2024 12:44:59 +0100 Subject: [PATCH 05/13] OAK-11000: confirm that removeMixin(mix:referenceable) fails when inherited (such as from nt:resource) --- .../oak/jcr/ProtectedPropertyTest.java | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index 744aa0a162c..97b9127c183 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; import java.util.HashSet; import java.util.Set; import java.util.UUID; @@ -34,6 +35,7 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.ConstraintViolationException; +import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeType; import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; @@ -169,7 +171,7 @@ public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixinButDanglingRe } @Test - public void changeUuidOnReferenceNode() throws RepositoryException { + public void changeUuidOnReferencedNodeWithOnlyMixin() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); test.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); @@ -177,33 +179,65 @@ public void changeUuidOnReferenceNode() throws RepositoryException { ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); session.save(); + try { + String newUuid = UUID.randomUUID().toString(); + updateJcrUuid(test, newUuid); + } finally { + ref.remove(); + test.remove(); + session.save(); + } + } + + @Test + public void changeUuidOnReferencedNodeWithInheritedMixin() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_RESOURCE); + test.setProperty("jcr:data", session.getValueFactory().createBinary(new ByteArrayInputStream(new byte[0]))); + session.save(); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); + session.save(); + + try { + String newUuid = UUID.randomUUID().toString(); + updateJcrUuid(test, newUuid); + fail("removing mixin:referenceable should fail on nt:resource"); + } catch (NoSuchNodeTypeException ex) { + // expected + } finally { + ref.remove(); + test.remove(); + session.save(); + } + } + + private static void updateJcrUuid(Node target, String newUUID) throws RepositoryException { + Session session = target.getSession(); + // temporary node for rewriting the references - Node tmp = testNode.addNode(TEST_NODE_NAME_TMP, NodeType.NT_UNSTRUCTURED); + Node tmp = target.getParent().addNode(TEST_NODE_NAME_TMP, NodeType.NT_UNSTRUCTURED); tmp.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); try { // find all existing references to the node for which we want to rewrite the jcr:uuid - Set referrers = getReferrers(test); + Set referrers = getReferrers(target); // move existing references to TEST_MODE_NAME to TEST_NODE_NAME_TMP setReferrersTo(referrers, tmp.getIdentifier()); session.save(); // rewrite jcr:uuid - test.removeMixin(NodeType.MIX_REFERENCEABLE); - String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); - test.addMixin(NodeType.MIX_REFERENCEABLE); + target.removeMixin(NodeType.MIX_REFERENCEABLE); + target.setProperty("jcr:uuid", newUUID); + target.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); // restore references - setReferrersTo(referrers, testUuid); + setReferrersTo(referrers, newUUID); session.save(); } finally { tmp.remove(); - ref.remove(); - test.remove(); session.save(); } } From 9b7814a0f337c204ec03dcd44d24aa45572fa4e0 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 5 Aug 2024 13:12:16 +0100 Subject: [PATCH 06/13] OAK-11000: confirm that setting jcr:uuid as custom property will fail when another referenceable node already has that UUID --- .../oak/jcr/ProtectedPropertyTest.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index 97b9127c183..a279569238f 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -98,8 +98,10 @@ public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws Rep session.save(); test.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); - // surprise: setting the mixin succeeds, and the subsequent state of - // the node doesn't have an autocreated UUID + // JCR spec + // (https://developer.adobe.com/experience-manager/reference-materials/spec/jcr/2.0/3_Repository_Model.html#3.8%20Referenceable%20Nodes) + // requests an "auto-created" property, so it might be a surprise + // that Oak actually keeps the application-assigned previous value. assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); } finally { test.remove(); @@ -107,6 +109,28 @@ public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws Rep } } + @Test + public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixinButWithConflict() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + + try { + String testUuid = ref.getProperty("jcr:uuid").getString(); + test.setProperty("jcr:uuid", testUuid); + // note this fails even though test hasn't be set to mix:referenceable + session.save(); + fail("Attempt so set a UUID already in use should fail"); + } catch (ConstraintViolationException ex) { + // expected + } finally { + test.remove(); + ref.remove(); + session.save(); + } + } + @Test public void jcrMixinReferenceableOnNtUnstructuredAfterSettingMixin() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); From 3173b5c761c921d1b541c5f06ab8c0b1aef77786 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Tue, 6 Aug 2024 14:27:55 +0100 Subject: [PATCH 07/13] OAK-11000: check that jcr:uuid is gone after removing from nt:unstructured (ack mbaedke) --- .../jackrabbit/oak/jcr/ProtectedPropertyTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index a279569238f..ea8bedcb7a6 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -28,6 +28,7 @@ import java.util.UUID; import javax.jcr.Node; +import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; @@ -155,7 +156,17 @@ public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixin() throws Rep test.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); try { + // check jcr:uuid is there + String prevUuid = test.getProperty("jcr:uuid").getString(); test.removeMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + // ist jcr:uuid gone now? + try { + String newUuid = test.getProperty("jcr:uuid").getString(); + fail("jcr:uuid should be gone after removing the mixin type, was " + prevUuid + ", now is " + newUuid); + } catch (PathNotFoundException ex) { + // expected + } String testUuid = UUID.randomUUID().toString(); test.setProperty("jcr:uuid", testUuid); session.save(); From 94013667fb16e3e707c289ee52061c4205587aa7 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Wed, 7 Aug 2024 11:42:07 +0100 Subject: [PATCH 08/13] OAK-11000: add variant that uses a temporary node type with unprotected jcr:uuid (ack stefan-egli), also minor refactoring --- .../oak/jcr/ProtectedPropertyTest.java | 99 +++++++++++++++++-- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index ea8bedcb7a6..7eb974f6743 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -38,6 +38,9 @@ import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.NodeTypeManager; +import javax.jcr.nodetype.NodeTypeTemplate; +import javax.jcr.nodetype.PropertyDefinitionTemplate; import org.apache.jackrabbit.oak.fixture.NodeStoreFixture; import org.junit.Before; @@ -216,7 +219,10 @@ public void changeUuidOnReferencedNodeWithOnlyMixin() throws RepositoryException try { String newUuid = UUID.randomUUID().toString(); - updateJcrUuid(test, newUuid); + updateJcrUuidUsingRemoveMixin(test, newUuid); + assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + session.save(); + assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); } finally { ref.remove(); test.remove(); @@ -235,7 +241,7 @@ public void changeUuidOnReferencedNodeWithInheritedMixin() throws RepositoryExce try { String newUuid = UUID.randomUUID().toString(); - updateJcrUuid(test, newUuid); + updateJcrUuidUsingRemoveMixin(test, newUuid); fail("removing mixin:referenceable should fail on nt:resource"); } catch (NoSuchNodeTypeException ex) { // expected @@ -246,13 +252,33 @@ public void changeUuidOnReferencedNodeWithInheritedMixin() throws RepositoryExce } } - private static void updateJcrUuid(Node target, String newUUID) throws RepositoryException { - Session session = target.getSession(); + @Test + public void changeUuidOnReferencedNodeWithInheritedMixinByChangingNodeTypeTemporarily() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_RESOURCE); + test.setProperty("jcr:data", session.getValueFactory().createBinary(new ByteArrayInputStream(new byte[0]))); + session.save(); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); + session.save(); + + try { + String newUuid = UUID.randomUUID().toString(); + updateJcrUuidUsingNodeTypeManager(test, newUuid); + assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + session.save(); + session.refresh(false); + assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + } finally { + ref.remove(); + test.remove(); + session.save(); + } + } + private static void updateJcrUuidUsingRemoveMixin(Node target, String newUUID) throws RepositoryException { // temporary node for rewriting the references Node tmp = target.getParent().addNode(TEST_NODE_NAME_TMP, NodeType.NT_UNSTRUCTURED); tmp.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); try { // find all existing references to the node for which we want to rewrite the jcr:uuid @@ -260,20 +286,52 @@ private static void updateJcrUuid(Node target, String newUUID) throws Repository // move existing references to TEST_MODE_NAME to TEST_NODE_NAME_TMP setReferrersTo(referrers, tmp.getIdentifier()); - session.save(); // rewrite jcr:uuid target.removeMixin(NodeType.MIX_REFERENCEABLE); target.setProperty("jcr:uuid", newUUID); target.addMixin(NodeType.MIX_REFERENCEABLE); - session.save(); // restore references setReferrersTo(referrers, newUUID); - session.save(); } finally { tmp.remove(); - session.save(); + } + } + + private static void updateJcrUuidUsingNodeTypeManager(Node target, String newUUID) throws RepositoryException { + // temporary node for rewriting the references + Node tmp = target.getParent().addNode(TEST_NODE_NAME_TMP, NodeType.NT_UNSTRUCTURED); + tmp.addMixin(NodeType.MIX_REFERENCEABLE); + + try { + String previousType = target.getPrimaryNodeType().getName(); + + // find all existing references to the node for which we want to rewrite the jcr:uuid + Set referrers = getReferrers(target); + + // move existing references to TEST_MODE_NAME to TEST_NODE_NAME_TMP + setReferrersTo(referrers, tmp.getIdentifier()); + + // rewrite jcr:uuid + String temporaryType = registerPrimaryTypeExtendingAndUnprotectingJcrUUID(target); + target.setPrimaryType(temporaryType); + target.setProperty("jcr:uuid", newUUID); + target.setPrimaryType(previousType); + unregisterPrimaryTypeExtendingAndUnprotectingJcrUUID(target, temporaryType); + + // restore references + setReferrersTo(referrers, newUUID); + + // assert temporary node type is gone + try { + target.getSession().getWorkspace().getNodeTypeManager().getNodeType(temporaryType); + fail("temporary node type should be removed"); + } catch (NoSuchNodeTypeException ex) { + // expected + } + } finally { + tmp.remove(); } } @@ -292,4 +350,27 @@ private static void setReferrersTo(Set referrers, String identifier) t p.getParent().setProperty(p.getName(), identifier); } } + + @SuppressWarnings("unchecked") + private static String registerPrimaryTypeExtendingAndUnprotectingJcrUUID(Node node) throws RepositoryException { + String tmpNodeTypeName = "tmp-" + UUID.randomUUID().toString(); + NodeTypeManager ntMgr = node.getSession().getWorkspace().getNodeTypeManager(); + + NodeTypeTemplate unprotectedNTT = ntMgr.createNodeTypeTemplate(); + unprotectedNTT.setName(tmpNodeTypeName); + unprotectedNTT.setDeclaredSuperTypeNames(new String[] {node.getPrimaryNodeType().getName()}); + PropertyDefinitionTemplate pdt = ntMgr.createPropertyDefinitionTemplate(); + pdt.setName("jcr:uuid"); + pdt.setProtected(false); + unprotectedNTT.getPropertyDefinitionTemplates().add(pdt); + ntMgr.registerNodeType(unprotectedNTT, true); + + return tmpNodeTypeName; + } + + private static void unregisterPrimaryTypeExtendingAndUnprotectingJcrUUID(Node node, String tmpType) throws RepositoryException { + NodeTypeManager ntMgr = node.getSession().getWorkspace().getNodeTypeManager(); + + ntMgr.unregisterNodeType(tmpType); + } } From a4cf43e51df7f30e7289e9c693b5664a5c600dce Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Wed, 7 Aug 2024 14:58:09 +0100 Subject: [PATCH 09/13] OAK-11000: use constants from java.jcr.Property --- .../oak/jcr/ProtectedPropertyTest.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index 7eb974f6743..d6a57d6aeab 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -76,11 +76,11 @@ public void setup() throws RepositoryException { public void jcrMixinCreatedOnNtUnstructured() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); try { - test.setProperty("jcr:created", false); + test.setProperty(Property.JCR_CREATED, false); session.save(); test.addMixin(NodeType.MIX_CREATED); session.save(); - assertEquals(false, test.getProperty("jcr:created").getBoolean()); + assertEquals(false, test.getProperty(Property.JCR_CREATED).getBoolean()); // in Oak, existing properties are left as-is (even the property // type), which means that after adding the mixin:created type, the // state of the node might be inconsistent with the mixin:created @@ -98,7 +98,7 @@ public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws Rep Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); try { String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); + test.setProperty(Property.JCR_UUID, testUuid); session.save(); test.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); @@ -106,7 +106,7 @@ public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws Rep // (https://developer.adobe.com/experience-manager/reference-materials/spec/jcr/2.0/3_Repository_Model.html#3.8%20Referenceable%20Nodes) // requests an "auto-created" property, so it might be a surprise // that Oak actually keeps the application-assigned previous value. - assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); + assertEquals(testUuid, test.getProperty(Property.JCR_UUID).getString()); } finally { test.remove(); session.save(); @@ -121,8 +121,8 @@ public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixinButWithConfli session.save(); try { - String testUuid = ref.getProperty("jcr:uuid").getString(); - test.setProperty("jcr:uuid", testUuid); + String testUuid = ref.getProperty(Property.JCR_UUID).getString(); + test.setProperty(Property.JCR_UUID, testUuid); // note this fails even though test hasn't be set to mix:referenceable session.save(); fail("Attempt so set a UUID already in use should fail"); @@ -142,7 +142,7 @@ public void jcrMixinReferenceableOnNtUnstructuredAfterSettingMixin() throws Repo session.save(); try { String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); + test.setProperty(Property.JCR_UUID, testUuid); session.save(); fail("Setting jcr:uuid after adding mixin:referenceable should fail"); } catch (ConstraintViolationException ex) { @@ -160,22 +160,22 @@ public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixin() throws Rep session.save(); try { // check jcr:uuid is there - String prevUuid = test.getProperty("jcr:uuid").getString(); + String prevUuid = test.getProperty(Property.JCR_UUID).getString(); test.removeMixin(NodeType.MIX_REFERENCEABLE); session.save(); // ist jcr:uuid gone now? try { - String newUuid = test.getProperty("jcr:uuid").getString(); + String newUuid = test.getProperty(Property.JCR_UUID).getString(); fail("jcr:uuid should be gone after removing the mixin type, was " + prevUuid + ", now is " + newUuid); } catch (PathNotFoundException ex) { // expected } String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); + test.setProperty(Property.JCR_UUID, testUuid); session.save(); test.addMixin(NodeType.MIX_REFERENCEABLE); session.save(); - assertEquals(testUuid, test.getProperty("jcr:uuid").getString()); + assertEquals(testUuid, test.getProperty(Property.JCR_UUID).getString()); Node check = session.getNodeByIdentifier(testUuid); assertTrue(test.isSame(check)); } finally { @@ -196,7 +196,7 @@ public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixinButDanglingRe try { test.removeMixin(NodeType.MIX_REFERENCEABLE); String testUuid = UUID.randomUUID().toString(); - test.setProperty("jcr:uuid", testUuid); + test.setProperty(Property.JCR_UUID, testUuid); session.save(); fail("Changing jcr:uuid causing a dangling refence should fail"); } catch (ReferentialIntegrityException ex) { @@ -220,9 +220,9 @@ public void changeUuidOnReferencedNodeWithOnlyMixin() throws RepositoryException try { String newUuid = UUID.randomUUID().toString(); updateJcrUuidUsingRemoveMixin(test, newUuid); - assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); session.save(); - assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); } finally { ref.remove(); test.remove(); @@ -233,7 +233,7 @@ public void changeUuidOnReferencedNodeWithOnlyMixin() throws RepositoryException @Test public void changeUuidOnReferencedNodeWithInheritedMixin() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_RESOURCE); - test.setProperty("jcr:data", session.getValueFactory().createBinary(new ByteArrayInputStream(new byte[0]))); + test.setProperty(Property.JCR_DATA, session.getValueFactory().createBinary(new ByteArrayInputStream(new byte[0]))); session.save(); Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); @@ -255,7 +255,7 @@ public void changeUuidOnReferencedNodeWithInheritedMixin() throws RepositoryExce @Test public void changeUuidOnReferencedNodeWithInheritedMixinByChangingNodeTypeTemporarily() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_RESOURCE); - test.setProperty("jcr:data", session.getValueFactory().createBinary(new ByteArrayInputStream(new byte[0]))); + test.setProperty(Property.JCR_DATA, session.getValueFactory().createBinary(new ByteArrayInputStream(new byte[0]))); session.save(); Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); @@ -264,10 +264,10 @@ public void changeUuidOnReferencedNodeWithInheritedMixinByChangingNodeTypeTempor try { String newUuid = UUID.randomUUID().toString(); updateJcrUuidUsingNodeTypeManager(test, newUuid); - assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); session.save(); session.refresh(false); - assertEquals(newUuid, test.getProperty("jcr:uuid").getString()); + assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); } finally { ref.remove(); test.remove(); @@ -289,7 +289,7 @@ private static void updateJcrUuidUsingRemoveMixin(Node target, String newUUID) t // rewrite jcr:uuid target.removeMixin(NodeType.MIX_REFERENCEABLE); - target.setProperty("jcr:uuid", newUUID); + target.setProperty(Property.JCR_UUID, newUUID); target.addMixin(NodeType.MIX_REFERENCEABLE); // restore references @@ -316,7 +316,7 @@ private static void updateJcrUuidUsingNodeTypeManager(Node target, String newUUI // rewrite jcr:uuid String temporaryType = registerPrimaryTypeExtendingAndUnprotectingJcrUUID(target); target.setPrimaryType(temporaryType); - target.setProperty("jcr:uuid", newUUID); + target.setProperty(Property.JCR_UUID, newUUID); target.setPrimaryType(previousType); unregisterPrimaryTypeExtendingAndUnprotectingJcrUUID(target, temporaryType); @@ -360,7 +360,7 @@ private static String registerPrimaryTypeExtendingAndUnprotectingJcrUUID(Node no unprotectedNTT.setName(tmpNodeTypeName); unprotectedNTT.setDeclaredSuperTypeNames(new String[] {node.getPrimaryNodeType().getName()}); PropertyDefinitionTemplate pdt = ntMgr.createPropertyDefinitionTemplate(); - pdt.setName("jcr:uuid"); + pdt.setName(Property.JCR_UUID); pdt.setProtected(false); unprotectedNTT.getPropertyDefinitionTemplates().add(pdt); ntMgr.registerNodeType(unprotectedNTT, true); From 1399ce18d95705b087f6c305bef6b7a840c34761 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 12 Aug 2024 11:17:32 +0100 Subject: [PATCH 10/13] OAK-11000: test overlapping updates with same UUID using two sessions --- .../oak/jcr/ProtectedPropertyTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index d6a57d6aeab..5799d1a55f6 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -230,6 +230,44 @@ public void changeUuidOnReferencedNodeWithOnlyMixin() throws RepositoryException } } + @Test + public void changeUuidOnReferencedNodeWithOnlyMixin2Sessions() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + test.addMixin(NodeType.MIX_REFERENCEABLE); + session.save(); + Node ref = testNode.addNode(TEST_NODE_NAME_REF, NodeType.NT_UNSTRUCTURED); + ref.setProperty("reference", test.getIdentifier(), PropertyType.REFERENCE); + session.save(); + + Session session2 = createAdminSession(); + Node testNode2 = session2.getNode(testNode.getPath()); + Node test2 = testNode2.addNode(TEST_NODE_NAME + "2", NodeType.NT_UNSTRUCTURED); + test2.addMixin(NodeType.MIX_REFERENCEABLE); + session2.save(); + Node ref2 = testNode.addNode(TEST_NODE_NAME_REF + "2", NodeType.NT_UNSTRUCTURED); + ref2.setProperty("reference", test2.getIdentifier(), PropertyType.REFERENCE); + session2.save(); + + try { + String newUuid = UUID.randomUUID().toString(); + updateJcrUuidUsingRemoveMixin(test, newUuid); + updateJcrUuidUsingRemoveMixin(test2, newUuid); + assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); + assertEquals(newUuid, test2.getProperty(Property.JCR_UUID).getString()); + session.save(); + session2.save(); + fail("saving 2nd session should fail"); + } catch (ConstraintViolationException ex) { + // expected + } finally { + ref2.remove(); + test2.remove(); + ref.remove(); + test.remove(); + session2.logout(); + } + } + @Test public void changeUuidOnReferencedNodeWithInheritedMixin() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_RESOURCE); From c80264259b8c99dafd5eb7f1b56eb5c65a46538e Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 12 Aug 2024 11:37:24 +0100 Subject: [PATCH 11/13] OAK-11000: add tests for getNodeByIdentifier --- .../org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index 5799d1a55f6..c3afc55398c 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -223,6 +223,7 @@ public void changeUuidOnReferencedNodeWithOnlyMixin() throws RepositoryException assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); session.save(); assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); + assertTrue(test.isSame(session.getNodeByIdentifier(newUuid))); } finally { ref.remove(); test.remove(); @@ -255,6 +256,7 @@ public void changeUuidOnReferencedNodeWithOnlyMixin2Sessions() throws Repository assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); assertEquals(newUuid, test2.getProperty(Property.JCR_UUID).getString()); session.save(); + assertTrue(test.isSame(session.getNodeByIdentifier(newUuid))); session2.save(); fail("saving 2nd session should fail"); } catch (ConstraintViolationException ex) { @@ -304,8 +306,8 @@ public void changeUuidOnReferencedNodeWithInheritedMixinByChangingNodeTypeTempor updateJcrUuidUsingNodeTypeManager(test, newUuid); assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); session.save(); - session.refresh(false); assertEquals(newUuid, test.getProperty(Property.JCR_UUID).getString()); + assertTrue(test.isSame(session.getNodeByIdentifier(newUuid))); } finally { ref.remove(); test.remove(); From f4fc2a2cc7ff283800919dfd5af5bc4f067d62e9 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Mon, 12 Aug 2024 11:54:38 +0100 Subject: [PATCH 12/13] OAK-11000: adjust expection for exceptions for DOCUMENT_NS --- .../org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index c3afc55398c..649bbb44a25 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -259,7 +259,8 @@ public void changeUuidOnReferencedNodeWithOnlyMixin2Sessions() throws Repository assertTrue(test.isSame(session.getNodeByIdentifier(newUuid))); session2.save(); fail("saving 2nd session should fail"); - } catch (ConstraintViolationException ex) { + // SEGMENT_OK fails with the former, DOCUMENT_NS with the latter + } catch (ConstraintViolationException | ReferentialIntegrityException ex) { // expected } finally { ref2.remove(); From fbc3bf1fe1ef0b0bb980b804f344d9cc57521e08 Mon Sep 17 00:00:00 2001 From: Julian Reschke Date: Tue, 13 Aug 2024 08:44:45 +0100 Subject: [PATCH 13/13] OAK-11000: add tests for just setting conflicting jcr:uuids on nodes without mixin:ref --- .../oak/jcr/ProtectedPropertyTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java index 649bbb44a25..3756b2b2ca6 100644 --- a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -153,6 +153,50 @@ public void jcrMixinReferenceableOnNtUnstructuredAfterSettingMixin() throws Repo } } + @Test + public void setSameUuidOnTwoNtUnstructuredNodes() throws RepositoryException { + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + Node test2 = testNode.addNode(TEST_NODE_NAME + "2", NodeType.NT_UNSTRUCTURED); + session.save(); + try { + String testUuid = UUID.randomUUID().toString(); + test.setProperty(Property.JCR_UUID, testUuid); + test2.setProperty(Property.JCR_UUID, testUuid); + session.save(); + fail("should not allow the same UUID on two different nodes"); + } catch (ConstraintViolationException ex) { + // expected + } finally { + test2.remove(); + test.remove(); + session.save(); + } + } + + @Test + public void setSameUuidOnTwoNtUnstructuredNodesTwoSessions() throws RepositoryException { + Session session2 = createAdminSession(); + Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED); + Node test2 = session2.getNode(testNode.getParent().getPath()).addNode(TEST_NODE_NAME + "2", NodeType.NT_UNSTRUCTURED); + session.save(); + session2.save(); + try { + String testUuid = UUID.randomUUID().toString(); + test.setProperty(Property.JCR_UUID, testUuid); + test2.setProperty(Property.JCR_UUID, testUuid); + session.save(); + session2.save(); + fail("should not allow the same UUID on two different nodes"); + } catch (ConstraintViolationException ex) { + // expected + } finally { + test2.remove(); + test.remove(); + session.save(); + session2.logout(); + } + } + @Test public void jcrMixinReferenceableOnNtUnstructuredAfterRemovingMixin() throws RepositoryException { Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED);