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 ) @@ -16,55 +16,6 @@ */ package org.apache.jackrabbit.oak.jcr.session; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Iterators.transform; -import static com.google.common.collect.Sets.newLinkedHashSet; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; -import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; -import static org.apache.jackrabbit.oak.api.Type.NAME; -import static org.apache.jackrabbit.oak.api.Type.NAMES; -import static org.apache.jackrabbit.oak.jcr.session.SessionImpl.checkIndexOnName; -import static org.apache.jackrabbit.oak.util.TreeUtil.getNames; - -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.Calendar; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; -import javax.jcr.AccessDeniedException; -import javax.jcr.Binary; -import javax.jcr.InvalidItemStateException; -import javax.jcr.Item; -import javax.jcr.ItemExistsException; -import javax.jcr.ItemNotFoundException; -import javax.jcr.ItemVisitor; -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.PathNotFoundException; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.UnsupportedRepositoryOperationException; -import javax.jcr.Value; -import javax.jcr.lock.Lock; -import javax.jcr.lock.LockManager; -import javax.jcr.nodetype.ConstraintViolationException; -import javax.jcr.nodetype.NodeDefinition; -import javax.jcr.nodetype.NodeType; -import javax.jcr.nodetype.NodeTypeManager; -import javax.jcr.version.Version; -import javax.jcr.version.VersionException; -import javax.jcr.version.VersionHistory; - import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @@ -97,6 +48,55 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.jcr.AccessDeniedException; +import javax.jcr.Binary; +import javax.jcr.InvalidItemStateException; +import javax.jcr.Item; +import javax.jcr.ItemExistsException; +import javax.jcr.ItemNotFoundException; +import javax.jcr.ItemVisitor; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.PathNotFoundException; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.Value; +import javax.jcr.lock.Lock; +import javax.jcr.lock.LockManager; +import javax.jcr.nodetype.ConstraintViolationException; +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; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterators.transform; +import static com.google.common.collect.Sets.newLinkedHashSet; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.oak.api.Type.NAME; +import static org.apache.jackrabbit.oak.api.Type.NAMES; +import static org.apache.jackrabbit.oak.jcr.session.SessionImpl.checkIndexOnName; +import static org.apache.jackrabbit.oak.util.TreeUtil.getNames; + /** * TODO document * @@ -115,6 +115,10 @@ */ private static final Logger LOG = LoggerFactory.getLogger(NodeImpl.class); + public NodeImpl(T dlg, SessionContext sessionContext) { + super(dlg, sessionContext); + } + @CheckForNull public static NodeImpl createNodeOrNull( @CheckForNull NodeDelegate delegate, @Nonnull SessionContext context) @@ -128,8 +132,8 @@ @Nonnull public static NodeImpl createNode( - @Nonnull NodeDelegate delegate, @Nonnull SessionContext context) - throws RepositoryException { + @Nonnull NodeDelegate delegate, @Nonnull SessionContext context) + throws RepositoryException { PropertyDelegate pd = delegate.getPropertyOrNull(JCR_PRIMARYTYPE); String type = pd != null ? pd.getString() : null; if (JcrConstants.NT_VERSION.equals(type)) { @@ -145,12 +149,38 @@ } } - public NodeImpl(T dlg, SessionContext sessionContext) { - super(dlg, sessionContext); + //---------------------------------------------------------------< Item >--- + + /** + * Removes all {@code null} values from the given array. + * + * @param values value array + * @return value list without {@code null} entries + */ + private static List compact(Value[] values) { + List list = Lists.newArrayListWithCapacity(values.length); + for (Value value : values) { + if (value != null) { + list.add(value); - } + } + } + return list; + } - //---------------------------------------------------------------< Item >--- + private static boolean isOrderable(Node node) throws RepositoryException { + if (node.getPrimaryNodeType().hasOrderableChildNodes()) { + return true; + } + NodeType[] types = node.getMixinNodeTypes(); + for (NodeType type : types) { + if (type.hasOrderableChildNodes()) { + return true; + } + } + return false; + } + /** * @see javax.jcr.Item#isNode() */ @@ -210,6 +240,8 @@ }); } + //---------------------------------------------------------------< Node >--- + /** * @see javax.jcr.Item#remove() */ @@ -237,8 +269,6 @@ visitor.visit(this); } - //---------------------------------------------------------------< Node >--- - /** * @see Node#addNode(String) */ @@ -248,7 +278,27 @@ return addNode(relPath, null); } - @Override @Nonnull + //-------------------------------------------------------< setProperty >-- + // + // The setProperty() variants below follow the same pattern: + // + // if (value != null) { + // return internalSetProperty(name, ...); + // } else { + // return internalRemoveProperty(name); + // } + // + // In addition the value and value type information is pre-processed + // according to the method signature before being passed to + // internalSetProperty(). The methods that take a non-nullable + // primitive value as an argument can skip the if clause. + // + // Note that due to backwards compatibility reasons (OAK-395) none + // of the methods will ever return null, even if asked to remove + // a non-existing property! See internalRemoveProperty() for details. + + @Override + @Nonnull public Node addNode(final String relPath, String primaryNodeTypeName) throws RepositoryException { final String oakPath = getOakPathOrThrowNotFound(relPath); @@ -296,7 +346,7 @@ NodeDelegate added = parent.addChild(oakName, oakTypeName); if (added == null) { - throw new ItemExistsException(format("Node [%s/%s] exists", getNodePath(),oakName)); + throw new ItemExistsException(format("Node [%s/%s] exists", getNodePath(), oakName)); } return createNode(added, sessionContext); } @@ -325,26 +375,8 @@ }); } - //-------------------------------------------------------< setProperty >-- - // - // The setProperty() variants below follow the same pattern: - // - // if (value != null) { - // return internalSetProperty(name, ...); - // } else { - // return internalRemoveProperty(name); - // } - // - // In addition the value and value type information is pre-processed - // according to the method signature before being passed to - // internalSetProperty(). The methods that take a non-nullable - // primitive value as an argument can skip the if clause. - // - // Note that due to backwards compatibility reasons (OAK-395) none - // of the methods will ever return null, even if asked to remove - // a non-existing property! See internalRemoveProperty() for details. - - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, Value value) throws RepositoryException { if (value != null) { @@ -354,7 +386,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, Value value, int type) throws RepositoryException { if (value != null) { @@ -370,7 +403,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, Value[] values) throws RepositoryException { if (values != null) { @@ -381,7 +415,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String jcrName, Value[] values, int type) throws RepositoryException { if (values != null) { @@ -397,7 +432,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, String[] values) throws RepositoryException { if (values != null) { @@ -409,7 +445,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, String[] values, int type) throws RepositoryException { if (values != null) { @@ -425,7 +462,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, String value) throws RepositoryException { if (value != null) { @@ -436,7 +474,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, String value, int type) throws RepositoryException { if (value != null) { @@ -452,7 +491,9 @@ } } - @Override @Nonnull @SuppressWarnings("deprecation") + @Override + @Nonnull + @SuppressWarnings("deprecation") public Property setProperty(String name, InputStream value) throws RepositoryException { if (value != null) { @@ -463,7 +504,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, Binary value) throws RepositoryException { if (value != null) { @@ -474,21 +516,24 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, boolean value) throws RepositoryException { Value v = getValueFactory().createValue(value); return internalSetProperty(name, v, false); } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, double value) throws RepositoryException { Value v = getValueFactory().createValue(value); return internalSetProperty(name, v, false); } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, BigDecimal value) throws RepositoryException { if (value != null) { @@ -499,14 +544,16 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, long value) throws RepositoryException { Value v = getValueFactory().createValue(value); return internalSetProperty(name, v, false); } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, Calendar value) throws RepositoryException { if (value != null) { @@ -517,7 +564,8 @@ } } - @Override @Nonnull + @Override + @Nonnull public Property setProperty(String name, Node value) throws RepositoryException { if (value != null) { @@ -556,6 +604,7 @@ Iterator children = node.getChildren(); return new NodeIteratorAdapter(nodeIterator(children)) { private long size = -2; + @Override public long getSize() { if (size == -2) { @@ -667,7 +716,7 @@ return ItemNameMatcher.matches(toJcrPath(entry.getName()), namePattern); } }); - return new PropertyIteratorAdapter(propertyIterator(delegate.iterator())){ + return new PropertyIteratorAdapter(propertyIterator(delegate.iterator())) { @Override public long getSize() { return delegate.getSize(); @@ -691,7 +740,7 @@ return ItemNameMatcher.matches(toJcrPath(entry.getName()), nameGlobs); } }); - return new PropertyIteratorAdapter(propertyIterator(delegate.iterator())){ + return new PropertyIteratorAdapter(propertyIterator(delegate.iterator())) { @Override public long getSize() { return delegate.getSize(); @@ -723,8 +772,8 @@ return getNode(name); } else { throw new ItemNotFoundException( - "Primary item " + name + - " does not exist on node " + NodeImpl.this); + "Primary item " + name + + " does not exist on node " + NodeImpl.this); } } }); @@ -961,6 +1010,7 @@ "Cannot add mixin type. Node [%s] is checked in.", getNodePath())); } } + @Override public void performVoid() throws RepositoryException { dlg.addMixin(oakTypeName); @@ -990,6 +1040,7 @@ sessionContext.getAccessManager().checkPermissions(dlg.getTree(), prop, Permissions.NODE_TYPE_MANAGEMENT); } } + @Override public void performVoid() throws RepositoryException { dlg.removeMixin(oakTypeName); @@ -1006,7 +1057,7 @@ public Boolean perform() throws RepositoryException { PropertyState prop = PropertyStates.createProperty(JCR_MIXINTYPES, singleton(oakTypeName), NAMES); return sessionContext.getAccessManager().hasPermissions( - node.getTree(), prop, Permissions.NODE_TYPE_MANAGEMENT) + node.getTree(), prop, Permissions.NODE_TYPE_MANAGEMENT) && !node.isProtected() && getVersionManager().isCheckedOut(toJcrPath(dlg.getPath())) // TODO: avoid nested calls && node.canAddMixin(oakTypeName); @@ -1049,7 +1100,6 @@ })); } - @Override public void update(final String srcWorkspace) throws RepositoryException { sessionDelegate.performVoid(new ItemWriteOperation("update") { @@ -1119,7 +1169,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 @@ -1210,12 +1262,14 @@ return getLockManager().holdsLock(getPath()); } - @Override @Nonnull + @Override + @Nonnull public Lock getLock() throws RepositoryException { return getLockManager().getLock(getPath()); } - @Override @Nonnull + @Override + @Nonnull public Lock lock(boolean isDeep, boolean isSessionScoped) throws RepositoryException { return getLockManager().lock( @@ -1227,7 +1281,8 @@ getLockManager().unlock(getPath()); } - @Override @Nonnull + @Override + @Nonnull public NodeIterator getSharedSet() { return new NodeIteratorAdapter(singleton(this)); } @@ -1364,11 +1419,13 @@ @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())); } } + @Nonnull @Override public Property perform() throws RepositoryException { @@ -1393,18 +1450,19 @@ oakName, compact(values), Type.fromTag(type, true)); if (values.length > MV_PROPERTY_WARN_THRESHOLD) { - LOG.warn("Large multi valued property [{}/{}] detected ({} values).",dlg.getPath(), jcrName, values.length); + LOG.warn("Large multi valued property [{}/{}] detected ({} values).", dlg.getPath(), jcrName, values.length); } return perform(new ItemWriteOperation("internalSetProperty") { @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())); } } + @Nonnull @Override public Property perform() throws RepositoryException { @@ -1420,23 +1478,8 @@ }); } - /** - * Removes all {@code null} values from the given array. - * - * @param values value array - * @return value list without {@code null} entries - */ - private static List compact(Value[] values) { - List list = Lists.newArrayListWithCapacity(values.length); - for (Value value : values) { - if (value != null) { - list.add(value); - } - } - return list; - } + //-----------------------------------------------------< JackrabbitNode >--- - private Property internalRemoveProperty(final String jcrName) throws RepositoryException { final String oakName = getOakName(checkNotNull(jcrName)); @@ -1444,11 +1487,13 @@ @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())); } } + @Nonnull @Override public Property perform() throws RepositoryException { @@ -1469,15 +1514,13 @@ }); } - //-----------------------------------------------------< JackrabbitNode >--- - /** * Simplified implementation of {@link JackrabbitNode#rename(String)}. In * contrast to the implementation in Jackrabbit 2.x which was operating on * the NodeState level directly, this implementation does a move plus * subsequent reorder on the JCR API due to a missing support for renaming * on the OAK API. - * + *

* Note, that this also has an impact on how permissions are enforced: In * Jackrabbit 2.x the rename just required permission to modify the child * collection on the parent, whereas a move did the full permission check. @@ -1535,20 +1578,6 @@ }); } - private static boolean isOrderable(Node node) throws RepositoryException { - if (node.getPrimaryNodeType().hasOrderableChildNodes()) { - return true; - } - - NodeType[] types = node.getMixinNodeTypes(); - for (NodeType type : types) { - if (type.hasOrderableChildNodes()) { - return true; - } - } - return false; - } - /** * Simplified implementation of the {@link org.apache.jackrabbit.api.JackrabbitNode#setMixins(String[])} * method that adds all mixin types that are not yet present on this node @@ -1583,6 +1612,7 @@ sessionContext.getAccessManager().checkPermissions(dlg.getTree(), mixinProp.getPropertyState(), Permissions.NODE_TYPE_MANAGEMENT); } } + @Override public void performVoid() throws RepositoryException { dlg.setMixins(oakTypeNames); @@ -1595,12 +1625,19 @@ * the SessionDelegate#perform and preferred instead of getPath * as it provides direct access to path */ - private String getNodePath(){ + 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 { - private final NodeDelegate node; + private final NodeDelegate node; private final Predicate predicate; PropertyIteratorDelegate(NodeDelegate node, Predicate predicate) { 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()); + } } }