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 new file mode 100644 index 00000000000..3756b2b2ca6 --- /dev/null +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/ProtectedPropertyTest.java @@ -0,0 +1,461 @@ +/* + * 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.io.ByteArrayInputStream; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.PathNotFoundException; +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.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; +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 ProtectedPropertyTest 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 ProtectedPropertyTest(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(Property.JCR_CREATED, false);
+ session.save();
+ test.addMixin(NodeType.MIX_CREATED);
+ session.save();
+ 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
+ // 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();
+ }
+ }
+
+ @Test
+ public void jcrMixinReferenceableOnNtUnstructuredBeforeSettingMixin() throws RepositoryException {
+ Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_UNSTRUCTURED);
+ try {
+ String testUuid = UUID.randomUUID().toString();
+ test.setProperty(Property.JCR_UUID, testUuid);
+ session.save();
+ test.addMixin(NodeType.MIX_REFERENCEABLE);
+ session.save();
+ // 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(Property.JCR_UUID).getString());
+ } finally {
+ test.remove();
+ session.save();
+ }
+ }
+
+ @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(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");
+ } 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);
+ test.addMixin(NodeType.MIX_REFERENCEABLE);
+ session.save();
+ try {
+ String testUuid = UUID.randomUUID().toString();
+ test.setProperty(Property.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 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);
+ test.addMixin(NodeType.MIX_REFERENCEABLE);
+ session.save();
+ try {
+ // check jcr:uuid is there
+ 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(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(Property.JCR_UUID, testUuid);
+ session.save();
+ test.addMixin(NodeType.MIX_REFERENCEABLE);
+ session.save();
+ assertEquals(testUuid, test.getProperty(Property.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(Property.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 changeUuidOnReferencedNodeWithOnlyMixin() 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 {
+ String newUuid = UUID.randomUUID().toString();
+ updateJcrUuidUsingRemoveMixin(test, newUuid);
+ 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();
+ session.save();
+ }
+ }
+
+ @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();
+ assertTrue(test.isSame(session.getNodeByIdentifier(newUuid)));
+ session2.save();
+ fail("saving 2nd session should fail");
+ // SEGMENT_OK fails with the former, DOCUMENT_NS with the latter
+ } catch (ConstraintViolationException | ReferentialIntegrityException 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);
+ 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);
+ session.save();
+
+ try {
+ String newUuid = UUID.randomUUID().toString();
+ updateJcrUuidUsingRemoveMixin(test, newUuid);
+ fail("removing mixin:referenceable should fail on nt:resource");
+ } catch (NoSuchNodeTypeException ex) {
+ // expected
+ } finally {
+ ref.remove();
+ test.remove();
+ session.save();
+ }
+ }
+
+ @Test
+ public void changeUuidOnReferencedNodeWithInheritedMixinByChangingNodeTypeTemporarily() throws RepositoryException {
+ Node test = testNode.addNode(TEST_NODE_NAME, NodeType.NT_RESOURCE);
+ 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);
+ session.save();
+
+ try {
+ String newUuid = UUID.randomUUID().toString();
+ updateJcrUuidUsingNodeTypeManager(test, newUuid);
+ 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();
+ 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);
+
+ try {
+ // find all existing references to the node for which we want to rewrite the jcr:uuid
+ Set