Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java (revision e6ac57bfad743966e174f843d5c129b263a70a4e) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/NodeImpl.java (revision ) @@ -61,6 +61,7 @@ import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.NodeTypeManager; +import javax.jcr.version.OnParentVersionAction; import javax.jcr.version.Version; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; @@ -1119,7 +1120,9 @@ @Override public boolean isCheckedOut() throws RepositoryException { try { - return getVersionManager().isCheckedOut(getPath()); + boolean isCheckedOut = getVersionManager().isCheckedOut(getPath()); + + return isCheckedOut || this.getDefinition().getOnParentVersion() == OnParentVersionAction.IGNORE; } catch (UnsupportedRepositoryOperationException ex) { // when versioning is not supported all nodes are considered to be // checked out @@ -1364,7 +1367,8 @@ @Override public void checkPreconditions() throws RepositoryException { super.checkPreconditions(); - if (!isCheckedOut()) { + + if (!isCheckedOut() && getOPV(dlg.getTree(), state) != OnParentVersionAction.IGNORE) { throw new VersionException(format( "Cannot set property. Node [%s] is checked in.", getNodePath())); } @@ -1400,7 +1404,7 @@ @Override public void checkPreconditions() throws RepositoryException { super.checkPreconditions(); - if (!isCheckedOut()) { + if (!isCheckedOut() && getOPV(dlg.getTree(), state) != OnParentVersionAction.IGNORE) { throw new VersionException(format( "Cannot set property. Node [%s] is checked in.", getNodePath())); } @@ -1444,7 +1448,8 @@ @Override public void checkPreconditions() throws RepositoryException { super.checkPreconditions(); - if (!isCheckedOut()) { + PropertyDelegate property = dlg.getPropertyOrNull(oakName); + if (!isCheckedOut() && getOPV(dlg.getTree(), property.getPropertyState()) != OnParentVersionAction.IGNORE) { throw new VersionException(format( "Cannot remove property. Node [%s] is checked in.", getNodePath())); } @@ -1597,6 +1602,13 @@ */ private String getNodePath(){ return dlg.getPath(); + } + + private int getOPV(Tree nodeTree, PropertyState property) + throws RepositoryException { + return getNodeTypeManager().getDefinition(nodeTree, + property, false).getOnParentVersion(); + } private static class PropertyIteratorDelegate { Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/OpvIgnoreTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/OpvIgnoreTest.java (revision e6ac57bfad743966e174f843d5c129b263a70a4e) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/version/OpvIgnoreTest.java (revision ) @@ -18,6 +18,13 @@ import javax.annotation.Nonnull; import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeDefinitionTemplate; +import javax.jcr.nodetype.NodeTypeManager; +import javax.jcr.nodetype.NodeTypeTemplate; +import javax.jcr.nodetype.PropertyDefinitionTemplate; import javax.jcr.security.AccessControlManager; import javax.jcr.security.Privilege; import javax.jcr.version.OnParentVersionAction; @@ -32,6 +39,8 @@ import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal; import org.apache.jackrabbit.test.AbstractJCRTest; +import java.util.List; + /** * Test OPV IGNORE */ @@ -90,5 +99,150 @@ Node frozenChild = frozen.getNode(nodeName1); assertTrue(frozenChild.hasNode(nodeName2)); assertFalse(frozenChild.hasNode(AccessControlConstants.REP_POLICY)); + } + + //OAK-3328 + public void testWritePropertyWithIgnoreOPVAfterCheckIn() throws RepositoryException { + try { + Node ignoreTestNode = testRootNode.addNode("ignoreTestNode", JcrConstants.NT_UNSTRUCTURED); + String ignoredPropertyName = "ignoredProperty"; + NodeTypeTemplate mixinWithIgnoreProperty = createNodeTypeWithIgnoreOPVProperty(ignoredPropertyName); + + Node node = ignoreTestNode.addNode("testNode", testNodeType); + node.addMixin(mixinWithIgnoreProperty.getName()); + node.setProperty(ignoredPropertyName, "initial value"); + node.addMixin(mixVersionable); + superuser.save(); + VersionManager vMgr = superuser.getWorkspace().getVersionManager(); + if (!node.isCheckedOut()) { + vMgr.checkout(node.getPath()); + } + vMgr.checkin(node.getPath()); + node.setProperty(ignoredPropertyName, "next value"); + superuser.save(); + Property ignoreProperty = node.getProperty(ignoredPropertyName); + assertEquals("next value", ignoreProperty.getString()); + + } finally { + cleanUpTestRoot(superuser); + } + } + + //OAK-3328 + public void testRemovePropertyWithIgnoreOPVAfterCheckIn() throws RepositoryException { + try { + Node ignoreTestNode = testRootNode.addNode("ignoreTestNode", JcrConstants.NT_UNSTRUCTURED); + String ignoredPropertyName = "test:ignoredProperty"; + NodeTypeTemplate mixinWithIgnoreProperty = createNodeTypeWithIgnoreOPVProperty(ignoredPropertyName); + + Node node = ignoreTestNode.addNode("testNode", testNodeType); + node.addMixin(mixinWithIgnoreProperty.getName()); + node.setProperty(ignoredPropertyName, "initial value"); + node.addMixin(mixVersionable); + superuser.save(); + VersionManager vMgr = superuser.getWorkspace().getVersionManager(); + if (!node.isCheckedOut()) { + vMgr.checkout(node.getPath()); + } + vMgr.checkin(node.getPath()); + node.getProperty(ignoredPropertyName).remove(); + superuser.save(); + assertFalse(node.hasProperty(ignoredPropertyName)); + + } finally { + cleanUpTestRoot(superuser); + } + } + + //OAK-3328 + public void testAddChildNodeWithIgnoreOPVAfterCheckIn() throws RepositoryException { + try { + Node ignoreTestNode = testRootNode.addNode("ignoreTestNode", JcrConstants.NT_UNSTRUCTURED); + String nodeTypeName = "testOpvIgnore"; + NodeDefinitionTemplate nodeDefinition = createNodeDefinitionWithIgnoreOPVNode(nodeTypeName); + + ignoreTestNode.addMixin(JcrConstants.MIX_VERSIONABLE); + ignoreTestNode.addMixin(nodeTypeName); + superuser.save(); + + VersionManager vMgr = superuser.getWorkspace().getVersionManager(); + if (!ignoreTestNode.isCheckedOut()) { + vMgr.checkout(ignoreTestNode.getPath()); + } + vMgr.checkin(ignoreTestNode.getPath()); + + Node expected = ignoreTestNode.addNode(nodeDefinition.getName(), NodeTypeConstants.NT_OAK_UNSTRUCTURED); + + superuser.save(); + Node childNode = ignoreTestNode.getNode(nodeDefinition.getName()); + assertTrue(expected.isSame(childNode)); + + } finally { + cleanUpTestRoot(superuser); + } + } + + //OAK-3328 + public void testRemoveChildNodeWithIgnoreOPVAfterCheckIn() throws RepositoryException { + try { + Node ignoreTestNode = testRootNode.addNode("ignoreTestNode", JcrConstants.NT_UNSTRUCTURED); + String nodeTypeName = "testOpvIgnore"; + NodeDefinitionTemplate nodeDefinition = createNodeDefinitionWithIgnoreOPVNode(nodeTypeName); + + ignoreTestNode.addMixin(JcrConstants.MIX_VERSIONABLE); + ignoreTestNode.addMixin(nodeTypeName); + Node expected = ignoreTestNode.addNode(nodeDefinition.getName(), NodeTypeConstants.NT_OAK_UNSTRUCTURED); + superuser.save(); + + VersionManager vMgr = superuser.getWorkspace().getVersionManager(); + if (!ignoreTestNode.isCheckedOut()) { + vMgr.checkout(ignoreTestNode.getPath()); + } + vMgr.checkin(ignoreTestNode.getPath()); + + Node child = ignoreTestNode.getNode(nodeDefinition.getName()); + child.remove(); + + superuser.save(); + assertFalse(ignoreTestNode.hasNode(nodeDefinition.getName())); + + } finally { + cleanUpTestRoot(superuser); + } + } + + private NodeTypeTemplate createNodeTypeWithIgnoreOPVProperty(String propertyName) throws RepositoryException { + NodeTypeManager manager = superuser.getWorkspace().getNodeTypeManager(); + + NodeTypeTemplate nt = manager.createNodeTypeTemplate(); + nt.setName("testType"); + nt.setMixin(true); + PropertyDefinitionTemplate opt = manager.createPropertyDefinitionTemplate(); + opt.setMandatory(false); + opt.setName(propertyName); + opt.setRequiredType(PropertyType.STRING); + opt.setOnParentVersion(OnParentVersionAction.IGNORE); + List pdt = nt.getPropertyDefinitionTemplates(); + pdt.add(opt); + manager.registerNodeType(nt, true); + + return nt; + } + + private NodeDefinitionTemplate createNodeDefinitionWithIgnoreOPVNode(String nodeTypeName) throws RepositoryException { + NodeTypeManager manager = superuser.getWorkspace().getNodeTypeManager(); + + NodeDefinitionTemplate def = manager.createNodeDefinitionTemplate(); + def.setOnParentVersion(OnParentVersionAction.IGNORE); + def.setName("child"); + def.setRequiredPrimaryTypeNames(new String[]{JcrConstants.NT_BASE}); + + NodeTypeTemplate tmpl = manager.createNodeTypeTemplate(); + tmpl.setName(nodeTypeName); + tmpl.setMixin(true); + tmpl.getNodeDefinitionTemplates().add(def); + manager.registerNodeType(tmpl, true); + + return def; } } \ No newline at end of file Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/PropertyImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/PropertyImpl.java (revision e6ac57bfad743966e174f843d5c129b263a70a4e) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/PropertyImpl.java (revision ) @@ -37,6 +37,7 @@ import javax.jcr.ValueFactory; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.PropertyDefinition; +import javax.jcr.version.OnParentVersionAction; import javax.jcr.version.VersionException; import org.apache.jackrabbit.oak.api.Tree.Status; @@ -113,7 +114,7 @@ @Override public void checkPreconditions() throws RepositoryException { super.checkPreconditions(); - if (!getParent().isCheckedOut()) { + if (!getParent().isCheckedOut() && getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) { throw new VersionException( "Cannot set property. Node is checked in."); } @@ -463,7 +464,7 @@ @Override public void checkPreconditions() throws RepositoryException { super.checkPreconditions(); - if (!getParent().isCheckedOut()) { + if (!getParent().isCheckedOut() && getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) { throw new VersionException( "Cannot set property. Node is checked in."); } @@ -499,7 +500,7 @@ @Override public void checkPreconditions() throws RepositoryException { super.checkPreconditions(); - if (!getParent().isCheckedOut()) { + if (!getParent().isCheckedOut() && getDefinition().getOnParentVersion() != OnParentVersionAction.IGNORE) { throw new VersionException( "Cannot set property. Node is checked in."); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java (revision e6ac57bfad743966e174f843d5c129b263a70a4e) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionEditor.java (revision ) @@ -27,6 +27,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.jcr.nodetype.NodeDefinition; +import javax.jcr.version.OnParentVersionAction; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.CommitFailedException; @@ -86,10 +88,10 @@ // deleted or versionable -> check if it was checked in // a node cannot be modified if it was checked in // unless it has a new identifier - isReadOnly = wasCheckedIn() && !hasNewIdentifier(); + isReadOnly = wasCheckedIn() && !hasNewIdentifier() && !isIgnoreOnOPV(); } else { // otherwise inherit from parent - isReadOnly = parent != null && parent.isReadOnly; + isReadOnly = parent != null && parent.isReadOnly && !isIgnoreOnOPV(); } } @@ -109,7 +111,7 @@ vMgr.restore(node, after.getValue(Type.REFERENCE), null); return; } - if (!isReadOnly) { + if (!isReadOnly || getOPV(after) == OnParentVersionAction.IGNORE) { return; } // JCR allows to put a lock on a checked in node. @@ -125,7 +127,7 @@ public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException { if (!isVersionable()) { - if (!isVersionProperty(after) && isReadOnly) { + if (!isVersionProperty(after) && isReadOnly && getOPV(after) != OnParentVersionAction.IGNORE) { throwCheckedIn("Cannot change property " + after.getName() + " on checked in node"); } @@ -147,7 +149,7 @@ vMgr.restore(node, baseVersion, null); } else if (isVersionProperty(after)) { throwProtected(after.getName()); - } else if (isReadOnly) { + } else if (isReadOnly && getOPV(after) != OnParentVersionAction.IGNORE) { throwCheckedIn("Cannot change property " + after.getName() + " on checked in node"); } @@ -157,7 +159,7 @@ public void propertyDeleted(PropertyState before) throws CommitFailedException { if (isReadOnly) { - if (!isVersionProperty(before) && !isLockProperty(before)) { + if (!isVersionProperty(before) && !isLockProperty(before) && getOPV(before) != OnParentVersionAction.IGNORE) { throwCheckedIn("Cannot delete property on checked in node"); } } @@ -256,5 +258,28 @@ throws CommitFailedException { throw new CommitFailedException(CommitFailedException.CONSTRAINT, 100, "Property is protected: " + name); + } + + private boolean isIgnoreOnOPV() throws CommitFailedException { + if (this.parent != null) { + try { + NodeDefinition definition = this.vMgr.getNodeTypeManager().getDefinition(TreeFactory.createTree(parent.node), this.name); + return definition.getOnParentVersion() == OnParentVersionAction.IGNORE; + } catch (Exception e) { + throw new CommitFailedException(CommitFailedException.VERSION, + VersionExceptionCode.UNEXPECTED_REPOSITORY_EXCEPTION.ordinal(), e.getMessage()); + } + } + return false; + } + + private int getOPV(PropertyState property) throws CommitFailedException { + try { + return this.vMgr.getNodeTypeManager().getDefinition(TreeFactory.createReadOnlyTree(this.node.getNodeState()), + property, false).getOnParentVersion(); + } catch (Exception e) { + throw new CommitFailedException(CommitFailedException.VERSION, + VersionExceptionCode.UNEXPECTED_REPOSITORY_EXCEPTION.ordinal(), e.getMessage()); + } } }