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 (jsedd...@gmail.com) for the patch and to Tomek Rekawek (treka...@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); + } +}