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, <bae...@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 > (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); > + } > +} > >