Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/MigrationOptions.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/MigrationOptions.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/MigrationOptions.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/MigrationOptions.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,385 @@
+/*
+ * 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.cli.parser;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MigrationOptions {
+
+    private static final Logger log = 
LoggerFactory.getLogger(MigrationOptions.class);
+
+    private static final DateFormat DATE_FORMAT = new 
SimpleDateFormat("yyyy-MM-dd");
+
+    private final boolean copyBinaries;
+
+    private final boolean disableMmap;
+
+    private final int cacheSizeInMB;
+
+    private final Calendar copyVersions;
+
+    private final Calendar copyOrphanedVersions;
+
+    private final String[] includePaths;
+
+    private final String[] excludePaths;
+
+    private final String[] mergePaths;
+
+    private final boolean failOnError;
+
+    private final boolean earlyShutdown;
+
+    private final boolean skipInitialization;
+
+    private final boolean skipNameCheck;
+
+    private final boolean ignoreMissingBinaries;
+
+    private final boolean verify;
+
+    private final boolean onlyVerify;
+
+    private final boolean skipCheckpoints;
+
+    private final String srcUser;
+
+    private final String srcPassword;
+
+    private final String dstUser;
+
+    private final String dstPassword;
+
+    private final String srcFbs;
+
+    private final String srcFds;
+
+    private final String srcS3Config;
+
+    private final String srcS3;
+
+    private final String dstFbs;
+
+    private final String dstFds;
+
+    private final String dstS3Config;
+
+    private final String dstS3;
+
+    private final Boolean srcExternalBlobs;
+
+    public MigrationOptions(MigrationCliArguments args) throws 
CliArgumentException {
+        this.disableMmap = args.hasOption(OptionParserFactory.DISABLE_MMAP);
+        this.copyBinaries = args.hasOption(OptionParserFactory.COPY_BINARIES);
+        if (args.hasOption(OptionParserFactory.CACHE_SIZE)) {
+            this.cacheSizeInMB = 
args.getIntOption(OptionParserFactory.CACHE_SIZE);
+        } else {
+            this.cacheSizeInMB = 256;
+        }
+
+        final Calendar epoch = Calendar.getInstance();
+        epoch.setTimeInMillis(0);
+        if (args.hasOption(OptionParserFactory.COPY_VERSIONS)) {
+            this.copyVersions = 
parseVersionCopyArgument(args.getOption(OptionParserFactory.COPY_VERSIONS));
+        } else {
+            this.copyVersions = epoch;
+        }
+        if (args.hasOption(OptionParserFactory.COPY_ORPHANED_VERSIONS)) {
+            this.copyOrphanedVersions = 
parseVersionCopyArgument(args.getOption(OptionParserFactory.COPY_ORPHANED_VERSIONS));
+        } else {
+            this.copyOrphanedVersions = epoch;
+        }
+        this.includePaths = 
checkPaths(args.getOptionList(OptionParserFactory.INCLUDE_PATHS));
+        this.excludePaths = 
checkPaths(args.getOptionList(OptionParserFactory.EXCLUDE_PATHS));
+        this.mergePaths = 
checkPaths(args.getOptionList(OptionParserFactory.MERGE_PATHS));
+        this.failOnError = args.hasOption(OptionParserFactory.FAIL_ON_ERROR);
+        this.earlyShutdown = 
args.hasOption(OptionParserFactory.EARLY_SHUTDOWN);
+        this.skipInitialization = 
args.hasOption(OptionParserFactory.SKIP_INIT);
+        this.skipNameCheck = 
args.hasOption(OptionParserFactory.SKIP_NAME_CHECK);
+        this.ignoreMissingBinaries = 
args.hasOption(OptionParserFactory.IGNORE_MISSING_BINARIES);
+        this.verify = args.hasOption(OptionParserFactory.VERIFY);
+        this.onlyVerify = args.hasOption(OptionParserFactory.ONLY_VERIFY);
+        this.skipCheckpoints = 
args.hasOption(OptionParserFactory.SKIP_CHECKPOINTS);
+
+        this.srcUser = args.getOption(OptionParserFactory.SRC_USER);
+        this.srcPassword = args.getOption(OptionParserFactory.SRC_USER);
+        this.dstUser = args.getOption(OptionParserFactory.DST_USER);
+        this.dstPassword = args.getOption(OptionParserFactory.DST_PASSWORD);
+
+        this.srcFbs = args.getOption(OptionParserFactory.SRC_FBS);
+        this.srcFds = args.getOption(OptionParserFactory.SRC_FDS);
+        this.srcS3 = args.getOption(OptionParserFactory.SRC_S3);
+        this.srcS3Config = args.getOption(OptionParserFactory.SRC_S3_CONFIG);
+
+        this.dstFbs = args.getOption(OptionParserFactory.DST_FBS);
+        this.dstFds = args.getOption(OptionParserFactory.DST_FDS);
+        this.dstS3 = args.getOption(OptionParserFactory.DST_S3);
+        this.dstS3Config = args.getOption(OptionParserFactory.DST_S3_CONFIG);
+
+        if (args.hasOption(OptionParserFactory.SRC_EXTERNAL_BLOBS)) {
+            this.srcExternalBlobs = Boolean
+                    .valueOf(OptionParserFactory.SRC_EXTERNAL_BLOBS);
+        } else {
+            this.srcExternalBlobs = null;
+        }
+    }
+
+    public boolean isCopyBinaries() {
+        return copyBinaries;
+    }
+
+    public boolean isDisableMmap() {
+        return disableMmap;
+    }
+
+    public int getCacheSizeInMB() {
+        return cacheSizeInMB;
+    }
+
+    public Calendar getCopyVersions() {
+        return copyVersions;
+    }
+
+    public Calendar getCopyOrphanedVersions() {
+        return copyOrphanedVersions;
+    }
+
+    public String[] getIncludePaths() {
+        return includePaths;
+    }
+
+    public String[] getExcludePaths() {
+        return excludePaths;
+    }
+
+    public String[] getMergePaths() {
+        return mergePaths;
+    }
+
+    public boolean isFailOnError() {
+        return failOnError;
+    }
+
+    public boolean isEarlyShutdown() {
+        return earlyShutdown;
+    }
+
+    public boolean isSkipInitialization() {
+        return skipInitialization;
+    }
+
+    public boolean isSkipNameCheck() {
+        return skipNameCheck;
+    }
+
+    public boolean isIgnoreMissingBinaries() {
+        return ignoreMissingBinaries;
+    }
+
+    public boolean isVerify() {
+        return verify;
+    }
+
+    public boolean isOnlyVerify() {
+        return onlyVerify;
+    }
+
+    public boolean isSkipCheckpoints() {
+        return skipCheckpoints;
+    }
+
+    public String getSrcUser() {
+        return srcUser;
+    }
+
+    public String getSrcPassword() {
+        return srcPassword;
+    }
+
+    public String getDstUser() {
+        return dstUser;
+    }
+
+    public String getDstPassword() {
+        return dstPassword;
+    }
+
+    public String getSrcFbs() {
+        return srcFbs;
+    }
+
+    public String getSrcFds() {
+        return srcFds;
+    }
+
+    public String getSrcS3Config() {
+        return srcS3Config;
+    }
+
+    public String getSrcS3() {
+        return srcS3;
+    }
+
+    public String getDstFbs() {
+        return dstFbs;
+    }
+
+    public String getDstFds() {
+        return dstFds;
+    }
+
+    public String getDstS3Config() {
+        return dstS3Config;
+    }
+
+    public String getDstS3() {
+        return dstS3;
+    }
+
+    public boolean isSrcFds() {
+        return StringUtils.isNotBlank(srcFds);
+    }
+
+    public boolean isSrcFbs() {
+        return StringUtils.isNotBlank(srcFbs);
+    }
+
+    public boolean isSrcS3() {
+        return StringUtils.isNotBlank(srcS3) && 
StringUtils.isNotBlank(srcS3Config);
+    }
+
+    public boolean isDstFds() {
+        return StringUtils.isNotBlank(dstFds);
+    }
+
+    public boolean isDstFbs() {
+        return StringUtils.isNotBlank(dstFbs);
+    }
+
+    public boolean isDstS3() {
+        return StringUtils.isNotBlank(dstS3) && 
StringUtils.isNotBlank(dstS3Config);
+    }
+
+    public boolean isSrcBlobStoreDefined() {
+        return isSrcFbs() || isSrcFds() || isSrcS3();
+    }
+
+    public boolean isDstBlobStoreDefined() {
+        return isDstFbs() || isDstFds() || isDstS3();
+    }
+
+    public void logOptions() {
+        if (disableMmap) {
+            log.info("Disabling memory mapped file access for Segment Store");
+        }
+
+        if (copyVersions == null) {
+            log.info("copyVersions parameter set to false");
+        } else {
+            log.info("copyVersions parameter set to {}", 
DATE_FORMAT.format(copyVersions.getTime()));
+        }
+
+        if (copyOrphanedVersions == null) {
+            log.info("copyOrphanedVersions parameter set to false");
+        } else {
+            log.info("copyOrphanedVersions parameter set to {}", 
DATE_FORMAT.format(copyOrphanedVersions.getTime()));
+        }
+
+        if (includePaths != null) {
+            log.info("paths to include: {}", (Object) includePaths);
+        }
+
+        if (excludePaths != null) {
+            log.info("paths to exclude: {}", (Object) excludePaths);
+        }
+
+        if (failOnError) {
+            log.info("Unreadable nodes will cause failure of the entire 
transaction");
+        }
+
+        if (earlyShutdown) {
+            log.info("Source repository would be shutdown post copying of 
nodes");
+        }
+
+        if (skipInitialization) {
+            log.info("The repository initialization will be skipped");
+        }
+
+        if (skipNameCheck) {
+            log.info("Test for long-named nodes will be disabled");
+        }
+
+        if (ignoreMissingBinaries) {
+            log.info("Missing binaries won't break the migration");
+        }
+
+        if (srcExternalBlobs != null) {
+            log.info("Source DataStore external blobs: {}", srcExternalBlobs);
+        }
+
+        if (skipCheckpoints) {
+            log.info("Checkpoints won't be migrated");
+        }
+
+        log.info("Cache size: {} MB", cacheSizeInMB);
+
+    }
+
+    private static Calendar parseVersionCopyArgument(String string) {
+        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 = Calendar.getInstance();
+            try {
+                calendar.setTime(DATE_FORMAT.parse(string));
+            } catch (ParseException e) {
+                return null;
+            }
+        } else {
+            calendar = null;
+        }
+        return calendar;
+    }
+
+    public Boolean getSrcExternalBlobs() {
+        return srcExternalBlobs;
+    }
+
+    private static String[] checkPaths(String[] paths) throws 
CliArgumentException {
+        if (paths == null) {
+            return paths;
+        }
+        for (String p : paths) {
+            if (!PathUtils.isValid(p)) {
+                throw new CliArgumentException("Following path is not valid: " 
+ p, 1);
+            }
+        }
+        return paths;
+    }
+
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/OptionParserFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/OptionParserFactory.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/OptionParserFactory.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/OptionParserFactory.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,157 @@
+/*
+ * 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.cli.parser;
+
+import static java.util.Arrays.asList;
+
+import joptsimple.OptionParser;
+
+public class OptionParserFactory {
+
+    public static final String COPY_BINARIES = "copy-binaries";
+
+    public static final String DISABLE_MMAP = "disable-mmap";
+
+    public static final String FAIL_ON_ERROR = "fail-on-error";
+
+    public static final String IGNORE_MISSING_BINARIES = 
"ignore-missing-binaries";
+
+    public static final String EARLY_SHUTDOWN = "early-shutdown";
+
+    public static final String CACHE_SIZE = "cache";
+
+    public static final String HELP = "help";
+
+    public static final String DST_USER = "user";
+
+    public static final String DST_PASSWORD = "password";
+
+    public static final String SRC_USER = "src-user";
+
+    public static final String SRC_PASSWORD = "src-password";
+
+    public static final String SRC_FBS = "src-fileblobstore";
+
+    public static final String SRC_FDS = "src-datastore";
+
+    public static final String SRC_S3 = "src-s3datastore";
+
+    public static final String SRC_S3_CONFIG = "src-s3config";
+
+    public static final String SRC_EXTERNAL_BLOBS = "src-external-ds";
+
+    public static final String DST_FDS = "datastore";
+
+    public static final String DST_FBS = "fileblobstore";
+
+    public static final String DST_S3 = "s3datastore";
+
+    public static final String DST_S3_CONFIG = "s3config";
+
+    public static final String COPY_VERSIONS = "copy-versions";
+
+    public static final String COPY_ORPHANED_VERSIONS = 
"copy-orphaned-versions";
+
+    public static final String INCLUDE_PATHS = "include-paths";
+
+    public static final String EXCLUDE_PATHS = "exclude-paths";
+
+    public static final String MERGE_PATHS = "merge-paths";
+
+    public static final String SKIP_INIT = "skip-init";
+
+    public static final String SKIP_NAME_CHECK = "skip-name-check";
+
+    public static final String VERIFY = "verify";
+
+    public static final String ONLY_VERIFY = "only-verify";
+
+    public static final String SKIP_CHECKPOINTS = "skip-checkpoints";
+
+    public static OptionParser create() {
+        OptionParser op = new OptionParser();
+        addUsageOptions(op);
+        addBlobOptions(op);
+        addRdbOptions(op);
+        addPathsOptions(op);
+        addVersioningOptions(op);
+        addMiscOptions(op);
+        return op;
+    }
+
+    private static void addUsageOptions(OptionParser op) {
+        op.acceptsAll(asList("h", "?", HELP), "show help").forHelp();
+    }
+
+    private static void addBlobOptions(OptionParser op) {
+        op.accepts(COPY_BINARIES, "Copy binary content. Use this to disable 
use of existing DataStore in new repo");
+        op.accepts(SRC_FDS, "Datastore directory to be used as a source 
FileDataStore").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(SRC_FBS, "Datastore directory to be used as a source 
FileBlobStore").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(SRC_S3, "Datastore directory to be used for the source 
S3").withRequiredArg().ofType(String.class);
+        op.accepts(SRC_S3_CONFIG, "Configuration file for the source 
S3DataStore").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(DST_FDS, "Datastore directory to be used as a target 
FileDataStore").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(DST_FBS, "Datastore directory to be used as a target 
FileBlobStore").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(DST_S3, "Datastore directory to be used for the target 
S3").withRequiredArg().ofType(String.class);
+        op.accepts(DST_S3_CONFIG, "Configuration file for the target 
S3DataStore").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(IGNORE_MISSING_BINARIES, "Don't break the migration if some 
binaries are missing");
+        op.accepts(SRC_EXTERNAL_BLOBS, "Flag specifying if the source Store 
has external references or not");
+    }
+
+    private static void addRdbOptions(OptionParser op) {
+        op.accepts(SRC_USER, "Source rdb 
user").withRequiredArg().ofType(String.class);
+        op.accepts(SRC_PASSWORD, "Source rdb 
password").withRequiredArg().ofType(String.class);
+        op.accepts(DST_USER, "Target rdb 
user").withRequiredArg().ofType(String.class);
+        op.accepts(DST_PASSWORD, "Target rdb 
password").withRequiredArg().ofType(String.class);
+    }
+
+    private static void addPathsOptions(OptionParser op) {
+        op.accepts(INCLUDE_PATHS, "Comma-separated list of paths to include 
during copy.").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(EXCLUDE_PATHS, "Comma-separated list of paths to exclude 
during copy.").withRequiredArg()
+                .ofType(String.class);
+        op.accepts(MERGE_PATHS, "Comma-separated list of paths to merge during 
copy.").withRequiredArg()
+                .ofType(String.class);
+    }
+
+    private static void addVersioningOptions(OptionParser op) {
+        op.accepts(COPY_VERSIONS,
+                "Copy the version storage. Parameters: { true | false | 
yyyy-mm-dd }. Defaults to true.")
+                .withRequiredArg().ofType(String.class);
+        op.accepts(COPY_ORPHANED_VERSIONS,
+                "Allows to skip copying orphaned versions. Parameters: { true 
| false | yyyy-mm-dd }. Defaults to true.")
+                .withRequiredArg().ofType(String.class);
+    }
+
+    private static void addMiscOptions(OptionParser op) {
+        op.accepts(DISABLE_MMAP, "Disable memory mapped file access for 
Segment Store");
+        op.accepts(FAIL_ON_ERROR, "Fail completely if nodes can't be read from 
the source repo");
+        op.accepts(EARLY_SHUTDOWN,
+                "Shutdown the source repository after nodes are copied and 
before the commit hooks are applied");
+        op.accepts(CACHE_SIZE, "Cache size in 
MB").withRequiredArg().ofType(Integer.class).defaultsTo(256);
+        op.accepts(SKIP_INIT, "Skip the repository initialization; only copy 
data");
+        op.accepts(SKIP_NAME_CHECK, "Skip the initial phase of testing node 
name lengths");
+        op.accepts(VERIFY, "After the sidegrade check whether the source 
repository is exactly the same as destination");
+        op.accepts(ONLY_VERIFY, "Performs only --" + VERIFY + ", without 
copying content");
+        op.accepts(SKIP_CHECKPOINTS, "Don't copy checkpoints on the full 
segment->segment migration");
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreArguments.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreArguments.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreArguments.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreArguments.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,264 @@
+/*
+ * 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.cli.parser;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.plugins.segment.SegmentVersion;
+import org.apache.jackrabbit.oak.upgrade.cli.node.StoreFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.jackrabbit.oak.upgrade.cli.parser.StoreType.JCR2_DIR;
+import static 
org.apache.jackrabbit.oak.upgrade.cli.parser.StoreType.JCR2_DIR_XML;
+import static org.apache.jackrabbit.oak.upgrade.cli.parser.StoreType.JCR2_XML;
+import static org.apache.jackrabbit.oak.upgrade.cli.parser.StoreType.SEGMENT;
+import static 
org.apache.jackrabbit.oak.upgrade.cli.parser.StoreType.getMatchingType;
+
+public class StoreArguments {
+
+    private static final String DEFAULT_CRX2_REPO = 
"crx-quickstart/repository";
+
+    private static final String REPOSITORY_XML = "repository.xml";
+
+    private static final Logger log = 
LoggerFactory.getLogger(StoreArguments.class);
+
+    private final MigrationOptions options;
+
+    private final StoreDescriptor src;
+
+    private final StoreDescriptor dst;
+
+    private Boolean srcHasExternalBlobRefs;
+
+    public StoreArguments(MigrationOptions options, List<String> arguments) 
throws CliArgumentException {
+        this.options = options;
+        List<StoreDescriptor> descriptors = createStoreDescriptors(arguments, 
options);
+
+        src = descriptors.get(0);
+        dst = descriptors.get(1);
+
+        if (options.getSrcExternalBlobs() != null) {
+            srcHasExternalBlobRefs = options.getSrcExternalBlobs();
+        }
+    }
+
+    public void logOptions() {
+        log.info("Source: {}", src);
+        log.info("Destination: {}", dst);
+
+        if (dst.getType() == SEGMENT) {
+            logSegmentVersion();
+        }
+    }
+
+    public StoreFactory getSrcStore() {
+        return src.getFactory(MigrationDirection.SRC, options);
+    }
+
+    public StoreFactory getDstStore() {
+        return dst.getFactory(MigrationDirection.DST, options);
+    }
+
+    public StoreType getSrcType() {
+        return src.getType();
+    }
+
+    public StoreType getDstType() {
+        return dst.getType();
+    }
+
+    String getSrcDescriptor() {
+        return src.toString();
+    }
+
+    String getDstDescriptor() {
+        return dst.toString();
+    }
+
+    public boolean isInPlaceUpgrade() {
+        if (src.getType() == JCR2_DIR_XML && dst.getType() == SEGMENT) {
+            return src.getPath().equals(dst.getPath());
+        }
+        return false;
+    }
+
+    public String[] getSrcPaths() {
+        return src.getPaths();
+    }
+
+    public boolean srcUsesEmbeddedDatastore() throws IOException {
+        if (srcHasExternalBlobRefs == null) {
+            srcHasExternalBlobRefs = 
src.getFactory(StoreArguments.MigrationDirection.SRC, 
options).hasExternalBlobReferences();
+        }
+        return !srcHasExternalBlobRefs;
+    }
+
+    private static List<StoreDescriptor> createStoreDescriptors(List<String> 
arguments, MigrationOptions options) throws CliArgumentException {
+        List<StoreDescriptor> descriptors = mapToStoreDescriptors(arguments);
+        mergeCrx2Descriptors(descriptors);
+        addSegmentAsDestination(descriptors);
+        validateDescriptors(descriptors, options);
+        return descriptors;
+    }
+
+    private static List<StoreDescriptor> mapToStoreDescriptors(List<String> 
arguments) throws CliArgumentException {
+        List<StoreDescriptor> descriptors = new ArrayList<StoreDescriptor>();
+        boolean jcr2Dir = false;
+        boolean jcr2Xml = false;
+        for (String argument : arguments) {
+            StoreType type = getMatchingType(argument);
+            if (type == JCR2_DIR) {
+                if (jcr2Dir) {
+                    type = SEGMENT;
+                }
+                jcr2Dir = true;
+            }
+            if (type == JCR2_DIR_XML) {
+                if (jcr2Xml) {
+                    throw new CliArgumentException("Too many repository.xml 
files passed as arguments", 1);
+                }
+                jcr2Xml = true;
+            }
+            descriptors.add(new StoreDescriptor(type, argument));
+        }
+        return descriptors;
+    }
+
+    private static void mergeCrx2Descriptors(List<StoreDescriptor> 
descriptors) {
+        int crx2DirIndex = -1;
+        int crx2XmlIndex = -1;
+        for (int i = 0; i < descriptors.size(); i++) {
+            StoreType type = descriptors.get(i).getType();
+            if (type == JCR2_DIR) {
+                crx2DirIndex = i;
+            } else if (type == JCR2_XML) {
+                crx2XmlIndex = i;
+            }
+        }
+
+        if (crx2DirIndex > -1 || crx2XmlIndex > -1) {
+            String repoDir;
+            if (crx2DirIndex > -1) {
+                repoDir = descriptors.get(crx2DirIndex).getPath();
+                descriptors.set(crx2DirIndex, null);
+            } else {
+                repoDir = DEFAULT_CRX2_REPO;
+            }
+            String repoXml;
+            if (crx2XmlIndex > -1) {
+                repoXml = descriptors.get(crx2XmlIndex).getPath();
+                descriptors.set(crx2XmlIndex, null);
+            } else {
+                repoXml = repoDir + "/" + REPOSITORY_XML;
+            }
+            descriptors.add(0, new StoreDescriptor(JCR2_DIR_XML, repoDir, 
repoXml));
+
+            Iterator<StoreDescriptor> it = descriptors.iterator();
+            while (it.hasNext()) {
+                if (it.next() == null) {
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    private static void addSegmentAsDestination(List<StoreDescriptor> 
descriptors) {
+        if (descriptors.size() == 1) {
+            StoreType type = descriptors.get(0).getType();
+            if (type == JCR2_DIR_XML) {
+                String crx2Dir = descriptors.get(0).getPath();
+                descriptors.add(new StoreDescriptor(SEGMENT, crx2Dir));
+                log.info("In place migration between JCR2 and SegmentNodeStore 
in {}", crx2Dir);
+            }
+        }
+    }
+
+    private static void validateDescriptors(List<StoreDescriptor> descriptors, 
MigrationOptions options) throws CliArgumentException {
+        if (descriptors.size() < 2) {
+            throw new CliArgumentException("Not enough node store arguments: " 
+ descriptors.toString(), 1);
+        } else if (descriptors.size() > 2) {
+            throw new CliArgumentException("Too much node store arguments: " + 
descriptors.toString(), 1);
+        } else if (descriptors.get(1).getType() == JCR2_DIR_XML) {
+            throw new CliArgumentException("Can't use CRX2 as a destination", 
1);
+        }
+        StoreDescriptor src = descriptors.get(0);
+        StoreDescriptor dst = descriptors.get(1);
+        if (src.getType() == dst.getType() && 
src.getPath().equals(dst.getPath())) {
+            throw new CliArgumentException("The source and the destination is 
the same repository.", 1);
+        }
+        if (src.getType() == StoreType.JCR2_DIR_XML && 
options.isSrcBlobStoreDefined()) {
+            throw new CliArgumentException("The --src-datastore can't be used 
for the repository upgrade. Source datastore configuration is placed in the 
repository.xml file.", 1);
+        }
+    }
+
+    private static void logSegmentVersion() {
+        SegmentVersion[] versions = SegmentVersion.values();
+        SegmentVersion lastVersion = versions[versions.length - 1];
+        log.info("Using Oak segment format {} - please make sure your version 
of AEM supports that format",
+                lastVersion);
+        if (lastVersion == SegmentVersion.V_11) {
+            log.info("Requires Oak 1.0.12, 1.1.7 or later");
+        }
+    }
+
+    enum MigrationDirection {
+        SRC, DST
+    }
+
+    private static class StoreDescriptor {
+
+        private final String[] paths;
+
+        private final StoreType type;
+
+        public StoreDescriptor(StoreType type, String... paths) {
+            this.type = type;
+            this.paths = paths;
+        }
+
+        public String[] getPaths() {
+            return paths;
+        }
+
+        public String getPath() {
+            return paths[0];
+        }
+
+        public StoreType getType() {
+            return type;
+        }
+
+        public StoreFactory getFactory(MigrationDirection direction, 
MigrationOptions options) {
+            return type.createFactory(paths, direction, options);
+        }
+
+        @Override
+        public String toString() {
+            if (paths.length == 1) {
+                return String.format("%s[%s]", type, getPath());
+            } else {
+                return String.format("%s%s", type, 
Arrays.toString(getPaths()));
+            }
+        }
+
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,154 @@
+/*
+ * 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.cli.parser;
+
+import static 
org.apache.jackrabbit.oak.upgrade.cli.node.Jackrabbit2Factory.isJcr2Repository;
+import static 
org.apache.jackrabbit.oak.upgrade.cli.node.Jackrabbit2Factory.isRepositoryXml;
+
+import org.apache.jackrabbit.oak.upgrade.cli.node.Jackrabbit2Factory;
+import org.apache.jackrabbit.oak.upgrade.cli.node.JdbcFactory;
+import org.apache.jackrabbit.oak.upgrade.cli.node.MongoFactory;
+import org.apache.jackrabbit.oak.upgrade.cli.node.SegmentFactory;
+import org.apache.jackrabbit.oak.upgrade.cli.node.StoreFactory;
+import 
org.apache.jackrabbit.oak.upgrade.cli.parser.StoreArguments.MigrationDirection;
+
+public enum StoreType {
+    JCR2_XML {
+        @Override
+        public boolean matches(String argument) {
+            return isRepositoryXml(argument);
+        }
+
+        @Override
+        public StoreFactory createFactory(String[] paths, MigrationDirection 
direction, MigrationOptions migrationOptions) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isSupportLongNames() {
+            return true;
+        }
+    },
+    JCR2_DIR {
+        @Override
+        public boolean matches(String argument) {
+            return isJcr2Repository(argument);
+        }
+
+        @Override
+        public StoreFactory createFactory(String[] paths, MigrationDirection 
direction, MigrationOptions migrationOptions) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isSupportLongNames() {
+            return true;
+        }
+    },
+    JCR2_DIR_XML {
+        @Override
+        public boolean matches(String argument) {
+            return false;
+        }
+
+        @Override
+        public StoreFactory createFactory(String[] paths, MigrationDirection 
direction, MigrationOptions migrationOptions) {
+            return new StoreFactory(new Jackrabbit2Factory(paths[0], 
paths[1]));
+        }
+
+        @Override
+        public boolean isSupportLongNames() {
+            return true;
+        }
+    },
+    JDBC {
+        @Override
+        public boolean matches(String argument) {
+            return argument.startsWith("jdbc:");
+        }
+
+        @Override
+        public StoreFactory createFactory(String[] paths, MigrationDirection 
direction, MigrationOptions migrationOptions) {
+            String username, password;
+            if (direction == MigrationDirection.SRC) {
+                username = migrationOptions.getSrcUser();
+                password = migrationOptions.getSrcPassword();
+            } else {
+                username = migrationOptions.getDstUser();
+                password = migrationOptions.getDstPassword();
+            }
+            return new StoreFactory(
+                    new JdbcFactory(paths[0], 
migrationOptions.getCacheSizeInMB(), username, password, direction == 
MigrationDirection.SRC));
+        }
+
+        @Override
+        public boolean isSupportLongNames() {
+            return false;
+        }
+    },
+    MONGO {
+        @Override
+        public boolean matches(String argument) {
+            return argument.startsWith("mongodb://");
+        }
+
+        @Override
+        public StoreFactory createFactory(String[] paths, MigrationDirection 
direction, MigrationOptions migrationOptions) {
+            return new StoreFactory(new MongoFactory(paths[0], 
migrationOptions.getCacheSizeInMB(), direction == MigrationDirection.SRC));
+        }
+
+        @Override
+        public boolean isSupportLongNames() {
+            return false;
+        }
+    },
+    SEGMENT {
+        @Override
+        public boolean matches(String argument) {
+            return true;
+        }
+
+        @Override
+        public StoreFactory createFactory(String[] paths, MigrationDirection 
direction, MigrationOptions migrationOptions) {
+            return new StoreFactory(new SegmentFactory(paths[0], 
migrationOptions.isDisableMmap(), direction == MigrationDirection.SRC));
+        }
+
+        @Override
+        public boolean isSupportLongNames() {
+            return true;
+        }
+    };
+
+    public static StoreType getMatchingType(String argument) {
+        for (StoreType t : values()) {
+            if (t.matches(argument)) {
+                return t;
+            }
+        }
+        return SEGMENT;
+    }
+
+    public abstract boolean matches(String argument);
+
+    public abstract StoreFactory createFactory(String[] paths, 
MigrationDirection direction, MigrationOptions migrationOptions);
+
+    public abstract boolean isSupportLongNames();
+
+    public boolean isSegment() {
+        return this == SEGMENT;
+    }
+}
\ No newline at end of file

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,240 @@
+/*
+ * 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.nodestate;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyStates;
+import org.apache.jackrabbit.oak.spi.state.AbstractNodeState;
+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.spi.state.NodeStateDiff;
+import org.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import static com.google.common.base.Predicates.notNull;
+import static 
org.apache.jackrabbit.oak.plugins.tree.TreeConstants.OAK_CHILD_ORDER;
+
+public abstract class AbstractDecoratedNodeState extends AbstractNodeState {
+
+    protected final NodeState delegate;
+
+    protected AbstractDecoratedNodeState(@Nonnull final NodeState delegate) {
+        this.delegate = delegate;
+    }
+
+    protected boolean hideChild(@Nonnull final String name, @Nonnull final 
NodeState delegateChild) {
+        return false;
+    }
+
+    @Nonnull
+    protected abstract NodeState decorateChild(@Nonnull final String name, 
@Nonnull final NodeState delegateChild);
+
+    @Nonnull
+    private NodeState decorate(@Nonnull final String name, @Nonnull final 
NodeState child) {
+        return hideChild(name, child) ? EmptyNodeState.MISSING_NODE : 
decorateChild(name, child);
+    }
+
+    protected boolean hideProperty(@Nonnull final String name) {
+        return false;
+    }
+
+    @CheckForNull
+    protected abstract PropertyState decorateProperty(@Nonnull final 
PropertyState delegatePropertyState);
+
+    @CheckForNull
+    private PropertyState decorate(@Nullable final PropertyState property) {
+        return property == null || hideProperty(property.getName()) ? null : 
decorateProperty(property);
+    }
+
+    /**
+     * Convenience method to help implementations that hide nodes set the
+     * :childOrder (OAK_CHILD_ORDER) property to its correct value.
+     * <br>
+     * Intended to be used to implement {@link 
#decorateProperty(PropertyState)}.
+     *
+     * @param nodeState The current node state.
+     * @param propertyState The property that chould be checked.
+     * @return The original propertyState, unless the property is called 
{@code :childOrder}.
+     */
+    protected static PropertyState fixChildOrderPropertyState(NodeState 
nodeState, PropertyState propertyState) {
+        if (propertyState != null && 
OAK_CHILD_ORDER.equals(propertyState.getName())) {
+            final Collection<String> childNodeNames = new ArrayList<String>();
+            Iterables.addAll(childNodeNames, nodeState.getChildNodeNames());
+            final Iterable<String> values = Iterables.filter(
+                    propertyState.getValue(Type.NAMES), 
Predicates.in(childNodeNames));
+            return PropertyStates.createProperty(OAK_CHILD_ORDER, values, 
Type.NAMES);
+        }
+        return propertyState;
+    }
+
+    /**
+     * The AbstractDecoratedNodeState implementation returns a 
ReadOnlyBuilder, which
+     * will fail for any mutable operation.
+     *
+     * This method can be overridden to return a different NodeBuilder 
implementation.
+     *
+     * @return a NodeBuilder instance corresponding to this NodeState.
+     */
+    @Override
+    @Nonnull
+    public NodeBuilder builder() {
+        return new ReadOnlyBuilder(this);
+    }
+
+    @Override
+    public boolean exists() {
+        return delegate.exists();
+    }
+
+    @Override
+    public boolean hasChildNode(@Nonnull final String name) {
+        return getChildNode(name).exists();
+    }
+
+    @Override
+    @Nonnull
+    public NodeState getChildNode(@Nonnull final String name) throws 
IllegalArgumentException {
+        return decorate(name, delegate.getChildNode(name));
+    }
+
+    @Override
+    @Nonnull
+    public Iterable<? extends ChildNodeEntry> getChildNodeEntries() {
+        final Iterable<ChildNodeEntry> transformed = Iterables.transform(
+                delegate.getChildNodeEntries(),
+                new Function<ChildNodeEntry, ChildNodeEntry>() {
+                    @Nullable
+                    @Override
+                    public ChildNodeEntry apply(@Nullable final ChildNodeEntry 
childNodeEntry) {
+                        if (childNodeEntry != null) {
+                            final String name = childNodeEntry.getName();
+                            final NodeState nodeState = decorate(name, 
childNodeEntry.getNodeState());
+                            if (nodeState.exists()) {
+                                return new MemoryChildNodeEntry(name, 
nodeState);
+                            }
+                        }
+                        return null;
+                    }
+                }
+        );
+        return Iterables.filter(transformed, notNull());
+    }
+
+    @Override
+    @CheckForNull
+    public PropertyState getProperty(@Nonnull String name) {
+        return decorate(delegate.getProperty(name));
+    }
+
+    @Override
+    @Nonnull
+    public Iterable<? extends PropertyState> getProperties() {
+        final Iterable<PropertyState> propertyStates = Iterables.transform(
+                delegate.getProperties(),
+                new Function<PropertyState, PropertyState>() {
+                    @Override
+                    @CheckForNull
+                    public PropertyState apply(@Nullable final PropertyState 
propertyState) {
+                        return decorate(propertyState);
+                    }
+                }
+        );
+        return Iterables.filter(propertyStates, notNull());
+    }
+
+    /**
+     * Note that any implementation-specific optimizations of wrapped 
NodeStates
+     * will not work if a AbstractDecoratedNodeState is passed into their 
{@code #equals()}
+     * method. This implementation will compare the wrapped NodeState, 
however. So
+     * optimizations work when calling {@code #equals()} on a 
ReportingNodeState.
+     *
+     * @param other Object to compare with this NodeState.
+     * @return true if the given object is equal to this NodeState, false 
otherwise.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other == null) {
+            return false;
+        }
+
+        if (this.getClass() == other.getClass()) {
+            final AbstractDecoratedNodeState o = (AbstractDecoratedNodeState) 
other;
+            return delegate.equals(o.delegate);
+        }
+
+        return delegate.equals(other);
+    }
+
+    @Override
+    public boolean compareAgainstBaseState(final NodeState base, final 
NodeStateDiff diff) {
+        return AbstractNodeState.compareAgainstBaseState(this, base, new 
DecoratingDiff(diff, this));
+    }
+
+    private static class DecoratingDiff implements NodeStateDiff {
+
+        private final NodeStateDiff diff;
+
+        private AbstractDecoratedNodeState nodeState;
+
+        private DecoratingDiff(final NodeStateDiff diff, final 
AbstractDecoratedNodeState nodeState) {
+            this.diff = diff;
+            this.nodeState = nodeState;
+        }
+
+        @Override
+        public boolean childNodeAdded(final String name, final NodeState 
after) {
+            return diff.childNodeAdded(name, nodeState.decorate(name, after));
+        }
+
+        @Override
+        public boolean childNodeChanged(final String name, final NodeState 
before, final NodeState after) {
+            return diff.childNodeChanged(name, before, 
nodeState.decorate(name, after));
+        }
+
+        @Override
+        public boolean childNodeDeleted(final String name, final NodeState 
before) {
+            return diff.childNodeDeleted(name, before);
+        }
+
+        @Override
+        public boolean propertyAdded(final PropertyState after) {
+            return diff.propertyAdded(nodeState.decorate(after));
+        }
+
+        @Override
+        public boolean propertyChanged(final PropertyState before, final 
PropertyState after) {
+            return diff.propertyChanged(nodeState.decorate(before), 
nodeState.decorate(after));
+        }
+
+        @Override
+        public boolean propertyDeleted(final PropertyState before) {
+            return diff.propertyDeleted(nodeState.decorate(before));
+        }
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,245 @@
+/*
+ * 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.nodestate;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Set;
+
+/**
+ * NodeState implementation that decorates another node-state instance
+ * in order to hide subtrees or partial subtrees from the consumer of
+ * the API.
+ * <br>
+ * The set of visible subtrees is defined by two parameters: include paths
+ * and exclude paths, both of which are sets of absolute paths.
+ * <br>
+ * Any paths that are equal or are descendants of one of the
+ * <b>excluded paths</b> are hidden by this implementation.
+ * <br>
+ * For all <b>included paths</b>, the direct ancestors, the node-state at
+ * the path itself and all descendants are visible. Any siblings of the
+ * defined path or its ancestors are implicitly hidden (unless made visible
+ * by another include path).
+ * <br>
+ * The implementation delegates to the decorated node-state instance and
+ * filters out hidden node-states in the following methods:
+ * <ul>
+ *     <li>{@link #exists()}</li>
+ *     <li>{@link #hasChildNode(String)}</li>
+ *     <li>{@link #getChildNodeEntries()}</li>
+ * </ul>
+ * Additionally, hidden node-state names are removed from the property
+ * {@code :childOrder} in the following two methods:
+ * <ul>
+ *     <li>{@link #getProperties()}</li>
+ *     <li>{@link #getProperty(String)}</li>
+ * </ul>
+ */
+public class FilteringNodeState extends AbstractDecoratedNodeState {
+
+    public static final Set<String> ALL = ImmutableSet.of("/");
+
+    public static final Set<String> NONE = ImmutableSet.of();
+
+    private final String path;
+
+    private final Set<String> includedPaths;
+
+    private final Set<String> excludedPaths;
+
+    /**
+     * Factory method that conditionally decorates the given node-state
+     * iff the node-state is (a) hidden itself or (b) has hidden descendants.
+     *
+     * @param path The path where the node-state should be assumed to be 
located.
+     * @param delegate The node-state to decorate.
+     * @param includePaths A Set of paths that should be visible. Defaults to 
["/"] if {@code null}.
+     * @param excludePaths A Set of paths that should be hidden. Empty if 
{@code null}.
+     * @return The decorated node-state if required, the original node-state 
if decoration is unnecessary.
+     */
+    @Nonnull
+    public static NodeState wrap(
+            @Nonnull final String path,
+            @Nonnull final NodeState delegate,
+            @Nullable final Set<String> includePaths,
+            @Nullable final Set<String> excludePaths
+    ) {
+        final Set<String> includes = defaultIfEmpty(includePaths, ALL);
+        final Set<String> excludes = defaultIfEmpty(excludePaths, NONE);
+        if (hasHiddenDescendants(path, includes, excludes)) {
+            return new FilteringNodeState(path, delegate, includes, excludes);
+        }
+        return delegate;
+    }
+
+    private FilteringNodeState(
+            @Nonnull final String path,
+            @Nonnull final NodeState delegate,
+            @Nonnull final Set<String> includedPaths,
+            @Nonnull final Set<String> excludedPaths
+    ) {
+        super(delegate);
+        this.path = path;
+        this.includedPaths = includedPaths;
+        this.excludedPaths = excludedPaths;
+    }
+
+    @Nonnull
+    @Override
+    protected NodeState decorateChild(@Nonnull final String name, @Nonnull 
final NodeState child) {
+        final String childPath = PathUtils.concat(path, name);
+        return wrap(childPath, child, includedPaths, excludedPaths);
+    }
+
+    @Override
+    protected boolean hideChild(@Nonnull final String name, @Nonnull final 
NodeState delegateChild) {
+        return isHidden(PathUtils.concat(path, name), includedPaths, 
excludedPaths);
+    }
+
+    @Override
+    protected PropertyState decorateProperty(@Nonnull final PropertyState 
propertyState) {
+        return fixChildOrderPropertyState(this, propertyState);
+    }
+
+    /**
+     * Utility method to determine whether a given path should is hidden given 
the
+     * include paths and exclude paths.
+     *
+     * @param path Path to be checked
+     * @param includes Include paths
+     * @param excludes Exclude paths
+     * @return Whether the {@code path} is hidden or not.
+     */
+    private static boolean isHidden(
+            @Nonnull final String path,
+            @Nonnull final Set<String> includes,
+            @Nonnull final Set<String> excludes
+    ) {
+        return isExcluded(path, excludes) || !isIncluded(path, includes);
+    }
+
+    /**
+     * Utility method to determine whether the path itself or any of its 
descendants should
+     * be hidden given the include paths and exclude paths.
+     *
+     * @param path Path to be checked
+     * @param includePaths Include paths
+     * @param excludePaths Exclude paths
+     * @return Whether the {@code path} or any of its descendants are hidden 
or not.
+     */
+    private static boolean hasHiddenDescendants(
+            @Nonnull final String path,
+            @Nonnull final Set<String> includePaths,
+            @Nonnull final Set<String> excludePaths
+    ) {
+        return isHidden(path, includePaths, excludePaths)
+                || isAncestorOfAnyPath(path, excludePaths)
+                || isAncestorOfAnyPath(path, includePaths);
+    }
+
+    /**
+     * Utility method to check whether a given set of include paths cover the 
given
+     * {@code path}. I.e. whether the path is visible or implicitly hidden due 
to the
+     * lack of a matching include path.
+     * <br>
+     * Note: the ancestors of every include path are considered visible.
+     *
+     * @param path Path to be checked
+     * @param includePaths Include paths
+     * @return Whether the path is covered by the include paths or not.
+     */
+    private static boolean isIncluded(@Nonnull final String path, @Nonnull 
final Set<String> includePaths) {
+        return isAncestorOfAnyPath(path, includePaths)
+                || includePaths.contains(path)
+                || isDescendantOfAnyPath(path, includePaths);
+    }
+
+    /**
+     * Utility method to check whether a given set of exclude paths cover the 
given
+     * {@code path}. I.e. whether the path is hidden due to the presence of a
+     * matching exclude path.
+     *
+     * @param path Path to be checked
+     * @param excludePaths Exclude paths
+     * @return Whether the path is covered by the excldue paths or not.
+     */
+    private static boolean isExcluded(@Nonnull final String path, @Nonnull 
final Set<String> excludePaths) {
+        return excludePaths.contains(path) || isDescendantOfAnyPath(path, 
excludePaths);
+    }
+
+    /**
+     * Utility method to check whether any of the provided {@code paths} is a 
descendant
+     * of the given ancestor path.
+     *
+     * @param ancestor Ancestor path
+     * @param paths Paths that may be descendants of {@code ancestor}.
+     * @return true if {@code paths} contains a descendant of {@code 
ancestor}, false otherwise.
+     */
+    private static boolean isAncestorOfAnyPath(@Nonnull final String ancestor, 
@Nonnull final Set<String> paths) {
+        for (final String p : paths) {
+            if (PathUtils.isAncestor(ancestor, p)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Utility method to check whether any of the provided {@code paths} is an 
ancestor
+     * of the given descendant path.
+     *
+     * @param descendant Descendant path
+     * @param paths Paths that may be ancestors of {@code descendant}.
+     * @return true if {@code paths} contains an ancestor of {@code 
descendant}, false otherwise.
+     */
+    private static boolean isDescendantOfAnyPath(@Nonnull final String 
descendant, @Nonnull final Set<String> paths) {
+        for (final String p : paths) {
+            if (PathUtils.isAncestor(p, descendant)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Utility method to return the given {@code Set} if it is not empty and a 
default Set otherwise.
+     *
+     * @param value Value to check for emptiness
+     * @param defaultValue Default value
+     * @return return the given {@code Set} if it is not empty and a default 
Set otherwise
+     */
+    @Nonnull
+    private static <T> Set<T> defaultIfEmpty(@Nullable Set<T> value, @Nonnull 
Set<T> defaultValue) {
+        return !isEmpty(value) ? value : defaultValue;
+    }
+
+    /**
+     * Utility method to check whether a Set is empty, i.e. null or of size 0.
+     *
+     * @param set The Set to check.
+     * @return true if empty, false otherwise
+     */
+    private static <T> boolean isEmpty(@Nullable final Set<T> set) {
+        return set == null || set.isEmpty();
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,80 @@
+/*
+ * 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.nodestate;
+
+import com.google.common.base.Charsets;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.document.util.Utils;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+
+public class NameFilteringNodeState extends AbstractDecoratedNodeState {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(NameFilteringNodeState.class);
+
+    private static final int NODE_NAME_LIMIT = 150;
+    /**
+     * Max character size in bytes in UTF8 = 4. Therefore if the number of 
characters is smaller
+     * than NODE_NAME_LIMIT / 4 we don't need to count bytes.
+     */
+    private static final int SAFE_NODE_NAME_LENGTH = NODE_NAME_LIMIT / 4;
+
+    public static NodeState wrap(final NodeState delegate) {
+        return new NameFilteringNodeState(delegate);
+    }
+
+    private NameFilteringNodeState(final NodeState delegate) {
+        super(delegate);
+    }
+
+    @Override
+    protected boolean hideChild(@Nonnull final String name, @Nonnull final 
NodeState delegateChild) {
+        if (isNameTooLong(name)) {
+            LOG.warn("Node name '{}' too long. Skipping child of {}", name, 
this);
+            return true;
+        }
+        return super.hideChild(name, delegateChild);
+    }
+
+    @Override
+    @Nonnull
+    protected NodeState decorateChild(@Nonnull final String name, @Nonnull 
final NodeState delegateChild) {
+        return wrap(delegateChild);
+    }
+
+    @Override
+    protected PropertyState decorateProperty(@Nonnull final PropertyState 
delegatePropertyState) {
+        return fixChildOrderPropertyState(this, delegatePropertyState);
+    }
+
+    /**
+     * This method checks whether the name is no longer than the maximum node
+     * name length supported by the DocumentNodeStore.
+     *
+     * @param name
+     *            to check
+     * @return true if the name is longer than {@link Utils#NODE_NAME_LIMIT}
+     */
+    public static boolean isNameTooLong(@Nonnull String name) {
+        // OAK-1589: maximum supported length of name for DocumentNodeStore
+        // is 150 bytes. Skip the sub tree if the the name is too long
+        return name.length() > SAFE_NODE_NAME_LENGTH && 
name.getBytes(Charsets.UTF_8).length > NODE_NAME_LIMIT;
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,425 @@
+/*
+ * 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.nodestate;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+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.spi.state.NodeStateUtils;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.copyOf;
+import static com.google.common.collect.ImmutableSet.of;
+import static java.util.Collections.emptySet;
+
+/**
+ * The NodeStateCopier and NodeStateCopier.Builder classes allow
+ * recursively copying a NodeState to a NodeBuilder.
+ * <br>
+ * The copy algorithm is optimized for copying nodes between two
+ * different NodeStore instances, i.e. where comparing NodeStates
+ * is imprecise and/or expensive.
+ * <br>
+ * The algorithm does a post-order traversal. I.e. it copies
+ * changed leaf-nodes first.
+ * <br>
+ * The work for a traversal without any differences between
+ * {@code source} and {@code target} is equivalent to the single
+ * execution of a naive equals implementation.
+ * <br>
+ * <b>Usage:</b> For most use-cases the Builder API should be
+ * preferred. It allows setting {@code includePaths},
+ * {@code excludePaths} and {@code mergePaths}.
+ * <br>
+ * <b>Include paths:</b> if include paths are set, only these paths
+ * and their sub-trees are copied. Any nodes that are not within the
+ * scope of an include path are <i>implicitly excluded</i>.
+ * <br>
+ * <b>Exclude paths:</b> if exclude paths are set, any nodes matching
+ * or below the excluded path are not copied. If an excluded node does
+ * exist in the target, it is removed (see also merge paths).
+ * <b>Merge paths:</b> if merge paths are set, any nodes matching or
+ * below the merged path will not be deleted from target, even if they
+ * are missing in (or excluded from) the source.
+ */
+public class NodeStateCopier {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(NodeStateCopier.class);
+
+    private final Set<String> includePaths;
+
+    private final Set<String> excludePaths;
+
+    private final Set<String> mergePaths;
+
+    private NodeStateCopier(Set<String> includePaths, Set<String> 
excludePaths, Set<String> mergePaths) {
+        this.includePaths = includePaths;
+        this.excludePaths = excludePaths;
+        this.mergePaths = mergePaths;
+    }
+
+    /**
+     * Create a NodeStateCopier.Builder.
+     *
+     * @return a NodeStateCopier.Builder
+     * @see org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier.Builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Shorthand method to copy one NodeStore to another. The changes in the
+     * target NodeStore are automatically persisted.
+     *
+     * @param source NodeStore to copy from.
+     * @param target NodeStore to copy to.
+     * @return true if the target has been modified
+     * @throws CommitFailedException if the operation fails
+     * @see 
org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier.Builder#copy(NodeStore,
 NodeStore)
+     */
+    public static boolean copyNodeStore(@Nonnull final NodeStore source, 
@Nonnull final NodeStore target)
+            throws CommitFailedException {
+        return builder().copy(checkNotNull(source), checkNotNull(target));
+    }
+
+    /**
+     * Copies all changed properties from the source NodeState to the target
+     * NodeBuilder instance.
+     *
+     * @param source The NodeState to copy from.
+     * @param target The NodeBuilder to copy to.
+     * @return Whether changes were made or not.
+     */
+    public static boolean copyProperties(NodeState source, NodeBuilder target) 
{
+        boolean hasChanges = false;
+
+        // remove removed properties
+        for (final PropertyState property : target.getProperties()) {
+            final String name = property.getName();
+            if (!source.hasProperty(name)) {
+                target.removeProperty(name);
+                hasChanges = true;
+            }
+        }
+
+        // add new properties and change changed properties
+        for (PropertyState property : source.getProperties()) {
+            if (!property.equals(target.getProperty(property.getName()))) {
+                target.setProperty(property);
+                hasChanges = true;
+            }
+        }
+        return hasChanges;
+    }
+
+    private boolean copyNodeState(@Nonnull final NodeState sourceRoot, 
@Nonnull final NodeBuilder targetRoot) {
+        final NodeState wrappedSource = FilteringNodeState.wrap("/", 
sourceRoot, this.includePaths, this.excludePaths);
+        boolean hasChanges = false;
+        for (String includePath : this.includePaths) {
+            hasChanges = copyMissingAncestors(sourceRoot, targetRoot, 
includePath) || hasChanges;
+            final NodeState sourceState = 
NodeStateUtils.getNode(wrappedSource, includePath);
+            if (sourceState.exists()) {
+                final NodeBuilder targetBuilder = 
getChildNodeBuilder(targetRoot, includePath);
+                hasChanges = copyNodeState(sourceState, targetBuilder, 
includePath, this.mergePaths) || hasChanges;
+            }
+        }
+        return hasChanges;
+    }
+
+    /**
+     * Recursively copies the source NodeState to the target NodeBuilder.
+     * <br>
+     * Nodes that exist in the {@code target} but not in the {@code source}
+     * are removed, unless they are descendants of one of the {@code 
mergePaths}.
+     * This is determined by checking if the {@code currentPath} is a 
descendant
+     * of any of the {@code mergePaths}.
+     * <br>
+     * <b>Note:</b> changes are not persisted.
+     *
+     * @param source NodeState to copy from
+     * @param target NodeBuilder to copy to
+     * @param currentPath The path of both the source and target arguments.
+     * @param mergePaths A Set of paths under which existing nodes should be
+     *                   preserved, even if the do not exist in the source.
+     * @return An indication of whether there were changes or not.
+     */
+    private static boolean copyNodeState(@Nonnull final NodeState source, 
@Nonnull final NodeBuilder target,
+                                         @Nonnull final String currentPath, 
@Nonnull final Set<String> mergePaths) {
+
+
+        boolean hasChanges = false;
+
+        // delete deleted children
+        for (final String childName : target.getChildNodeNames()) {
+            if (!source.hasChildNode(childName) && 
!isMerge(PathUtils.concat(currentPath, childName), mergePaths)) {
+                target.setChildNode(childName, EmptyNodeState.MISSING_NODE);
+                hasChanges = true;
+            }
+        }
+
+        for (ChildNodeEntry child : source.getChildNodeEntries()) {
+            final String childName = child.getName();
+            final NodeState childSource = child.getNodeState();
+            if (!target.hasChildNode(childName)) {
+                // add new children
+                target.setChildNode(childName, childSource);
+                hasChanges = true;
+            } else {
+                // recurse into existing children
+                final NodeBuilder childTarget = target.getChildNode(childName);
+                final String childPath = PathUtils.concat(currentPath, 
childName);
+                hasChanges = copyNodeState(childSource, childTarget, 
childPath, mergePaths) || hasChanges;
+            }
+        }
+
+        hasChanges = copyProperties(source, target) || hasChanges;
+
+        if (hasChanges) {
+            LOG.trace("Node {} has changes", target);
+        }
+
+        return hasChanges;
+    }
+
+    private static boolean isMerge(String path, Set<String> mergePaths) {
+        for (String mergePath : mergePaths) {
+            if (PathUtils.isAncestor(mergePath, path) || 
mergePath.equals(path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Ensure that all ancestors of {@code path} are present in {@code 
targetRoot}. Copies any
+     * missing ancestors from {@code sourceRoot}.
+     *
+     * @param sourceRoot NodeState to copy from
+     * @param targetRoot NodeBuilder to copy to
+     * @param path The path along which ancestors should be copied.
+     */
+    private static boolean copyMissingAncestors(
+            final NodeState sourceRoot, final NodeBuilder targetRoot, final 
String path) {
+        NodeState current = sourceRoot;
+        NodeBuilder currentBuilder = targetRoot;
+        boolean hasChanges = false;
+        for (String name : PathUtils.elements(path)) {
+            if (current.hasChildNode(name)) {
+                final boolean targetHasChild = 
currentBuilder.hasChildNode(name);
+                current = current.getChildNode(name);
+                currentBuilder = currentBuilder.child(name);
+                if (!targetHasChild) {
+                    hasChanges = copyProperties(current, currentBuilder) || 
hasChanges;
+                }
+            }
+        }
+        return hasChanges;
+    }
+
+    /**
+     * Allows retrieving a NodeBuilder by path relative to the given root 
NodeBuilder.
+     *
+     * All NodeBuilders are created via {@link NodeBuilder#child(String)} and 
are thus
+     * implicitly created.
+     *
+     * @param root The NodeBuilder to consider the root node.
+     * @param path An absolute or relative path, which is evaluated as a 
relative path under the root NodeBuilder.
+     * @return a NodeBuilder instance, never null
+     */
+    @Nonnull
+    private static NodeBuilder getChildNodeBuilder(@Nonnull final NodeBuilder 
root, @Nonnull final String path) {
+        NodeBuilder child = root;
+        for (String name : PathUtils.elements(path)) {
+            child = child.child(name);
+        }
+        return child;
+    }
+
+    /**
+     * The NodeStateCopier.Builder allows configuring a NodeState copy 
operation with
+     * {@code includePaths}, {@code excludePaths} and {@code mergePaths}.
+     * <br>
+     * <b>Include paths</b> can define which paths should be copied from the 
source to the
+     * target.
+     * <br>
+     * <b>Exclude paths</b> allow restricting which paths should be copied. 
This is
+     * especially useful when there are individual nodes in an included path 
that
+     * should not be copied.
+     * <br>
+     * By default copying will remove items that already exist in the target 
but do
+     * not exist in the source. If this behaviour is undesired that is where 
merge
+     * paths come in.
+     * <br>
+     * <b>Merge paths</b> dictate in which parts of the tree the copy 
operation should
+     * be <i>additive</i>, i.e. the content from source is merged with the 
content
+     * in the target. Nodes that are present in the target but not in the 
source are
+     * then not deleted. However, in the case where nodes are present in both 
the source
+     * and the target, the node from the source is copied with its properties 
and any
+     * properties previously present on the target's node are lost.
+     * <br>
+     * Finally, using one of the {@code copy} methods, NodeStores or 
NodeStates can
+     * be copied.
+     */
+    public static class Builder {
+
+        private Set<String> includePaths = of("/");
+
+        private Set<String> excludePaths = emptySet();
+
+        private Set<String> mergePaths = emptySet();
+
+        private Builder() {}
+
+
+        /**
+         * Set include paths.
+         *
+         * @param paths include paths
+         * @return this Builder instance
+         * @see NodeStateCopier#NodeStateCopier(Set, Set, Set)
+         */
+        @Nonnull
+        public Builder include(@Nonnull Set<String> paths) {
+            if (!checkNotNull(paths).isEmpty()) {
+                this.includePaths = copyOf(paths);
+            }
+            return this;
+        }
+
+        /**
+         * Convenience wrapper for {@link #include(Set)}.
+         *
+         * @param paths include paths
+         * @return this Builder instance
+         * @see NodeStateCopier#NodeStateCopier(Set, Set, Set)
+         */
+        @Nonnull
+        public Builder include(@Nonnull String... paths) {
+            return include(copyOf(checkNotNull(paths)));
+        }
+
+        /**
+         * Set exclude paths.
+         *
+         * @param paths exclude paths
+         * @return this Builder instance
+         * @see NodeStateCopier#NodeStateCopier(Set, Set, Set)
+         */
+        @Nonnull
+        public Builder exclude(@Nonnull Set<String> paths) {
+            if (!checkNotNull(paths).isEmpty()) {
+                this.excludePaths = copyOf(paths);
+            }
+            return this;
+        }
+
+        /**
+         * Convenience wrapper for {@link #exclude(Set)}.
+         *
+         * @param paths exclude paths
+         * @return this Builder instance
+         * @see NodeStateCopier#NodeStateCopier(Set, Set, Set)
+         */
+        @Nonnull
+        public Builder exclude(@Nonnull String... paths) {
+            return exclude(copyOf(checkNotNull(paths)));
+        }
+
+        /**
+         * Set merge paths.
+         *
+         * @param paths merge paths
+         * @return this Builder instance
+         * @see NodeStateCopier#NodeStateCopier(Set, Set, Set)
+         */
+        @Nonnull
+        public Builder merge(@Nonnull Set<String> paths) {
+            if (!checkNotNull(paths).isEmpty()) {
+                this.mergePaths = copyOf(paths);
+            }
+            return this;
+        }
+
+        /**
+         * Convenience wrapper for {@link #merge(Set)}.
+         *
+         * @param paths merge paths
+         * @return this Builder instance
+         * @see NodeStateCopier#NodeStateCopier(Set, Set, Set)
+         */
+        @Nonnull
+        public Builder merge(@Nonnull String... paths) {
+            return merge(copyOf(checkNotNull(paths)));
+        }
+
+        /**
+         * Creates a NodeStateCopier to copy the {@code sourceRoot} NodeState 
to the
+         * {@code targetRoot} NodeBuilder, using any include, exclude and 
merge paths
+         * set on this NodeStateCopier.Builder.
+         * <br>
+         * It is the responsibility of the caller to persist any changes using 
e.g.
+         * {@link NodeStore#merge(NodeBuilder, CommitHook, CommitInfo)}.
+         *
+         * @param sourceRoot NodeState to copy from
+         * @param targetRoot NodeBuilder to copy to
+         * @return true if there were any changes, false if sourceRoot and 
targetRoot represent
+         *         the same content
+         */
+        public boolean copy(@Nonnull final NodeState sourceRoot, @Nonnull 
final NodeBuilder targetRoot) {
+            final NodeStateCopier copier = new NodeStateCopier(includePaths, 
excludePaths, mergePaths);
+            return copier.copyNodeState(checkNotNull(sourceRoot), 
checkNotNull(targetRoot));
+        }
+
+        /**
+         * Creates a NodeStateCopier to copy the {@code source} NodeStore to 
the
+         * {@code target} NodeStore, using any include, exclude and merge paths
+         * set on this NodeStateCopier.Builder.
+         * <br>
+         * Changes are automatically persisted with empty CommitHooks and 
CommitInfo
+         * via {@link NodeStore#merge(NodeBuilder, CommitHook, CommitInfo)}.
+         *
+         * @param source NodeStore to copy from
+         * @param target NodeStore to copy to
+         * @return true if there were any changes, false if source and target 
represent
+         *         the same content
+         * @throws CommitFailedException if the copy operation fails
+         */
+        public boolean copy(@Nonnull final NodeStore source, @Nonnull final 
NodeStore target)
+                throws CommitFailedException {
+            final NodeBuilder targetRoot = 
checkNotNull(target).getRoot().builder();
+            if (copy(checkNotNull(source).getRoot(), targetRoot)) {
+                target.merge(targetRoot, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+                return true;
+            }
+            return false;
+        }
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,75 @@
+/*
+ * 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.nodestate.report;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.slf4j.Logger;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A Reporter implementation that logs every nth node
+ * and/or any nth property to the given logger on {@code info}
+ * level.
+ */
+public class LoggingReporter extends PeriodicReporter {
+
+    private final Logger logger;
+
+    private final String verb;
+
+    /**
+     * Constructor that allows setting the intervals to log node and property
+     * accesses to a given logger.
+     *
+     * @param logger              The logger to log the progress to.
+     * @param nodeLogInterval     Every how many nodes a log message should be 
written.
+     * @param propertyLogInterval Every how many properties a log message 
should be written.
+     */
+    public LoggingReporter(final Logger logger, final int nodeLogInterval, 
final int propertyLogInterval) {
+        this(logger, "Loading", nodeLogInterval, propertyLogInterval);
+    }
+
+    /**
+     * Like {@link #LoggingReporter(Logger, int, int)}, however this 
constructor allow
+     * to customize the verb of the log message.
+     * <br>
+     * The messages are of the format: "{verb} node #100: /path/to/the/node
+     *
+     * @param logger              The logger to log the progress to.
+     * @param verb                The verb to use for logging.
+     * @param nodeLogInterval     Every how many nodes a log message should be 
written.
+     * @param propertyLogInterval Every how many properties a log message 
should be written.
+     */
+    public LoggingReporter(final Logger logger, final String verb, final int 
nodeLogInterval, final int propertyLogInterval) {
+        super(nodeLogInterval, propertyLogInterval);
+        this.logger = logger;
+        this.verb = verb;
+    }
+
+    @Override
+    protected void reportPeriodicNode(final long count, @Nonnull final 
ReportingNodeState nodeState) {
+        logger.info("{} node #{}: {}", verb, count, nodeState.getPath());
+    }
+
+    @Override
+    protected void reportPeriodicProperty(final long count, @Nonnull final 
ReportingNodeState parent, @Nonnull final String propertyName) {
+        logger.info("{} properties #{}: {}", verb, count, 
PathUtils.concat(parent.getPath(), propertyName));
+    }
+}

Added: 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java?rev=1792993&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java
 Fri Apr 28 07:16:13 2017
@@ -0,0 +1,94 @@
+/*
+ * 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.nodestate.report;
+
+import javax.annotation.Nonnull;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Abstract class that simplifies development of a Reporter
+ * that should only report every nth event (node or property seen).
+ */
+public abstract class PeriodicReporter implements Reporter {
+
+    private final int nodeLogInterval;
+
+    private final int propertyLogInterval;
+
+    private AtomicLong nodes = new AtomicLong(0);
+
+    private AtomicLong properties = new AtomicLong(0);
+
+    protected PeriodicReporter(final int nodeLogInterval, final int 
propertyLogInterval) {
+        this.nodeLogInterval = nodeLogInterval;
+        this.propertyLogInterval = propertyLogInterval;
+    }
+
+    /**
+     * Reset the node and property counts to 0. Inheriting implementations
+     * may reset their own internal state.
+     */
+    protected void reset() {
+        nodes.set(0);
+        properties.set(0);
+    }
+
+    /**
+     * Callback called every nth time a node is accessed.
+     *
+     * @param count The count of reported nodes.
+     * @param nodeState The node that was reported.
+     */
+    protected abstract void reportPeriodicNode(
+            final long count, @Nonnull final ReportingNodeState nodeState);
+
+    /**
+     * Callback called every nth time a property is accessed.
+     *
+     * @param count The count of reported properties.
+     * @param parent The parent node of the reported property.
+     * @param propertyName The name of the reported property.
+     */
+    protected abstract void reportPeriodicProperty(
+            final long count, @Nonnull final ReportingNodeState parent, 
@Nonnull final String propertyName);
+
+    @Override
+    public final void reportNode(@Nonnull final ReportingNodeState nodeState) {
+        if (nodeLogInterval == -1) {
+            return;
+        }
+
+        final long count = nodes.incrementAndGet();
+        if (count % nodeLogInterval == 0) {
+            reportPeriodicNode(count, nodeState);
+        }
+    }
+
+    @Override
+    public final void reportProperty(@Nonnull final ReportingNodeState parent, 
@Nonnull final String propertyName) {
+        if (propertyLogInterval == -1) {
+            return;
+        }
+
+        final long count = properties.incrementAndGet();
+        if (count % propertyLogInterval == 0) {
+            reportPeriodicProperty(count, parent, propertyName);
+        }
+    }
+}


Reply via email to