You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ba...@apache.org on 2015/08/06 15:57:59 UTC

svn commit: r1694498 - in /jackrabbit/oak/trunk: oak-run/src/main/java/org/apache/jackrabbit/oak/run/ oak-run/src/test/java/org/apache/jackrabbit/oak/run/ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/ oak-upgrade/src/main/java/org/apache...

Author: baedke
Date: Thu Aug  6 13:57:58 2015
New Revision: 1694498

URL: http://svn.apache.org/r1694498
Log:
OAK-2776: Upgrade should allow to skip copying versions

Initial implementation. Full credit goes to Julian Sedding (jsedding@gmail.com) for the patch and to Tomek Rekawek (trekawek@gmail.com) for aligning it with the current trunk and integrating into oak-run.

Added:
    jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java
    jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java
    jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java
Modified:
    jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java
    jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java
    jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java

Modified: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java?rev=1694498&r1=1694497&r2=1694498&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java (original)
+++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java Thu Aug  6 13:57:58 2015
@@ -28,8 +28,12 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -60,6 +64,8 @@ import joptsimple.ArgumentAcceptingOptio
 import joptsimple.OptionParser;
 import joptsimple.OptionSet;
 import joptsimple.OptionSpec;
+
+import org.apache.commons.lang.time.DateUtils;
 import org.apache.jackrabbit.core.RepositoryContext;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
 import org.apache.jackrabbit.oak.Oak;
@@ -939,6 +945,8 @@ public final class Main {
     private static void upgrade(String[] args) throws Exception {
         OptionParser parser = new OptionParser();
         parser.accepts("datastore", "keep data store");
+        ArgumentAcceptingOptionSpec<String> copyVersions = parser.accepts("copy-versions", "copy referenced versions. valid arguments: true|false|yyyy-mm-dd").withRequiredArg().defaultsTo("true");
+        ArgumentAcceptingOptionSpec<String> copyOrphanedVersions = parser.accepts("copy-orphaned-versions", "copy all versions. valid arguments: true|false|yyyy-mm-dd").withRequiredArg().defaultsTo("true");
         OptionSpec<String> nonOption = parser.nonOptions();
         OptionSet options = parser.parse(args);
 
@@ -967,6 +975,7 @@ public final class Main {
                                     new RepositoryUpgrade(source, target);
                             upgrade.setCopyBinariesByReference(
                                     options.has("datastore"));
+                            setCopyVersionOptions(copyVersions.value(options), copyOrphanedVersions.value(options), upgrade);
                             upgrade.copy(null);
                         } finally {
                             target.dispose();
@@ -996,6 +1005,26 @@ public final class Main {
         }
     }
 
+    private static void setCopyVersionOptions(String copyVersions, String copyOrphanedVersions, RepositoryUpgrade upgrade) throws ParseException {
+        upgrade.setCopyVersions(parseVersionCopyArgument(copyVersions));
+        upgrade.setCopyOrphanedVersions(parseVersionCopyArgument(copyOrphanedVersions));
+    }
+
+    static Calendar parseVersionCopyArgument(String string) throws ParseException {
+        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+        final Calendar calendar;
+
+        if (Boolean.parseBoolean(string)) {
+            calendar = Calendar.getInstance();
+            calendar.setTimeInMillis(0);
+        } else if (string != null && string.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
+            calendar = DateUtils.toCalendar(df.parse(string));
+        } else {
+            calendar = null;
+        }
+        return calendar;
+    }
+
     private static void server(String defaultUri, String[] args) throws Exception {
         OptionParser parser = new OptionParser();
 

Added: jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java (added)
+++ jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,53 @@
+/*
+ * 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.run;
+
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ParseVersionCopyArgumentTest {
+
+    @Test
+    public void parseTrue() throws ParseException {
+        for (String argument : Arrays.asList("true", "TRUE", "TrUe")) {
+            final Calendar result = Main.parseVersionCopyArgument(argument);
+            Assert.assertEquals(0, result.getTimeInMillis());
+        }
+    }
+
+    @Test
+    public void parseDate() throws ParseException {
+        final Calendar result = Main.parseVersionCopyArgument("2013-01-01");
+        Assert.assertEquals(new GregorianCalendar(2013, 0, 1), result);
+    }
+
+    @Test
+    public void parseFalse() throws ParseException {
+        for (String argument : Arrays.asList("false", "FaLse", "", "xyz", null)) {
+            final Calendar result = Main.parseVersionCopyArgument(argument);
+            Assert.assertNull(result);
+        }
+    }
+}

Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,46 @@
+package org.apache.jackrabbit.oak.upgrade;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+
+import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+public class DescendantsIterator extends AbstractLazyIterator<NodeState> {
+
+    private final Deque<Iterator<? extends ChildNodeEntry>> stack = new ArrayDeque<Iterator<? extends ChildNodeEntry>>();
+
+    private final int maxLevel;
+
+    public DescendantsIterator(NodeState root, int maxLevel) {
+        this.maxLevel = maxLevel;
+        stack.push(root.getChildNodeEntries().iterator());
+    }
+
+    @Override
+    protected NodeState getNext() {
+        if (!fillStack()) {
+            return null;
+        }
+        return stack.peekFirst().next().getNodeState();
+    }
+
+    private boolean fillStack() {
+        while (stack.size() < maxLevel || !stack.peekFirst().hasNext()) {
+            Iterator<? extends ChildNodeEntry> topIterator = stack.peekFirst();
+            if (topIterator.hasNext()) {
+                final NodeState nextNode = topIterator.next().getNodeState();
+                stack.push(nextNode.getChildNodeEntries().iterator());
+            } else {
+                stack.pop();
+                if (stack.isEmpty()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java?rev=1694498&r1=1694497&r2=1694498&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java (original)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java Thu Aug  6 13:57:58 2015
@@ -32,7 +32,6 @@ import static org.apache.jackrabbit.JcrC
 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.JcrConstants.MIX_REFERENCEABLE;
 import static org.apache.jackrabbit.JcrConstants.MIX_VERSIONABLE;
 import static org.apache.jackrabbit.JcrConstants.NT_BASE;
@@ -46,7 +45,6 @@ import static org.apache.jackrabbit.oak.
 import static org.apache.jackrabbit.oak.api.Type.NAMES;
 import static org.apache.jackrabbit.oak.api.Type.STRING;
 import static org.apache.jackrabbit.oak.plugins.tree.impl.TreeConstants.OAK_CHILD_ORDER;
-import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -139,8 +137,6 @@ class JackrabbitNodeState extends Abstra
      */
     private final Map<String, String> uriToPrefix;
 
-    private final Map<String, String> versionablePaths;
-
     private final boolean useBinaryReferences;
 
     private final Map<String, NodeId> nodes;
@@ -158,7 +154,6 @@ class JackrabbitNodeState extends Abstra
             String workspaceName,
             NodeState root,
             Map<String, String> uriToPrefix,
-            Map<String, String> versionablePaths,
             boolean copyBinariesByReference,
             boolean skipOnError
     ) throws RepositoryException {
@@ -169,7 +164,6 @@ class JackrabbitNodeState extends Abstra
                 versionPM, root, uriToPrefix,
                 VERSION_STORAGE_NODE_ID, "/jcr:system/jcr:versionStorage",
                 null,
-                versionablePaths,
                 emptyMountPoints,
                 copyBinariesByReference,
                 skipOnError
@@ -179,7 +173,6 @@ class JackrabbitNodeState extends Abstra
                 versionPM, root, uriToPrefix,
                 ACTIVITIES_NODE_ID, "/jcr:system/jcr:activities",
                 null,
-                versionablePaths,
                 emptyMountPoints,
                 copyBinariesByReference,
                 skipOnError
@@ -193,7 +186,7 @@ class JackrabbitNodeState extends Abstra
         );
         return new JackrabbitNodeState(
                 pm, root, uriToPrefix, ROOT_NODE_ID, "/",
-                workspaceName, versionablePaths, mountPoints, copyBinariesByReference, skipOnError);
+                workspaceName, mountPoints, copyBinariesByReference, skipOnError);
     }
 
     private JackrabbitNodeState(
@@ -209,7 +202,6 @@ class JackrabbitNodeState extends Abstra
         this.isVersionHistory = parent.isVersionHistory;
         this.isFrozenNode = parent.isFrozenNode;
         this.uriToPrefix = parent.uriToPrefix;
-        this.versionablePaths = parent.versionablePaths;
         this.useBinaryReferences = parent.useBinaryReferences;
         this.properties = createProperties(bundle);
         this.nodes = createNodes(bundle);
@@ -217,7 +209,6 @@ class JackrabbitNodeState extends Abstra
         this.mountPoints = parent.mountPoints;
         this.nodeStateCache = parent.nodeStateCache;
         setChildOrder();
-        setVersionablePaths();
         fixFrozenUuid();
         logNewNode(this);
     }
@@ -225,7 +216,7 @@ class JackrabbitNodeState extends Abstra
     JackrabbitNodeState(
             PersistenceManager source, NodeState root,
             Map<String, String> uriToPrefix, NodeId id, String path,
-            String workspaceName, Map<String, String> versionablePaths,
+            String workspaceName,
             Map<NodeId, JackrabbitNodeState> mountPoints,
             boolean useBinaryReferences, boolean skipOnError) {
         this.parent = null;
@@ -239,7 +230,6 @@ class JackrabbitNodeState extends Abstra
         this.isVersionHistory = new TypePredicate(root, NT_VERSIONHISTORY);
         this.isFrozenNode = new TypePredicate(root, NT_FROZENNODE);
         this.uriToPrefix = uriToPrefix;
-        this.versionablePaths = versionablePaths;
         this.mountPoints = mountPoints;
         final int cacheSize = 50; // cache size 50 results in > 25% cache hits during version copy
         this.nodeStateCache = new LinkedHashMap<NodeId, JackrabbitNodeState>(cacheSize, 0.75f, true) {
@@ -380,28 +370,6 @@ class JackrabbitNodeState extends Abstra
         }
     }
 
-    private void setVersionablePaths() {
-        if (isVersionable.apply(this)) {
-            String uuid = getString(JCR_VERSIONHISTORY);
-            if (uuid != null) {
-                versionablePaths.put(uuid, getPath());
-            }
-        } else if (isVersionHistory.apply(this)) {
-            String uuid = getString(JCR_UUID);
-            String path = versionablePaths.get(uuid);
-            if (path != null) {
-                properties.put(workspaceName, PropertyStates.createProperty(
-                        workspaceName, path, Type.PATH));
-
-                Set<String> mixins = newLinkedHashSet(getNames(JCR_MIXINTYPES));
-                if (mixins.add(MIX_REP_VERSIONABLE_PATHS)) {
-                    properties.put(JCR_MIXINTYPES, PropertyStates.createProperty(
-                            JCR_MIXINTYPES, mixins, Type.NAMES));
-                }
-            }
-        }
-    }
-
     private Map<String, NodeId> createNodes(NodePropBundle bundle) {
         Map<String, NodeId> children = newLinkedHashMap();
         for (ChildNodeEntry entry : bundle.getChildNodeEntries()) {
@@ -728,4 +696,4 @@ class JackrabbitNodeState extends Abstra
         log.warn(getPath() + ": " + message, cause);
     }
 
-}
\ No newline at end of file
+}

Modified: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java?rev=1694498&r1=1694497&r2=1694498&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java (original)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java Thu Aug  6 13:57:58 2015
@@ -26,6 +26,7 @@ import static com.google.common.collect.
 import static com.google.common.collect.Sets.newHashSet;
 import static com.google.common.collect.Sets.union;
 import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
