Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/cli/parser/StoreType.java Fri Apr 28 07:18:26 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.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/AbstractDecoratedNodeState.java Fri Apr 28 07:18:26 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.impl.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.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/FilteringNodeState.java Fri Apr 28 07:18:26 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.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NameFilteringNodeState.java Fri Apr 28 07:18:26 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; + } +} Modified: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java?rev=1792995&r1=1792994&r2=1792995&view=diff ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java (original) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/NodeStateCopier.java Fri Apr 28 07:18:26 2017 @@ -20,20 +20,24 @@ import org.apache.jackrabbit.oak.api.Com 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.Collections; 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 @@ -49,14 +53,46 @@ import static com.google.common.base.Pre * 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() { - // no instances + 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(); } /** @@ -65,16 +101,13 @@ public class NodeStateCopier { * * @param source NodeStore to copy from. * @param target NodeStore to copy to. - * @throws CommitFailedException + * @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 { - final NodeBuilder builder = checkNotNull(target).getRoot().builder(); - final boolean hasChanges = copyNodeState(checkNotNull(source).getRoot(), builder, "/", Collections.<String>emptySet()); - if (hasChanges) { - source.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); - } - return hasChanges; + return builder().copy(checkNotNull(source), checkNotNull(target)); } /** @@ -107,6 +140,20 @@ public class NodeStateCopier { 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> @@ -124,8 +171,8 @@ public class NodeStateCopier { * preserved, even if the do not exist in the source. * @return An indication of whether there were changes or not. */ - public static boolean copyNodeState(@Nonnull final NodeState source, @Nonnull final NodeBuilder target, - @Nonnull final String currentPath, @Nonnull final Set<String> mergePaths) { + private static boolean copyNodeState(@Nonnull final NodeState source, @Nonnull final NodeBuilder target, + @Nonnull final String currentPath, @Nonnull final Set<String> mergePaths) { boolean hasChanges = false; @@ -170,4 +217,209 @@ public class NodeStateCopier { } 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.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/LoggingReporter.java Fri Apr 28 07:18:26 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.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/PeriodicReporter.java Fri Apr 28 07:18:26 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); + } + } +} Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/Reporter.java Fri Apr 28 07:18:26 2017 @@ -0,0 +1,45 @@ +/* + * 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; + +/** + * A {@code Reporter} receives callbacks for every NodeState + * and PropertyState that was accessed via a {ReportingNodeState} + * instance. + */ +public interface Reporter { + + /** + * Callback reporting that the given {@code nodeState} was accessed. + * + * @param nodeState The accessed {@code ReportingNodeState} instance. + */ + void reportNode(@Nonnull final ReportingNodeState nodeState); + + /** + * Callback reporting that the property named {@code propertyName} + * was accessed on the {@code parent} node. + * + * @param parent The parent node state of the reported property. + * @param propertyName The name of the reported property. + */ + void reportProperty(@Nonnull final ReportingNodeState parent, @Nonnull final String propertyName); +} Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/report/ReportingNodeState.java Fri Apr 28 07:18:26 2017 @@ -0,0 +1,118 @@ +/* + * 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.api.PropertyState; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.upgrade.nodestate.AbstractDecoratedNodeState; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A decoration layer for NodeState instances that intercepts + * all accesses to NodeStates and PropertyStates (getters) and + * informs a {@link Reporter} via its callbacks that the respective + * NodeStates or PropertyStates have been accessed. + * <br> + * The decoration is deep, i.e. any child NodeStates will be + * decorated as well and will report to the same {@code Reporter} + * instance. + * <br> + * For convenience, a {@link PeriodicReporter} abstract class exists. + * This simplifies reporting every nth node/property only. + * <br> + * Note: Multiple accesses to the same node or property are each + * reported. Therefore if exactly counting unique accesses is a + * requirement, the reporter needs to take care of de-duplication. + * + * @see Reporter + * @see PeriodicReporter + * @see LoggingReporter + */ +public class ReportingNodeState extends AbstractDecoratedNodeState { + + private final ReportingNodeState parent; + private final String name; + private final Reporter reporter; + + /** + * Allows wrapping a NodeState as a ReportingNodeState. The wrapped + * NodeState is treated as the root of a tree (i.e. path is "/"). + * <br> + * Any children accessed via this NodeState are also wrapped. Each + * wrapped NodeState is also reported to the provided Reporter. + * + * @param nodeState The NodeState to be wrapped. + * @param reporter The reporter to report to. + * @return the wrapped NodeState. + */ + public static NodeState wrap(NodeState nodeState, Reporter reporter) { + return wrapAndReport(null, "/", nodeState, reporter); + } + + private static NodeState wrapAndReport(@Nullable ReportingNodeState parent, @Nonnull String name, + @Nonnull NodeState delegate, @Nonnull Reporter reporter) { + final ReportingNodeState nodeState = new ReportingNodeState(parent, name, delegate, reporter); + reporter.reportNode(nodeState); + return nodeState; + } + + private ReportingNodeState(ReportingNodeState parent, String name, NodeState delegate, Reporter reporter) { + super(delegate); + this.parent = parent; + this.name = name; + this.reporter = reporter; + } + + /** + * ReportingNodeState instances provide access to their path via their + * parent hierarchy. Note that calculating the path on every access may + * incur a significant performance penalty. + * + * @return The path of the ReportingNodeState instance, assuming that + * the first wrapped instance is the root node. + */ + public String getPath() { + if (parent == null) { + return name; + } + return PathUtils.concat(this.parent.getPath(), name); + } + + @Nonnull + @Override + protected NodeState decorateChild(@Nonnull final String name, @Nonnull final NodeState delegateChild) { + return wrapAndReport(this, name, delegateChild, reporter); + } + + @Override + @CheckForNull + protected PropertyState decorateProperty(@Nonnull final PropertyState delegatePropertyState) { + reporter.reportProperty(this, delegatePropertyState.getName()); + return delegatePropertyState; + } + + @Override + public String toString() { + return "ReportingNodeState{" + getPath() + ", " + delegate.toString() + "}"; + } +} Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/security/AuthorizableFolderEditor.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/security/AuthorizableFolderEditor.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/security/AuthorizableFolderEditor.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/security/AuthorizableFolderEditor.java Fri Apr 28 07:18:26 2017 @@ -0,0 +1,113 @@ +/* + * 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.security; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.DefaultEditor; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.commit.EditorProvider; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.oak.spi.security.user.UserConstants.NT_REP_AUTHORIZABLE_FOLDER; + +/** + * There are occasions where in old JR2 repositories not all ancestors on + * the users path are of type {@code rep:AuthorizableFolder}, thus leading + * to exceptions after repository upgrade. + * <br> + * In order to avoid such situations, this hook verifies that all nodes on + * the users and groups paths are of type {@code rep:AuthorizableFolder} and + * fixes the node-type if it is incorrect. + */ +public class AuthorizableFolderEditor extends DefaultEditor { + + private static final Logger LOG = LoggerFactory.getLogger(AuthorizableFolderEditor.class); + + private final NodeBuilder currentBuilder; + + private final String groupsPath; + + private final String usersPath; + + private final String path; + + public AuthorizableFolderEditor(final NodeBuilder builder, final String path, final String groupsPath, final String usersPath) { + this.currentBuilder = builder; + this.groupsPath = groupsPath; + this.usersPath = usersPath; + this.path = path; + } + + public static EditorProvider provider(final String groupsPath, final String usersPath) { + return new EditorProvider() { + @Override + public Editor getRootEditor(final NodeState before, + final NodeState after, + final NodeBuilder builder, + final CommitInfo info) throws CommitFailedException { + return new AuthorizableFolderEditor(builder, "/", groupsPath, usersPath); + } + }; + } + + @Override + public void propertyAdded(final PropertyState after) throws CommitFailedException { + propertyChanged(null, after); + } + + @Override + public void propertyChanged(final PropertyState before, final PropertyState after) throws CommitFailedException { + if (JCR_PRIMARYTYPE.equals(after.getName())) { + String nodeType = after.getValue(Type.STRING); + LOG.debug("Found {}/jcr:primaryType = {}", path, nodeType); + if (!nodeType.equals(NT_REP_AUTHORIZABLE_FOLDER) && expectAuthorizableFolder(path)) { + LOG.info("Changed {}/jcr:primaryType from {} to {}", path, nodeType, NT_REP_AUTHORIZABLE_FOLDER); + currentBuilder.setProperty(JCR_PRIMARYTYPE, NT_REP_AUTHORIZABLE_FOLDER, Type.NAME); + } + } + } + + private boolean expectAuthorizableFolder(final String path) { + return !PathUtils.denotesRoot(path) + && (PathUtils.isAncestor(path, groupsPath) || path.equals(groupsPath) + || PathUtils.isAncestor(path, usersPath) || path.equals(usersPath)); + } + + @Override + public Editor childNodeAdded(final String name, final NodeState after) throws CommitFailedException { + return childNodeChanged(name, null, after); + } + + @Override + public Editor childNodeChanged(final String name, final NodeState before, final NodeState after) throws CommitFailedException { + final String childPath = PathUtils.concat(path, name); + if (expectAuthorizableFolder(childPath)) { + LOG.debug("Found {} - descending", childPath); + return new AuthorizableFolderEditor(currentBuilder.child(name), childPath, groupsPath, usersPath); + } else { + return null; + } + } +} Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopier.java Fri Apr 28 07:18:26 2017 @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.upgrade.version; + +import static org.apache.jackrabbit.JcrConstants.JCR_CREATED; +import static org.apache.jackrabbit.JcrConstants.NT_VERSION; + +import java.util.Calendar; +import java.util.Iterator; + +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate; +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.upgrade.DescendantsIterator; +import org.apache.jackrabbit.oak.upgrade.nodestate.NodeStateCopier; +import org.apache.jackrabbit.util.ISO8601; + +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.VERSION_STORE_PATH; + +import static org.apache.jackrabbit.oak.upgrade.version.VersionHistoryUtil.getRelativeVersionHistoryPath; +import static org.apache.jackrabbit.oak.upgrade.version.VersionHistoryUtil.getVersionHistoryNodeState; + +/** + * This class allows to copy the version history, optionally filtering it with a + * given date. + */ +public class VersionCopier { + + private final TypePredicate isVersion; + + private final NodeState sourceVersionStorage; + + private final NodeBuilder targetVersionStorage; + + private final NodeBuilder targetRoot; + + public VersionCopier(NodeBuilder targetRoot, NodeState sourceVersionStorage, NodeBuilder targetVersionStorage) { + this.isVersion = new TypePredicate(targetRoot.getNodeState(), NT_VERSION); + this.sourceVersionStorage = sourceVersionStorage; + this.targetVersionStorage = targetVersionStorage; + this.targetRoot = targetRoot; + } + + public static void copyVersionStorage(NodeBuilder targetRoot, NodeState sourceVersionStorage, NodeBuilder targetVersionStorage, VersionCopyConfiguration config) { + final Iterator<NodeState> versionStorageIterator = new DescendantsIterator(sourceVersionStorage, 3); + final VersionCopier versionCopier = new VersionCopier(targetRoot, sourceVersionStorage, targetVersionStorage); + + while (versionStorageIterator.hasNext()) { + final NodeState versionHistoryBucket = versionStorageIterator.next(); + for (String versionHistory : versionHistoryBucket.getChildNodeNames()) { + versionCopier.copyVersionHistory(versionHistory, config.getOrphanedMinDate()); + } + } + } + + /** + * Copy history filtering versions using passed date and returns {@code + * true} if the history has been copied. + * + * @param versionableUuid + * Name of the version history node + * @param minDate + * Only versions older than this date will be copied + * @return {@code true} if at least one version has been copied + */ + public boolean copyVersionHistory(String versionableUuid, Calendar minDate) { + final String versionHistoryPath = getRelativeVersionHistoryPath(versionableUuid); + final NodeState sourceVersionHistory = getVersionHistoryNodeState(sourceVersionStorage, versionableUuid); + final Calendar lastModified = getVersionHistoryLastModified(sourceVersionHistory); + + if (sourceVersionHistory.exists() && (lastModified.after(minDate) || minDate.getTimeInMillis() == 0)) { + NodeStateCopier.builder() + .include(versionHistoryPath) + .merge(VERSION_STORE_PATH) + .copy(sourceVersionStorage, targetVersionStorage); + return true; + } + return false; + } + + private Calendar getVersionHistoryLastModified(final NodeState versionHistory) { + Calendar youngest = Calendar.getInstance(); + youngest.setTimeInMillis(0); + for (final ChildNodeEntry entry : versionHistory.getChildNodeEntries()) { + final NodeState version = entry.getNodeState(); + if (!isVersion.apply(version)) { + continue; + } + if (version.hasProperty(JCR_CREATED)) { + final Calendar created = ISO8601.parse(version.getProperty(JCR_CREATED).getValue(Type.DATE)); + if (created.after(youngest)) { + youngest = created; + } + } + } + return youngest; + } +} Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionCopyConfiguration.java Fri Apr 28 07:18:26 2017 @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.upgrade.version; + +import java.util.Calendar; + +/** + * This class allows to configure the behaviour of the version copier. + */ +public class VersionCopyConfiguration { + + private Calendar copyVersions; + + private Calendar copyOrphanedVersions; + + public VersionCopyConfiguration() { + final Calendar epoch = Calendar.getInstance(); + epoch.setTimeInMillis(0); + this.copyVersions = epoch; + this.copyOrphanedVersions = epoch; + } + + public void setCopyVersions(Calendar copyVersions) { + this.copyVersions = copyVersions; + } + + public void setCopyOrphanedVersions(Calendar copyOrphanedVersions) { + this.copyOrphanedVersions = copyOrphanedVersions; + } + + public Calendar getVersionsMinDate() { + return copyVersions; + } + + public Calendar getOrphanedMinDate() { + if (copyVersions == null) { + return copyVersions; + } else if (copyOrphanedVersions != null && copyVersions.after(copyOrphanedVersions)) { + return copyVersions; + } else { + return copyOrphanedVersions; + } + } + + public boolean isCopyVersions() { + return copyVersions != null; + } + + public boolean skipOrphanedVersionsCopy() { + return copyVersions == null || copyOrphanedVersions == null; + } + + public boolean isCopyAll() { + return copyVersions != null && copyVersions.getTimeInMillis() == 0 && copyOrphanedVersions != null && copyOrphanedVersions.getTimeInMillis() == 0; + } + +} \ No newline at end of file Added: jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java?rev=1792995&view=auto ============================================================================== --- jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java (added) +++ jackrabbit/oak/branches/1.2/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/version/VersionHistoryUtil.java Fri Apr 28 07:18:26 2017 @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.oak.upgrade.version; + +import static com.google.common.collect.Iterables.concat; +import static java.util.Collections.singleton; +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; +import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM; +import static org.apache.jackrabbit.JcrConstants.JCR_VERSIONSTORAGE; +import static org.apache.jackrabbit.oak.plugins.version.VersionConstants.REP_VERSIONSTORAGE; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.base.Joiner; + +public class VersionHistoryUtil { + + public static String getRelativeVersionHistoryPath(String versionableUuid) { + return Joiner.on('/').join(concat( + singleton(""), + getRelativeVersionHistoryPathSegments(versionableUuid), + singleton(versionableUuid))); + } + + /** + * Constructs the version history path based on the versionable's UUID. + * + * @param root The root NodeState below which to look for the version. + * @param versionableUuid The String representation of the versionable's UUID. + * @return The NodeState corresponding to the version history, or {@code null} + * if it does not exist. + */ + static NodeState getVersionHistoryNodeState(NodeState versionStorage, String versionableUuid) { + NodeState historyParent = versionStorage; + for (String segment : getRelativeVersionHistoryPathSegments(versionableUuid)) { + historyParent = historyParent.getChildNode(segment); + } + return historyParent.getChildNode(versionableUuid); + } + + static NodeBuilder getVersionHistoryBuilder(NodeBuilder versionStorage, String versionableUuid) { + NodeBuilder history = versionStorage; + for (String segment : getRelativeVersionHistoryPathSegments(versionableUuid)) { + history = history.getChildNode(segment); + } + return history.getChildNode(versionableUuid); + } + + private static List<String> getRelativeVersionHistoryPathSegments(String versionableUuid) { + final List<String> segments = new ArrayList<String>(); + for (int i = 0; i < 3; i++) { + segments.add(versionableUuid.substring(i * 2, i * 2 + 2)); + } + return segments; + } + + public static NodeState getVersionStorage(NodeState root) { + return root.getChildNode(JCR_SYSTEM).getChildNode(JCR_VERSIONSTORAGE); + } + + public static NodeBuilder getVersionStorage(NodeBuilder root) { + return root.getChildNode(JCR_SYSTEM).getChildNode(JCR_VERSIONSTORAGE); + } + + public static NodeBuilder createVersionStorage(NodeBuilder root) { + NodeBuilder vs = root.child(JCR_SYSTEM).child(JCR_VERSIONSTORAGE); + if (!vs.hasProperty(JCR_PRIMARYTYPE)) { + vs.setProperty(JCR_PRIMARYTYPE, REP_VERSIONSTORAGE, Type.NAME); + } + return vs; + } + +}
