From 173125ede96a8442d3008463de296f6c95076b80 Mon Sep 17 00:00:00 2001 From: Jukka Zitting Date: Thu, 21 Nov 2013 12:00:51 -0500 Subject: [PATCH] OAK-1179: Use dedicated Path class for handling paths in Oak Quick draft, not complete! --- .../org/apache/jackrabbit/oak/namepath/Path.java | 266 +++++++++++++++++++++ .../oak/spi/commit/SubtreeExcludingValidator.java | 22 +- .../jackrabbit/oak/jcr/delegate/ItemDelegate.java | 5 +- .../jackrabbit/oak/jcr/delegate/NodeDelegate.java | 87 ++++--- .../oak/jcr/delegate/PropertyDelegate.java | 6 +- .../oak/jcr/delegate/SessionDelegate.java | 121 ++++++---- .../oak/jcr/delegate/VersionDelegate.java | 4 +- .../oak/jcr/delegate/VersionHistoryDelegate.java | 17 +- .../oak/jcr/delegate/VersionManagerDelegate.java | 36 +-- .../jackrabbit/oak/jcr/session/ItemImpl.java | 23 +- .../jackrabbit/oak/jcr/session/SessionContext.java | 11 +- .../jackrabbit/oak/jcr/session/SessionImpl.java | 23 +- .../oak/jcr/version/ReadWriteVersionManager.java | 8 +- 13 files changed, 474 insertions(+), 155 deletions(-) create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/Path.java diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/Path.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/Path.java new file mode 100644 index 0000000..b7ae3f9 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/namepath/Path.java @@ -0,0 +1,266 @@ +/* + * 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.namepath; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * Type-safe wrapper for path strings. + *

+ * This class can be used to represent paths that match the following syntax: + *

+ * path           ::= "" | non-empty-path
+ * non-empty-path ::= name | name "/" non-empty-path
+ * name           ::= name-char | name-char name
+ * name-char      ::= { any character except "/" }
+ * 
+ *

+ * In other words, only relative paths are supported, so one should always + * keep track of the context of a Path instance. + */ +public final class Path implements Comparable { + + /** + * A name-path split. Used to allow the split methods to return both the + * name that was split from the path and the remaining part of the path. + * Depending on the split method used, the name part is either the first + * or last element of the path. The remaining path may be empty, if the + * path that was split only contained a single element. + */ + public static final class Split { + + private final Path path; + + private final String name; + + private Split(Path path, String name) { + this.path = path; + this.name = name; + } + + /** + * Returns the path part of this split. + * + * @return path + */ + public Path getPath() { + return path; + } + + /** + * Returns the name part of this split. + * + * @return name + */ + public String getName() { + return name; + } + + } + + /** + * Singleton instance of the empty path. + */ + public static final Path EMPTY_PATH = new Path(""); + + /** + * Creates a wrapper for the given path. The path should match the + * syntax described in the main {@link Path} javadoc. + * + * @param path path string + * @return path wrapper + */ + public static Path create(@Nonnull String path) { + assert path != null; + if (path.isEmpty()) { + return EMPTY_PATH; + } else { + // using assert instead of checkArgument() for performance reasons + assert !path.startsWith("/"); + assert !path.endsWith("/"); + assert !path.contains("//"); + return new Path(path); + } + } + + @Nonnull + private final String path; + + private Path(@Nonnull String path) { + this.path = path; + } + + /** + * Checks whether this is the empty path. + * + * @return {@code true} if this is the empty path, {@code false} otherwise + */ + public boolean isEmpty() { + return this == EMPTY_PATH; + } + + /** + * Appends the given name to this path and returns the resulting path. + * The name should match the {@code name} syntax described in the + * main {@link Path} javadoc. + * + * @param name name string + * @return wrapper for the new path + */ + @Nonnull + public Path append(String name) { + // using assert instead of checkArgument() for performance reasons + assert name != null; + assert !name.isEmpty(); + assert name.indexOf('/') == -1; + if (isEmpty()) { + return new Path(name); + } else { + return new Path(path + "/" + name); + } + } + + /** + * Returns the + * @param depth number of elements to include in the ancestor path + * @return ancestor path, or {@code null} if depth is larger than + * the number of elements in this path + */ + @CheckForNull + public Path getAncestor(int depth) { + if (depth == 0) { + return EMPTY_PATH; + } else { + int slash = path.indexOf('/'); + while (--depth > 0 && slash != -1) { + slash = path.indexOf('/', slash + 1); + } + if (depth != 0) { + return null; + } else if (slash == -1) { + return this; + } else { + return new Path(path.substring(0, slash)); + } + } + } + + /** + * Splits the first element off this path. Useful for path traversal: + *

+     * Path path = ...;
+     * while (!path.isEmpty()) {
+     *     Path.Split split = path.splitHead();
+     *     traverseTo(split.getName()); // step down the path
+     *     path = split.getPath();
+     * }
+     * 
+ * + * @return path split + * @throws IllegalStateException if this path is empty + */ + @Nonnull + public Split splitHead() throws IllegalStateException { + int slash = path.indexOf('/'); + if (slash != -1) { + return new Split( + new Path(path.substring(slash + 1)), + path.substring(0, slash)); + } else if (isEmpty()) { + throw new IllegalStateException("Cannot split the empty path"); + } else { + return new Split(EMPTY_PATH, path); + } + } + + /** + * Splits the last element off this path. Useful for separating the + * parent path: + *
+     * Path path = ...;
+     * if (!path.isEmpty()) {
+     *     Path.Split split = path.splitTail();
+     *     Path parent = split.getPath();
+     *     Path name = split.getName();
+     *     // process parent + name
+     * } else {
+     *     // process empty path
+     * }
+     * 
+ * + * @return path split + * @throws IllegalStateException if this path is empty + */ + @Nonnull + public Split splitTail() throws IllegalStateException { + int slash = path.lastIndexOf('/'); + if (slash != -1) { + return new Split( + new Path(path.substring(0, slash)), + path.substring(slash + 1)); + } else if (isEmpty()) { + throw new IllegalStateException("Cannot split the empty path"); + } else { + return new Split(EMPTY_PATH, path); + } + } + + /** + * Returns the string representation of this path as an absolute path. + * + * @return absolute path string + */ + @Nonnull + public String toAbsoluteString() { + if (isEmpty()) { + return "/"; + } else { + return "/" + path; + } + } + + //--------------------------------------------------------< Comparable >-- + + @Override + public int compareTo(Path that) { + return path.compareTo(that.path); + } + + //------------------------------------------------------------< Object >-- + + /** + * Returns the path string. + * + * @return path string + */ + @Override @Nonnull + public String toString() { + return path; + } + + @Override + public boolean equals(Object that) { + return that instanceof Path && path.equals(((Path) that).path); + } + + @Override + public int hashCode() { + return path.hashCode(); + } + +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java index 27ad28a..5f50da4 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeExcludingValidator.java @@ -21,11 +21,9 @@ package org.apache.jackrabbit.oak.spi.commit; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import java.util.Arrays; -import java.util.List; - import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.spi.state.NodeState; /** @@ -39,20 +37,17 @@ public class SubtreeExcludingValidator extends DefaultValidator { private final Validator validator; - private final String head; - - private final List tail; + protected final String head; - public SubtreeExcludingValidator(Validator validator, String... path) { - this(validator, Arrays.asList(path)); - } + protected final Path tail; - protected SubtreeExcludingValidator(Validator validator, List path) { + public SubtreeExcludingValidator(Validator validator, Path path) { this.validator = checkNotNull(validator); checkNotNull(path); checkArgument(!path.isEmpty()); - this.head = path.get(0); - this.tail = path.subList(1, path.size()); + Path.Split split = path.splitHead(); + this.head = split.getName(); + this.tail = split.getPath(); } @Override @@ -101,7 +96,8 @@ public class SubtreeExcludingValidator extends DefaultValidator { } } - protected SubtreeExcludingValidator createValidator(Validator validator, List path) { + protected SubtreeExcludingValidator createValidator( + Validator validator, Path path) { return new SubtreeExcludingValidator(validator, path); } diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java index 2ddd37e..1e197c8 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/ItemDelegate.java @@ -25,6 +25,7 @@ import javax.jcr.InvalidItemStateException; import javax.jcr.RepositoryException; import org.apache.jackrabbit.oak.api.Tree.Status; +import org.apache.jackrabbit.oak.namepath.Path; /** * Abstract base class for {@link NodeDelegate} and {@link PropertyDelegate} @@ -95,11 +96,11 @@ public abstract class ItemDelegate { public abstract String getName(); /** - * Get the path of this item + * Get the path of this item relative to the root. * @return oak path of this item */ @Nonnull - public abstract String getPath(); + public abstract Path getPath(); /** * Get the parent of this item or {@code null}. diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java index 8816fbe..fc07a0b 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/NodeDelegate.java @@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.jcr.delegate; import java.util.Iterator; import java.util.List; import java.util.Set; + import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.jcr.InvalidItemStateException; @@ -32,6 +33,7 @@ import javax.jcr.security.AccessControlException; import com.google.common.base.Function; import com.google.common.base.Predicate; + import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Root; @@ -39,12 +41,15 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Tree.Status; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager; import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; import org.apache.jackrabbit.oak.util.TreeUtil; import static com.google.common.base.Objects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.addAll; import static com.google.common.collect.Iterables.contains; import static com.google.common.collect.Iterators.filter; @@ -109,8 +114,8 @@ public class NodeDelegate extends ItemDelegate { @Override @Nonnull - public String getPath() { - return tree.getPath(); + public Path getPath() { + return Path.create(tree.getPath().substring(1)); } @Override @@ -247,33 +252,40 @@ public class NodeDelegate extends ItemDelegate { * no such property exists */ @CheckForNull - public PropertyDelegate getPropertyOrNull(String relPath) - throws RepositoryException { - Tree parent = getTree(PathUtils.getParentPath(relPath)); - String name = PathUtils.getName(relPath); - if (parent != null) { - PropertyDelegate property = - new PropertyDelegate(sessionDelegate, parent, name); - if (property.exists()) { - return property; - } + public PropertyDelegate getPropertyOrNull(Path relPath) { + PropertyDelegate property = getProperty(relPath); + if (property.exists()) { + return property; + } else { + return null; + } + } + + @CheckForNull + public PropertyDelegate getPropertyByName(String name) { + PropertyDelegate property = + new PropertyDelegate(sessionDelegate, tree, name); + if (property.exists()) { + return property; + } else { + return null; } - return null; } /** - * Get a property. In contrast to {@link #getPropertyOrNull(String)} this + * Get a property. In contrast to {@link #getPropertyOrNull(Path)} this * method never returns {@code null}. In the case where no property exists * at the given path, the returned property delegate throws an * {@code InvalidItemStateException} on access. See See OAK-395. * - * @param relPath oak path + * @param relPath oak path relative to this node * @return property at the path given by {@code relPath}. */ @Nonnull - public PropertyDelegate getProperty(String relPath) throws RepositoryException { - Tree parent = getTree(PathUtils.getParentPath(relPath)); - String name = PathUtils.getName(relPath); + public PropertyDelegate getProperty(Path relPath) { + Path.Split split = relPath.splitTail(); + Tree parent = getTree(split.getPath()); + String name = split.getName(); return new PropertyDelegate(sessionDelegate, parent, name); } @@ -318,17 +330,34 @@ public class NodeDelegate extends ItemDelegate { /** * Get child node * - * @param relPath oak path + * @param relPath oak path relative to this node * @return node at the path given by {@code relPath} or {@code null} if * no such node exists */ @CheckForNull - public NodeDelegate getChild(String relPath) throws RepositoryException { + public NodeDelegate getChild(Path relPath) { if (relPath.isEmpty()) { return this; } Tree tree = getTree(relPath); - return tree == null || !tree.exists() ? null : new NodeDelegate(sessionDelegate, tree); + if (tree.exists()) { + return new NodeDelegate(sessionDelegate, tree); + } else { + return null; + } + } + + @CheckForNull + public NodeDelegate getChildByName(String name) { + checkNotNull(name); + checkArgument(!name.isEmpty()); + checkArgument(name.indexOf('/') == -1); + Tree child = tree.getChild(name); + if (child.exists()) { + return new NodeDelegate(sessionDelegate, child); + } else { + return null; + } } /** @@ -769,10 +798,10 @@ public class NodeDelegate extends ItemDelegate { } public void lock(boolean isDeep) throws RepositoryException { - String path = getPath(); + Path path = getPath(); Root root = sessionDelegate.getContentSession().getLatestRoot(); - Tree tree = root.getTree(path); + Tree tree = sessionDelegate.getTree(root.getTree("/"), path); if (!tree.exists()) { throw new ItemNotFoundException("Node " + path + " does not exist"); } else if (!isNodeType(tree, MIX_LOCKABLE, root)) { @@ -801,10 +830,10 @@ public class NodeDelegate extends ItemDelegate { } public void unlock() throws RepositoryException { - String path = getPath(); + Path path = getPath(); Root root = sessionDelegate.getContentSession().getLatestRoot(); - Tree tree = root.getTree(path); + Tree tree = sessionDelegate.getTree(root.getTree("/"), path); if (!tree.exists()) { throw new ItemNotFoundException("Node " + path + " does not exist"); } else if (!isNodeType(tree, MIX_LOCKABLE, root)) { @@ -851,12 +880,8 @@ public class NodeDelegate extends ItemDelegate { // -----------------------------------------------------------< private >--- - private Tree getTree(String relPath) throws RepositoryException { - if (PathUtils.isAbsolute(relPath)) { - throw new RepositoryException("Not a relative path: " + relPath); - } - - return TreeUtil.getTree(tree, relPath); + private Tree getTree(Path path) { + return sessionDelegate.getTree(tree, path); } private String getUserID() { diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/PropertyDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/PropertyDelegate.java index 18f542a..1d6d5aa 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/PropertyDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/PropertyDelegate.java @@ -28,7 +28,7 @@ import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Tree.Status; import org.apache.jackrabbit.oak.api.Type; -import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.namepath.Path; /** * {@code PropertyDelegate} serve as internal representations of {@code Property}s. @@ -71,8 +71,8 @@ public class PropertyDelegate extends ItemDelegate { } @Override @Nonnull - public String getPath() { - return PathUtils.concat(parent.getPath(), name); + public Path getPath() { + return Path.create(parent.getPath().substring(1)).append(name); } @Override @CheckForNull diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java index b591b16..bc86b31 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java @@ -17,12 +17,10 @@ package org.apache.jackrabbit.oak.jcr.delegate; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Lists.newArrayList; -import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot; -import static org.apache.jackrabbit.oak.commons.PathUtils.elements; +import static org.apache.jackrabbit.oak.commons.PathUtils.denotesCurrent; +import static org.apache.jackrabbit.oak.commons.PathUtils.denotesParent; import java.io.IOException; -import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -42,6 +40,7 @@ import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.jcr.security.AccessManager; import org.apache.jackrabbit.oak.jcr.session.RefreshStrategy; import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager; import org.apache.jackrabbit.oak.spi.commit.CommitHook; import org.apache.jackrabbit.oak.spi.commit.Editor; @@ -224,7 +223,7 @@ public class SessionDelegate { root.commit(userData, hook); } - public void checkProtectedNode(String path) throws RepositoryException { + public void checkProtectedNode(Path path) throws RepositoryException { NodeDelegate node = getNode(path); if (node == null) { throw new PathNotFoundException( @@ -263,44 +262,68 @@ public class SessionDelegate { @CheckForNull public NodeDelegate getRootNode() { - return getNode("/"); + return getNode(Path.EMPTY_PATH); } /** * {@code NodeDelegate} at the given path - * @param path Oak path - * @return The {@code NodeDelegate} at {@code path} or {@code null} if - * none exists or not accessible. + * @param path Oak path relative to the root + * @return the {@code NodeDelegate} at {@code path}, + * or {@code null} if the none does not exist or is not accessible */ @CheckForNull - public NodeDelegate getNode(String path) { - Tree tree = root.getTree(path); + public NodeDelegate getNode(Path path) { + Tree tree = getTree(path); return tree.exists() ? new NodeDelegate(this, tree) : null; } + private Tree getTree(Path path) { + return getTree(root.getTree("/"), path); + } + + Tree getTree(Tree tree, Path path) { + while (!path.isEmpty()) { + Path.Split split = path.splitHead(); + String name = split.getName(); + if (PathUtils.denotesParent(name)) { + tree = tree.getParent(); + } else if (!PathUtils.denotesCurrent(name)) { + tree = tree.getChild(name); + } + path = split.getPath(); + } + return tree; + } + /** * Returns the node or property delegate at the given path. * - * @param path Oak path + * @param path Oak path relative to root * @return node or property delegate, or {@code null} if none exists */ @CheckForNull - public ItemDelegate getItem(String path) { - String name = PathUtils.getName(path); - if (name.isEmpty()) { + public ItemDelegate getItem(Path path) { + if (path.isEmpty()) { return getRootNode(); - } else { - Tree parent = root.getTree(PathUtils.getParentPath(path)); - if (parent.hasProperty(name)) { - return new PropertyDelegate(this, parent, name); - } - Tree child = parent.getChild(name); - if (child.exists()) { - return new NodeDelegate(this, child); - } else { - return null; - } } + + Path.Split split = path.splitTail(); + String name = split.getName(); + if (denotesCurrent(name) || denotesParent(name)) { + return getNode(path); + } + + Tree parent = getTree(split.getPath()); + if (parent.hasProperty(name)) { + return new PropertyDelegate(this, parent, name); + } + + Tree child = parent.getChild(name); + if (child.exists()) { + return new NodeDelegate(this, child); + } + + return null; } @CheckForNull @@ -316,11 +339,19 @@ public class SessionDelegate { * none exists or not accessible. */ @CheckForNull - public PropertyDelegate getProperty(String path) { - Tree parent = root.getTree(PathUtils.getParentPath(path)); - String name = PathUtils.getName(path); - return parent.hasProperty(name) ? new PropertyDelegate(this, parent, - name) : null; + public PropertyDelegate getProperty(Path path) { + if (path.isEmpty()) { + return null; + } else { + Path.Split split = path.splitTail(); + PropertyDelegate property = new PropertyDelegate( + this, getTree(split.getPath()), split.getName()); + if (property.exists()) { + return property; + } else { + return null; + } + } } public boolean hasPendingChanges() { @@ -345,8 +376,8 @@ public class SessionDelegate { * @param path * @throws RepositoryException */ - public void save(final String path) throws RepositoryException { - if (denotesRoot(path)) { + public void save(final Path path) throws RepositoryException { + if (path.isEmpty()) { // root path save(); } else { try { @@ -478,41 +509,35 @@ public class SessionDelegate { private static class ItemSaveValidator extends SubtreeExcludingValidator { /** - * Name of the property whose {@link #propertyChanged(PropertyState, PropertyState)} to - * ignore or {@code null} if no property should be ignored. - */ - private final String ignorePropertyChange; - - /** * Create a new validator that only throws a {@link CommitFailedException} whenever * there are changes not contained in the subtree rooted at {@code path}. * @param path */ - public ItemSaveValidator(String path) { + public ItemSaveValidator(Path path) { this(new FailingValidator(CommitFailedException.UNSUPPORTED, 0, "Failed to save subtree at " + path + ". There are " + - "transient modifications outside that subtree."), - newArrayList(elements(path))); + "transient modifications outside that subtree."), + path); } - private ItemSaveValidator(Validator validator, List path) { + private ItemSaveValidator(Validator validator, Path path) { super(validator, path); - // Ignore property changes if this is the head of the path. - // This allows for calling save on a changed property. - ignorePropertyChange = path.size() == 1 ? path.get(0) : null; } @Override public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException { - if (!before.getName().equals(ignorePropertyChange)) { + if (tail.isEmpty() && head.equals(before.getName())) { + // Ignore property changes if this is the head of the path. + // This allows for calling save on a changed property. + } else { super.propertyChanged(before, after); } } @Override protected SubtreeExcludingValidator createValidator( - Validator validator, final List path) { + Validator validator, Path path) { return new ItemSaveValidator(validator, path); } } diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java index 5f8567f..e00f7f4 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionDelegate.java @@ -47,7 +47,7 @@ public class VersionDelegate extends NodeDelegate { @Nonnull NodeDelegate getFrozenNode() throws RepositoryException { - NodeDelegate frozenNode = getChild(JcrConstants.JCR_FROZENNODE); + NodeDelegate frozenNode = getChildByName(JcrConstants.JCR_FROZENNODE); if (frozenNode == null) { throw new RepositoryException("Inconsistent version storage. " + "Version at " + getPath() + " does not have a jcr:frozenNode"); @@ -58,7 +58,7 @@ public class VersionDelegate extends NodeDelegate { @Nonnull public Iterable getPredecessors() throws RepositoryException { - PropertyDelegate p = getPropertyOrNull(JCR_PREDECESSORS); + PropertyDelegate p = getPropertyByName(JCR_PREDECESSORS); if (p == null) { throw new RepositoryException("Inconsistent version storage. " + "Version does not have a " + JCR_PREDECESSORS + " property."); diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java index 682bbd5..05bfc1b 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionHistoryDelegate.java @@ -136,9 +136,9 @@ public class VersionHistoryDelegate extends NodeDelegate { SortedMap versions = new TreeMap(); for (Iterator it = getChildren(); it.hasNext(); ) { NodeDelegate n = it.next(); - String primaryType = n.getProperty(JcrConstants.JCR_PRIMARYTYPE).getString(); - if (primaryType.equals(VersionConstants.NT_VERSION)) { - PropertyDelegate created = n.getPropertyOrNull(JcrConstants.JCR_CREATED); + PropertyDelegate type = n.getPropertyByName(JcrConstants.JCR_PRIMARYTYPE); + if (type != null && VersionConstants.NT_VERSION.equals(type.getString())) { + PropertyDelegate created = n.getPropertyByName(JcrConstants.JCR_CREATED); if (created != null) { Long cal = ISO8601.parse(created.getDate()).getTimeInMillis(); versions.put(cal, n.getName()); @@ -159,14 +159,17 @@ public class VersionHistoryDelegate extends NodeDelegate { throws RepositoryException { String id = getVersionableIdentifier(); NodeDelegate versionable = sessionDelegate.getNodeByIdentifier(id); - if (versionable == null - || versionable.getPropertyOrNull(JCR_BASEVERSION) == null) { + if (versionable == null) { return Iterators.emptyIterator(); } + PropertyDelegate base = versionable.getPropertyByName(JCR_BASEVERSION); + if (base == null) { + return Iterators.emptyIterator(); + } + Deque linearVersions = new ArrayDeque(); VersionManagerDelegate vMgr = VersionManagerDelegate.create(sessionDelegate); - VersionDelegate version = vMgr.getVersionByIdentifier( - versionable.getProperty(JCR_BASEVERSION).getString()); + VersionDelegate version = vMgr.getVersionByIdentifier(base.getString()); while (version != null) { linearVersions.add(version); version = version.getLinearPredecessor(); diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java index 4e39a5e..1e3df80 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/VersionManagerDelegate.java @@ -17,6 +17,7 @@ package org.apache.jackrabbit.oak.jcr.delegate; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import static org.apache.jackrabbit.JcrConstants.JCR_BASEVERSION; import static org.apache.jackrabbit.JcrConstants.JCR_FROZENMIXINTYPES; import static org.apache.jackrabbit.JcrConstants.JCR_FROZENPRIMARYTYPE; @@ -34,7 +35,6 @@ import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.version.LabelExistsVersionException; import javax.jcr.version.VersionException; -import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; @@ -129,27 +129,33 @@ public class VersionManagerDelegate { @Nonnull String oakName, @Nonnull VersionDelegate vd) throws RepositoryException { - NodeDelegate frozen = vd.getFrozenNode(); - PropertyState primaryType = frozen.getProperty( - JCR_FROZENPRIMARYTYPE).getPropertyState(); - PropertyState uuid = frozen.getProperty( - JCR_FROZENUUID).getPropertyState(); - PropertyDelegate mixinTypes = frozen.getPropertyOrNull(JCR_FROZENMIXINTYPES); - if (parent.getChild(oakName) == null) { + if (parent.getChildByName(oakName) == null) { // create a sentinel node with a jcr:baseVersion pointing // to the version to restore Tree t = parent.getTree().addChild(oakName); - t.setProperty(JCR_PRIMARYTYPE, primaryType.getValue(Type.NAME), Type.NAME); - t.setProperty(JCR_UUID, uuid.getValue(Type.STRING), Type.STRING); - if (mixinTypes != null && mixinTypes.getPropertyState().count() > 0) { + NodeDelegate frozen = vd.getFrozenNode(); + + PropertyDelegate primary = + frozen.getPropertyByName(JCR_FROZENPRIMARYTYPE); + checkState(primary != null); + t.setProperty(JCR_PRIMARYTYPE, primary.getString(), Type.NAME); + + PropertyDelegate mixins = + frozen.getPropertyByName(JCR_FROZENMIXINTYPES); + if (mixins != null && mixins.getPropertyState().count() > 0) { t.setProperty(JCR_MIXINTYPES, - mixinTypes.getPropertyState().getValue(Type.NAMES), + mixins.getPropertyState().getValue(Type.NAMES), Type.NAMES); } + + PropertyDelegate uuid = frozen.getPropertyByName(JCR_FROZENUUID); + checkState(uuid != null); + t.setProperty(JCR_UUID, uuid.getString(), Type.STRING); + t.setProperty(JCR_BASEVERSION, vd.getIdentifier(), Type.REFERENCE); t.setProperty(JCR_VERSIONHISTORY, vd.getParent().getIdentifier(), Type.REFERENCE); } else { - Tree t = parent.getChild(oakName).getTree(); + Tree t = parent.getChildByName(oakName).getTree(); t.setProperty(JCR_BASEVERSION, RESTORE_PREFIX + vd.getIdentifier(), Type.REFERENCE); } } @@ -179,7 +185,7 @@ public class VersionManagerDelegate { Root fresh = sessionDelegate.getContentSession().getLatestRoot(); VersionStorage storage = new VersionStorage(fresh); String vhRelPath = PathUtils.relativize(VersionStorage.VERSION_STORAGE_PATH, - checkNotNull(versionHistory).getPath()); + checkNotNull(versionHistory).getPath().toAbsoluteString()); versionManager.addVersionLabel(storage, vhRelPath, checkNotNull(version).getName(), checkNotNull(oakVersionLabel), moveLabel); @@ -202,7 +208,7 @@ public class VersionManagerDelegate { Root fresh = sessionDelegate.getContentSession().getLatestRoot(); VersionStorage storage = new VersionStorage(fresh); String vhRelPath = PathUtils.relativize(VersionStorage.VERSION_STORAGE_PATH, - checkNotNull(versionHistory).getPath()); + checkNotNull(versionHistory).getPath().toAbsoluteString()); versionManager.removeVersionLabel(storage, vhRelPath, checkNotNull(oakVersionLabel)); } diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/ItemImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/ItemImpl.java index 6534a03..f1b2733 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/ItemImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/ItemImpl.java @@ -17,6 +17,7 @@ package org.apache.jackrabbit.oak.jcr.session; import java.util.List; + import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.jcr.AccessDeniedException; @@ -41,6 +42,7 @@ import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate; import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; import org.apache.jackrabbit.oak.jcr.session.operation.ItemOperation; import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.plugins.memory.MemoryPropertyBuilder; import org.apache.jackrabbit.oak.plugins.nodetype.write.ReadWriteNodeTypeManager; import org.slf4j.Logger; @@ -151,7 +153,7 @@ abstract class ItemImpl implements Item { return toJcrPath(perform(new ItemOperation(dlg) { @Override public String perform() { - return item.getPath(); + return item.getPath().toAbsoluteString(); } })); } @@ -173,22 +175,13 @@ abstract class ItemImpl implements Item { ItemDelegate ancestor = perform(new ItemOperation(dlg) { @Override public ItemDelegate perform() throws RepositoryException { - String path = item.getPath(); - - int slash = 0; - for (int i = 0; i < depth - 1; i++) { - slash = PathUtils.getNextSlash(path, slash + 1); - if (slash == -1) { - throw new ItemNotFoundException( - path + ": Invalid ancestor depth " + depth); - } - } - slash = PathUtils.getNextSlash(path, slash + 1); - if (slash == -1) { + Path path = item.getPath(); + Path ancestorPath = path.getAncestor(depth); + if (path == ancestorPath) { return item; + } else { + return sessionDelegate.getNode(ancestorPath); } - - return sessionDelegate.getNode(path.substring(0, slash)); } }); diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java index dff9421..dee9efd 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java @@ -56,6 +56,7 @@ import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation; import org.apache.jackrabbit.oak.namepath.LocalNameMapper; import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; import org.apache.jackrabbit.oak.plugins.value.ValueFactoryImpl; import org.apache.jackrabbit.oak.spi.commit.Observable; @@ -105,10 +106,10 @@ public class SessionContext implements NamePathMapper { private ObservationManagerImpl observationManager; /** Paths (tokens) of all open scoped locks held by this session. */ - private final Set openScopedLocks = newTreeSet(); + private final Set openScopedLocks = newTreeSet(); /** Paths of all session scoped locks held by this session. */ - private final Set sessionScopedLocks = newHashSet(); + private final Set sessionScopedLocks = newHashSet(); public SessionContext( @Nonnull Repository repository, @Nonnull SecurityProvider securityProvider, @@ -264,11 +265,11 @@ public class SessionContext implements NamePathMapper { return observationManager; } - public Set getOpenScopedLocks() { + public Set getOpenScopedLocks() { return openScopedLocks; } - public Set getSessionScopedLocks() { + public Set getSessionScopedLocks() { return sessionScopedLocks; } @@ -388,7 +389,7 @@ public class SessionContext implements NamePathMapper { delegate.perform(new SessionOperation() { @Override public Void perform() throws RepositoryException { - Iterator iterator = sessionScopedLocks.iterator(); + Iterator iterator = sessionScopedLocks.iterator(); while (iterator.hasNext()) { NodeDelegate node = delegate.getNode(iterator.next()); if (node != null) { diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java index 2088730..29b5a75 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java @@ -63,6 +63,7 @@ import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; import org.apache.jackrabbit.oak.jcr.session.operation.SessionOperation; import org.apache.jackrabbit.oak.jcr.security.AccessManager; import org.apache.jackrabbit.oak.jcr.xml.ImportHandler; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials; import org.apache.jackrabbit.oak.spi.security.authorization.permission.Permissions; import org.slf4j.Logger; @@ -123,21 +124,23 @@ public class SessionImpl implements JackrabbitSession { } @Nonnull - private String getOakPathOrThrow(String absPath) throws RepositoryException { + private Path getOakPathOrThrow(String absPath) throws RepositoryException { String p = sessionContext.getOakPathOrThrow(absPath); - if (!PathUtils.isAbsolute(p)) { + if (p.startsWith("/")) { + return Path.create(p.substring(1)); + } else { throw new RepositoryException("Not an absolute path: " + absPath); } - return p; } @Nonnull - private String getOakPathOrThrowNotFound(String absPath) throws PathNotFoundException { - return sessionContext.getOakPathOrThrowNotFound(absPath); + private Path getOakPathOrThrowNotFound(String absPath) throws PathNotFoundException { + String oakPath = sessionContext.getOakPathOrThrowNotFound(absPath); + return Path.create(oakPath.substring(1)); } @CheckForNull - private ItemImpl getItemInternal(@Nonnull String oakPath) + private ItemImpl getItemInternal(@Nonnull Path oakPath) throws RepositoryException { ItemDelegate item = sd.getItem(oakPath); if (item instanceof NodeDelegate) { @@ -180,7 +183,7 @@ public class SessionImpl implements JackrabbitSession { if (absPath.equals("/")) { return null; } else { - final String oakPath = getOakPathOrThrow(absPath); + final Path oakPath = getOakPathOrThrow(absPath); return perform(new ReadOperation() { @Override public Property perform() throws RepositoryException { @@ -350,8 +353,8 @@ public class SessionImpl implements JackrabbitSession { @Override public void move(String srcAbsPath, final String destAbsPath) throws RepositoryException { checkIndexOnName(sessionContext, destAbsPath); - final String srcOakPath = getOakPathOrThrowNotFound(srcAbsPath); - final String destOakPath = getOakPathOrThrowNotFound(destAbsPath); + final Path srcOakPath = getOakPathOrThrowNotFound(srcAbsPath); + final Path destOakPath = getOakPathOrThrowNotFound(destAbsPath); sd.perform(new WriteOperation() { @Override public void checkPreconditions() throws RepositoryException { @@ -588,7 +591,7 @@ public class SessionImpl implements JackrabbitSession { @Override public boolean hasPermission(String absPath, final String actions) throws RepositoryException { - final String oakPath = getOakPathOrThrow(absPath); + final Path oakPath = getOakPathOrThrow(absPath); return perform(new ReadOperation() { @Override public Boolean perform() throws RepositoryException { diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java index 2d14b3b..aef4a7a 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/version/ReadWriteVersionManager.java @@ -32,6 +32,7 @@ import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; import org.apache.jackrabbit.oak.namepath.NamePathMapper; +import org.apache.jackrabbit.oak.namepath.Path; import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; import org.apache.jackrabbit.oak.plugins.version.ReadOnlyVersionManager; import org.apache.jackrabbit.oak.util.TreeUtil; @@ -133,7 +134,7 @@ public class ReadWriteVersionManager extends ReadOnlyVersionManager { * Performs a checkout on a versionable tree. * * @param workspaceRoot a fresh workspace root without pending changes. - * @param versionablePath the absolute path to the versionable node to check out. + * @param path the path (relative to root) of the versionable node to check out * @throws UnsupportedRepositoryOperationException * if the versionable tree isn't actually * versionable. @@ -144,12 +145,11 @@ public class ReadWriteVersionManager extends ReadOnlyVersionManager { * not absolute. */ public void checkout(@Nonnull Root workspaceRoot, - @Nonnull String versionablePath) + @Nonnull Path path) throws UnsupportedRepositoryOperationException, InvalidItemStateException, RepositoryException { checkState(!workspaceRoot.hasPendingChanges()); - checkArgument(PathUtils.isAbsolute(versionablePath)); - Tree versionable = workspaceRoot.getTree(versionablePath); + Tree versionable = workspaceRoot.getTree(path.toAbsoluteString()); if (!isVersionable(versionable)) { throw new UnsupportedRepositoryOperationException( versionable.getPath() + " is not versionable"); -- 1.8.3.msysgit.0