+import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE;
 import static org.apache.jackrabbit.oak.plugins.name.Namespaces.addCustomMapping;
 import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.NODE_TYPES_PATH;
 import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_ALL;
@@ -35,6 +36,7 @@ import static org.apache.jackrabbit.oak.
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Calendar;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -110,6 +112,9 @@ import org.apache.jackrabbit.oak.spi.sta
 import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier;
 import org.apache.jackrabbit.oak.upgrade.security.GroupEditorProvider;
 import org.apache.jackrabbit.oak.upgrade.security.RestrictionEditorProvider;
+import org.apache.jackrabbit.oak.upgrade.version.VersionCopier;
+import org.apache.jackrabbit.oak.upgrade.version.VersionCopyConfiguration;
+import org.apache.jackrabbit.oak.upgrade.version.VersionableEditor;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.QNodeDefinition;
 import org.apache.jackrabbit.spi.QNodeTypeDefinition;
@@ -168,6 +173,8 @@ public class RepositoryUpgrade {
 
     private List<CommitHook> customCommitHooks = null;
 
+    private VersionCopyConfiguration versionCopyConfiguration = new VersionCopyConfiguration();
+
     /**
      * Copies the contents of the repository in the given source directory
      * to the given target node store.
@@ -288,6 +295,41 @@ public class RepositoryUpgrade {
     }
 
     /**
+     * Configures the version storage copy. Be default all versions are copied.
+     * One may disable it completely by setting {@code null} here or limit it to
+     * a selected date range: {@code <minDate, now()>}.
+     * 
+     * @param minDate
+     *            minimum date of the versions to copy or {@code null} to
+     *            disable the storage version copying completely. Default value:
+     *            {@code 1970-01-01 00:00:00}.
+     */
+    public void setCopyVersions(Calendar minDate) {
+        versionCopyConfiguration.setCopyVersions(minDate);
+    }
+
+    /**
+     * Configures copying of the orphaned version histories (eg. ones that are
+     * not referenced by the existing nodes). By default all orphaned version
+     * histories are copied. One may disable it completely by setting
+     * {@code null} here or limit it to a selected date range:
+     * {@code <minDate, now()>}. <br/>
+     * <br/>
+     * Please notice, that this option is overriden by the
+     * {@link #setCopyVersions(Calendar)}. You can't copy orphaned versions
+     * older than set in {@link #setCopyVersions(Calendar)} and if you set
+     * {@code null} there, this option will be ignored.
+     * 
+     * @param minDate
+     *            minimum date of the orphaned versions to copy or {@code null}
+     *            to not copy them at all. Default value:
+     *            {@code 1970-01-01 00:00:00}.
+     */
+    public void setCopyOrphanedVersions(Calendar minDate) {
+        versionCopyConfiguration.setCopyOrphanedVersions(minDate);
+    }
+
+    /**
      * Copies the full content from the source to the target repository.
      * <p>
      * The source repository <strong>must not be modified</strong> while
@@ -364,11 +406,10 @@ public class RepositoryUpgrade {
             new TypeEditorProvider(false).getRootEditor(
                     base, builder.getNodeState(), builder, null);
 
-            Map<String, String> versionablePaths = newHashMap();
             NodeState root = builder.getNodeState();
 
             final NodeState sourceState = JackrabbitNodeState.createRootNodeState(
-                    source, workspaceName, root, uriToPrefix, versionablePaths, copyBinariesByReference, skipOnError);
+                    source, workspaceName, root, uriToPrefix, copyBinariesByReference, skipOnError);
 
             final Stopwatch watch = Stopwatch.createStarted();
 
@@ -377,10 +418,15 @@ public class RepositoryUpgrade {
             builder.getNodeState(); // on TarMK this does call triggers the actual copy
             logger.info("Upgrading workspace content completed in {}s ({})", watch.elapsed(TimeUnit.SECONDS), watch);
 
-            watch.reset().start();
-            logger.info("Copying version store content");
-            copyVersionStore(sourceState, builder);
-            logger.debug("Upgrading version store content completed in {}s ({}).", watch.elapsed(TimeUnit.SECONDS), watch);
+            if (!versionCopyConfiguration.skipOrphanedVersionsCopy()) {
+                logger.info("Copying version storage");
+                watch.reset().start();
+                copyVersionStorage(sourceState, builder);
+                builder.getNodeState(); // on TarMK this does call triggers the actual copy
+                logger.info("Version storage copied in {}s ({})", watch.elapsed(TimeUnit.SECONDS), watch);
+            } else {
+                logger.info("Skipping the version storage as the copyOrphanedVersions is set to false");
+            }
 
             watch.reset().start();
             logger.info("Applying default commit hooks");
@@ -396,7 +442,9 @@ public class RepositoryUpgrade {
             // hooks specific to the upgrade, need to run first
             hooks.add(new EditorHook(new CompositeEditorProvider(
                     new RestrictionEditorProvider(),
-                    new GroupEditorProvider(groupsPath)
+                    new GroupEditorProvider(groupsPath),
+                    // copy referenced version histories
+                    new VersionableEditor.Provider(sourceState, workspaceName, versionCopyConfiguration)
             )));
 
             // security-related hooks
@@ -768,10 +816,10 @@ public class RepositoryUpgrade {
         return tmpl;
     }
 
-    private void copyWorkspace(NodeState sourceState, NodeBuilder builder, String workspaceName)
+    private String copyWorkspace(NodeState sourceState, NodeBuilder builder, String workspaceName)
             throws RepositoryException {
         final Set<String> includes = calculateEffectiveIncludePaths(sourceState);
-        final Set<String> excludes = union(copyOf(this.excludePaths), of("/jcr:system/jcr:versionStorage", "/jcr:system/jcr:activities"));
+        final Set<String> excludes = union(copyOf(this.excludePaths), of("/jcr:system/jcr:versionStorage"));
         final Set<String> merges = union(copyOf(this.mergePaths), of("/jcr:system"));
 
         logger.info("Copying workspace {} [i: {}, e: {}, m: {}]", workspaceName, includes, excludes, merges);
@@ -781,14 +829,22 @@ public class RepositoryUpgrade {
                 .exclude(excludes)
                 .merge(merges)
                 .copy(sourceState, builder);
+
+        return workspaceName;
     }
 
-    private void copyVersionStore(NodeState sourceState, NodeBuilder builder)
+    private void copyVersionStorage(NodeState sourceState, NodeBuilder builder)
             throws RepositoryException {
-        NodeStateCopier.builder()
-                .include("/jcr:system/jcr:versionStorage", "/jcr:system/jcr:activities")
-                .merge("/jcr:system")
-                .copy(sourceState, builder);
+        final NodeState versionStorage = sourceState.getChildNode(JCR_SYSTEM).getChildNode(JCR_VERSIONSTORAGE);
+        final Iterator<NodeState> versionStorageIterator = new DescendantsIterator(versionStorage, 3);
+        final VersionCopier versionCopier = new VersionCopier(sourceState, builder);
+
+        while (versionStorageIterator.hasNext()) {
+            final NodeState versionHistoryBucket = versionStorageIterator.next();
+            for (String versionHistory : versionHistoryBucket.getChildNodeNames()) {
+                versionCopier.copyVersionHistory(versionHistory, versionCopyConfiguration.getOrphanedMinDate());
+            }
+        }
     }
 
     private Set<String> calculateEffectiveIncludePaths(NodeState state) {

Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,97 @@
+/*
+ * 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.upgrade.version;
+
+import static org.apache.jackrabbit.JcrConstants.JCR_CREATED;
+import static org.apache.jackrabbit.JcrConstants.NT_VERSION;
+
+import java.util.Calendar;
+
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier;
+import org.apache.jackrabbit.util.ISO8601;
+
+import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.VERSION_STORE_PATH;
+
+import static org.apache.jackrabbit.oak.upgrade.version.VersionHistoryUtil.getVersionHistoryPath;
+import static org.apache.jackrabbit.oak.upgrade.version.VersionHistoryUtil.getVersionHistoryNodeState;
+
+/**
+ * This class allows to copy the version history, optionally filtering it with a
+ * given date.
+ */
+public class VersionCopier {
+
+    private final TypePredicate isVersion;
+
+    private final NodeState sourceRoot;
+
+    private final NodeBuilder rootBuilder;
+
+    public VersionCopier(NodeState sourceRoot, NodeBuilder rootBuilder) {
+        this.isVersion = new TypePredicate(rootBuilder.getNodeState(), NT_VERSION);
+        this.sourceRoot = sourceRoot;
+        this.rootBuilder = rootBuilder;
+    }
+
+    /**
+     * Copy history filtering versions using passed date and returns @{code
+     * true} if at least one version has been copied.
+     * 
+     * @param versionableUuid
+     *            Name of the version history node
+     * @param minDate
+     *            Only versions older than this date will be copied
+     * @return {@code true} if at least one version has been copied
+     */
+    public boolean copyVersionHistory(String versionableUuid, Calendar minDate) {
+        final String versionHistoryPath = getVersionHistoryPath(versionableUuid);
+        final NodeState versionHistory = getVersionHistoryNodeState(sourceRoot, versionableUuid);
+        final Calendar lastModified = getVersionHistoryLastModified(versionHistory);
+
+        if (lastModified.after(minDate) || minDate.getTimeInMillis() == 0) {
+            NodeStateCopier.builder()
+                    .include(versionHistoryPath)
+                    .merge(VERSION_STORE_PATH)
+                    .copy(sourceRoot, rootBuilder);
+            return true;
+        }
+        return false;
+    }
+
+    private Calendar getVersionHistoryLastModified(final NodeState versionHistory) {
+        Calendar youngest = Calendar.getInstance();
+        youngest.setTimeInMillis(0);
+        for (final ChildNodeEntry entry : versionHistory.getChildNodeEntries()) {
+            final NodeState version = entry.getNodeState();
+            if (!isVersion.apply(version)) {
+                continue;
+            }
+            if (version.hasProperty(JCR_CREATED)) {
+                final Calendar created = ISO8601.parse(version.getProperty(JCR_CREATED).getValue(Type.DATE));
+                if (created.after(youngest)) {
+                    youngest = created;
+                }
+            }
+        }
+        return youngest;
+    }
+}

Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,67 @@
+/*
+ * 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.upgrade.version;
+
+import java.util.Calendar;
+
+/**
+ * This class allows to configure the behaviour of the version copier.
+ */
+public class VersionCopyConfiguration {
+
+    private Calendar copyVersions;
+
+    private Calendar copyOrphanedVersions;
+
+    public VersionCopyConfiguration() {
+        final Calendar epoch = Calendar.getInstance();
+        epoch.setTimeInMillis(0);
+        this.copyVersions = epoch;
+        this.copyOrphanedVersions = epoch;
+    }
+
+    public void setCopyVersions(Calendar copyVersions) {
+        this.copyVersions = copyVersions;
+    }
+
+    public void setCopyOrphanedVersions(Calendar copyOrphanedVersions) {
+        this.copyOrphanedVersions = copyOrphanedVersions;
+    }
+
+    public Calendar getVersionsMinDate() {
+        return copyVersions;
+    }
+
+    public Calendar getOrphanedMinDate() {
+        if (copyVersions == null) {
+            return copyVersions;
+        } else if (copyOrphanedVersions != null && copyVersions.after(copyOrphanedVersions)) {
+            return copyVersions;
+        } else {
+            return copyOrphanedVersions;
+        }
+    }
+
+    public boolean isCopyVersions() {
+        return copyVersions != null;
+    }
+
+    public boolean skipOrphanedVersionsCopy() {
+        return copyVersions == null || copyOrphanedVersions == null;
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,67 @@
+/*
+ * 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.upgrade.version;
+
+import static com.google.common.collect.Iterables.concat;
+import static java.util.Collections.singleton;
+import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
+import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import com.google.common.base.Joiner;
+
+public class VersionHistoryUtil {
+
+    public static String getVersionHistoryPath(String versionableUuid) {
+        return Joiner.on('/').join(concat(
+                singleton(""),
+                getVersionHistoryPathSegments(versionableUuid),
+                singleton(versionableUuid)));
+    }
+
+    static NodeState getVersionHistoryNodeState(NodeState root, String versionableUuid) {
+        NodeState historyParent = root;
+        for (String segment : getVersionHistoryPathSegments(versionableUuid)) {
+            historyParent = historyParent.getChildNode(segment);
+        }
+        return historyParent.getChildNode(versionableUuid);
+    }
+
+    static NodeBuilder getVersionHistoryBuilder(NodeBuilder root, String versionableUuid) {
+        NodeBuilder history = root;
+        for (String segment : getVersionHistoryPathSegments(versionableUuid)) {
+            history = history.getChildNode(segment);
+        }
+        return history.getChildNode(versionableUuid);
+    }
+
+    private static List<String> getVersionHistoryPathSegments(String versionableUuid) {
+        final List<String> segments = new ArrayList<String>();
+        segments.add(JCR_SYSTEM);
+        segments.add(JCR_VERSIONSTORAGE);
+        for (int i = 0; i < 3; i++) {
+            segments.add(versionableUuid.substring(i * 2, i * 2 + 2));
+        }
+        return segments;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,226 @@
+/*
+ * 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.upgrade.version;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import java.util.Set;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION;
+import static org.apache.jackrabbit.JcrConstants.JCR_ISCHECKEDOUT;
+import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
+import static org.apache.jackrabbit.JcrConstants.JCR_PREDECESSORS;
+import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
+import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY;
+import static org.apache.jackrabbit.JcrConstants.MIX_REFERENCEABLE;
+import static org.apache.jackrabbit.JcrConstants.MIX_VERSIONABLE;
+import static org.apache.jackrabbit.oak.plugins.memory.MultiGenericPropertyState.nameProperty;
+import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
+
+/**
+ * The VersionableEditor provides two possible ways to handle
+ * versionable nodes:
+ * <ul>
+ *     <li>it can copy the version histories of versionable nodes, or</li>
+ *     <li>
+ *         it can skip copying version histories and remove the
+ *         {@code mix:versionable} mixin together with any related
+ *         properties (see {@link #removeVersionProperties(NodeBuilder)}).
+ *     </li>
+ * </ul>
+ */
+public class VersionableEditor extends DefaultEditor {
+
+    private static final Set<String> SKIPPED_PATHS = of("/oak:index", "/jcr:system/jcr:versionStorage");
+
+    private final Provider provider;
+
+    private final NodeBuilder rootBuilder;
+
+    private final TypePredicate isReferenceable;
+
+    private final TypePredicate isVersionable;
+
+    private final VersionCopier versionCopier;
+
+    private String path;
+
+    private VersionableEditor(Provider provider, NodeBuilder builder) {
+        this.provider = provider;
+        this.rootBuilder = builder;
+        this.isVersionable = new TypePredicate(builder.getNodeState(), MIX_VERSIONABLE);
+        this.isReferenceable = new TypePredicate(builder.getNodeState(), MIX_REFERENCEABLE);
+        this.versionCopier = new VersionCopier(provider.sourceRoot, builder);
+        this.path = "/";
+    }
+
+    public static class Provider implements EditorProvider {
+
+        private final NodeState sourceRoot;
+
+        private final String workspaceName;
+
+        private final VersionCopyConfiguration config;
+
+        public Provider(NodeState sourceRoot, String workspaceName, VersionCopyConfiguration config) {
+            this.sourceRoot = sourceRoot;
+            this.workspaceName = workspaceName;
+            this.config = config;
+        }
+
+        @Override
+        public Editor getRootEditor(NodeState before, NodeState after, NodeBuilder builder, CommitInfo info) throws CommitFailedException {
+            return new VersionableEditor(this, builder);
+        }
+    }
+
+    @Override
+    public Editor childNodeAdded(String name, NodeState after) throws CommitFailedException {
+        final String path = PathUtils.concat(this.path, name);
+        // skip deleted nodes and well known paths that may not contain versionable nodes
+        if (after == null || SKIPPED_PATHS.contains(path)) {
+            return null;
+        }
+
+        // assign path field only after checking that we don't skip this subtree
+        this.path = path;
+
+        final VersionCopyConfiguration c = provider.config;
+        if (isVersionable.apply(after)) {
+            final String versionableUuid = getProperty(after, JCR_UUID, Type.STRING);
+            boolean versionHistoryExists = isVersionHistoryExists(versionableUuid);
+            if (c.isCopyVersions() && c.skipOrphanedVersionsCopy()) {
+                versionHistoryExists = copyVersionHistory(after);
+            } else if (c.isCopyVersions() && !c.skipOrphanedVersionsCopy()) {
+                // all version histories have been copied, but maybe the date
+                // range for orphaned entries is narrower
+                if (c.getOrphanedMinDate().after(c.getVersionsMinDate())) {
+                    versionHistoryExists = copyVersionHistory(after);
+                }
+            } else {
+                versionHistoryExists = false;
+            }
+
+            if (versionHistoryExists) {
+                setVersionablePath(versionableUuid);
+            } else {
+                removeVersionProperties(getNodeBuilder(rootBuilder, this.path));
+            }
+        }
+
+        return this;
+    }
+
+    private boolean copyVersionHistory(NodeState versionable) {
+        assert versionable.exists();
+
+        final String versionableUuid = versionable.getProperty(JCR_UUID).getValue(Type.STRING);
+        return versionCopier.copyVersionHistory(versionableUuid, provider.config.getVersionsMinDate());
+    }
+
+    private void setVersionablePath(String versionableUuid) {
+        final NodeBuilder versionHistory = VersionHistoryUtil.getVersionHistoryBuilder(rootBuilder, versionableUuid);
+        versionHistory.setProperty(provider.workspaceName, path, Type.PATH);
+        addMixin(versionHistory, MIX_REP_VERSIONABLE_PATHS);
+    }
+
+    private boolean isVersionHistoryExists(String versionableUuid) {
+        return VersionHistoryUtil.getVersionHistoryNodeState(rootBuilder.getNodeState(), versionableUuid).exists();
+    }
+
+    private void removeVersionProperties(final NodeBuilder versionableBuilder) {
+        assert versionableBuilder.exists();
+
+        removeMixin(versionableBuilder, MIX_VERSIONABLE);
+
+        // we don't know if the UUID is otherwise referenced,
+        // so make sure the node remains referencable
+        if (!isReferenceable.apply(versionableBuilder.getNodeState())) {
+            addMixin(versionableBuilder, MIX_REFERENCEABLE);
+        }
+
+        versionableBuilder.removeProperty(JCR_VERSIONHISTORY);
+        versionableBuilder.removeProperty(JCR_PREDECESSORS);
+        versionableBuilder.removeProperty(JCR_BASEVERSION);
+        versionableBuilder.removeProperty(JCR_ISCHECKEDOUT);
+    }
+
+    @Override
+    public Editor childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
+        return childNodeAdded(name, after);
+    }
+
+    @Override
+    public Editor childNodeDeleted(String name, NodeState before) throws CommitFailedException {
+        return childNodeAdded(name, null);
+    }
+
+    @Override
+    public void leave(NodeState before, NodeState after) throws CommitFailedException {
+        this.path = PathUtils.getParentPath(this.path);
+    }
+
+    private static <T> T getProperty(NodeState state, String name, Type<T> type) {
+        if (state.hasProperty(name)) {
+            return state.getProperty(name).getValue(type);
+        }
+        return null;
+    }
+
+    private static NodeBuilder getNodeBuilder(NodeBuilder root, String path) {
+        NodeBuilder builder = root;
+        for (String name : PathUtils.elements(path)) {
+            builder = builder.getChildNode(name);
+        }
+        return builder;
+    }
+
+    private static void addMixin(NodeBuilder builder, String name) {
+        if (builder.hasProperty(JCR_MIXINTYPES)) {
+            final Set<String> mixins = newHashSet(builder.getProperty(JCR_MIXINTYPES).getValue(Type.NAMES));
+            if (mixins.add(name)) {
+                builder.setProperty(nameProperty(JCR_MIXINTYPES, mixins));
+            }
+        } else {
+            builder.setProperty(nameProperty(JCR_MIXINTYPES, of(name)));
+        }
+    }
+
+    private static void removeMixin(NodeBuilder builder, String name) {
+        if (builder.hasProperty(JCR_MIXINTYPES)) {
+            final Set<String> mixins = newHashSet(builder.getProperty(JCR_MIXINTYPES).getValue(Type.NAMES));
+            if (mixins.remove(name)) {
+                if (mixins.isEmpty()) {
+                    builder.removeProperty(JCR_MIXINTYPES);
+                } else {
+                    builder.setProperty(nameProperty(JCR_MIXINTYPES, mixins));
+                }
+            }
+        }
+    }
+}

Modified: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java?rev=1694498&r1=1694497&r2=1694498&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java (original)
+++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java Thu Aug  6 13:57:58 2015
@@ -47,7 +47,7 @@ import static org.junit.Assert.assertTru
 
 public abstract class AbstractRepositoryUpgradeTest {
 
-    protected static final Credentials CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray());
+    public static final Credentials CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray());
 
     private static NodeStore targetNodeStore;
 

Added: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,277 @@
+/*
+ * 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.upgrade;
+
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.upgrade.util.VersionCopyTestUtils.RepositoryUpgradeSetup;
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.version.VersionManager;
+
+import java.io.File;
+import java.util.Calendar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
+import static org.apache.jackrabbit.oak.upgrade.util.VersionCopyTestUtils.createVersionableNode;
+import static org.apache.jackrabbit.oak.upgrade.util.VersionCopyTestUtils.isVersionable;
+
+public class CopyVersionHistoryTest extends AbstractRepositoryUpgradeTest {
+
+    private static final String VERSIONABLES_OLD = "/versionables/old";
+
+    private static final String VERSIONABLES_OLD_ORPHANED = "/versionables/oldOrphaned";
+
+    private static final String VERSIONABLES_YOUNG = "/versionables/young";
+
+    private static final String VERSIONABLES_YOUNG_ORPHANED = "/versionables/youngOrphaned";
+
+    private static Calendar betweenHistories;
+
+    /**
+     * Home directory of source repository.
+     */
+    private static File source;
+
+    private static String oldOrphanedHistory;
+    private static String youngOrphanedHistory;
+    private static String oldHistory;
+    private static String youngHistory;
+
+    @Override
+    protected void createSourceContent(Repository repository) throws Exception {
+        final Session session = repository.login(CREDENTIALS);
+
+        oldHistory = createVersionableNode(session, VERSIONABLES_OLD);
+        oldOrphanedHistory = createVersionableNode(session, VERSIONABLES_OLD_ORPHANED);
+        Thread.sleep(10);
+        betweenHistories = Calendar.getInstance();
+        Thread.sleep(10);
+        youngOrphanedHistory = createVersionableNode(session, VERSIONABLES_YOUNG_ORPHANED);
+        youngHistory = createVersionableNode(session, VERSIONABLES_YOUNG);
+
+        session.getNode(VERSIONABLES_OLD_ORPHANED).remove();
+        session.getNode(VERSIONABLES_YOUNG_ORPHANED).remove();
+        session.save();
+    }
+
+    @Override
+    protected void doUpgradeRepository(File source, NodeStore target) throws RepositoryException {
+        // abuse this method to capture the source repo directory
+        CopyVersionHistoryTest.source = source;
+    }
+
+    @AfterClass
+    public static void teardown() {
+        CopyVersionHistoryTest.source = null;
+    }
+
+    @Test
+    public void copyAllVersions() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                // copying all versions is enabled by default
+            }
+        });
+        assertTrue(isVersionable(session, VERSIONABLES_OLD));
+        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
+        assertExisting(session, oldOrphanedHistory, youngOrphanedHistory, oldHistory, youngHistory);
+        assertHasVersionablePath(session, oldHistory, youngHistory);
+    }
+
+    @Test
+    public void referencedSinceDate() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyVersions(betweenHistories);
+            }
+        });
+
+        assertFalse(isVersionable(session, VERSIONABLES_OLD));
+        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldHistory, oldOrphanedHistory);
+        assertExisting(session, youngHistory, youngOrphanedHistory);
+        assertHasVersionablePath(session, youngHistory);
+    }
+
+    @Test
+    public void referencedOlderThanOrphaned() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyOrphanedVersions(betweenHistories);
+            }
+        });
+
+        assertTrue(isVersionable(session, VERSIONABLES_OLD));
+        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldOrphanedHistory);
+        assertExisting(session, oldHistory, youngHistory, youngOrphanedHistory);
+        assertHasVersionablePath(session, oldHistory, youngHistory);
+    }
+
+    @Test
+    public void onlyReferenced() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyOrphanedVersions(null);
+            }
+        });
+        assertTrue(isVersionable(session, VERSIONABLES_OLD));
+        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldOrphanedHistory, youngOrphanedHistory);
+        assertExisting(session, oldHistory, youngHistory);
+        assertHasVersionablePath(session, oldHistory, youngHistory);
+    }
+
+    @Test
+    public void onlyReferencedAfterDate() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyVersions(betweenHistories);
+                upgrade.setCopyOrphanedVersions(null);
+            }
+        });
+        assertFalse(isVersionable(session, VERSIONABLES_OLD));
+        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldHistory, oldOrphanedHistory, youngOrphanedHistory);
+        assertExisting(session, youngHistory);
+        assertHasVersionablePath(session, youngHistory);
+    }
+
+    @Test
+    public void onlyOrphaned() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyVersions(null);
+            }
+        });
+
+        assertFalse(isVersionable(session, VERSIONABLES_OLD));
+        assertFalse(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldHistory, youngHistory, oldOrphanedHistory, youngOrphanedHistory);
+    }
+
+    @Test
+    public void onlyOrphanedAfterDate() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyVersions(null);
+                upgrade.setCopyOrphanedVersions(betweenHistories);
+            }
+        });
+
+        assertFalse(isVersionable(session, VERSIONABLES_OLD));
+        assertFalse(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldHistory, youngHistory, oldOrphanedHistory, youngOrphanedHistory);
+    }
+
+    @Test
+    public void dontCopyVersionHistory() throws RepositoryException {
+        assert source != null;
+
+        Session session = performCopy(source, new RepositoryUpgradeSetup() {
+            @Override
+            public void setup(RepositoryUpgrade upgrade) {
+                upgrade.setCopyVersions(null);
+                upgrade.setCopyOrphanedVersions(null);
+            }
+        });
+
+        assertFalse(isVersionable(session, VERSIONABLES_OLD));
+        assertFalse(isVersionable(session, VERSIONABLES_YOUNG));
+        assertMissing(session, oldHistory, youngHistory, oldOrphanedHistory, youngOrphanedHistory);
+    }
+
+    public Session performCopy(File source, RepositoryUpgradeSetup setup) throws RepositoryException {
+        final RepositoryConfig sourceConfig = RepositoryConfig.create(source);
+        final RepositoryContext sourceContext = RepositoryContext.create(sourceConfig);
+        final NodeStore targetNodeStore = new MemoryNodeStore();
+        try {
+            final RepositoryUpgrade upgrade = new RepositoryUpgrade(sourceContext, targetNodeStore);
+            setup.setup(upgrade);
+            upgrade.copy(null);
+        } finally {
+            sourceContext.getRepository().shutdown();
+        }
+
+        final Repository repository = new Jcr(new Oak(targetNodeStore)).createRepository();
+        return repository.login(AbstractRepositoryUpgradeTest.CREDENTIALS);
+    }
+
+    private static void assertExisting(final Session session, final String... paths) throws RepositoryException {
+        for (final String path : paths) {
+            final String relPath = path.substring(1);
+            assertTrue("node " + path + " should exist", session.getRootNode().hasNode(relPath));
+        }
+    }
+
+    private static void assertMissing(final Session session, final String... paths) throws RepositoryException {
+        for (final String path : paths) {
+            final String relPath = path.substring(1);
+            assertFalse("node " + path + " should not exist", session.getRootNode().hasNode(relPath));
+        }
+    }
+    
+    public static void assertHasVersionablePath(final Session session, final String... historyPaths) throws RepositoryException {
+        for (String historyPath : historyPaths) {
+            final String workspaceName = session.getWorkspace().getName();
+            final Node versionHistory = session.getNode(historyPath);
+            assertTrue(versionHistory.isNodeType(MIX_REP_VERSIONABLE_PATHS));
+            assertTrue(versionHistory.hasProperty(workspaceName));
+            final Property pathProperty = versionHistory.getProperty(workspaceName);
+            assertEquals(PropertyType.PATH, pathProperty.getType());
+    
+            final VersionManager vm = session.getWorkspace().getVersionManager();
+            assertEquals(historyPath, vm.getVersionHistory(pathProperty.getString()).getPath());
+        }
+    }
+}

