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); + } + } +}
