Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java (revision 1571980) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/ReadWriteVersionManager.java (working copy) @@ -18,12 +18,14 @@ */ package org.apache.jackrabbit.oak.plugins.version; +import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.jcr.RepositoryException; @@ -39,10 +41,12 @@ import org.apache.jackrabbit.oak.core.ImmutableRoot; import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager; +import org.apache.jackrabbit.oak.plugins.memory.PropertyBuilder; import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; import org.apache.jackrabbit.oak.plugins.tree.ImmutableTree; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.util.ISO8601; +import org.apache.jackrabbit.util.Text; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -161,6 +165,52 @@ return node; } + void removeVersion(String versionRelPath) throws CommitFailedException { + String historyRelPath = PathUtils.getAncestorPath(versionRelPath, 1); + String versionName = Text.getName(versionRelPath); + + NodeBuilder vh = resolve(versionStorageNode, historyRelPath); + + if (JCR_ROOTVERSION.equals(versionName)) { + String msg = "Removal of root version not allowed."; + throw new CommitFailedException(CommitFailedException.VERSION, VersionExceptionCode.ROOT_VERSION_REMOVAL.ordinal(), msg); + } + + NodeBuilder versionNode = vh.getChildNode(versionName); + String versionId = versionNode.getProperty(JCR_UUID).getValue(Type.STRING); + // unregister from labels + for (String label : getVersionLabels(versionRelPath, versionId)) { + removeVersionLabel(historyRelPath, label); + } + // reconnected predecessors and successors of the version being removed + PropertyState successorIds = versionNode.getProperty(JCR_SUCCESSORS); + PropertyState predecessorIds = versionNode.getProperty(JCR_PREDECESSORS); + + for (String succId : successorIds.getValue(Type.REFERENCES)) { + NodeBuilder successor = getVersionById(vh, succId); + + PropertyBuilder pb = new PropertyBuilder(Type.REFERENCE).setArray(); + pb.setName(JCR_PREDECESSORS).setValues(successor.getProperty(JCR_PREDECESSORS).getValue(Type.REFERENCES)); + + pb.removeValue(versionId); + pb.setValues(predecessorIds.getValue(Type.REFERENCES)); + + successor.setProperty(pb.getPropertyState()); + } + + for (String predId : predecessorIds.getValue(Type.REFERENCES)) { + NodeBuilder predecessor = getVersionById(vh, predId); + PropertyBuilder pb = new PropertyBuilder(Type.REFERENCE).setArray(); + pb.setName(JCR_SUCCESSORS).setValues(predecessor.getProperty(JCR_SUCCESSORS).getValue(Type.REFERENCES)); + + pb.removeValue(versionId); + pb.setValues(successorIds.getValue(Type.REFERENCES)); + + predecessor.setProperty(pb.getPropertyState()); + } + versionNode.remove(); + } + public void checkout(NodeBuilder versionable) { versionable.setProperty(JCR_ISCHECKEDOUT, true, Type.BOOLEAN); PropertyState baseVersion = versionable.getProperty(JCR_BASEVERSION); @@ -516,4 +566,29 @@ return history.child(JCR_VERSIONLABELS); } + @Nonnull + private Iterable getVersionLabels(@Nonnull String historyRelPath, @Nonnull String versionId) throws CommitFailedException { + List labels = new ArrayList(); + NodeBuilder labelNode = getVersionLabelsFor(historyRelPath); + for (PropertyState ps : labelNode.getProperties()) { + if (Type.REFERENCE == ps.getType()) { + if (versionId.equals(ps.getValue(Type.REFERENCE))) { + labels.add(ps.getName()); + } + } + } + return labels; + } + + @CheckForNull + private NodeBuilder getVersionById(@Nonnull NodeBuilder vhBuilder, @Nonnull String versionId) { + for (String childName : vhBuilder.getChildNodeNames()) { + NodeBuilder nb = vhBuilder.getChildNode(childName); + PropertyState uuid = nb.getProperty(JCR_UUID); + if (uuid != null && versionId.equals(uuid.getValue(Type.STRING))) { + return nb; + } + } + return null; + } } Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionConstants.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionConstants.java (revision 1571980) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionConstants.java (working copy) @@ -68,6 +68,11 @@ String REP_REMOVE_VERSION_LABELS = "rep:removeVersionLabels"; /** + * Version operation property name to remove version(s). + */ + String REP_REMOVE_VERSION = "rep:removeVersion"; + + /** * Prefix of the jcr:baseVersion value for a restore. */ String RESTORE_PREFIX = "restore-"; Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java (revision 1571980) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionExceptionCode.java (working copy) @@ -30,7 +30,8 @@ OPV_ABORT_ITEM_PRESENT("Item with OPV ABORT action present"), NO_VERSION_TO_RESTORE("No suitable version to restore"), LABEL_EXISTS("Version label already exists"), - NO_SUCH_VERSION_LABEL("No such version label"); + NO_SUCH_VERSION_LABEL("No such version label"), + ROOT_VERSION_REMOVAL("Attempt to remove root version"); private final String desc; Index: trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionStorageEditor.java =================================================================== --- trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionStorageEditor.java (revision 1571980) +++ trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/version/VersionStorageEditor.java (working copy) @@ -40,6 +40,7 @@ import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONLABELS; import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE; import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_ADD_VERSION_LABELS; +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_REMOVE_VERSION; import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_REMOVE_VERSION_LABELS; /** @@ -53,21 +54,28 @@ *
  • {@link VersionConstants#REP_ADD_VERSION_LABELS}: adds version labels to * existing version histories. The property is multi-valued and each value is a * PATH, which looks like this: - * <version-history-path>/jcr:versionLabels/<version-label>/<version-name>. - * The version-history-path is a relative path to the version + * {@code <version-history-path>/jcr:versionLabels/<version-label>/<version-name>}. + * The {@code version-history-path} is a relative path to the version * history node starting at the /jcr:system/jcr:versionStorage node. * An attempt to add a version label that already exists will result in a * {@link CommitFailedException}.
  • *
  • {@link VersionConstants#REP_REMOVE_VERSION_LABELS}: removes version labels from * existing version histories. The property is multi-valued and each value is a * PATH, which looks like this: - * <version-history-path>/jcr:versionLabels/<version-label>/<version-name>. - * The version-history-path is a relative path to the version + * {@code <version-history-path>/jcr:versionLabels/<version-label>/<version-name>}. + * The {@code version-history-path} is a relative path to the version * history node starting at the /jcr:system/jcr:versionStorage node. The - * <version-name> part is ignored when labels are removed and + * {@code <version-name>} part is ignored when labels are removed and * can be anything, though it must be a valid JCR/Oak name. * An attempt to remove a version label, which does not exist, will result in a * {@link CommitFailedException}.
  • + *
  • {@link VersionConstants#REP_REMOVE_VERSION}: : removes a version from + * existing version histories, the associated labels and fixes the version tree. + * The property is multi-valued and each value is a + * PATH, which looks like this: + * {@code <version-history-path>/<version-name>}. + * The {@code version-history-path} is a relative path to the version + * history node starting at the /jcr:system/jcr:versionStorage node.
  • * */ class VersionStorageEditor extends DefaultEditor { @@ -112,14 +120,16 @@ @Override public void propertyAdded(PropertyState after) throws CommitFailedException { - if (after.getName().equals(REP_REMOVE_VERSION_LABELS)) { - operations.put(1, - new RemoveVersionLabels(after.getValue(Type.PATHS))); - versionStorageNode.removeProperty(after.getName()); - } else if (after.getName().equals(REP_ADD_VERSION_LABELS)) { - operations.put(2, - new AddVersionLabels(after.getValue(Type.PATHS))); - versionStorageNode.removeProperty(after.getName()); + String name = after.getName(); + if (REP_REMOVE_VERSION_LABELS.equals(name)) { + operations.put(1, new RemoveVersionLabels(after.getValue(Type.PATHS))); + versionStorageNode.removeProperty(name); + } else if (REP_ADD_VERSION_LABELS.equals(name)) { + operations.put(2, new AddVersionLabels(after.getValue(Type.PATHS))); + versionStorageNode.removeProperty(name); + } else if (REP_REMOVE_VERSION.equals(name)) { + operations.put(3, new RemoveVersion(after.getValue(Type.PATHS))); + versionStorageNode.removeProperty(name); } } @@ -179,6 +189,22 @@ } } + private class RemoveVersion implements Operation { + + private final Iterable versionPaths; + + private RemoveVersion(@Nonnull Iterable versionPaths) { + this.versionPaths = versionPaths; + } + + @Override + public void perform() throws CommitFailedException { + for (String path : versionPaths) { + getVersionManager().removeVersion(path); + } + } + } + private static class VersionLabel { private final String versionHistoryPath; Index: trunk/oak-jcr/pom.xml =================================================================== --- trunk/oak-jcr/pom.xml (revision 1571980) +++ trunk/oak-jcr/pom.xml (working copy) @@ -96,11 +96,6 @@ org.apache.jackrabbit.test.api.version.MergeSubNodeTest - - org.apache.jackrabbit.oak.jcr.security.authorization.VersionManagementTest#testRemoveVersion - org.apache.jackrabbit.oak.jcr.security.authorization.VersionManagementTest#testRemoveVersion2 - org.apache.jackrabbit.oak.jcr.security.authorization.VersionManagementTest#testRemoveVersion3 - org.apache.jackrabbit.oak.jcr.security.authorization.MultipleMoveTest#testMoveSubTreeBack4 org.apache.jackrabbit.oak.jcr.security.authorization.MultipleMoveTest#testMoveDestParent2 org.apache.jackrabbit.oak.jcr.security.authorization.MultipleMoveTest#testMoveDestParent4 Index: trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java =================================================================== --- trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java (revision 1571980) +++ trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java (working copy) @@ -16,9 +16,6 @@ */ package org.apache.jackrabbit.oak.jcr.delegate; -import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION; - import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -26,21 +23,22 @@ import java.util.Iterator; import java.util.List; import java.util.Set; - import javax.annotation.Nonnull; import javax.jcr.InvalidItemStateException; import javax.jcr.RepositoryException; import javax.jcr.version.LabelExistsVersionException; import javax.jcr.version.VersionException; +import com.google.common.base.Function; +import com.google.common.collect.Iterators; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.version.VersionConstants; -import com.google.common.base.Function; -import com.google.common.collect.Iterators; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION; /** * {@code VersionHistoryDelegate}... @@ -182,6 +180,11 @@ vMgr.removeVersionLabel(this, oakVersionLabel); } + public void removeVersion(@Nonnull String oakVersionName) throws RepositoryException { + VersionManagerDelegate vMgr = VersionManagerDelegate.create(sessionDelegate); + vMgr.removeVersion(this, oakVersionName); + } + //-----------------------------< internal >--------------------------------- /** Index: trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java =================================================================== --- trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java (revision 1571980) +++ trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java (working copy) @@ -16,17 +16,6 @@ */ package org.apache.jackrabbit.oak.jcr.delegate; -import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION; -import static org.apache.jackrabbit.JcrConstants.JCR_FROZENMIXINTYPES; -import static org.apache.jackrabbit.JcrConstants.JCR_FROZENPRIMARYTYPE; -import static org.apache.jackrabbit.JcrConstants.JCR_FROZENUUID; -import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; -import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; -import static org.apache.jackrabbit.JcrConstants.JCR_UUID; -import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY; -import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.RESTORE_PREFIX; - import javax.annotation.Nonnull; import javax.jcr.InvalidItemStateException; import javax.jcr.RepositoryException; @@ -42,6 +31,17 @@ import org.apache.jackrabbit.oak.jcr.version.ReadWriteVersionManager; import org.apache.jackrabbit.oak.jcr.version.VersionStorage; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION; +import static org.apache.jackrabbit.JcrConstants.JCR_FROZENMIXINTYPES; +import static org.apache.jackrabbit.JcrConstants.JCR_FROZENPRIMARYTYPE; +import static org.apache.jackrabbit.JcrConstants.JCR_FROZENUUID; +import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.JcrConstants.JCR_UUID; +import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY; +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.RESTORE_PREFIX; + /** * {@code VersionManagerDelegate}... */ @@ -207,6 +207,24 @@ checkNotNull(oakVersionLabel)); } + /** + * Removes a version from the given history. + * + * @param versionHistory the version history delegate. + * @param oakVersionName the version name + * @throws RepositoryException if an error occurs. + */ + public void removeVersion(@Nonnull VersionHistoryDelegate versionHistory, + @Nonnull String oakVersionName) throws RepositoryException { + // perform operation on fresh storage to not interfere + // with pending changes in the workspace. + Root fresh = sessionDelegate.getContentSession().getLatestRoot(); + VersionStorage storage = new VersionStorage(fresh); + String vhRelPath = PathUtils.relativize(VersionStorage.VERSION_STORAGE_PATH, + checkNotNull(versionHistory).getPath()); + versionManager.removeVersion(storage, vhRelPath, oakVersionName); + } + //----------------------------< internal >---------------------------------- /** Index: trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java =================================================================== --- trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java (revision 1571980) +++ trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java (working copy) @@ -46,6 +46,7 @@ import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONLABELS; import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_ADD_VERSION_LABELS; import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_REMOVE_VERSION_LABELS; +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_REMOVE_VERSION; /** * {@code ReadWriteVersionManager}... @@ -235,5 +236,28 @@ } } + public void removeVersion(@Nonnull VersionStorage versionStorage, + @Nonnull String versionHistoryOakRelPath, + @Nonnull String oakVersionName) + throws RepositoryException { + Tree versionHistory = TreeUtil.getTree(versionStorage.getTree(), versionHistoryOakRelPath); + if (!versionHistory.exists()) { + throw new VersionException("Version history " + versionHistoryOakRelPath + " does not exist on this version storage"); + } + Tree version = versionHistory.getChild(oakVersionName); + if (!version.exists()) { + throw new VersionException("Version " + oakVersionName + " does not exist on this version history"); + } + String versionPath = PathUtils.concat(versionHistoryOakRelPath, oakVersionName); + versionStorage.getTree().setProperty(REP_REMOVE_VERSION, Collections.singleton(versionPath), Type.PATHS); + try { + sessionDelegate.commit(versionStorage.getRoot()); + refresh(); + } catch (CommitFailedException e) { + versionStorage.refresh(); + throw e.asRepositoryException(); + } + } + // TODO: more methods that modify versions } Index: trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionHistoryImpl.java =================================================================== --- trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionHistoryImpl.java (revision 1571980) +++ trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/VersionHistoryImpl.java (working copy) @@ -222,10 +222,18 @@ } @Override - public void removeVersion(String versionName) + public void removeVersion(final String versionName) throws ReferentialIntegrityException, AccessDeniedException, UnsupportedRepositoryOperationException, VersionException, RepositoryException { - TODO.unimplemented().doNothing(); + + perform(new SessionOperation(true) { + @Override + public Void perform() throws RepositoryException { + String oakName = sessionContext.getOakName(versionName); + dlg.removeVersion(oakName); + return null; + } + }); } } Index: trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/VersionManagementTest.java =================================================================== --- trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/VersionManagementTest.java (revision 1571980) +++ trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/authorization/VersionManagementTest.java (working copy) @@ -116,7 +116,6 @@ /** * @since oak (DIFF: jr required jcr:versionManagement privilege on the version store) */ - @Ignore("OAK-168") // FIXME: waiting for basic version mgt @Test public void testRemoveVersion() throws Exception { Node n = createVersionableNode(superuser.getNode(path)); @@ -127,6 +126,7 @@ Node testNode = trn.getNode(n.getName()); Version v = testNode.checkin(); testNode.checkout(); + testNode.checkin(); // removing a version must be allowed testNode.getVersionHistory().removeVersion(v.getName()); @@ -135,7 +135,6 @@ /** * @since oak (DIFF: jr required jcr:versionManagement privilege on the version store) */ - @Ignore("OAK-168") // FIXME: waiting for basic version mgt @Test public void testRemoveVersion2() throws Exception { Node n = createVersionableNode(superuser.getNode(path)); @@ -161,7 +160,6 @@ /** * @since oak (DIFF: jr required jcr:versionManagement privilege on the version store) */ - @Ignore("OAK-168") // FIXME: waiting for basic version mgt @Test public void testRemoveVersion3() throws Exception { Node n = createVersionableNode(superuser.getNode(path));