Added: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java?rev=1694498&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java (added)
+++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java Thu Aug  6 13:57:58 2015
@@ -0,0 +1,57 @@
+package org.apache.jackrabbit.oak.upgrade.util;
+
+import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionManager;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade;
+
+public class VersionCopyTestUtils {
+
+    public static String createVersionableNode(Session session, String versionablePath)
+            throws RepositoryException, InterruptedException {
+        final VersionManager versionManager = session.getWorkspace().getVersionManager();
+        final Node versionable = JcrUtils.getOrCreateUniqueByPath(session.getRootNode(), versionablePath,
+                JcrConstants.NT_UNSTRUCTURED);
+        versionable.addMixin("mix:versionable");
+        versionable.setProperty("version", "root");
+        session.save();
+
+        final String path = versionable.getPath();
+        final List<String> versionNames = new ArrayList<String>();
+        for (int i = 0; i < 3; i++) {
+            versionable.setProperty("version", "1." + i);
+            session.save();
+            final Version v = versionManager.checkpoint(path);
+            versionNames.add(v.getName());
+        }
+
+        final VersionHistory history = versionManager.getVersionHistory(path);
+        for (final String versionName : versionNames) {
+            history.addVersionLabel(versionName, String.format("version %s", versionName), false);
+        }
+        return history.getPath();
+    }
+
+    public static boolean isVersionable(Session session, String path) throws RepositoryException {
+        return session.getNode(path).isNodeType(JcrConstants.MIX_VERSIONABLE);
+    }
+
+    public interface RepositoryUpgradeSetup {
+        void setup(RepositoryUpgrade upgrade);
+    }
+}



