Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/NullLocation.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.Tree.Status;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\n\n/**\n * This {@code TreeLocation} refers to an invalid location in a tree. That is\n * to a location where no item resides.\n */\nfinal class NullLocation implements TreeLocation {\n static final TreeLocation NULL = new NullLocation();\n\n private NullLocation() {\n }\n\n /**\n * @return {@code NULL}\n */\n @Override\n public TreeLocation getParent() {\n return NULL;\n }\n\n /**\n * @return {@code NULL}\n */\n @Override\n public TreeLocation getChild(String relPath) {\n return NULL;\n }\n\n /**\n * @return {@code false}\n */\n @Override\n public boolean exists() {\n return false;\n }\n\n /**\n * @return {@code null}\n */\n @Override\n public String getPath() {\n return null;\n }\n\n /**\n * @return Always {@code false}.\n */\n @Override\n public boolean remove() {\n return false;\n }\n\n /**\n * @return {@code null}\n */\n @Override\n public Tree getTree() {\n return null;\n }\n\n /**\n * @return {@code null}\n */\n @Override\n public PropertyState getProperty() {\n return null;\n }\n\n /**\n * @return {@code null}\n */\n @Override\n public Status getStatus() {\n return null;\n }\n\n /**\n * @return {@code false}\n */\n @Override\n public boolean set(PropertyState property) {\n return false;\n }\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/NullLocation.java (revision e972da424b3e6acb3250f15abd8d6e020cda7854) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/NullLocation.java (revision ) @@ -22,15 +22,28 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Tree.Status; import org.apache.jackrabbit.oak.api.TreeLocation; +import org.apache.jackrabbit.oak.commons.PathUtils; +import static com.google.common.base.Preconditions.checkArgument; + /** * This {@code TreeLocation} refers to an invalid location in a tree. That is * to a location where no item resides. */ final class NullLocation implements TreeLocation { - static final TreeLocation NULL = new NullLocation(); + public static final NullLocation NULL = new NullLocation(); + private final TreeLocation parent; + private final String name; + + public NullLocation(TreeLocation parent, String name) { + this.parent = parent; + this.name = name; + } + private NullLocation() { + this.parent = this; + this.name = ""; } /** @@ -38,7 +51,7 @@ */ @Override public TreeLocation getParent() { - return NULL; + return parent; } /** @@ -46,9 +59,16 @@ */ @Override public TreeLocation getChild(String relPath) { - return NULL; + checkArgument(!PathUtils.isAbsolute(relPath), "Not a relative path: " + relPath); + + TreeLocation child = this; + for (String e : PathUtils.elements(relPath)) { + child = new NullLocation(child, e); - } + } + return child; + } + /** * @return {@code false} */ @@ -57,12 +77,9 @@ return false; } - /** - * @return {@code null} - */ @Override public String getPath() { - return null; + return parent == this ? "" : PathUtils.concat(parent.getPath(), name); } /** Index: oak-core/src/main/java/org/apache/jackrabbit/oak/api/TreeLocation.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.api;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nonnull;\n\nimport org.apache.jackrabbit.oak.api.Tree.Status;\n\n/**\n * A {@code TreeLocation} denotes a location inside a tree.\n * It can either refer to a inner node (that is a {@link org.apache.jackrabbit.oak.api.Tree})\n * or to a leaf (that is a {@link org.apache.jackrabbit.oak.api.PropertyState}).\n * {@code TreeLocation} instances provide methods for navigating trees. {@code TreeLocation}\n * instances are immutable and navigating a tree always results in new {@code TreeLocation}\n * instances. Navigation never fails. Errors are deferred until the underlying item itself is\n * accessed. That is, if a {@code TreeLocation} points to an item which does not exist or\n * is unavailable otherwise (i.e. due to access control restrictions) accessing the tree\n * will return {@code null} at this point.\n */\npublic interface TreeLocation {\n\n /**\n * Navigate to the parent\n * @return a {@code TreeLocation} for the parent of this location.\n */\n @Nonnull\n TreeLocation getParent();\n\n /**\n * Navigate to a child through a relative path. A relative path consists of a\n * possibly empty lists of names separated by forward slashes.\n * @param relPath relative path to the child\n * @return a {@code TreeLocation} for a child with the given {@code name}.\n */\n @Nonnull\n TreeLocation getChild(String relPath);\n\n /**\n * Determine whether the underlying {@link org.apache.jackrabbit.oak.api.Tree} or\n * {@link org.apache.jackrabbit.oak.api.PropertyState} for this {@code TreeLocation}\n * is available.\n * @return {@code true} if either {@link #getTree()} or {@link #getParent()} is non {@code null}.\n * {@code false} otherwise.\n */\n boolean exists();\n\n /**\n * Get the underlying {@link org.apache.jackrabbit.oak.api.Tree} for this {@code TreeLocation}.\n * @return underlying {@code Tree} instance or {@code null} if not available.\n */\n @CheckForNull\n Tree getTree();\n\n /**\n * Get the underlying {@link org.apache.jackrabbit.oak.api.PropertyState} for this {@code TreeLocation}.\n * @return underlying {@code PropertyState} instance or {@code null} if not available.\n */\n @CheckForNull\n PropertyState getProperty();\n\n /**\n * {@link org.apache.jackrabbit.oak.api.Tree.Status} of the underlying item or {@code null} if no\n * such item exists.\n * @return\n */\n @CheckForNull\n Status getStatus();\n\n /**\n * The path of the underlying item or {@code null} if no such item exists.\n * @return path\n */\n @CheckForNull\n String getPath();\n\n /**\n * Remove the underlying item.\n *\n * @return {@code true} if the item was removed, {@code false} otherwise.\n */\n boolean remove();\n\n /**\n * Set the underlying property of this {@code TreeLocation}. If the underlying item is\n * not a property, this method return {@code false}.\n * @param property The property to set\n * @return {@code true} if the property state was set, {@code false} otherwise.\n */\n boolean set(PropertyState property);\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/api/TreeLocation.java (revision e972da424b3e6acb3250f15abd8d6e020cda7854) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/TreeLocation.java (revision ) @@ -25,8 +25,9 @@ /** * A {@code TreeLocation} denotes a location inside a tree. - * It can either refer to a inner node (that is a {@link org.apache.jackrabbit.oak.api.Tree}) - * or to a leaf (that is a {@link org.apache.jackrabbit.oak.api.PropertyState}). + * It can either refer to a inner node (that is a {@link org.apache.jackrabbit.oak.api.Tree}), + * to a leaf (that is a {@link org.apache.jackrabbit.oak.api.PropertyState}) or to an + * invalid location which refers to neither of the former. * {@code TreeLocation} instances provide methods for navigating trees. {@code TreeLocation} * instances are immutable and navigating a tree always results in new {@code TreeLocation} * instances. Navigation never fails. Errors are deferred until the underlying item itself is @@ -37,7 +38,7 @@ public interface TreeLocation { /** - * Navigate to the parent + * Navigate to the parent or an invalid location for the root of the hierarchy. * @return a {@code TreeLocation} for the parent of this location. */ @Nonnull @@ -76,18 +77,18 @@ PropertyState getProperty(); /** - * {@link org.apache.jackrabbit.oak.api.Tree.Status} of the underlying item or {@code null} if no - * such item exists. - * @return + * Get the {@link org.apache.jackrabbit.oak.api.Tree.Status} of the underlying item for this + * {@code TreeLocation}. + * @return underlying status or {@code null} if not available. */ @CheckForNull Status getStatus(); /** - * The path of the underlying item or {@code null} if no such item exists. + * The path of this location * @return path */ - @CheckForNull + @Nonnull String getPath(); /** Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractPropertyLocation.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements. See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.Tree.Status;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\nimport org.apache.jackrabbit.oak.commons.PathUtils;\n\nimport static com.google.common.base.Preconditions.checkNotNull;\n\n/**\n * AbstractPropertyLocation... TODO\n */\nabstract class AbstractPropertyLocation> implements TreeLocation {\n\n protected final L parentLocation;\n protected final String name;\n\n AbstractPropertyLocation(L parentLocation, String name) {\n this.parentLocation = checkNotNull(parentLocation);\n this.name = checkNotNull(name);\n }\n\n @Override\n public L getParent() {\n return parentLocation;\n }\n\n @Override\n public TreeLocation getChild(String relPath) {\n return NullLocation.NULL;\n }\n\n @Override\n public boolean exists() {\n Status status = getStatus();\n return status != null && status != Status.REMOVED && getProperty() != null;\n }\n\n @Override\n public String getPath() {\n return PathUtils.concat(parentLocation.getPath(), name);\n }\n\n @Override\n public Tree getTree() {\n return null;\n }\n} =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractPropertyLocation.java (revision e972da424b3e6acb3250f15abd8d6e020cda7854) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/AbstractPropertyLocation.java (revision ) @@ -21,6 +21,7 @@ import org.apache.jackrabbit.oak.api.TreeLocation; import org.apache.jackrabbit.oak.commons.PathUtils; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -43,7 +44,14 @@ @Override public TreeLocation getChild(String relPath) { - return NullLocation.NULL; + checkArgument(!PathUtils.isAbsolute(relPath), "Not a relative path: " + relPath); + + TreeLocation child = this; + for (String name : PathUtils.elements(relPath)) { + child = new NullLocation(child, name); + } + + return child; } @Override \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/ReadOnlyTree.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport java.util.Iterator;\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Root;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.commons.PathUtils;\nimport org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\n\npublic class ReadOnlyTree implements Tree {\n\n /**\n * Parent of this tree, {@code null} for the root\n */\n private final ReadOnlyTree parent;\n\n /**\n * Name of this tree\n */\n private final String name;\n\n /**\n * Underlying node state\n */\n private final NodeState state;\n\n public ReadOnlyTree(NodeState rootState) {\n this(null, \"\", rootState);\n }\n\n public ReadOnlyTree(@Nullable ReadOnlyTree parent, @Nonnull String name, @Nonnull NodeState state) {\n this.parent = parent;\n this.name = checkNotNull(name);\n this.state = checkNotNull(state);\n checkArgument(!name.isEmpty() || parent == null);\n }\n\n public static ReadOnlyTree createFromRoot(Root root) {\n if (root instanceof RootImpl) {\n return new ReadOnlyTree(((RootImpl) root).getBaseState());\n } else if (root instanceof ReadOnlyRoot) {\n return ((ReadOnlyRoot) root).getTree(\"/\");\n } else {\n throw new IllegalArgumentException(\"Unsupported Root implementation.\");\n }\n }\n\n @Override\n public String getName() {\n return name;\n }\n\n @Override\n public boolean isRoot() {\n return parent == null;\n }\n\n @Override\n public String getPath() {\n if (isRoot()) {\n // shortcut\n return \"/\";\n }\n\n StringBuilder sb = new StringBuilder();\n buildPath(sb);\n return sb.toString();\n }\n\n private void buildPath(StringBuilder sb) {\n if (parent != null) {\n parent.buildPath(sb);\n sb.append('/').append(name);\n }\n }\n\n @Override\n public Tree getParent() {\n return parent;\n }\n\n @Override\n public PropertyState getProperty(String name) {\n return state.getProperty(name);\n }\n\n @Override\n public Status getPropertyStatus(String name) {\n if (hasProperty(name)) {\n return Status.EXISTING;\n } else {\n return null;\n }\n }\n\n @Override\n public boolean hasProperty(String name) {\n return state.getProperty(name) != null;\n }\n\n @Override\n public long getPropertyCount() {\n return state.getPropertyCount();\n }\n\n @Override\n public Iterable getProperties() {\n return state.getProperties();\n }\n\n @Override\n public ReadOnlyTree getChild(String name) {\n NodeState child = state.getChildNode(name);\n if (child != null) {\n return new ReadOnlyTree(this, name, child);\n } else {\n return null;\n }\n }\n\n @Override\n public Status getStatus() {\n return Status.EXISTING;\n }\n\n @Override\n public TreeLocation getLocation() {\n return new NodeLocation(this);\n }\n\n @Override\n public boolean hasChild(String name) {\n return state.getChildNode(name) != null;\n }\n\n @Override\n public long getChildrenCount() {\n return state.getChildNodeCount();\n }\n\n /**\n * This implementation does not respect ordered child nodes, but always\n * returns them in some implementation specific order.\n *

\n * TODO: respect orderable children (needed?)\n *\n * @return the children.\n */\n @Override\n public Iterable getChildren() {\n return new Iterable() {\n @Override\n public Iterator iterator() {\n final Iterator iterator =\n state.getChildNodeEntries().iterator();\n return new Iterator() {\n @Override\n public boolean hasNext() {\n return iterator.hasNext();\n }\n\n @Override\n public Tree next() {\n ChildNodeEntry entry = iterator.next();\n return new ReadOnlyTree(\n ReadOnlyTree.this,\n entry.getName(), entry.getNodeState());\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n };\n }\n };\n }\n\n @Override\n public Tree addChild(String name) {\n throw new UnsupportedOperationException();\n }\n\n @Override\n public boolean remove() {\n throw new UnsupportedOperationException();\n }\n\n @Override\n public boolean orderBefore(String name) {\n throw new UnsupportedOperationException();\n }\n\n @Override\n public void setProperty(PropertyState property) {\n throw new UnsupportedOperationException();\n }\n\n @Override\n public void setProperty(String name, T value) {\n throw new UnsupportedOperationException();\n }\n\n @Override\n public void setProperty(String name, T value, Type type) {\n throw new UnsupportedOperationException();\n }\n\n @Override\n public void removeProperty(String name) {\n throw new UnsupportedOperationException();\n }\n\n //-------------------------------------------------------< TreeLocation >---\n\n private final class NodeLocation extends AbstractNodeLocation {\n\n private NodeLocation(ReadOnlyTree tree) {\n super(tree);\n }\n\n @Override\n public TreeLocation getParent() {\n return tree.parent == null\n ? NullLocation.NULL\n : new NodeLocation(tree.parent);\n }\n\n @Override\n public TreeLocation getChild(String relPath) {\n checkArgument(!relPath.startsWith(\"/\"));\n if (relPath.isEmpty()) {\n return this;\n }\n\n ReadOnlyTree child = tree;\n String parentPath = PathUtils.getParentPath(relPath);\n for (String name : PathUtils.elements(parentPath)) {\n child = child.getChild(name);\n if (child == null) {\n return NullLocation.NULL;\n }\n }\n\n String name = PathUtils.getName(relPath);\n PropertyState property = child.getProperty(name);\n if (property != null) {\n return new PropertyLocation(new NodeLocation(child), name);\n } else {\n child = child.getChild(name);\n return child == null\n ? NullLocation.NULL\n : new NodeLocation(child);\n }\n }\n\n @Override\n public boolean remove() {\n return false;\n }\n\n @Override\n public Tree getTree() {\n return tree;\n }\n\n @Override\n public Status getStatus() {\n return tree.getStatus();\n }\n }\n\n private final class PropertyLocation extends AbstractPropertyLocation {\n\n private PropertyLocation(NodeLocation parentLocation, String name) {\n super(parentLocation, name);\n }\n\n @Override\n public boolean remove() {\n return false;\n }\n\n @Override\n public PropertyState getProperty() {\n return parentLocation.tree.getProperty(name);\n }\n\n @Override\n public Status getStatus() {\n return Status.EXISTING;\n }\n\n @Override\n public boolean set(PropertyState property) {\n return false;\n }\n }\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/ReadOnlyTree.java (revision e972da424b3e6acb3250f15abd8d6e020cda7854) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/ReadOnlyTree.java (revision ) @@ -255,29 +255,33 @@ @Override public TreeLocation getChild(String relPath) { - checkArgument(!relPath.startsWith("/")); + checkArgument(!PathUtils.isAbsolute(relPath), "Not a relative path: " + relPath); if (relPath.isEmpty()) { return this; } - ReadOnlyTree child = tree; - String parentPath = PathUtils.getParentPath(relPath); - for (String name : PathUtils.elements(parentPath)) { - child = child.getChild(name); - if (child == null) { - return NullLocation.NULL; + int slash = PathUtils.getNextSlash(relPath, 0); + if (slash == -1) { + ReadOnlyTree child = tree.getChild(relPath); + if (child != null) { + return new NodeLocation(child); } + + PropertyState prop = tree.getProperty(relPath); + if (prop != null) { + return new PropertyLocation(this, relPath); - } + } + return new NullLocation(this, relPath); + } + else { + String name = relPath.substring(0, slash); + String tail = relPath.substring(slash + 1, relPath.length()); - String name = PathUtils.getName(relPath); - PropertyState property = child.getProperty(name); - if (property != null) { - return new PropertyLocation(new NodeLocation(child), name); - } else { - child = child.getChild(name); - return child == null - ? NullLocation.NULL - : new NodeLocation(child); + ReadOnlyTree child = tree.getChild(name); + if (child != null) { + return new NodeLocation(child).getChild(tail); + } + return new NullLocation(this, name).getChild(tail); } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.security.AccessController;\nimport java.security.PrivilegedAction;\nimport java.util.Collections;\nimport javax.annotation.Nonnull;\nimport javax.security.auth.Subject;\n\nimport org.apache.jackrabbit.oak.api.Blob;\nimport org.apache.jackrabbit.oak.api.BlobFactory;\nimport org.apache.jackrabbit.oak.api.CommitFailedException;\nimport org.apache.jackrabbit.oak.api.QueryEngine;\nimport org.apache.jackrabbit.oak.api.Root;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\nimport org.apache.jackrabbit.oak.plugins.index.diffindex.UUIDDiffIndexProviderWrapper;\nimport org.apache.jackrabbit.oak.query.QueryEngineImpl;\nimport org.apache.jackrabbit.oak.spi.observation.ChangeExtractor;\nimport org.apache.jackrabbit.oak.spi.query.CompositeQueryIndexProvider;\nimport org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;\nimport org.apache.jackrabbit.oak.spi.security.authorization.AccessControlConfiguration;\nimport org.apache.jackrabbit.oak.spi.security.authorization.OpenAccessControlConfiguration;\nimport org.apache.jackrabbit.oak.spi.security.authorization.PermissionProvider;\nimport org.apache.jackrabbit.oak.spi.security.principal.SystemPrincipal;\nimport org.apache.jackrabbit.oak.spi.state.NodeBuilder;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\nimport org.apache.jackrabbit.oak.spi.state.NodeStateDiff;\nimport org.apache.jackrabbit.oak.spi.state.NodeStore;\nimport org.apache.jackrabbit.oak.spi.state.NodeStoreBranch;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static org.apache.jackrabbit.oak.commons.PathUtils.getName;\nimport static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath;\n\npublic class RootImpl implements Root {\n\n /**\n * Number of {@link #updated} calls for which changes are kept in memory.\n */\n private static final int PURGE_LIMIT = 100;\n\n /**\n * The underlying store to which this root belongs\n */\n private final NodeStore store;\n\n private final Subject subject;\n\n /**\n * The access control context provider.\n */\n private final AccessControlConfiguration accConfiguration;\n\n /**\n * Current branch this root operates on\n */\n private NodeStoreBranch branch;\n\n /**\n * Current root {@code Tree}\n */\n private TreeImpl rootTree;\n\n /**\n * Number of {@link #updated} occurred so since the lase\n * purge.\n */\n private int modCount;\n\n private final QueryIndexProvider indexProvider;\n\n /**\n * New instance bases on a given {@link NodeStore} and a workspace\n *\n * @param store node store\n * @param workspaceName name of the workspace\n * @param subject the subject.\n * @param accConfiguration the access control context provider.\n * @param indexProvider the query index provider.\n */\n @SuppressWarnings(\"UnusedParameters\")\n public RootImpl(NodeStore store,\n String workspaceName,\n Subject subject,\n AccessControlConfiguration accConfiguration,\n QueryIndexProvider indexProvider) {\n this.store = checkNotNull(store);\n this.subject = checkNotNull(subject);\n this.accConfiguration = checkNotNull(accConfiguration);\n this.indexProvider = indexProvider;\n refresh();\n }\n\n // TODO: review if this constructor really makes sense and cannot be replaced.\n public RootImpl(NodeStore store) {\n this.store = checkNotNull(store);\n this.subject = new Subject(true, Collections.singleton(SystemPrincipal.INSTANCE), Collections.emptySet(), Collections.emptySet());\n this.accConfiguration = new OpenAccessControlConfiguration();\n this.indexProvider = new CompositeQueryIndexProvider();\n refresh();\n }\n\n /**\n * Oak level variant of {@link org.apache.jackrabbit.oak.api.ContentSession#getLatestRoot()}\n * to be used when no {@code ContentSession} is available.\n *\n * @return A new Root instance.\n * @see org.apache.jackrabbit.oak.api.ContentSession#getLatestRoot()\n */\n public Root getLatest() {\n checkLive();\n RootImpl root = new RootImpl(store, null, subject, accConfiguration, getIndexProvider()) {\n @Override\n protected void checkLive() {\n RootImpl.this.checkLive();\n }\n };\n return root;\n }\n\n /**\n * Called whenever a method on this instance or on any {@code Tree} instance\n * obtained from this {@code Root} is called. This default implementation\n * does nothing. Sub classes may override this method and throw an exception\n * indicating that this {@code Root} instance is not live anymore (e.g. because\n * the session has been logged out already).\n */\n protected void checkLive() {\n\n }\n\n //---------------------------------------------------------------< Root >---\n @Override\n public boolean move(String sourcePath, String destPath) {\n checkLive();\n TreeImpl source = rootTree.getTree(sourcePath);\n if (source == null) {\n return false;\n }\n TreeImpl destParent = rootTree.getTree(getParentPath(destPath));\n if (destParent == null) {\n return false;\n }\n\n String destName = getName(destPath);\n if (destParent.hasChild(destName)) {\n return false;\n }\n\n purgePendingChanges();\n source.moveTo(destParent, destName);\n boolean success = branch.move(sourcePath, destPath);\n reset();\n if (success) {\n getTree(getParentPath(sourcePath)).updateChildOrder();\n getTree(getParentPath(destPath)).updateChildOrder();\n }\n return success;\n }\n\n @Override\n public boolean copy(String sourcePath, String destPath) {\n checkLive();\n purgePendingChanges();\n boolean success = branch.copy(sourcePath, destPath);\n reset();\n if (success) {\n getTree(getParentPath(destPath)).updateChildOrder();\n }\n return success;\n }\n\n @Override\n public TreeImpl getTree(String path) {\n checkLive();\n return rootTree.getTree(path);\n }\n\n @Override\n public TreeLocation getLocation(String path) {\n checkLive();\n checkArgument(path.startsWith(\"/\"));\n return rootTree.getLocation().getChild(path.substring(1));\n }\n\n @Override\n public void rebase() {\n checkLive();\n if (!store.getRoot().equals(rootTree.getBaseState())) {\n purgePendingChanges();\n branch.rebase();\n rootTree = TreeImpl.createRoot(this);\n }\n }\n\n @Override\n public final void refresh() {\n checkLive();\n branch = store.branch();\n rootTree = TreeImpl.createRoot(this);\n }\n\n @Override\n public void commit() throws CommitFailedException {\n checkLive();\n rebase();\n purgePendingChanges();\n CommitFailedException exception = Subject.doAs(\n getCombinedSubject(), new PrivilegedAction() {\n @Override\n public CommitFailedException run() {\n try {\n branch.merge();\n return null;\n } catch (CommitFailedException e) {\n return e;\n }\n }\n });\n if (exception != null) {\n throw exception;\n }\n refresh();\n }\n\n // TODO: find a better solution for passing in additional principals\n private Subject getCombinedSubject() {\n Subject accSubject = Subject.getSubject(AccessController.getContext());\n if (accSubject == null) {\n return subject;\n } else {\n Subject combinedSubject = new Subject(false,\n subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials());\n combinedSubject.getPrincipals().addAll(accSubject.getPrincipals());\n combinedSubject.getPrivateCredentials().addAll(accSubject.getPrivateCredentials());\n combinedSubject.getPublicCredentials().addAll((accSubject.getPublicCredentials()));\n return combinedSubject;\n }\n }\n\n @Override\n public boolean hasPendingChanges() {\n checkLive();\n return !getBaseState().equals(rootTree.getNodeState());\n }\n\n @Nonnull\n public ChangeExtractor getChangeExtractor() {\n checkLive();\n return new ChangeExtractor() {\n private NodeState baseLine = store.getRoot();\n\n @Override\n public void getChanges(NodeStateDiff diff) {\n NodeState head = store.getRoot();\n head.compareAgainstBaseState(baseLine, diff);\n baseLine = head;\n }\n };\n }\n\n @Override\n public QueryEngine getQueryEngine() {\n checkLive();\n return new QueryEngineImpl(getIndexProvider()) {\n\n @Override\n protected NodeState getRootState() {\n return rootTree.getNodeState();\n }\n\n @Override\n protected Root getRootTree() {\n return RootImpl.this;\n }\n\n };\n }\n\n @Nonnull\n @Override\n public BlobFactory getBlobFactory() {\n checkLive();\n\n return new BlobFactory() {\n @Override\n public Blob createBlob(InputStream inputStream) throws IOException {\n checkLive();\n return store.createBlob(inputStream);\n }\n };\n }\n\n private QueryIndexProvider getIndexProvider() {\n if (hasPendingChanges()) {\n return new UUIDDiffIndexProviderWrapper(indexProvider,\n getBaseState(), rootTree.getNodeState());\n }\n return indexProvider;\n }\n\n //-----------------------------------------------------------< internal >---\n\n /**\n * Returns the node state from which the current branch was created.\n *\n * @return base node state\n */\n @Nonnull\n NodeState getBaseState() {\n return branch.getBase();\n }\n\n NodeBuilder createRootBuilder() {\n return branch.getRoot().builder();\n }\n\n // TODO better way to determine purge limit. See OAK-175\n void updated() {\n if (++modCount > PURGE_LIMIT) {\n modCount = 0;\n purgePendingChanges();\n }\n }\n\n PermissionProvider getPermissionProvider() {\n return accConfiguration.getPermissionProvider(this, subject.getPrincipals());\n }\n\n //------------------------------------------------------------< private >---\n\n /**\n * Purge all pending changes to the underlying {@link NodeStoreBranch}.\n */\n private void purgePendingChanges() {\n branch.setRoot(rootTree.getNodeState());\n reset();\n }\n\n /**\n * Reset the root builder to the branch's current root state\n */\n private void reset() {\n rootTree.getNodeBuilder().reset(branch.getRoot());\n }\n\n}\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision e972da424b3e6acb3250f15abd8d6e020cda7854) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/RootImpl.java (revision ) @@ -32,6 +32,7 @@ import org.apache.jackrabbit.oak.api.QueryEngine; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.TreeLocation; +import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.diffindex.UUIDDiffIndexProviderWrapper; import org.apache.jackrabbit.oak.query.QueryEngineImpl; import org.apache.jackrabbit.oak.spi.observation.ChangeExtractor; @@ -199,7 +200,7 @@ @Override public TreeLocation getLocation(String path) { checkLive(); - checkArgument(path.startsWith("/")); + checkArgument(PathUtils.isAbsolute(path), "Not an absolute path: " + path); return rootTree.getLocation().getChild(path.substring(1)); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP <+>/*\n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * \"License\"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */\npackage org.apache.jackrabbit.oak.core;\n\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.Set;\n\nimport javax.annotation.CheckForNull;\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\nimport com.google.common.base.Function;\nimport com.google.common.base.Predicate;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.Sets;\nimport org.apache.jackrabbit.oak.api.PropertyState;\nimport org.apache.jackrabbit.oak.api.Tree;\nimport org.apache.jackrabbit.oak.api.TreeLocation;\nimport org.apache.jackrabbit.oak.api.Type;\nimport org.apache.jackrabbit.oak.commons.PathUtils;\nimport org.apache.jackrabbit.oak.plugins.memory.MemoryPropertyBuilder;\nimport org.apache.jackrabbit.oak.plugins.memory.MultiStringPropertyState;\nimport org.apache.jackrabbit.oak.spi.state.NodeBuilder;\nimport org.apache.jackrabbit.oak.spi.state.NodeState;\nimport org.apache.jackrabbit.oak.spi.state.NodeStateUtils;\nimport org.apache.jackrabbit.oak.spi.state.PropertyBuilder;\n\nimport static com.google.common.base.Preconditions.checkArgument;\nimport static com.google.common.base.Preconditions.checkNotNull;\nimport static org.apache.jackrabbit.oak.commons.PathUtils.elements;\n\npublic class TreeImpl implements Tree {\n\n /**\n * Internal and hidden property that contains the child order\n */\n public static final String OAK_CHILD_ORDER = \":childOrder\";\n\n /**\n * Underlying {@code Root} of this {@code Tree} instance\n */\n private final RootImpl root;\n\n /**\n * Parent of this tree. Null for the root.\n */\n private TreeImpl parent;\n\n /**\n * Name of this tree\n */\n private String name;\n\n /**\n * Lazily initialised {@code NodeBuilder} for the underlying node state\n */\n NodeBuilder nodeBuilder;\n\n private TreeImpl(RootImpl root, TreeImpl parent, String name) {\n this.root = checkNotNull(root);\n this.parent = parent;\n this.name = checkNotNull(name);\n }\n\n @Nonnull\n static TreeImpl createRoot(final RootImpl root) {\n return new TreeImpl(root, null, \"\") {\n @Override\n protected NodeState getBaseState() {\n return root.getBaseState();\n }\n\n @Override\n protected synchronized NodeBuilder getNodeBuilder() {\n if (nodeBuilder == null) {\n nodeBuilder = root.createRootBuilder();\n }\n return nodeBuilder;\n }\n };\n }\n\n @Override\n public String getName() {\n root.checkLive();\n return name;\n }\n\n @Override\n public boolean isRoot() {\n root.checkLive();\n return parent == null;\n }\n\n @Override\n public String getPath() {\n root.checkLive();\n if (isRoot()) {\n // shortcut\n return \"/\";\n }\n\n StringBuilder sb = new StringBuilder();\n buildPath(sb);\n return sb.toString();\n }\n\n @Override\n public Tree getParent() {\n root.checkLive();\n if (parent != null && canRead(parent)) {\n return parent;\n } else {\n return null;\n }\n }\n\n @Override\n public PropertyState getProperty(String name) {\n root.checkLive();\n PropertyState property = internalGetProperty(name);\n if (canRead(property)) {\n return property;\n } else {\n return null;\n }\n }\n\n @Override\n public Status getPropertyStatus(String name) {\n // TODO: see OAK-212\n root.checkLive();\n Status nodeStatus = getStatus();\n if (nodeStatus == Status.NEW) {\n return (hasProperty(name)) ? Status.NEW : null;\n } else if (nodeStatus == Status.REMOVED) {\n return Status.REMOVED; // FIXME not correct if no property existed with that name\n } else {\n PropertyState head = internalGetProperty(name);\n if (head != null && !canRead(head)) {\n // no permission to read status information for existing property\n return null;\n }\n\n NodeState parentBase = getBaseState();\n PropertyState base = parentBase == null ? null : parentBase.getProperty(name);\n if (head == null) {\n return (base == null) ? null : Status.REMOVED;\n } else {\n if (base == null) {\n return Status.NEW;\n } else if (head.equals(base)) {\n return Status.EXISTING;\n } else {\n return Status.MODIFIED;\n }\n }\n }\n }\n\n @Override\n public boolean hasProperty(String name) {\n root.checkLive();\n return getProperty(name) != null;\n }\n\n @Override\n public long getPropertyCount() {\n root.checkLive();\n return Iterables.size(getProperties());\n }\n\n @Override\n public Iterable getProperties() {\n root.checkLive();\n return Iterables.filter(getNodeBuilder().getProperties(),\n new Predicate() {\n @Override\n public boolean apply(PropertyState propertyState) {\n return canRead(propertyState);\n }\n });\n }\n\n @Override\n public TreeImpl getChild(String name) {\n root.checkLive();\n TreeImpl child = internalGetChild(name);\n if (child != null && canRead(child)) {\n return child;\n } else {\n return null;\n }\n }\n\n private boolean isRemoved() {\n if (isRoot()) {\n return false;\n }\n if (parent.nodeBuilder == null) {\n return false;\n }\n if (parent.nodeBuilder.isRemoved()) {\n return true;\n }\n return getNodeBuilder().isRemoved();\n }\n\n @Override\n public Status getStatus() {\n root.checkLive();\n\n if (isRemoved()) {\n return Status.REMOVED;\n }\n\n NodeBuilder builder = getNodeBuilder();\n if (builder.isNew()) {\n return Status.NEW;\n } else if (builder.isModified()) {\n return Status.MODIFIED;\n } else {\n return Status.EXISTING;\n }\n }\n\n @Override\n public boolean hasChild(String name) {\n root.checkLive();\n return getChild(name) != null;\n }\n\n @Override\n public long getChildrenCount() {\n // TODO: make sure cnt respects access control\n root.checkLive();\n return getNodeBuilder().getChildNodeCount();\n }\n\n @Override\n public Iterable getChildren() {\n root.checkLive();\n Iterable childNames;\n if (hasOrderableChildren()) {\n childNames = getOrderedChildNames();\n } else {\n childNames = getNodeBuilder().getChildNodeNames();\n }\n return Iterables.filter(Iterables.transform(\n childNames,\n new Function() {\n @Override\n public Tree apply(String input) {\n return new TreeImpl(root, TreeImpl.this, input);\n }\n }),\n new Predicate() {\n @Override\n public boolean apply(Tree tree) {\n return tree != null && canRead(tree);\n }\n });\n }\n\n @Override\n public Tree addChild(String name) {\n root.checkLive();\n if (!hasChild(name)) {\n getNodeBuilder().child(name);\n if (hasOrderableChildren()) {\n getNodeBuilder().setProperty(\n MemoryPropertyBuilder.copy(Type.STRING, internalGetProperty(OAK_CHILD_ORDER))\n .addValue(name)\n .getPropertyState());\n }\n root.updated();\n }\n\n TreeImpl child = getChild(name);\n assert child != null;\n return child;\n }\n\n @Override\n public boolean remove() {\n root.checkLive();\n if (isRemoved()) {\n throw new IllegalStateException(\"Cannot remove removed tree\");\n }\n\n if (!isRoot() && parent.hasChild(name)) {\n NodeBuilder builder = parent.getNodeBuilder();\n builder.removeNode(name);\n if (parent.hasOrderableChildren()) {\n builder.setProperty(\n MemoryPropertyBuilder.copy(Type.STRING, parent.internalGetProperty(OAK_CHILD_ORDER))\n .removeValue(name)\n .getPropertyState()\n );\n }\n root.updated();\n return true;\n } else {\n return false;\n }\n }\n\n @Override\n public boolean orderBefore(final String name) {\n root.checkLive();\n if (isRoot()) {\n // root does not have siblings\n return false;\n }\n if (name != null && !parent.hasChild(name)) {\n // so such sibling or not accessible\n return false;\n }\n // perform the reorder\n parent.ensureChildOrderProperty();\n // all siblings but not this one\n Iterable filtered = Iterables.filter(\n parent.getOrderedChildNames(),\n new Predicate() {\n @Override\n public boolean apply(@Nullable String input) {\n return !TreeImpl.this.getName().equals(input);\n }\n });\n // create head and tail\n Iterable head;\n Iterable tail;\n if (name == null) {\n head = filtered;\n tail = Collections.emptyList();\n } else {\n int idx = Iterables.indexOf(filtered, new Predicate() {\n @Override\n public boolean apply(@Nullable String input) {\n return name.equals(input);\n }\n });\n head = Iterables.limit(filtered, idx);\n tail = Iterables.skip(filtered, idx);\n }\n // concatenate head, this name and tail\n parent.getNodeBuilder().setProperty(MultiStringPropertyState.stringProperty(OAK_CHILD_ORDER, Iterables.concat(head, Collections.singleton(getName()), tail))\n );\n root.updated();\n return true;\n }\n\n @Override\n public void setProperty(PropertyState property) {\n root.checkLive();\n NodeBuilder builder = getNodeBuilder();\n builder.setProperty(property);\n root.updated();\n }\n\n @Override\n public void setProperty(String name, T value) {\n root.checkLive();\n NodeBuilder builder = getNodeBuilder();\n builder.setProperty(name, value);\n root.updated();\n }\n\n @Override\n public void setProperty(String name, T value, Type type) {\n root.checkLive();\n NodeBuilder builder = getNodeBuilder();\n builder.setProperty(name, value, type);\n root.updated();\n }\n\n @Override\n public void removeProperty(String name) {\n root.checkLive();\n NodeBuilder builder = getNodeBuilder();\n builder.removeProperty(name);\n root.updated();\n }\n\n @Override\n public TreeLocation getLocation() {\n root.checkLive();\n return new NodeLocation(this);\n }\n\n //----------------------------------------------------------< protected >---\n\n @CheckForNull\n protected NodeState getBaseState() {\n if (isRemoved()) {\n throw new IllegalStateException(\"Cannot get the base state of a removed tree\");\n }\n\n NodeState parentBaseState = parent.getBaseState();\n return parentBaseState == null\n ? null\n : parentBaseState.getChildNode(name);\n }\n\n @Nonnull\n protected synchronized NodeBuilder getNodeBuilder() {\n if (nodeBuilder == null) {\n nodeBuilder = parent.getNodeBuilder().child(name);\n }\n return nodeBuilder;\n }\n\n //-----------------------------------------------------------< internal >---\n\n /**\n * Move this tree to the parent at {@code destParent} with the new name\n * {@code destName}.\n *\n * @param destParent new parent for this tree\n * @param destName new name for this tree\n */\n void moveTo(TreeImpl destParent, String destName) {\n if (isRemoved()) {\n throw new IllegalStateException(\"Cannot move removed tree\");\n }\n\n name = destName;\n parent = destParent;\n }\n\n @Nonnull\n NodeState getNodeState() {\n return getNodeBuilder().getNodeState();\n }\n\n /**\n * Get a tree for the tree identified by {@code path}.\n *\n * @param path the path to the child\n * @return a {@link Tree} instance for the child at {@code path} or\n * {@code null} if no such tree exits or if the tree is not accessible.\n */\n @CheckForNull\n TreeImpl getTree(String path) {\n checkArgument(path.startsWith(\"/\"));\n TreeImpl child = this;\n for (String name : elements(path)) {\n child = child.internalGetChild(name);\n if (child == null) {\n return null;\n }\n }\n return (canRead(child)) ? child : null;\n }\n\n /**\n * Update the child order with children that have been removed or added.\n * Added children are appended to the end of the {@link #OAK_CHILD_ORDER}\n * property.\n */\n void updateChildOrder() {\n if (!hasOrderableChildren()) {\n return;\n }\n Set names = Sets.newLinkedHashSet();\n for (String name : getOrderedChildNames()) {\n if (getNodeBuilder().hasChildNode(name)) {\n names.add(name);\n }\n }\n for (String name : getNodeBuilder().getChildNodeNames()) {\n names.add(name);\n }\n PropertyBuilder builder = MemoryPropertyBuilder.array(\n Type.STRING, OAK_CHILD_ORDER);\n builder.setValues(names);\n getNodeBuilder().setProperty(builder.getPropertyState());\n }\n\n //------------------------------------------------------------< private >---\n\n private TreeImpl internalGetChild(String childName) {\n return getNodeBuilder().hasChildNode(childName)\n ? new TreeImpl(root, this, childName)\n : null;\n }\n\n private PropertyState internalGetProperty(String propertyName) {\n return getNodeBuilder().getProperty(propertyName);\n }\n\n private void buildPath(StringBuilder sb) {\n if (!isRoot()) {\n parent.buildPath(sb);\n sb.append('/').append(name);\n }\n }\n\n private boolean canRead(Tree tree) {\n // FIXME: access control eval must have full access to the tree\n // FIXME: special handling for access control item and version content\n return root.getPermissionProvider().canRead(tree);\n }\n\n private boolean canRead(PropertyState property) {\n // FIXME: access control eval must have full access to the tree/property\n // FIXME: special handling for access control item and version content\n return (property != null)\n && root.getPermissionProvider().canRead(this, property)\n && !NodeStateUtils.isHidden(property.getName());\n }\n\n /**\n * @return {@code true} if this tree has orderable children;\n * {@code false} otherwise.\n */\n private boolean hasOrderableChildren() {\n return internalGetProperty(OAK_CHILD_ORDER) != null;\n }\n\n /**\n * Returns the ordered child names. This method must only be called when\n * this tree {@link #hasOrderableChildren()}.\n *\n * @return the ordered child names.\n */\n private Iterable getOrderedChildNames() {\n assert hasOrderableChildren();\n return new Iterable() {\n @Override\n public Iterator iterator() {\n return new Iterator() {\n final PropertyState childOrder = internalGetProperty(OAK_CHILD_ORDER);\n int index = 0;\n\n @Override\n public boolean hasNext() {\n return index < childOrder.count();\n }\n\n @Override\n public String next() {\n return childOrder.getValue(Type.STRING, index++);\n }\n\n @Override\n public void remove() {\n throw new UnsupportedOperationException();\n }\n };\n }\n };\n }\n\n /**\n * Ensures that the {@link #OAK_CHILD_ORDER} exists. This method will create\n * the property if it doesn't exist and initialize the value with the names\n * of the children as returned by {@link NodeBuilder#getChildNodeNames()}.\n */\n private void ensureChildOrderProperty() {\n PropertyState childOrder = getNodeBuilder().getProperty(OAK_CHILD_ORDER);\n if (childOrder == null) {\n getNodeBuilder().setProperty(\n MultiStringPropertyState.stringProperty(OAK_CHILD_ORDER, getNodeBuilder().getChildNodeNames()));\n }\n }\n\n //-------------------------------------------------------< TreeLocation >---\n\n private final class NodeLocation extends AbstractNodeLocation {\n\n private NodeLocation(TreeImpl tree) {\n super(tree);\n }\n\n @Override\n public TreeLocation getParent() {\n return tree.parent == null\n ? NullLocation.NULL\n : new NodeLocation(tree.parent);\n }\n\n @Override\n public TreeLocation getChild(String relPath) {\n checkArgument(!relPath.startsWith(\"/\"));\n if (relPath.isEmpty()) {\n return this;\n }\n\n TreeImpl child = tree;\n String parentPath = PathUtils.getParentPath(relPath);\n for (String name : PathUtils.elements(parentPath)) {\n child = child.internalGetChild(name);\n if (child == null) {\n return NullLocation.NULL;\n }\n }\n\n String name = PathUtils.getName(relPath);\n PropertyState property = child.internalGetProperty(name);\n if (property != null) {\n return new PropertyLocation(new NodeLocation(child), name);\n } else {\n child = child.internalGetChild(name);\n return child == null\n ? NullLocation.NULL\n : new NodeLocation(child);\n }\n }\n\n @Override\n public boolean remove() {\n return tree.remove();\n }\n\n @Override\n public Tree getTree() {\n return canRead(tree) ? tree : null;\n }\n }\n\n private final class PropertyLocation extends AbstractPropertyLocation {\n\n private PropertyLocation(NodeLocation parentLocation, String name) {\n super(parentLocation, name);\n }\n\n @Override\n public PropertyState getProperty() {\n PropertyState property = parentLocation.tree.internalGetProperty(name);\n return canRead(property)\n ? property\n : null;\n }\n\n @Override\n public Status getStatus() {\n return parentLocation.tree.getPropertyStatus(name);\n }\n\n @Override\n public boolean set(PropertyState property) {\n parentLocation.tree.setProperty(property);\n return true;\n }\n\n /**\n * Remove the underlying property\n *\n * @return {@code true} on success false otherwise\n */\n @Override\n public boolean remove() {\n parentLocation.tree.removeProperty(name);\n return true;\n }\n }\n\n}\n\n\n =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java (revision e972da424b3e6acb3250f15abd8d6e020cda7854) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/core/TreeImpl.java (revision ) @@ -599,29 +599,33 @@ @Override public TreeLocation getChild(String relPath) { - checkArgument(!relPath.startsWith("/")); + checkArgument(!PathUtils.isAbsolute(relPath), "Not a relative path: " + relPath); if (relPath.isEmpty()) { return this; } - TreeImpl child = tree; - String parentPath = PathUtils.getParentPath(relPath); - for (String name : PathUtils.elements(parentPath)) { - child = child.internalGetChild(name); - if (child == null) { - return NullLocation.NULL; + int slash = PathUtils.getNextSlash(relPath, 0); + if (slash == -1) { + TreeImpl child = tree.internalGetChild(relPath); + if (child != null) { + return new NodeLocation(child); } + + PropertyState prop = tree.internalGetProperty(relPath); + if (prop != null) { + return new PropertyLocation(this, relPath); - } + } + return new NullLocation(this, relPath); + } + else { + String name = relPath.substring(0, slash); + String tail = relPath.substring(slash + 1, relPath.length()); - String name = PathUtils.getName(relPath); - PropertyState property = child.internalGetProperty(name); - if (property != null) { - return new PropertyLocation(new NodeLocation(child), name); - } else { - child = child.internalGetChild(name); - return child == null - ? NullLocation.NULL - : new NodeLocation(child); + TreeImpl child = tree.internalGetChild(name); + if (child != null) { + return new NodeLocation(child).getChild(tail); + } + return new NullLocation(this, name).getChild(tail); } } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/core/TreeLocationTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/core/TreeLocationTest.java (revision ) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/core/TreeLocationTest.java (revision ) @@ -0,0 +1,111 @@ +/* + * 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.core; + +import org.apache.jackrabbit.oak.Oak; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.api.Tree; +import org.apache.jackrabbit.oak.api.TreeLocation; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.apache.jackrabbit.oak.core.NullLocation.NULL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class TreeLocationTest { + + private Root root; + + @Before + public void setUp() throws CommitFailedException { + ContentSession session = new Oak().createContentSession(); + + // Add test content + root = session.getLatestRoot(); + Tree tree = root.getTree("/"); + tree.setProperty("a", 1); + tree.setProperty("b", 2); + tree.setProperty("c", 3); + tree.addChild("x"); + tree.addChild("y"); + tree.addChild("z"); + root.commit(); + + // Acquire a fresh new root to avoid problems from lingering state + root = session.getLatestRoot(); + } + + @After + public void tearDown() { + root = null; + } + + @Test + public void testNullLocation() { + TreeLocation xyz = NULL.getChild("x/y/z"); + Assert.assertEquals("x/y/z", xyz.getPath()); + assertEquals("x/y", xyz.getParent().getPath()); + assertEquals("x", xyz.getParent().getParent().getPath()); + assertEquals(NULL, xyz.getParent().getParent().getParent()); + } + + @Test + public void testParentOfRoot() { + TreeLocation rootLocation = root.getLocation("/"); + assertEquals(NULL, rootLocation.getParent()); + } + + @Test + public void testNodeLocation() { + TreeLocation x = root.getLocation("/x"); + assertNotNull(x.getTree()); + + TreeLocation xyz = x.getChild("y/z"); + assertEquals("/x/y/z", xyz.getPath()); + assertNull(xyz.getTree()); + + TreeLocation xy = xyz.getParent(); + assertEquals("/x/y", xy.getPath()); + assertNull(xy.getTree()); + + assertEquals(x.getTree(), xy.getParent().getTree()); + } + + @Test + public void testPropertyLocation() { + TreeLocation a = root.getLocation("/a"); + assertNotNull(a.getProperty()); + + TreeLocation abc = a.getChild("b/c"); + assertEquals("/a/b/c", abc.getPath()); + assertNull(abc.getProperty()); + + TreeLocation ab = abc.getParent(); + assertEquals("/a/b", ab.getPath()); + assertNull(ab.getProperty()); + + assertEquals(a.getProperty(), ab.getParent().getProperty()); + } +}