Re: svn commit: r1694498 - in /jackrabbit/oak/trunk: oak-run/src/main/java/org/apache/jackrabbit/oak/run/ oak-run/src/test/java/org/apache/jackrabbit/oak/run/ oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/ oak-upgrade/src/main/java/org/apache...

Posted by Julian Sedding <js...@gmail.com>.
Thanks Manfred for getting this in!

For completeness regarding credits (Manfred couldn't know): I created
the initial patch and developed some doubts over time. Tomek took care
of adapting the code to address those doubts and create a superior
feature (IMHO). I assisted at that stage with code reviews and
suggestions only. So thanks Tomek!

Regards
Julian


On Thu, Aug 6, 2015 at 3:57 PM,  <ba...@apache.org> wrote:
> Author: baedke
> Date: Thu Aug  6 13:57:58 2015
> New Revision: 1694498
>
> URL: http://svn.apache.org/r1694498
> Log:
> OAK-2776: Upgrade should allow to skip copying versions
>
> Initial implementation. Full credit goes to Julian Sedding (jsedding@gmail.com) for the patch and to Tomek Rekawek (trekawek@gmail.com) for aligning it with the current trunk and integrating into oak-run.
>
> Added:
>     jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java
>     jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java
>     jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java
> Modified:
>     jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java
>     jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java
>     jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java
>
> Modified: jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java?rev=1694498&r1=1694497&r2=1694498&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java (original)
> +++ jackrabbit/oak/trunk/oak-run/src/main/java/org/apache/jackrabbit/oak/run/Main.java Thu Aug  6 13:57:58 2015
> @@ -28,8 +28,12 @@ import java.io.File;
>  import java.io.IOException;
>  import java.io.InputStream;
>  import java.sql.Timestamp;
> +import java.text.DateFormat;
> +import java.text.ParseException;
> +import java.text.SimpleDateFormat;
>  import java.util.ArrayList;
>  import java.util.Arrays;
> +import java.util.Calendar;
>  import java.util.Collections;
>  import java.util.HashMap;
>  import java.util.HashSet;
> @@ -60,6 +64,8 @@ import joptsimple.ArgumentAcceptingOptio
>  import joptsimple.OptionParser;
>  import joptsimple.OptionSet;
>  import joptsimple.OptionSpec;
> +
> +import org.apache.commons.lang.time.DateUtils;
>  import org.apache.jackrabbit.core.RepositoryContext;
>  import org.apache.jackrabbit.core.config.RepositoryConfig;
>  import org.apache.jackrabbit.oak.Oak;
> @@ -939,6 +945,8 @@ public final class Main {
>      private static void upgrade(String[] args) throws Exception {
>          OptionParser parser = new OptionParser();
>          parser.accepts("datastore", "keep data store");
> +        ArgumentAcceptingOptionSpec<String> copyVersions = parser.accepts("copy-versions", "copy referenced versions. valid arguments: true|false|yyyy-mm-dd").withRequiredArg().defaultsTo("true");
> +        ArgumentAcceptingOptionSpec<String> copyOrphanedVersions = parser.accepts("copy-orphaned-versions", "copy all versions. valid arguments: true|false|yyyy-mm-dd").withRequiredArg().defaultsTo("true");
>          OptionSpec<String> nonOption = parser.nonOptions();
>          OptionSet options = parser.parse(args);
>
> @@ -967,6 +975,7 @@ public final class Main {
>                                      new RepositoryUpgrade(source, target);
>                              upgrade.setCopyBinariesByReference(
>                                      options.has("datastore"));
> +                            setCopyVersionOptions(copyVersions.value(options), copyOrphanedVersions.value(options), upgrade);
>                              upgrade.copy(null);
>                          } finally {
>                              target.dispose();
> @@ -996,6 +1005,26 @@ public final class Main {
>          }
>      }
>
> +    private static void setCopyVersionOptions(String copyVersions, String copyOrphanedVersions, RepositoryUpgrade upgrade) throws ParseException {
> +        upgrade.setCopyVersions(parseVersionCopyArgument(copyVersions));
> +        upgrade.setCopyOrphanedVersions(parseVersionCopyArgument(copyOrphanedVersions));
> +    }
> +
> +    static Calendar parseVersionCopyArgument(String string) throws ParseException {
> +        final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
> +        final Calendar calendar;
> +
> +        if (Boolean.parseBoolean(string)) {
> +            calendar = Calendar.getInstance();
> +            calendar.setTimeInMillis(0);
> +        } else if (string != null && string.matches("^\\d{4}-\\d{2}-\\d{2}$")) {
> +            calendar = DateUtils.toCalendar(df.parse(string));
> +        } else {
> +            calendar = null;
> +        }
> +        return calendar;
> +    }
> +
>      private static void server(String defaultUri, String[] args) throws Exception {
>          OptionParser parser = new OptionParser();
>
>
> Added: jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java (added)
> +++ jackrabbit/oak/trunk/oak-run/src/test/java/org/apache/jackrabbit/oak/run/ParseVersionCopyArgumentTest.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,53 @@
> +/*
> + * 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.run;
> +
> +import java.text.ParseException;
> +import java.util.Arrays;
> +import java.util.Calendar;
> +import java.util.GregorianCalendar;
> +
> +import org.junit.Assert;
> +import org.junit.Test;
> +
> +public class ParseVersionCopyArgumentTest {
> +
> +    @Test
> +    public void parseTrue() throws ParseException {
> +        for (String argument : Arrays.asList("true", "TRUE", "TrUe")) {
> +            final Calendar result = Main.parseVersionCopyArgument(argument);
> +            Assert.assertEquals(0, result.getTimeInMillis());
> +        }
> +    }
> +
> +    @Test
> +    public void parseDate() throws ParseException {
> +        final Calendar result = Main.parseVersionCopyArgument("2013-01-01");
> +        Assert.assertEquals(new GregorianCalendar(2013, 0, 1), result);
> +    }
> +
> +    @Test
> +    public void parseFalse() throws ParseException {
> +        for (String argument : Arrays.asList("false", "FaLse", "", "xyz", null)) {
> +            final Calendar result = Main.parseVersionCopyArgument(argument);
> +            Assert.assertNull(result);
> +        }
> +    }
> +}
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/DescendantsIterator.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,46 @@
> +package org.apache.jackrabbit.oak.upgrade;
> +
> +import java.util.ArrayDeque;
> +import java.util.Deque;
> +import java.util.Iterator;
> +
> +import org.apache.jackrabbit.commons.iterator.AbstractLazyIterator;
> +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
> +import org.apache.jackrabbit.oak.spi.state.NodeState;
> +
> +public class DescendantsIterator extends AbstractLazyIterator<NodeState> {
> +
> +    private final Deque<Iterator<? extends ChildNodeEntry>> stack = new ArrayDeque<Iterator<? extends ChildNodeEntry>>();
> +
> +    private final int maxLevel;
> +
> +    public DescendantsIterator(NodeState root, int maxLevel) {
> +        this.maxLevel = maxLevel;
> +        stack.push(root.getChildNodeEntries().iterator());
> +    }
> +
> +    @Override
> +    protected NodeState getNext() {
> +        if (!fillStack()) {
> +            return null;
> +        }
> +        return stack.peekFirst().next().getNodeState();
> +    }
> +
> +    private boolean fillStack() {
> +        while (stack.size() < maxLevel || !stack.peekFirst().hasNext()) {
> +            Iterator<? extends ChildNodeEntry> topIterator = stack.peekFirst();
> +            if (topIterator.hasNext()) {
> +                final NodeState nextNode = topIterator.next().getNodeState();
> +                stack.push(nextNode.getChildNodeEntries().iterator());
> +            } else {
> +                stack.pop();
> +                if (stack.isEmpty()) {
> +                    return false;
> +                }
> +            }
> +        }
> +        return true;
> +    }
> +
> +}
>
> Modified: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java?rev=1694498&r1=1694497&r2=1694498&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java (original)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/JackrabbitNodeState.java Thu Aug  6 13:57:58 2015
> @@ -32,7 +32,6 @@ import static org.apache.jackrabbit.JcrC
>  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.JcrConstants.MIX_REFERENCEABLE;
>  import static org.apache.jackrabbit.JcrConstants.MIX_VERSIONABLE;
>  import static org.apache.jackrabbit.JcrConstants.NT_BASE;
> @@ -46,7 +45,6 @@ import static org.apache.jackrabbit.oak.
>  import static org.apache.jackrabbit.oak.api.Type.NAMES;
>  import static org.apache.jackrabbit.oak.api.Type.STRING;
>  import static org.apache.jackrabbit.oak.plugins.tree.impl.TreeConstants.OAK_CHILD_ORDER;
> -import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
>
>  import java.io.ByteArrayInputStream;
>  import java.io.IOException;
> @@ -139,8 +137,6 @@ class JackrabbitNodeState extends Abstra
>       */
>      private final Map<String, String> uriToPrefix;
>
> -    private final Map<String, String> versionablePaths;
> -
>      private final boolean useBinaryReferences;
>
>      private final Map<String, NodeId> nodes;
> @@ -158,7 +154,6 @@ class JackrabbitNodeState extends Abstra
>              String workspaceName,
>              NodeState root,
>              Map<String, String> uriToPrefix,
> -            Map<String, String> versionablePaths,
>              boolean copyBinariesByReference,
>              boolean skipOnError
>      ) throws RepositoryException {
> @@ -169,7 +164,6 @@ class JackrabbitNodeState extends Abstra
>                  versionPM, root, uriToPrefix,
>                  VERSION_STORAGE_NODE_ID, "/jcr:system/jcr:versionStorage",
>                  null,
> -                versionablePaths,
>                  emptyMountPoints,
>                  copyBinariesByReference,
>                  skipOnError
> @@ -179,7 +173,6 @@ class JackrabbitNodeState extends Abstra
>                  versionPM, root, uriToPrefix,
>                  ACTIVITIES_NODE_ID, "/jcr:system/jcr:activities",
>                  null,
> -                versionablePaths,
>                  emptyMountPoints,
>                  copyBinariesByReference,
>                  skipOnError
> @@ -193,7 +186,7 @@ class JackrabbitNodeState extends Abstra
>          );
>          return new JackrabbitNodeState(
>                  pm, root, uriToPrefix, ROOT_NODE_ID, "/",
> -                workspaceName, versionablePaths, mountPoints, copyBinariesByReference, skipOnError);
> +                workspaceName, mountPoints, copyBinariesByReference, skipOnError);
>      }
>
>      private JackrabbitNodeState(
> @@ -209,7 +202,6 @@ class JackrabbitNodeState extends Abstra
>          this.isVersionHistory = parent.isVersionHistory;
>          this.isFrozenNode = parent.isFrozenNode;
>          this.uriToPrefix = parent.uriToPrefix;
> -        this.versionablePaths = parent.versionablePaths;
>          this.useBinaryReferences = parent.useBinaryReferences;
>          this.properties = createProperties(bundle);
>          this.nodes = createNodes(bundle);
> @@ -217,7 +209,6 @@ class JackrabbitNodeState extends Abstra
>          this.mountPoints = parent.mountPoints;
>          this.nodeStateCache = parent.nodeStateCache;
>          setChildOrder();
> -        setVersionablePaths();
>          fixFrozenUuid();
>          logNewNode(this);
>      }
> @@ -225,7 +216,7 @@ class JackrabbitNodeState extends Abstra
>      JackrabbitNodeState(
>              PersistenceManager source, NodeState root,
>              Map<String, String> uriToPrefix, NodeId id, String path,
> -            String workspaceName, Map<String, String> versionablePaths,
> +            String workspaceName,
>              Map<NodeId, JackrabbitNodeState> mountPoints,
>              boolean useBinaryReferences, boolean skipOnError) {
>          this.parent = null;
> @@ -239,7 +230,6 @@ class JackrabbitNodeState extends Abstra
>          this.isVersionHistory = new TypePredicate(root, NT_VERSIONHISTORY);
>          this.isFrozenNode = new TypePredicate(root, NT_FROZENNODE);
>          this.uriToPrefix = uriToPrefix;
> -        this.versionablePaths = versionablePaths;
>          this.mountPoints = mountPoints;
>          final int cacheSize = 50; // cache size 50 results in > 25% cache hits during version copy
>          this.nodeStateCache = new LinkedHashMap<NodeId, JackrabbitNodeState>(cacheSize, 0.75f, true) {
> @@ -380,28 +370,6 @@ class JackrabbitNodeState extends Abstra
>          }
>      }
>
> -    private void setVersionablePaths() {
> -        if (isVersionable.apply(this)) {
> -            String uuid = getString(JCR_VERSIONHISTORY);
> -            if (uuid != null) {
> -                versionablePaths.put(uuid, getPath());
> -            }
> -        } else if (isVersionHistory.apply(this)) {
> -            String uuid = getString(JCR_UUID);
> -            String path = versionablePaths.get(uuid);
> -            if (path != null) {
> -                properties.put(workspaceName, PropertyStates.createProperty(
> -                        workspaceName, path, Type.PATH));
> -
> -                Set<String> mixins = newLinkedHashSet(getNames(JCR_MIXINTYPES));
> -                if (mixins.add(MIX_REP_VERSIONABLE_PATHS)) {
> -                    properties.put(JCR_MIXINTYPES, PropertyStates.createProperty(
> -                            JCR_MIXINTYPES, mixins, Type.NAMES));
> -                }
> -            }
> -        }
> -    }
> -
>      private Map<String, NodeId> createNodes(NodePropBundle bundle) {
>          Map<String, NodeId> children = newLinkedHashMap();
>          for (ChildNodeEntry entry : bundle.getChildNodeEntries()) {
> @@ -728,4 +696,4 @@ class JackrabbitNodeState extends Abstra
>          log.warn(getPath() + ": " + message, cause);
>      }
>
> -}
> \ No newline at end of file
> +}
>
> Modified: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java?rev=1694498&r1=1694497&r2=1694498&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java (original)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/RepositoryUpgrade.java Thu Aug  6 13:57:58 2015
> @@ -26,6 +26,7 @@ import static com.google.common.collect.
>  import static com.google.common.collect.Sets.newHashSet;
>  import static com.google.common.collect.Sets.union;
>  import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
> +import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE;
>  import static org.apache.jackrabbit.oak.plugins.name.Namespaces.addCustomMapping;
>  import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.NODE_TYPES_PATH;
>  import static org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants.JCR_ALL;
> @@ -35,6 +36,7 @@ import static org.apache.jackrabbit.oak.
>  import java.io.File;
>  import java.io.IOException;
>  import java.io.InputStream;
> +import java.util.Calendar;
>  import java.util.Collection;
>  import java.util.Iterator;
>  import java.util.List;
> @@ -110,6 +112,9 @@ import org.apache.jackrabbit.oak.spi.sta
>  import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier;
>  import org.apache.jackrabbit.oak.upgrade.security.GroupEditorProvider;
>  import org.apache.jackrabbit.oak.upgrade.security.RestrictionEditorProvider;
> +import org.apache.jackrabbit.oak.upgrade.version.VersionCopier;
> +import org.apache.jackrabbit.oak.upgrade.version.VersionCopyConfiguration;
> +import org.apache.jackrabbit.oak.upgrade.version.VersionableEditor;
>  import org.apache.jackrabbit.spi.Name;
>  import org.apache.jackrabbit.spi.QNodeDefinition;
>  import org.apache.jackrabbit.spi.QNodeTypeDefinition;
> @@ -168,6 +173,8 @@ public class RepositoryUpgrade {
>
>      private List<CommitHook> customCommitHooks = null;
>
> +    private VersionCopyConfiguration versionCopyConfiguration = new VersionCopyConfiguration();
> +
>      /**
>       * Copies the contents of the repository in the given source directory
>       * to the given target node store.
> @@ -288,6 +295,41 @@ public class RepositoryUpgrade {
>      }
>
>      /**
> +     * Configures the version storage copy. Be default all versions are copied.
> +     * One may disable it completely by setting {@code null} here or limit it to
> +     * a selected date range: {@code <minDate, now()>}.
> +     *
> +     * @param minDate
> +     *            minimum date of the versions to copy or {@code null} to
> +     *            disable the storage version copying completely. Default value:
> +     *            {@code 1970-01-01 00:00:00}.
> +     */
> +    public void setCopyVersions(Calendar minDate) {
> +        versionCopyConfiguration.setCopyVersions(minDate);
> +    }
> +
> +    /**
> +     * Configures copying of the orphaned version histories (eg. ones that are
> +     * not referenced by the existing nodes). By default all orphaned version
> +     * histories are copied. One may disable it completely by setting
> +     * {@code null} here or limit it to a selected date range:
> +     * {@code <minDate, now()>}. <br/>
> +     * <br/>
> +     * Please notice, that this option is overriden by the
> +     * {@link #setCopyVersions(Calendar)}. You can't copy orphaned versions
> +     * older than set in {@link #setCopyVersions(Calendar)} and if you set
> +     * {@code null} there, this option will be ignored.
> +     *
> +     * @param minDate
> +     *            minimum date of the orphaned versions to copy or {@code null}
> +     *            to not copy them at all. Default value:
> +     *            {@code 1970-01-01 00:00:00}.
> +     */
> +    public void setCopyOrphanedVersions(Calendar minDate) {
> +        versionCopyConfiguration.setCopyOrphanedVersions(minDate);
> +    }
> +
> +    /**
>       * Copies the full content from the source to the target repository.
>       * <p>
>       * The source repository <strong>must not be modified</strong> while
> @@ -364,11 +406,10 @@ public class RepositoryUpgrade {
>              new TypeEditorProvider(false).getRootEditor(
>                      base, builder.getNodeState(), builder, null);
>
> -            Map<String, String> versionablePaths = newHashMap();
>              NodeState root = builder.getNodeState();
>
>              final NodeState sourceState = JackrabbitNodeState.createRootNodeState(
> -                    source, workspaceName, root, uriToPrefix, versionablePaths, copyBinariesByReference, skipOnError);
> +                    source, workspaceName, root, uriToPrefix, copyBinariesByReference, skipOnError);
>
>              final Stopwatch watch = Stopwatch.createStarted();
>
> @@ -377,10 +418,15 @@ public class RepositoryUpgrade {
>              builder.getNodeState(); // on TarMK this does call triggers the actual copy
>              logger.info("Upgrading workspace content completed in {}s ({})", watch.elapsed(TimeUnit.SECONDS), watch);
>
> -            watch.reset().start();
> -            logger.info("Copying version store content");
> -            copyVersionStore(sourceState, builder);
> -            logger.debug("Upgrading version store content completed in {}s ({}).", watch.elapsed(TimeUnit.SECONDS), watch);
> +            if (!versionCopyConfiguration.skipOrphanedVersionsCopy()) {
> +                logger.info("Copying version storage");
> +                watch.reset().start();
> +                copyVersionStorage(sourceState, builder);
> +                builder.getNodeState(); // on TarMK this does call triggers the actual copy
> +                logger.info("Version storage copied in {}s ({})", watch.elapsed(TimeUnit.SECONDS), watch);
> +            } else {
> +                logger.info("Skipping the version storage as the copyOrphanedVersions is set to false");
> +            }
>
>              watch.reset().start();
>              logger.info("Applying default commit hooks");
> @@ -396,7 +442,9 @@ public class RepositoryUpgrade {
>              // hooks specific to the upgrade, need to run first
>              hooks.add(new EditorHook(new CompositeEditorProvider(
>                      new RestrictionEditorProvider(),
> -                    new GroupEditorProvider(groupsPath)
> +                    new GroupEditorProvider(groupsPath),
> +                    // copy referenced version histories
> +                    new VersionableEditor.Provider(sourceState, workspaceName, versionCopyConfiguration)
>              )));
>
>              // security-related hooks
> @@ -768,10 +816,10 @@ public class RepositoryUpgrade {
>          return tmpl;
>      }
>
> -    private void copyWorkspace(NodeState sourceState, NodeBuilder builder, String workspaceName)
> +    private String copyWorkspace(NodeState sourceState, NodeBuilder builder, String workspaceName)
>              throws RepositoryException {
>          final Set<String> includes = calculateEffectiveIncludePaths(sourceState);
> -        final Set<String> excludes = union(copyOf(this.excludePaths), of("/jcr:system/jcr:versionStorage", "/jcr:system/jcr:activities"));
> +        final Set<String> excludes = union(copyOf(this.excludePaths), of("/jcr:system/jcr:versionStorage"));
>          final Set<String> merges = union(copyOf(this.mergePaths), of("/jcr:system"));
>
>          logger.info("Copying workspace {} [i: {}, e: {}, m: {}]", workspaceName, includes, excludes, merges);
> @@ -781,14 +829,22 @@ public class RepositoryUpgrade {
>                  .exclude(excludes)
>                  .merge(merges)
>                  .copy(sourceState, builder);
> +
> +        return workspaceName;
>      }
>
> -    private void copyVersionStore(NodeState sourceState, NodeBuilder builder)
> +    private void copyVersionStorage(NodeState sourceState, NodeBuilder builder)
>              throws RepositoryException {
> -        NodeStateCopier.builder()
> -                .include("/jcr:system/jcr:versionStorage", "/jcr:system/jcr:activities")
> -                .merge("/jcr:system")
> -                .copy(sourceState, builder);
> +        final NodeState versionStorage = sourceState.getChildNode(JCR_SYSTEM).getChildNode(JCR_VERSIONSTORAGE);
> +        final Iterator<NodeState> versionStorageIterator = new DescendantsIterator(versionStorage, 3);
> +        final VersionCopier versionCopier = new VersionCopier(sourceState, builder);
> +
> +        while (versionStorageIterator.hasNext()) {
> +            final NodeState versionHistoryBucket = versionStorageIterator.next();
> +            for (String versionHistory : versionHistoryBucket.getChildNodeNames()) {
> +                versionCopier.copyVersionHistory(versionHistory, versionCopyConfiguration.getOrphanedMinDate());
> +            }
> +        }
>      }
>
>      private Set<String> calculateEffectiveIncludePaths(NodeState state) {
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,97 @@
> +/*
> + * 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.upgrade.version;
> +
> +import static org.apache.jackrabbit.JcrConstants.JCR_CREATED;
> +import static org.apache.jackrabbit.JcrConstants.NT_VERSION;
> +
> +import java.util.Calendar;
> +
> +import org.apache.jackrabbit.oak.api.Type;
> +import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
> +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
> +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
> +import org.apache.jackrabbit.oak.spi.state.NodeState;
> +import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier;
> +import org.apache.jackrabbit.util.ISO8601;
> +
> +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.VERSION_STORE_PATH;
> +
> +import static org.apache.jackrabbit.oak.upgrade.version.VersionHistoryUtil.getVersionHistoryPath;
> +import static org.apache.jackrabbit.oak.upgrade.version.VersionHistoryUtil.getVersionHistoryNodeState;
> +
> +/**
> + * This class allows to copy the version history, optionally filtering it with a
> + * given date.
> + */
> +public class VersionCopier {
> +
> +    private final TypePredicate isVersion;
> +
> +    private final NodeState sourceRoot;
> +
> +    private final NodeBuilder rootBuilder;
> +
> +    public VersionCopier(NodeState sourceRoot, NodeBuilder rootBuilder) {
> +        this.isVersion = new TypePredicate(rootBuilder.getNodeState(), NT_VERSION);
> +        this.sourceRoot = sourceRoot;
> +        this.rootBuilder = rootBuilder;
> +    }
> +
> +    /**
> +     * Copy history filtering versions using passed date and returns @{code
> +     * true} if at least one version has been copied.
> +     *
> +     * @param versionableUuid
> +     *            Name of the version history node
> +     * @param minDate
> +     *            Only versions older than this date will be copied
> +     * @return {@code true} if at least one version has been copied
> +     */
> +    public boolean copyVersionHistory(String versionableUuid, Calendar minDate) {
> +        final String versionHistoryPath = getVersionHistoryPath(versionableUuid);
> +        final NodeState versionHistory = getVersionHistoryNodeState(sourceRoot, versionableUuid);
> +        final Calendar lastModified = getVersionHistoryLastModified(versionHistory);
> +
> +        if (lastModified.after(minDate) || minDate.getTimeInMillis() == 0) {
> +            NodeStateCopier.builder()
> +                    .include(versionHistoryPath)
> +                    .merge(VERSION_STORE_PATH)
> +                    .copy(sourceRoot, rootBuilder);
> +            return true;
> +        }
> +        return false;
> +    }
> +
> +    private Calendar getVersionHistoryLastModified(final NodeState versionHistory) {
> +        Calendar youngest = Calendar.getInstance();
> +        youngest.setTimeInMillis(0);
> +        for (final ChildNodeEntry entry : versionHistory.getChildNodeEntries()) {
> +            final NodeState version = entry.getNodeState();
> +            if (!isVersion.apply(version)) {
> +                continue;
> +            }
> +            if (version.hasProperty(JCR_CREATED)) {
> +                final Calendar created = ISO8601.parse(version.getProperty(JCR_CREATED).getValue(Type.DATE));
> +                if (created.after(youngest)) {
> +                    youngest = created;
> +                }
> +            }
> +        }
> +        return youngest;
> +    }
> +}
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,67 @@
> +/*
> + * 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.upgrade.version;
> +
> +import java.util.Calendar;
> +
> +/**
> + * This class allows to configure the behaviour of the version copier.
> + */
> +public class VersionCopyConfiguration {
> +
> +    private Calendar copyVersions;
> +
> +    private Calendar copyOrphanedVersions;
> +
> +    public VersionCopyConfiguration() {
> +        final Calendar epoch = Calendar.getInstance();
> +        epoch.setTimeInMillis(0);
> +        this.copyVersions = epoch;
> +        this.copyOrphanedVersions = epoch;
> +    }
> +
> +    public void setCopyVersions(Calendar copyVersions) {
> +        this.copyVersions = copyVersions;
> +    }
> +
> +    public void setCopyOrphanedVersions(Calendar copyOrphanedVersions) {
> +        this.copyOrphanedVersions = copyOrphanedVersions;
> +    }
> +
> +    public Calendar getVersionsMinDate() {
> +        return copyVersions;
> +    }
> +
> +    public Calendar getOrphanedMinDate() {
> +        if (copyVersions == null) {
> +            return copyVersions;
> +        } else if (copyOrphanedVersions != null && copyVersions.after(copyOrphanedVersions)) {
> +            return copyVersions;
> +        } else {
> +            return copyOrphanedVersions;
> +        }
> +    }
> +
> +    public boolean isCopyVersions() {
> +        return copyVersions != null;
> +    }
> +
> +    public boolean skipOrphanedVersionsCopy() {
> +        return copyVersions == null || copyOrphanedVersions == null;
> +    }
> +
> +}
> \ No newline at end of file
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,67 @@
> +/*
> + * 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.upgrade.version;
> +
> +import static com.google.common.collect.Iterables.concat;
> +import static java.util.Collections.singleton;
> +import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM;
> +import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE;
> +
> +import java.util.ArrayList;
> +import java.util.List;
> +
> +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
> +import org.apache.jackrabbit.oak.spi.state.NodeState;
> +
> +import com.google.common.base.Joiner;
> +
> +public class VersionHistoryUtil {
> +
> +    public static String getVersionHistoryPath(String versionableUuid) {
> +        return Joiner.on('/').join(concat(
> +                singleton(""),
> +                getVersionHistoryPathSegments(versionableUuid),
> +                singleton(versionableUuid)));
> +    }
> +
> +    static NodeState getVersionHistoryNodeState(NodeState root, String versionableUuid) {
> +        NodeState historyParent = root;
> +        for (String segment : getVersionHistoryPathSegments(versionableUuid)) {
> +            historyParent = historyParent.getChildNode(segment);
> +        }
> +        return historyParent.getChildNode(versionableUuid);
> +    }
> +
> +    static NodeBuilder getVersionHistoryBuilder(NodeBuilder root, String versionableUuid) {
> +        NodeBuilder history = root;
> +        for (String segment : getVersionHistoryPathSegments(versionableUuid)) {
> +            history = history.getChildNode(segment);
> +        }
> +        return history.getChildNode(versionableUuid);
> +    }
> +
> +    private static List<String> getVersionHistoryPathSegments(String versionableUuid) {
> +        final List<String> segments = new ArrayList<String>();
> +        segments.add(JCR_SYSTEM);
> +        segments.add(JCR_VERSIONSTORAGE);
> +        for (int i = 0; i < 3; i++) {
> +            segments.add(versionableUuid.substring(i * 2, i * 2 + 2));
> +        }
> +        return segments;
> +    }
> +
> +}
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionableEditor.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,226 @@
> +/*
> + * 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.upgrade.version;
> +
> +import org.apache.jackrabbit.oak.api.CommitFailedException;
> +import org.apache.jackrabbit.oak.api.Type;
> +import org.apache.jackrabbit.oak.commons.PathUtils;
> +import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
> +import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
> +import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
> +import org.apache.jackrabbit.oak.spi.commit.Editor;
> +import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
> +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
> +import org.apache.jackrabbit.oak.spi.state.NodeState;
> +
> +import java.util.Set;
> +
> +import static com.google.common.collect.ImmutableSet.of;
> +import static com.google.common.collect.Sets.newHashSet;
> +import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION;
> +import static org.apache.jackrabbit.JcrConstants.JCR_ISCHECKEDOUT;
> +import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
> +import static org.apache.jackrabbit.JcrConstants.JCR_PREDECESSORS;
> +import static org.apache.jackrabbit.JcrConstants.JCR_UUID;
> +import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONHISTORY;
> +import static org.apache.jackrabbit.JcrConstants.MIX_REFERENCEABLE;
> +import static org.apache.jackrabbit.JcrConstants.MIX_VERSIONABLE;
> +import static org.apache.jackrabbit.oak.plugins.memory.MultiGenericPropertyState.nameProperty;
> +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
> +
> +/**
> + * The VersionableEditor provides two possible ways to handle
> + * versionable nodes:
> + * <ul>
> + *     <li>it can copy the version histories of versionable nodes, or</li>
> + *     <li>
> + *         it can skip copying version histories and remove the
> + *         {@code mix:versionable} mixin together with any related
> + *         properties (see {@link #removeVersionProperties(NodeBuilder)}).
> + *     </li>
> + * </ul>
> + */
> +public class VersionableEditor extends DefaultEditor {
> +
> +    private static final Set<String> SKIPPED_PATHS = of("/oak:index", "/jcr:system/jcr:versionStorage");
> +
> +    private final Provider provider;
> +
> +    private final NodeBuilder rootBuilder;
> +
> +    private final TypePredicate isReferenceable;
> +
> +    private final TypePredicate isVersionable;
> +
> +    private final VersionCopier versionCopier;
> +
> +    private String path;
> +
> +    private VersionableEditor(Provider provider, NodeBuilder builder) {
> +        this.provider = provider;
> +        this.rootBuilder = builder;
> +        this.isVersionable = new TypePredicate(builder.getNodeState(), MIX_VERSIONABLE);
> +        this.isReferenceable = new TypePredicate(builder.getNodeState(), MIX_REFERENCEABLE);
> +        this.versionCopier = new VersionCopier(provider.sourceRoot, builder);
> +        this.path = "/";
> +    }
> +
> +    public static class Provider implements EditorProvider {
> +
> +        private final NodeState sourceRoot;
> +
> +        private final String workspaceName;
> +
> +        private final VersionCopyConfiguration config;
> +
> +        public Provider(NodeState sourceRoot, String workspaceName, VersionCopyConfiguration config) {
> +            this.sourceRoot = sourceRoot;
> +            this.workspaceName = workspaceName;
> +            this.config = config;
> +        }
> +
> +        @Override
> +        public Editor getRootEditor(NodeState before, NodeState after, NodeBuilder builder, CommitInfo info) throws CommitFailedException {
> +            return new VersionableEditor(this, builder);
> +        }
> +    }
> +
> +    @Override
> +    public Editor childNodeAdded(String name, NodeState after) throws CommitFailedException {
> +        final String path = PathUtils.concat(this.path, name);
> +        // skip deleted nodes and well known paths that may not contain versionable nodes
> +        if (after == null || SKIPPED_PATHS.contains(path)) {
> +            return null;
> +        }
> +
> +        // assign path field only after checking that we don't skip this subtree
> +        this.path = path;
> +
> +        final VersionCopyConfiguration c = provider.config;
> +        if (isVersionable.apply(after)) {
> +            final String versionableUuid = getProperty(after, JCR_UUID, Type.STRING);
> +            boolean versionHistoryExists = isVersionHistoryExists(versionableUuid);
> +            if (c.isCopyVersions() && c.skipOrphanedVersionsCopy()) {
> +                versionHistoryExists = copyVersionHistory(after);
> +            } else if (c.isCopyVersions() && !c.skipOrphanedVersionsCopy()) {
> +                // all version histories have been copied, but maybe the date
> +                // range for orphaned entries is narrower
> +                if (c.getOrphanedMinDate().after(c.getVersionsMinDate())) {
> +                    versionHistoryExists = copyVersionHistory(after);
> +                }
> +            } else {
> +                versionHistoryExists = false;
> +            }
> +
> +            if (versionHistoryExists) {
> +                setVersionablePath(versionableUuid);
> +            } else {
> +                removeVersionProperties(getNodeBuilder(rootBuilder, this.path));
> +            }
> +        }
> +
> +        return this;
> +    }
> +
> +    private boolean copyVersionHistory(NodeState versionable) {
> +        assert versionable.exists();
> +
> +        final String versionableUuid = versionable.getProperty(JCR_UUID).getValue(Type.STRING);
> +        return versionCopier.copyVersionHistory(versionableUuid, provider.config.getVersionsMinDate());
> +    }
> +
> +    private void setVersionablePath(String versionableUuid) {
> +        final NodeBuilder versionHistory = VersionHistoryUtil.getVersionHistoryBuilder(rootBuilder, versionableUuid);
> +        versionHistory.setProperty(provider.workspaceName, path, Type.PATH);
> +        addMixin(versionHistory, MIX_REP_VERSIONABLE_PATHS);
> +    }
> +
> +    private boolean isVersionHistoryExists(String versionableUuid) {
> +        return VersionHistoryUtil.getVersionHistoryNodeState(rootBuilder.getNodeState(), versionableUuid).exists();
> +    }
> +
> +    private void removeVersionProperties(final NodeBuilder versionableBuilder) {
> +        assert versionableBuilder.exists();
> +
> +        removeMixin(versionableBuilder, MIX_VERSIONABLE);
> +
> +        // we don't know if the UUID is otherwise referenced,
> +        // so make sure the node remains referencable
> +        if (!isReferenceable.apply(versionableBuilder.getNodeState())) {
> +            addMixin(versionableBuilder, MIX_REFERENCEABLE);
> +        }
> +
> +        versionableBuilder.removeProperty(JCR_VERSIONHISTORY);
> +        versionableBuilder.removeProperty(JCR_PREDECESSORS);
> +        versionableBuilder.removeProperty(JCR_BASEVERSION);
> +        versionableBuilder.removeProperty(JCR_ISCHECKEDOUT);
> +    }
> +
> +    @Override
> +    public Editor childNodeChanged(String name, NodeState before, NodeState after) throws CommitFailedException {
> +        return childNodeAdded(name, after);
> +    }
> +
> +    @Override
> +    public Editor childNodeDeleted(String name, NodeState before) throws CommitFailedException {
> +        return childNodeAdded(name, null);
> +    }
> +
> +    @Override
> +    public void leave(NodeState before, NodeState after) throws CommitFailedException {
> +        this.path = PathUtils.getParentPath(this.path);
> +    }
> +
> +    private static <T> T getProperty(NodeState state, String name, Type<T> type) {
> +        if (state.hasProperty(name)) {
> +            return state.getProperty(name).getValue(type);
> +        }
> +        return null;
> +    }
> +
> +    private static NodeBuilder getNodeBuilder(NodeBuilder root, String path) {
> +        NodeBuilder builder = root;
> +        for (String name : PathUtils.elements(path)) {
> +            builder = builder.getChildNode(name);
> +        }
> +        return builder;
> +    }
> +
> +    private static void addMixin(NodeBuilder builder, String name) {
> +        if (builder.hasProperty(JCR_MIXINTYPES)) {
> +            final Set<String> mixins = newHashSet(builder.getProperty(JCR_MIXINTYPES).getValue(Type.NAMES));
> +            if (mixins.add(name)) {
> +                builder.setProperty(nameProperty(JCR_MIXINTYPES, mixins));
> +            }
> +        } else {
> +            builder.setProperty(nameProperty(JCR_MIXINTYPES, of(name)));
> +        }
> +    }
> +
> +    private static void removeMixin(NodeBuilder builder, String name) {
> +        if (builder.hasProperty(JCR_MIXINTYPES)) {
> +            final Set<String> mixins = newHashSet(builder.getProperty(JCR_MIXINTYPES).getValue(Type.NAMES));
> +            if (mixins.remove(name)) {
> +                if (mixins.isEmpty()) {
> +                    builder.removeProperty(JCR_MIXINTYPES);
> +                } else {
> +                    builder.setProperty(nameProperty(JCR_MIXINTYPES, mixins));
> +                }
> +            }
> +        }
> +    }
> +}
>
> Modified: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java?rev=1694498&r1=1694497&r2=1694498&view=diff
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java (original)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/AbstractRepositoryUpgradeTest.java Thu Aug  6 13:57:58 2015
> @@ -47,7 +47,7 @@ import static org.junit.Assert.assertTru
>
>  public abstract class AbstractRepositoryUpgradeTest {
>
> -    protected static final Credentials CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray());
> +    public static final Credentials CREDENTIALS = new SimpleCredentials("admin", "admin".toCharArray());
>
>      private static NodeStore targetNodeStore;
>
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/CopyVersionHistoryTest.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,277 @@
> +/*
> + * 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.upgrade;
> +
> +import org.apache.jackrabbit.core.RepositoryContext;
> +import org.apache.jackrabbit.core.config.RepositoryConfig;
> +import org.apache.jackrabbit.oak.Oak;
> +import org.apache.jackrabbit.oak.jcr.Jcr;
> +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
> +import org.apache.jackrabbit.oak.spi.state.NodeStore;
> +import org.apache.jackrabbit.oak.upgrade.util.VersionCopyTestUtils.RepositoryUpgradeSetup;
> +import org.junit.AfterClass;
> +import org.junit.Test;
> +
> +import javax.jcr.Node;
> +import javax.jcr.Property;
> +import javax.jcr.PropertyType;
> +import javax.jcr.Repository;
> +import javax.jcr.RepositoryException;
> +import javax.jcr.Session;
> +import javax.jcr.version.VersionManager;
> +
> +import java.io.File;
> +import java.util.Calendar;
> +
> +import static org.junit.Assert.assertEquals;
> +import static org.junit.Assert.assertFalse;
> +import static org.junit.Assert.assertTrue;
> +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
> +import static org.apache.jackrabbit.oak.upgrade.util.VersionCopyTestUtils.createVersionableNode;
> +import static org.apache.jackrabbit.oak.upgrade.util.VersionCopyTestUtils.isVersionable;
> +
> +public class CopyVersionHistoryTest extends AbstractRepositoryUpgradeTest {
> +
> +    private static final String VERSIONABLES_OLD = "/versionables/old";
> +
> +    private static final String VERSIONABLES_OLD_ORPHANED = "/versionables/oldOrphaned";
> +
> +    private static final String VERSIONABLES_YOUNG = "/versionables/young";
> +
> +    private static final String VERSIONABLES_YOUNG_ORPHANED = "/versionables/youngOrphaned";
> +
> +    private static Calendar betweenHistories;
> +
> +    /**
> +     * Home directory of source repository.
> +     */
> +    private static File source;
> +
> +    private static String oldOrphanedHistory;
> +    private static String youngOrphanedHistory;
> +    private static String oldHistory;
> +    private static String youngHistory;
> +
> +    @Override
> +    protected void createSourceContent(Repository repository) throws Exception {
> +        final Session session = repository.login(CREDENTIALS);
> +
> +        oldHistory = createVersionableNode(session, VERSIONABLES_OLD);
> +        oldOrphanedHistory = createVersionableNode(session, VERSIONABLES_OLD_ORPHANED);
> +        Thread.sleep(10);
> +        betweenHistories = Calendar.getInstance();
> +        Thread.sleep(10);
> +        youngOrphanedHistory = createVersionableNode(session, VERSIONABLES_YOUNG_ORPHANED);
> +        youngHistory = createVersionableNode(session, VERSIONABLES_YOUNG);
> +
> +        session.getNode(VERSIONABLES_OLD_ORPHANED).remove();
> +        session.getNode(VERSIONABLES_YOUNG_ORPHANED).remove();
> +        session.save();
> +    }
> +
> +    @Override
> +    protected void doUpgradeRepository(File source, NodeStore target) throws RepositoryException {
> +        // abuse this method to capture the source repo directory
> +        CopyVersionHistoryTest.source = source;
> +    }
> +
> +    @AfterClass
> +    public static void teardown() {
> +        CopyVersionHistoryTest.source = null;
> +    }
> +
> +    @Test
> +    public void copyAllVersions() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                // copying all versions is enabled by default
> +            }
> +        });
> +        assertTrue(isVersionable(session, VERSIONABLES_OLD));
> +        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertExisting(session, oldOrphanedHistory, youngOrphanedHistory, oldHistory, youngHistory);
> +        assertHasVersionablePath(session, oldHistory, youngHistory);
> +    }
> +
> +    @Test
> +    public void referencedSinceDate() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyVersions(betweenHistories);
> +            }
> +        });
> +
> +        assertFalse(isVersionable(session, VERSIONABLES_OLD));
> +        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldHistory, oldOrphanedHistory);
> +        assertExisting(session, youngHistory, youngOrphanedHistory);
> +        assertHasVersionablePath(session, youngHistory);
> +    }
> +
> +    @Test
> +    public void referencedOlderThanOrphaned() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyOrphanedVersions(betweenHistories);
> +            }
> +        });
> +
> +        assertTrue(isVersionable(session, VERSIONABLES_OLD));
> +        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldOrphanedHistory);
> +        assertExisting(session, oldHistory, youngHistory, youngOrphanedHistory);
> +        assertHasVersionablePath(session, oldHistory, youngHistory);
> +    }
> +
> +    @Test
> +    public void onlyReferenced() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyOrphanedVersions(null);
> +            }
> +        });
> +        assertTrue(isVersionable(session, VERSIONABLES_OLD));
> +        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldOrphanedHistory, youngOrphanedHistory);
> +        assertExisting(session, oldHistory, youngHistory);
> +        assertHasVersionablePath(session, oldHistory, youngHistory);
> +    }
> +
> +    @Test
> +    public void onlyReferencedAfterDate() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyVersions(betweenHistories);
> +                upgrade.setCopyOrphanedVersions(null);
> +            }
> +        });
> +        assertFalse(isVersionable(session, VERSIONABLES_OLD));
> +        assertTrue(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldHistory, oldOrphanedHistory, youngOrphanedHistory);
> +        assertExisting(session, youngHistory);
> +        assertHasVersionablePath(session, youngHistory);
> +    }
> +
> +    @Test
> +    public void onlyOrphaned() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyVersions(null);
> +            }
> +        });
> +
> +        assertFalse(isVersionable(session, VERSIONABLES_OLD));
> +        assertFalse(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldHistory, youngHistory, oldOrphanedHistory, youngOrphanedHistory);
> +    }
> +
> +    @Test
> +    public void onlyOrphanedAfterDate() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyVersions(null);
> +                upgrade.setCopyOrphanedVersions(betweenHistories);
> +            }
> +        });
> +
> +        assertFalse(isVersionable(session, VERSIONABLES_OLD));
> +        assertFalse(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldHistory, youngHistory, oldOrphanedHistory, youngOrphanedHistory);
> +    }
> +
> +    @Test
> +    public void dontCopyVersionHistory() throws RepositoryException {
> +        assert source != null;
> +
> +        Session session = performCopy(source, new RepositoryUpgradeSetup() {
> +            @Override
> +            public void setup(RepositoryUpgrade upgrade) {
> +                upgrade.setCopyVersions(null);
> +                upgrade.setCopyOrphanedVersions(null);
> +            }
> +        });
> +
> +        assertFalse(isVersionable(session, VERSIONABLES_OLD));
> +        assertFalse(isVersionable(session, VERSIONABLES_YOUNG));
> +        assertMissing(session, oldHistory, youngHistory, oldOrphanedHistory, youngOrphanedHistory);
> +    }
> +
> +    public Session performCopy(File source, RepositoryUpgradeSetup setup) throws RepositoryException {
> +        final RepositoryConfig sourceConfig = RepositoryConfig.create(source);
> +        final RepositoryContext sourceContext = RepositoryContext.create(sourceConfig);
> +        final NodeStore targetNodeStore = new MemoryNodeStore();
> +        try {
> +            final RepositoryUpgrade upgrade = new RepositoryUpgrade(sourceContext, targetNodeStore);
> +            setup.setup(upgrade);
> +            upgrade.copy(null);
> +        } finally {
> +            sourceContext.getRepository().shutdown();
> +        }
> +
> +        final Repository repository = new Jcr(new Oak(targetNodeStore)).createRepository();
> +        return repository.login(AbstractRepositoryUpgradeTest.CREDENTIALS);
> +    }
> +
> +    private static void assertExisting(final Session session, final String... paths) throws RepositoryException {
> +        for (final String path : paths) {
> +            final String relPath = path.substring(1);
> +            assertTrue("node " + path + " should exist", session.getRootNode().hasNode(relPath));
> +        }
> +    }
> +
> +    private static void assertMissing(final Session session, final String... paths) throws RepositoryException {
> +        for (final String path : paths) {
> +            final String relPath = path.substring(1);
> +            assertFalse("node " + path + " should not exist", session.getRootNode().hasNode(relPath));
> +        }
> +    }
> +
> +    public static void assertHasVersionablePath(final Session session, final String... historyPaths) throws RepositoryException {
> +        for (String historyPath : historyPaths) {
> +            final String workspaceName = session.getWorkspace().getName();
> +            final Node versionHistory = session.getNode(historyPath);
> +            assertTrue(versionHistory.isNodeType(MIX_REP_VERSIONABLE_PATHS));
> +            assertTrue(versionHistory.hasProperty(workspaceName));
> +            final Property pathProperty = versionHistory.getProperty(workspaceName);
> +            assertEquals(PropertyType.PATH, pathProperty.getType());
> +
> +            final VersionManager vm = session.getWorkspace().getVersionManager();
> +            assertEquals(historyPath, vm.getVersionHistory(pathProperty.getString()).getPath());
> +        }
> +    }
> +}
>
> Added: jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java
> URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java?rev=1694498&view=auto
> ==============================================================================
> --- jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java (added)
> +++ jackrabbit/oak/trunk/oak-upgrade/src/test/java/org/apache/jackrabbit/oak/upgrade/util/VersionCopyTestUtils.java Thu Aug  6 13:57:58 2015
> @@ -0,0 +1,57 @@
> +package org.apache.jackrabbit.oak.upgrade.util;
> +
> +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.MIX_REP_VERSIONABLE_PATHS;
> +import static org.junit.Assert.assertEquals;
> +import static org.junit.Assert.assertTrue;
> +
> +import java.util.ArrayList;
> +import java.util.List;
> +
> +import javax.jcr.Node;
> +import javax.jcr.Property;
> +import javax.jcr.PropertyType;
> +import javax.jcr.RepositoryException;
> +import javax.jcr.Session;
> +import javax.jcr.version.Version;
> +import javax.jcr.version.VersionHistory;
> +import javax.jcr.version.VersionManager;
> +
> +import org.apache.jackrabbit.JcrConstants;
> +import org.apache.jackrabbit.commons.JcrUtils;
> +import org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade;
> +
> +public class VersionCopyTestUtils {
> +
> +    public static String createVersionableNode(Session session, String versionablePath)
> +            throws RepositoryException, InterruptedException {
> +        final VersionManager versionManager = session.getWorkspace().getVersionManager();
> +        final Node versionable = JcrUtils.getOrCreateUniqueByPath(session.getRootNode(), versionablePath,
> +                JcrConstants.NT_UNSTRUCTURED);
> +        versionable.addMixin("mix:versionable");
> +        versionable.setProperty("version", "root");
> +        session.save();
> +
> +        final String path = versionable.getPath();
> +        final List<String> versionNames = new ArrayList<String>();
> +        for (int i = 0; i < 3; i++) {
> +            versionable.setProperty("version", "1." + i);
> +            session.save();
> +            final Version v = versionManager.checkpoint(path);
> +            versionNames.add(v.getName());
> +        }
> +
> +        final VersionHistory history = versionManager.getVersionHistory(path);
> +        for (final String versionName : versionNames) {
> +            history.addVersionLabel(versionName, String.format("version %s", versionName), false);
> +        }
> +        return history.getPath();
> +    }
> +
> +    public static boolean isVersionable(Session session, String path) throws RepositoryException {
> +        return session.getNode(path).isNodeType(JcrConstants.MIX_VERSIONABLE);
> +    }
> +
> +    public interface RepositoryUpgradeSetup {
> +        void setup(RepositoryUpgrade upgrade);
> +    }
> +}
>
>