diff --git a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/PersistentCacheTest.java b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/PersistentCacheTest.java index cf47e13c2f..e03996d14e 100644 --- a/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/PersistentCacheTest.java +++ b/oak-benchmarks/src/main/java/org/apache/jackrabbit/oak/benchmark/PersistentCacheTest.java @@ -26,8 +26,10 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreHelper; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.PathRev; import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.stats.StatisticsProvider; import javax.jcr.Repository; @@ -73,7 +75,9 @@ public class PersistentCacheTest extends AbstractTest { @Override protected void runTest() throws Exception { for (int i = 0; i < ITEMS_TO_ADD; i++) { - PathRev key = PathRev.fromString("/" + timestamp.getAndIncrement() + "@" + new Revision(timestamp.getAndIncrement(), 0, 0)); + Path p = Path.fromString("/" + timestamp.getAndIncrement()); + Revision r = new Revision(timestamp.getAndIncrement(), 0, 0); + PathRev key = new PathRev(p, new RevisionVector(r)); nodesCache.put(key, dns.getRoot()); nodesCache.getIfPresent(key); // read, so the entry is marked as used } diff --git a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java index 074ca483fe..d68320ea29 100644 --- a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java +++ b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreHelper.java @@ -194,13 +194,13 @@ public class DocumentNodeStoreHelper { private static class BlobReferences { - final String path; + final Path path; final long blobSize; final long garbageSize; final int numBlobs; final boolean exists; - public BlobReferences(String path, + public BlobReferences(Path path, long blobSize, int numBlobs, long garbageSize, diff --git a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LsdDocumentCommand.groovy b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LsdDocumentCommand.groovy index b813f3c5c8..55ec16eb84 100644 --- a/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LsdDocumentCommand.groovy +++ b/oak-run/src/main/groovy/org/apache/jackrabbit/oak/console/commands/LsdDocumentCommand.groovy @@ -22,6 +22,7 @@ import groovy.transform.CompileStatic import org.apache.jackrabbit.oak.console.ConsoleSession import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore import org.apache.jackrabbit.oak.plugins.document.NodeDocument +import org.apache.jackrabbit.oak.plugins.document.Path import org.apache.jackrabbit.oak.plugins.document.util.Utils import org.codehaus.groovy.tools.shell.CommandSupport import org.codehaus.groovy.tools.shell.Groovysh @@ -40,7 +41,7 @@ class LsdDocumentCommand extends CommandSupport{ Object execute(List args) { assert session.store instanceof DocumentNodeStore PrintWriter writer = io.out - String path = session.getWorkingPath(); + Path path = Path.fromString(session.getWorkingPath()); String fromKey = Utils.getKeyLowerLimit(path); String toKey = Utils.getKeyUpperLimit(path); int num = 0; diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/NodeStateEntryTraverser.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/NodeStateEntryTraverser.java index e898286c21..0b01fd5940 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/NodeStateEntryTraverser.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/NodeStateEntryTraverser.java @@ -31,6 +31,7 @@ import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentStore; import org.apache.jackrabbit.oak.plugins.document.mongo.MongoDocumentTraverser; @@ -94,14 +95,15 @@ public class NodeStateEntryTraverser implements Iterable, Closea } private boolean includeDoc(NodeDocument doc) { + String path = doc.getPath().toString(); return !doc.isSplitDocument() - && !NodeStateUtils.isHiddenPath(doc.getPath()) - && pathPredicate.test(doc.getPath()); + && !NodeStateUtils.isHiddenPath(path) + && pathPredicate.test(path); } @SuppressWarnings("StaticPseudoFunctionalStyleMethod") private Iterable getEntries(NodeDocument doc) { - String path = doc.getPath(); + Path path = doc.getPath(); DocumentNodeState nodeState = documentNodeStore.getNode(path, rootRevision); @@ -113,7 +115,7 @@ public class NodeStateEntryTraverser implements Iterable, Closea return transform( concat(singleton(nodeState), nodeState.getAllBundledNodesStates()), - dns -> new NodeStateEntry(dns, dns.getPath()) + dns -> new NodeStateEntry(dns, dns.getPath().toString()) ); } diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/SweepHelper.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/SweepHelper.java index 7c2160a64a..f449b20c0d 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/SweepHelper.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/SweepHelper.java @@ -62,7 +62,7 @@ public final class SweepHelper { sweeper.sweep(seeker.getCandidates(sweepRev.get().getTimestamp()), new NodeDocumentSweepListener() { @Override - public void sweepUpdate(Map updates) + public void sweepUpdate(Map updates) throws DocumentStoreException { // create an invalidate entry JournalEntry inv = JOURNAL.newDocument(store); diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java index 07fb41a197..fefd19eda7 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactory.java @@ -20,9 +20,13 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.io.Serializable; +import java.util.Comparator; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.jackrabbit.oak.plugins.document.Path; +import org.apache.jackrabbit.oak.plugins.document.PathComparator; import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.mapdb.BTreeKeySerializer; import org.mapdb.BTreeMap; import org.mapdb.DB; import org.mapdb.DBMaker; @@ -46,11 +50,41 @@ public class MapDBMapFactory extends MapFactory { } @Override - public BTreeMap create() { + public BTreeMap create() { return db.createTreeMap(String.valueOf(counter.incrementAndGet())) + .keySerializer(new PathSerializer()) .valueSerializer(new RevisionSerializer()) .counterEnable() - .makeStringMap(); + .make(); + } + + private static class PathSerializer + extends BTreeKeySerializer + implements Serializable { + + @Override + public void serialize(DataOutput out, int start, int end, Object[] keys) + throws IOException { + for (int i = start; i < end; i++) { + String p = keys[i].toString(); + out.writeUTF(p); + } + } + + @Override + public Object[] deserialize(DataInput in, int start, int end, int size) + throws IOException { + Object[] keys = new Object[size]; + for (int i = start; i < end; i++) { + keys[i] = Path.fromString(in.readUTF()); + } + return keys; + } + + @Override + public Comparator getComparator() { + return PathComparator.INSTANCE; + } } private static class RevisionSerializer implements Serializer, diff --git a/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/DocumentTraverserTest.java b/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/DocumentTraverserTest.java index cc10f58e94..83a6bf5518 100644 --- a/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/DocumentTraverserTest.java +++ b/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/DocumentTraverserTest.java @@ -27,6 +27,7 @@ import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentStoreTest; import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentStoreFixture; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.UpdateOp; import org.apache.jackrabbit.oak.plugins.document.util.CloseableIterable; import org.apache.jackrabbit.oak.plugins.document.util.Utils; @@ -62,6 +63,7 @@ public class DocumentTraverserTest extends AbstractDocumentStoreTest { CloseableIterable itr = traverser.getAllDocuments(Collection.NODES, id -> getPathFromId(id).startsWith("/a")); Set paths = StreamSupport.stream(itr.spliterator(), false) .map(NodeDocument::getPath) + .map(Path::toString) .collect(Collectors.toSet()); itr.close(); diff --git a/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java b/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java new file mode 100644 index 0000000000..cd1d177659 --- /dev/null +++ b/oak-run/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/MapDBMapFactoryTest.java @@ -0,0 +1,42 @@ +/* + * 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.plugins.document.util; + +import java.util.concurrent.ConcurrentMap; + +import org.apache.jackrabbit.oak.plugins.document.Path; +import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MapDBMapFactoryTest { + + @Test + public void mapDB() { + ConcurrentMap map = new MapDBMapFactory().create(); + for (int i = 0; i < 10000; i++) { + map.put(Path.fromString("/some/test/path/node-" + i), new Revision(i, 0, 1)); + } + for (int i = 0; i < 10000; i++) { + assertEquals( + new Revision(i, 0, 1), + map.get(Path.fromString("/some/test/path/node-" + i)) + ); + } + } +} diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/AbstractDocumentNodeState.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/AbstractDocumentNodeState.java index 66868431b7..bfa71c58c0 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/AbstractDocumentNodeState.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/AbstractDocumentNodeState.java @@ -36,7 +36,7 @@ public abstract class AbstractDocumentNodeState extends AbstractNodeState { LoggerFactory.getLogger(AbstractDocumentNodeState.class.getName() + ".perf")); - public abstract String getPath(); + public abstract Path getPath(); public abstract RevisionVector getLastRevision(); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/BlobCollector.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/BlobCollector.java index 17f26eddff..28d1191cec 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/BlobCollector.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/BlobCollector.java @@ -45,7 +45,7 @@ public class BlobCollector { Map valueMap = doc.getLocalMap(key); for (String v : valueMap.values()) { if (v != null) { - loadValue(v, blobs, doc.getPath()); + loadValue(v, blobs, doc.getPath().toString()); } } } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java index aa6511851a..089f3d4dbb 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Branch.java @@ -232,7 +232,7 @@ class Branch { * there is none in this branch. */ @Nullable - public Revision getUnsavedLastRevision(String path, + public Revision getUnsavedLastRevision(Path path, Revision readRevision) { readRevision = readRevision.asBranchRevision(); for (Revision r : commits.descendingKeySet()) { @@ -266,22 +266,22 @@ class Branch { * @return modified paths until {@code r}. * @throws IllegalArgumentException if r is not a branch revision. */ - Iterable getModifiedPathsUntil(@NotNull final Revision r) { + Iterable getModifiedPathsUntil(@NotNull final Revision r) { checkArgument(checkNotNull(r).isBranch(), "Not a branch revision: %s", r); if (!commits.containsKey(r)) { return Collections.emptyList(); } - Iterable> paths = transform(filter(commits.entrySet(), + Iterable> paths = transform(filter(commits.entrySet(), new Predicate>() { @Override public boolean apply(Map.Entry input) { return !input.getValue().isRebase() && input.getKey().compareRevisionTime(r) <= 0; } - }), new Function, Iterable>() { + }), new Function, Iterable>() { @Override - public Iterable apply(Map.Entry input) { + public Iterable apply(Map.Entry input) { return input.getValue().getModifiedPaths(); } }); @@ -310,9 +310,9 @@ class Branch { abstract void applyTo(UnsavedModifications trunk, Revision commit); - abstract boolean isModified(String path); + abstract boolean isModified(Path path); - abstract Iterable getModifiedPaths(); + abstract Iterable getModifiedPaths(); protected abstract boolean isRebase(); } @@ -322,7 +322,7 @@ class Branch { */ private static class BranchCommitImpl extends BranchCommit { - private final Set modifications = Sets.newHashSet(); + private final Set modifications = Sets.newHashSet(); BranchCommitImpl(RevisionVector base, Revision commit) { super(base, commit); @@ -330,18 +330,18 @@ class Branch { @Override void applyTo(UnsavedModifications trunk, Revision commit) { - for (String p : modifications) { + for (Path p : modifications) { trunk.put(p, commit); } } @Override - boolean isModified(String path) { // TODO: rather pass NodeDocument? + boolean isModified(Path path) { // TODO: rather pass NodeDocument? return modifications.contains(path); } @Override - Iterable getModifiedPaths() { + Iterable getModifiedPaths() { return modifications; } @@ -353,7 +353,7 @@ class Branch { //------------------< LastRevTracker >---------------------------------- @Override - public void track(String path) { + public void track(Path path) { modifications.add(path); } @@ -381,7 +381,7 @@ class Branch { } @Override - boolean isModified(String path) { + boolean isModified(Path path) { for (BranchCommit c : previous.values()) { if (c.isModified(path)) { return true; @@ -396,11 +396,11 @@ class Branch { } @Override - Iterable getModifiedPaths() { - Iterable> paths = transform(previous.values(), - new Function>() { + Iterable getModifiedPaths() { + Iterable> paths = transform(previous.values(), + new Function>() { @Override - public Iterable apply(BranchCommit branchCommit) { + public Iterable apply(BranchCommit branchCommit) { return branchCommit.getModifiedPaths(); } }); @@ -426,7 +426,7 @@ class Branch { //------------------< LastRevTracker >---------------------------------- @Override - public void track(String path) { + public void track(Path path) { throw new UnsupportedOperationException("RebaseCommit is read-only"); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Collision.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Collision.java index 237669ebfe..786f64a594 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Collision.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Collision.java @@ -145,8 +145,8 @@ class Collision { @NotNull Revision other, @NotNull DocumentStore store, @NotNull RevisionContext context) { - String p = document.getPath(); - String commitRootPath; + Path p = document.getPath(); + Path commitRootPath; // first check if we can mark the commit with the given revision if (document.containsRevision(revision)) { if (isCommitted(context.getCommitValue(revision, document))) { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java index 288f50bbe5..9c37dde2d3 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Commit.java @@ -62,7 +62,7 @@ public class Commit { protected final DocumentNodeStore nodeStore; private final RevisionVector baseRevision; private final Revision revision; - private final HashMap operations = new LinkedHashMap(); + private final HashMap operations = new LinkedHashMap<>(); private final Set collisions = new LinkedHashSet(); private Branch b; private Rollback rollback = Rollback.NONE; @@ -71,14 +71,14 @@ public class Commit { * List of all node paths which have been modified in this commit. In addition to the nodes * which are actually changed it also contains there parent node paths */ - private final HashSet modifiedNodes = new HashSet(); + private final HashSet modifiedNodes = new HashSet<>(); - private final HashSet addedNodes = new HashSet(); - private final HashSet removedNodes = new HashSet(); + private final HashSet addedNodes = new HashSet<>(); + private final HashSet removedNodes = new HashSet<>(); /** Set of all nodes which have binary properties. **/ - private final HashSet nodesWithBinaries = Sets.newHashSet(); - private final HashMap bundledNodes = Maps.newHashMap(); + private final HashSet nodesWithBinaries = new HashSet<>(); + private final HashMap bundledNodes = new HashMap<>(); /** * Create a new Commit. @@ -99,11 +99,11 @@ public class Commit { Commit(@NotNull DocumentNodeStore nodeStore, @NotNull Revision revision, @Nullable RevisionVector baseRevision, - @NotNull Map operations, - @NotNull Set addedNodes, - @NotNull Set removedNodes, - @NotNull Set nodesWithBinaries, - @NotNull Map bundledNodes) { + @NotNull Map operations, + @NotNull Set addedNodes, + @NotNull Set removedNodes, + @NotNull Set nodesWithBinaries, + @NotNull Map bundledNodes) { this(nodeStore, revision, baseRevision); this.operations.putAll(operations); this.addedNodes.addAll(addedNodes); @@ -112,7 +112,7 @@ public class Commit { this.bundledNodes.putAll(bundledNodes); } - UpdateOp getUpdateOperationForNode(String path) { + UpdateOp getUpdateOperationForNode(Path path) { UpdateOp op = operations.get(path); if (op == null) { op = createUpdateOp(path, revision, isBranchCommit()); @@ -121,7 +121,7 @@ public class Commit { return op; } - static UpdateOp createUpdateOp(String path, + static UpdateOp createUpdateOp(Path path, Revision revision, boolean isBranch) { String id = Utils.getIdFromPath(path); @@ -162,7 +162,7 @@ public class Commit { * modifications. */ @NotNull - Iterable getModifiedPaths() { + Iterable getModifiedPaths() { return modifiedNodes; } @@ -248,7 +248,7 @@ public class Commit { private void updateBinaryStatus() { DocumentStore store = this.nodeStore.getDocumentStore(); - for (String path : this.nodesWithBinaries) { + for (Path path : this.nodesWithBinaries) { NodeDocument nd = store.getIfCached(Collection.NODES, Utils.getIdFromPath(path)); if ((nd == null) || !nd.hasBinary()) { UpdateOp updateParentOp = getUpdateOperationForNode(path); @@ -305,10 +305,10 @@ public class Commit { // the visibility of the commit String commitValue = baseBranchRevision != null ? baseBranchRevision.getBranchRevision().toString() : "c"; DocumentStore store = nodeStore.getDocumentStore(); - String commitRootPath = null; + Path commitRootPath = null; if (baseBranchRevision != null) { // branch commits always use root node as commit root - commitRootPath = "/"; + commitRootPath = Path.ROOT; } ArrayList changedNodes = new ArrayList(); // operations are added to this list before they are executed, @@ -316,23 +316,24 @@ public class Commit { ArrayList opLog = new ArrayList(); // Compute the commit root - for (String p : operations.keySet()) { + for (Path p : operations.keySet()) { markChanged(p); if (commitRootPath == null) { commitRootPath = p; } else { - while (!PathUtils.isAncestor(commitRootPath, p)) { - commitRootPath = PathUtils.getParentPath(commitRootPath); - if (denotesRoot(commitRootPath)) { + while (!commitRootPath.isAncestorOf(p)) { + Path parent = commitRootPath.getParent(); + if (parent == null) { break; } + commitRootPath = parent; } } } rollback = new Rollback(revision, opLog, Utils.getIdFromPath(commitRootPath)); - for (String p : bundledNodes.keySet()){ + for (Path p : bundledNodes.keySet()){ markChanged(p); } @@ -345,7 +346,7 @@ public class Commit { store.create(JOURNAL, singletonList(doc.asUpdateOp(r))); } - int commitRootDepth = PathUtils.getDepth(commitRootPath); + int commitRootDepth = commitRootPath.getDepth(); // check if there are real changes on the commit root boolean commitRootHasChanges = operations.containsKey(commitRootPath); for (UpdateOp op : operations.values()) { @@ -476,14 +477,13 @@ public class Commit { } private void updateParentChildStatus() { - final Set processedParents = Sets.newHashSet(); - for (String path : addedNodes) { - if (denotesRoot(path)) { + final Set processedParents = Sets.newHashSet(); + for (Path path : addedNodes) { + Path parentPath = path.getParent(); + if (parentPath == null) { continue; } - String parentPath = PathUtils.getParentPath(path); - if (processedParents.contains(parentPath)) { continue; } @@ -725,10 +725,10 @@ public class Commit { */ void applyLastRevUpdates(boolean isBranchCommit) { LastRevTracker tracker = nodeStore.createTracker(revision, isBranchCommit); - for (String path : modifiedNodes) { + for (Path path : modifiedNodes) { UpdateOp op = operations.get(path); // track _lastRev only when path is not for a bundled node state - if ((op == null || !hasContentChanges(op) || denotesRoot(path)) + if ((op == null || !hasContentChanges(op) || path.isRoot()) && !isBundled(path)) { // track intermediate node and root tracker.track(path); @@ -743,33 +743,30 @@ public class Commit { * @param isBranchCommit whether this is a commit to a branch */ public void applyToCache(RevisionVector before, boolean isBranchCommit) { - HashMap> nodesWithChangedChildren = new HashMap>(); - for (String p : modifiedNodes) { - if (denotesRoot(p)) { + HashMap> nodesWithChangedChildren = new HashMap<>(); + for (Path p : modifiedNodes) { + if (p.isRoot()) { continue; } - String parent = PathUtils.getParentPath(p); - ArrayList list = nodesWithChangedChildren.get(parent); - if (list == null) { - list = new ArrayList(); - nodesWithChangedChildren.put(parent, list); - } + Path parent = p.getParent(); + ArrayList list = nodesWithChangedChildren + .computeIfAbsent(parent, k -> new ArrayList<>()); list.add(p); } // the commit revision with branch flag if this is a branch commit Revision rev = isBranchCommit ? revision.asBranchRevision() : revision; RevisionVector after = before.update(rev); DiffCache.Entry cacheEntry = nodeStore.getDiffCache().newEntry(before, after, true); - List added = new ArrayList(); - List removed = new ArrayList(); - List changed = new ArrayList(); - for (String path : modifiedNodes) { + List added = new ArrayList<>(); + List removed = new ArrayList<>(); + List changed = new ArrayList<>(); + for (Path path : modifiedNodes) { added.clear(); removed.clear(); changed.clear(); - ArrayList changes = nodesWithChangedChildren.get(path); + ArrayList changes = nodesWithChangedChildren.get(path); if (changes != null) { - for (String s : changes) { + for (Path s : changes) { if (addedNodes.contains(s)) { added.add(s); } else if (removedNodes.contains(s)) { @@ -801,41 +798,38 @@ public class Commit { * @param changed the list of changed child nodes * @param cacheEntry the cache entry changes are added to */ - private void addChangesToDiffCacheEntry(String path, - List added, - List removed, - List changed, + private void addChangesToDiffCacheEntry(Path path, + List added, + List removed, + List changed, DiffCache.Entry cacheEntry) { // update diff cache JsopWriter w = new JsopStream(); - for (String p : added) { - w.tag('+').key(PathUtils.getName(p)).object().endObject(); + for (Path p : added) { + w.tag('+').key(p.getName()).object().endObject(); } - for (String p : removed) { - w.tag('-').value(PathUtils.getName(p)); + for (Path p : removed) { + w.tag('-').value(p.getName()); } - for (String p : changed) { - w.tag('^').key(PathUtils.getName(p)).object().endObject(); + for (Path p : changed) { + w.tag('^').key(p.getName()).object().endObject(); } cacheEntry.append(path, w.toString()); } - private void markChanged(String path) { - if (!denotesRoot(path) && !PathUtils.isAbsolute(path)) { - throw new IllegalArgumentException("path: " + path); - } + private void markChanged(Path path) { while (true) { if (!modifiedNodes.add(path)) { break; } - if (denotesRoot(path)) { + path = path.getParent(); + if (path == null) { break; } - path = PathUtils.getParentPath(path); } } - private boolean isBundled(String path) { + private boolean isBundled(Path path) { return bundledNodes.containsKey(path); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilder.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilder.java index ae577393d0..1f4019b826 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilder.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilder.java @@ -30,6 +30,7 @@ import org.apache.jackrabbit.oak.spi.state.NodeState; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -43,14 +44,14 @@ class CommitBuilder { private final DocumentNodeStore nodeStore; private final Revision revision; private final RevisionVector baseRevision; - private final Map operations = new LinkedHashMap<>(); + private final Map operations = new LinkedHashMap<>(); - private final Set addedNodes = new HashSet<>(); - private final Set removedNodes = new HashSet<>(); + private final Set addedNodes = new HashSet<>(); + private final Set removedNodes = new HashSet<>(); /** Set of all nodes which have binary properties. **/ - private final Set nodesWithBinaries = new HashSet<>(); - private final Map bundledNodes = new HashMap<>(); + private final Set nodesWithBinaries = new HashSet<>(); + private final Map bundledNodes = new HashMap<>(); /** * Creates a new builder with a pseudo commit revision. Building the commit @@ -103,7 +104,7 @@ class CommitBuilder { * @return {@code this} builder. */ @NotNull - CommitBuilder addNode(@NotNull String path) { + CommitBuilder addNode(@NotNull Path path) { addNode(new DocumentNodeState(nodeStore, path, new RevisionVector(revision))); return this; } @@ -121,7 +122,7 @@ class CommitBuilder { throws DocumentStoreException { checkNotNull(node); - String path = node.getPath(); + Path path = node.getPath(); UpdateOp op = node.asOperation(revision); if (operations.containsKey(path)) { String msg = "Node already added: " + path; @@ -144,8 +145,8 @@ class CommitBuilder { * @return {@code this} builder. */ @NotNull - CommitBuilder addBundledNode(@NotNull String path, - @NotNull String bundlingRootPath) { + CommitBuilder addBundledNode(@NotNull Path path, + @NotNull Path bundlingRootPath) { checkNotNull(path); checkNotNull(bundlingRootPath); @@ -163,7 +164,7 @@ class CommitBuilder { * a node at the given {@code path} in this commit builder. */ @NotNull - CommitBuilder removeNode(@NotNull String path, + CommitBuilder removeNode(@NotNull Path path, @NotNull NodeState state) throws DocumentStoreException { checkNotNull(path); @@ -192,7 +193,7 @@ class CommitBuilder { * @return {@code this} builder. */ @NotNull - CommitBuilder updateProperty(@NotNull String path, + CommitBuilder updateProperty(@NotNull Path path, @NotNull String propertyName, @Nullable String value) { checkNotNull(path); @@ -212,7 +213,7 @@ class CommitBuilder { * @return {@code this} builder. */ @NotNull - CommitBuilder markNodeHavingBinary(@NotNull String path) { + CommitBuilder markNodeHavingBinary(@NotNull Path path) { checkNotNull(path); nodesWithBinaries.add(path); @@ -248,7 +249,7 @@ class CommitBuilder { checkNotNull(revision); Revision from = this.revision; - Map operations = Maps.transformValues( + Map operations = Maps.transformValues( this.operations, op -> rewrite(op, from, revision)); return new Commit(nodeStore, revision, baseRevision, operations, addedNodes, removedNodes, nodesWithBinaries, bundledNodes); @@ -266,7 +267,7 @@ class CommitBuilder { //-------------------------< internal >------------------------------------- - private UpdateOp getUpdateOperationForNode(String path) { + private UpdateOp getUpdateOperationForNode(Path path) { UpdateOp op = operations.get(path); if (op == null) { op = createUpdateOp(path, revision, isBranchCommit()); @@ -275,7 +276,7 @@ class CommitBuilder { return op; } - private static UpdateOp createUpdateOp(String path, + private static UpdateOp createUpdateOp(Path path, Revision revision, boolean isBranch) { String id = Utils.getIdFromPath(path); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java index 1ff59dd7ad..01afa8de83 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DiffCache.java @@ -47,12 +47,12 @@ abstract class DiffCache { @Nullable abstract String getChanges(@NotNull RevisionVector from, @NotNull RevisionVector to, - @NotNull String path, + @NotNull Path path, @Nullable Loader loader); /** * Starts a new cache entry for the diff cache. Actual changes are added - * to the entry with the {@link Entry#append(String, String)} method. + * to the entry with the {@link Entry#append(Path, String)} method. * * @param from the from revision. * @param to the to revision. @@ -73,7 +73,7 @@ abstract class DiffCache { /** * Parses the jsop diff returned by - * {@link #getChanges(RevisionVector, RevisionVector, String, Loader)} and reports the + * {@link #getChanges(RevisionVector, RevisionVector, Path, Loader)} and reports the * changes by calling the appropriate methods on {@link Diff}. * * @param jsop the jsop diff to parse. @@ -134,7 +134,7 @@ abstract class DiffCache { * @param path the path of the parent node. * @param changes the child node changes. */ - void append(@NotNull String path, + void append(@NotNull Path path, @NotNull String changes); /** diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentBranchRootNodeState.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentBranchRootNodeState.java index 81f2ad315a..e71b3a8ee1 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentBranchRootNodeState.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentBranchRootNodeState.java @@ -32,11 +32,12 @@ class DocumentBranchRootNodeState extends DocumentNodeState { DocumentBranchRootNodeState(@NotNull DocumentNodeStore store, @NotNull DocumentNodeStoreBranch branch, - @NotNull String path, + @NotNull Path path, @NotNull RevisionVector rootRevision, @Nullable RevisionVector lastRevision, - @NotNull BundlingContext bundlingContext) { - super(store, path, lastRevision, rootRevision, false, bundlingContext); + @NotNull BundlingContext bundlingContext, + int memory) { + super(store, path, lastRevision, rootRevision, false, bundlingContext, memory); this.store = store; this.branch = checkNotNull(branch); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java index 910fd0ccad..659f8371a7 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeState.java @@ -20,14 +20,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.common.collect.TreeTraverser; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.cache.CacheValue; @@ -35,7 +32,6 @@ import org.apache.jackrabbit.oak.commons.json.JsopBuilder; import org.apache.jackrabbit.oak.commons.json.JsopReader; import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; import org.apache.jackrabbit.oak.api.PropertyState; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.commons.json.JsopWriter; import org.apache.jackrabbit.oak.json.JsonSerializer; import org.apache.jackrabbit.oak.plugins.document.bundlor.BundlorUtils; @@ -82,7 +78,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach */ static final int MAX_FETCH_SIZE = INITIAL_FETCH_SIZE << 4; - private final String path; + private final Path path; private final RevisionVector lastRevision; private final RevisionVector rootRevision; private final boolean fromExternalChange; @@ -93,39 +89,42 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach private final BundlingContext bundlingContext; private AbstractDocumentNodeState cachedSecondaryState; + private int memory; DocumentNodeState(@NotNull DocumentNodeStore store, - @NotNull String path, + @NotNull Path path, @NotNull RevisionVector rootRevision) { this(store, path, rootRevision, Collections.emptyList(), false, null); } - DocumentNodeState(@NotNull DocumentNodeStore store, @NotNull String path, + DocumentNodeState(@NotNull DocumentNodeStore store, @NotNull Path path, @NotNull RevisionVector rootRevision, Iterable properties, boolean hasChildren, @Nullable RevisionVector lastRevision) { this(store, path, rootRevision, asMap(properties), - hasChildren, lastRevision, false); + hasChildren, 0, lastRevision, false); } - private DocumentNodeState(@NotNull DocumentNodeStore store, - @NotNull String path, - @NotNull RevisionVector rootRevision, - @NotNull Map properties, - boolean hasChildren, - @Nullable RevisionVector lastRevision, - boolean fromExternalChange) { + public DocumentNodeState(@NotNull DocumentNodeStore store, + @NotNull Path path, + @NotNull RevisionVector rootRevision, + @NotNull Map properties, + boolean hasChildren, + int memory, + @Nullable RevisionVector lastRevision, + boolean fromExternalChange) { this(store, path, lastRevision, rootRevision, - fromExternalChange, createBundlingContext(checkNotNull(properties), hasChildren)); + fromExternalChange, createBundlingContext(checkNotNull(properties), hasChildren), memory); } protected DocumentNodeState(@NotNull DocumentNodeStore store, - @NotNull String path, - @Nullable RevisionVector lastRevision, - @Nullable RevisionVector rootRevision, - boolean fromExternalChange, - BundlingContext bundlingContext) { + @NotNull Path path, + @Nullable RevisionVector lastRevision, + @NotNull RevisionVector rootRevision, + boolean fromExternalChange, + BundlingContext bundlingContext, + int memory) { this.store = checkNotNull(store); this.path = checkNotNull(path); this.rootRevision = checkNotNull(rootRevision); @@ -134,6 +133,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach this.properties = bundlingContext.getProperties(); this.bundlingContext = bundlingContext; this.hasChildren = bundlingContext.hasChildren(); + this.memory = memory; } /** @@ -155,7 +155,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach if (rootRevision.equals(root) && fromExternalChange == externalChange) { return this; } else { - return new DocumentNodeState(store, path, lastRevision, root, externalChange, bundlingContext); + return new DocumentNodeState(store, path, lastRevision, root, externalChange, bundlingContext, memory); } } @@ -165,7 +165,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach */ @NotNull public DocumentNodeState fromExternalChange() { - return new DocumentNodeState(store, path, lastRevision, rootRevision, true, bundlingContext); + return new DocumentNodeState(store, path, lastRevision, rootRevision, true, bundlingContext, memory); } /** @@ -180,9 +180,9 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach */ @NotNull DocumentNodeState asBranchRootState(@NotNull DocumentNodeStoreBranch branch) { - checkState(PathUtils.denotesRoot(path)); + checkState(path.isRoot()); checkState(getRootRevision().isBranch()); - return new DocumentBranchRootNodeState(store, branch, path, rootRevision, lastRevision, bundlingContext); + return new DocumentBranchRootNodeState(store, branch, path, rootRevision, lastRevision, bundlingContext, memory); } /** @@ -210,7 +210,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach } @Override - public String getPath() { + public Path getPath() { return path; } @@ -284,7 +284,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach return bundledChildCount; } - String name = null; + String name = ""; long count = 0; int fetchSize = INITIAL_FETCH_SIZE; long remaining = Math.max(max, 1); // fetch at least once @@ -347,7 +347,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach @NotNull @Override public NodeBuilder builder() { - if ("/".equals(getPath())) { + if (getPath().isRoot()) { if (getRootRevision().isBranch()) { throw new IllegalStateException("Cannot create builder from branched DocumentNodeState"); } else { @@ -416,8 +416,8 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach UpdateOp asOperation(@NotNull Revision revision) { String id = Utils.getIdFromPath(path); UpdateOp op = new UpdateOp(id, true); - if (Utils.isLongPath(path)) { - op.set(NodeDocument.PATH, path); + if (Utils.isIdFromLongPath(id)) { + op.set(NodeDocument.PATH, path.toString()); } NodeDocument.setModified(op, revision); NodeDocument.setDeleted(op, revision, false); @@ -430,36 +430,40 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach @Override public int getMemory() { - long size = 40 // shallow - + (lastRevision != null ? lastRevision.getMemory() : 0) - + rootRevision.getMemory() - + estimateMemoryUsage(path); - // rough approximation for properties - for (Map.Entry entry : bundlingContext.getAllProperties().entrySet()) { - // name - size += estimateMemoryUsage(entry.getKey()); - PropertyState propState = entry.getValue(); - if (propState.getType() != Type.BINARY - && propState.getType() != Type.BINARIES) { - for (int i = 0; i < propState.count(); i++) { - // size() returns length of string - // shallow memory: - // - 8 bytes per reference in values list - // - 48 bytes per string - // double usage per property because of parsed PropertyState - size += (56 + propState.size(i) * 2) * 2; + long size = memory; + if (size == 0) { + size = 40 // shallow + + (lastRevision != null ? lastRevision.getMemory() : 0) + + rootRevision.getMemory() + + path.getMemory(); + // rough approximation for properties + for (Map.Entry entry : bundlingContext.getAllProperties().entrySet()) { + // name + size += estimateMemoryUsage(entry.getKey()); + PropertyState propState = entry.getValue(); + if (propState.getType() != Type.BINARY + && propState.getType() != Type.BINARIES) { + for (int i = 0; i < propState.count(); i++) { + // size() returns length of string + // shallow memory: + // - 8 bytes per reference in values list + // - 48 bytes per string + // double usage per property because of parsed PropertyState + size += (56 + propState.size(i) * 2) * 2; + } + } else { + // calculate size based on blobId value + // referencing the binary in the blob store + // double the size because the parsed PropertyState + // will have a similarly sized blobId as well + size += (long)estimateMemoryUsage(asString(entry.getValue())) * 2; } - } else { - // calculate size based on blobId value - // referencing the binary in the blob store - // double the size because the parsed PropertyState - // will have a similarly sized blobId as well - size += (long)estimateMemoryUsage(asString(entry.getValue())) * 2; } - } - if (size > Integer.MAX_VALUE) { - log.debug("Estimated memory footprint larger than Integer.MAX_VALUE: {}.", size); - size = Integer.MAX_VALUE; + if (size > Integer.MAX_VALUE) { + log.debug("Estimated memory footprint larger than Integer.MAX_VALUE: {}.", size); + size = Integer.MAX_VALUE; + } + memory = (int) size; } return (int) size; } @@ -474,6 +478,19 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach .filter(dns -> !dns.getPath().equals(this.getPath()) ); //Exclude this } + /** + * Returns all properties, including bundled, as Json serialized value. + * + * @return all properties, including bundled. + */ + public Map getAllBundledProperties() { + Map allProps = new HashMap<>(); + for (Map.Entry e : bundlingContext.getAllProperties().entrySet()) { + allProps.put(e.getKey(), asString(e.getValue())); + } + return allProps; + } + //------------------------------< internal >-------------------------------- @Nullable @@ -500,7 +517,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach return null; } - return store.getNode(concat(getPath(), childNodeName), lastRevision); + return store.getNode(new Path(getPath(), childNodeName), lastRevision); } @Nullable @@ -517,13 +534,13 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach * {@code name}. * * @param name the name of the lower bound child node entry (exclusive) or - * {@code null}, if the method should start with the first known - * child node. + * the empty {@code String}, if the method should start with the + * first known child node. * @param limit the maximum number of child node entries to return. * @return the child node entries. */ @NotNull - private Iterable getChildNodeEntries(@Nullable String name, + private Iterable getChildNodeEntries(@NotNull String name, int limit) { Iterable children = store.getChildNodes(this, name, limit); return Iterables.transform(children, new Function() { @@ -533,7 +550,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach @NotNull @Override public String getName() { - return PathUtils.getName(input.getPath()); + return input.getPath().getName(); } @NotNull @@ -554,72 +571,6 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach return builder.build(); } - public String asString() { - JsopWriter json = new JsopBuilder(); - json.key("path").value(path); - json.key("rev").value(rootRevision.toString()); - if (lastRevision != null) { - json.key("lastRev").value(lastRevision.toString()); - } - if (hasChildren) { - json.key("hasChildren").value(true); - } - if (properties.size() > 0) { - json.key("prop").object(); - for (Map.Entry e : bundlingContext.getAllProperties().entrySet()) { - json.key(e.getKey()).value(asString(e.getValue())); - } - json.endObject(); - } - return json.toString(); - } - - public static DocumentNodeState fromString(DocumentNodeStore store, String s) { - JsopTokenizer json = new JsopTokenizer(s); - String path = null; - RevisionVector rootRev = null; - RevisionVector lastRev = null; - boolean hasChildren = false; - HashMap map = new HashMap(); - while (true) { - String k = json.readString(); - json.read(':'); - if ("path".equals(k)) { - path = json.readString(); - } else if ("rev".equals(k)) { - rootRev = RevisionVector.fromString(json.readString()); - } else if ("lastRev".equals(k)) { - lastRev = RevisionVector.fromString(json.readString()); - } else if ("hasChildren".equals(k)) { - hasChildren = json.read() == JsopReader.TRUE; - } else if ("prop".equals(k)) { - json.read('{'); - while (true) { - if (json.matches('}')) { - break; - } - k = json.readString(); - json.read(':'); - String v = json.readString(); - map.put(k, v); - json.matches(','); - } - } - if (json.matches(JsopReader.END)) { - break; - } - json.read(','); - } - List props = Lists.newArrayListWithCapacity(map.size()); - for (Entry e : map.entrySet()) { - String value = e.getValue(); - if (value != null) { - props.add(store.createPropertyState(e.getKey(), value)); - } - } - return new DocumentNodeState(store, path, rootRev, props, hasChildren, lastRev); - } - /** * A list of children for a node. */ @@ -706,7 +657,7 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach private class ChildNodeEntryIterator implements Iterator { - private String previousName; + private String previousName = ""; private Iterator current; private int fetchSize = INITIAL_FETCH_SIZE; private int currentRemaining = fetchSize; @@ -764,11 +715,12 @@ public class DocumentNodeState extends AbstractDocumentNodeState implements Cach private AbstractDocumentNodeState createBundledState(String childNodeName, Matcher child) { return new DocumentNodeState( store, - concat(path, childNodeName), + new Path(path, childNodeName), lastRevision, rootRevision, fromExternalChange, - bundlingContext.childContext(child)); + bundlingContext.childContext(child), + memory); } private Iterator getBundledChildren(){ diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java index 9ffb4403cd..270d0b239a 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateCache.java @@ -25,13 +25,13 @@ import org.jetbrains.annotations.Nullable; public interface DocumentNodeStateCache { DocumentNodeStateCache NOOP = new DocumentNodeStateCache() { @Override - public AbstractDocumentNodeState getDocumentNodeState(String path, @Nullable RevisionVector rootRevision, + public AbstractDocumentNodeState getDocumentNodeState(Path path, @Nullable RevisionVector rootRevision, RevisionVector lastRev) { return null; } @Override - public boolean isCached(String path) { + public boolean isCached(Path path) { return false; } }; @@ -47,14 +47,14 @@ public interface DocumentNodeStateCache { * path is not cached then null would be returned */ @Nullable - AbstractDocumentNodeState getDocumentNodeState(String path, RevisionVector rootRevision, RevisionVector lastRev); + AbstractDocumentNodeState getDocumentNodeState(Path path, RevisionVector rootRevision, RevisionVector lastRev); /** * Determines if given path is cached by this implementation * @param path path to check * @return true if given path is cached */ - boolean isCached(String path); + boolean isCached(Path path); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java index fbfbbd051c..7ba5d0c1cf 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStore.java @@ -21,19 +21,17 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.reverse; import static java.util.Collections.singletonList; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.jackrabbit.oak.api.CommitFailedException.OAK; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; -import static org.apache.jackrabbit.oak.commons.PathUtils.concat; import static org.apache.jackrabbit.oak.plugins.document.Collection.JOURNAL; import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES; import static org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder.MANY_CHILDREN_THRESHOLD; import static org.apache.jackrabbit.oak.plugins.document.NodeDocument.MODIFIED_IN_SECS_RESOLUTION; +import static org.apache.jackrabbit.oak.plugins.document.Path.ROOT; import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key; import static org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation; import static org.apache.jackrabbit.oak.plugins.document.util.Utils.alignWithExternalRevisions; @@ -100,7 +98,6 @@ import org.apache.jackrabbit.oak.commons.json.JsopWriter; import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.cache.CacheStats; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.json.BlobSerializer; import org.apache.jackrabbit.oak.plugins.document.util.LeaseCheckDocumentStoreWrapper; import org.apache.jackrabbit.oak.plugins.document.util.LoggingDocumentStoreWrapper; @@ -386,7 +383,7 @@ public final class DocumentNodeStore * * Key: PathRev, value: Children */ - private final Cache nodeChildrenCache; + private final Cache nodeChildrenCache; private final CacheStats nodeChildrenCacheStats; /** @@ -515,10 +512,10 @@ public final class DocumentNodeStore */ private final Set inDoubtTrunkCommits = Sets.newConcurrentHashSet(); - private final Predicate nodeCachePredicate; + private final Predicate nodeCachePredicate; public DocumentNodeStore(DocumentNodeStoreBuilder builder) { - this.nodeCachePredicate = builder.getNodeCachePredicate(); + this.nodeCachePredicate = builder.getNodeCachePathPredicate(); this.updateLimit = builder.getUpdateLimit(); this.commitValueResolver = new CachingCommitValueResolver( builder.getCommitValueCacheSize(), this::getSweepRevisions); @@ -591,7 +588,7 @@ public final class DocumentNodeStore this.lastRevRecoveryAgent = new LastRevRecoveryAgent(store, this, lastRevSeeker, clusterId -> this.signalClusterStateChange()); this.disableBranches = builder.isDisableBranches(); - this.missing = new DocumentNodeState(this, "MISSING", + this.missing = new DocumentNodeState(this, new Path("missing"), new RevisionVector(new Revision(0, 0, 0))) { @Override public int getMemory() { @@ -612,7 +609,7 @@ public final class DocumentNodeStore diffCache = builder.getDiffCache(this.clusterId); // check if root node exists - NodeDocument rootDoc = store.find(NODES, Utils.getIdFromPath("/")); + NodeDocument rootDoc = store.find(NODES, Utils.getIdFromPath(ROOT)); if (rootDoc == null) { if (readOnlyMode) { throw new DocumentStoreException("Unable to initialized a " + @@ -623,7 +620,7 @@ public final class DocumentNodeStore Revision commitRev = newRevision(); RevisionVector head = new RevisionVector(commitRev); Commit commit = new CommitBuilder(this, commitRev, null) - .addNode("/") + .addNode(ROOT) .build(); try { commit.applyToDocumentStore(); @@ -631,12 +628,12 @@ public final class DocumentNodeStore commit.rollback(); throw new IllegalStateException("Conflict while creating root document", e); } - unsavedLastRevisions.put("/", commitRev); + unsavedLastRevisions.put(ROOT, commitRev); sweepRevisions = sweepRevisions.pmax(head); setRoot(head); // make sure _lastRev is written back to store backgroundWrite(); - rootDoc = store.find(NODES, Utils.getIdFromPath("/")); + rootDoc = store.find(NODES, Utils.getIdFromPath(ROOT)); // at this point the root document must exist if (rootDoc == null) { throw new IllegalStateException("Root document does not exist"); @@ -653,7 +650,7 @@ public final class DocumentNodeStore "missing revision for clusterId " + clusterId + ": " + rootRev); } - unsavedLastRevisions.put("/", initialRev); + unsavedLastRevisions.put(ROOT, initialRev); // set initial sweep revision sweepRevisions = sweepRevisions.pmax(new RevisionVector(initialRev)); if (!readOnlyMode) { @@ -752,7 +749,7 @@ public final class DocumentNodeStore @Override public void headOfQueue(@NotNull Revision revision) { setRoot(getHeadRevision().update(revision)); - unsavedLastRevisions.put(ROOT_PATH, revision); + unsavedLastRevisions.put(ROOT, revision); } }); } catch (DocumentStoreException e) { @@ -1074,11 +1071,11 @@ public final class DocumentNodeStore return nodeCache; } - public Cache getNodeChildrenCache() { + public Cache getNodeChildrenCache() { return nodeChildrenCache; } - public Predicate getNodeCachePredicate() { + public Predicate getNodeCachePredicate() { return nodeCachePredicate; } @@ -1097,7 +1094,7 @@ public final class DocumentNodeStore } void invalidateNodeCache(String path, RevisionVector revision){ - nodeCache.invalidate(new PathRev(path, revision)); + nodeCache.invalidate(new PathRev(Path.fromString(path), revision)); } public int getPendingWriteCount() { @@ -1118,14 +1115,15 @@ public final class DocumentNodeStore } @Nullable - AbstractDocumentNodeState getSecondaryNodeState(@NotNull final String path, + AbstractDocumentNodeState getSecondaryNodeState(@NotNull final Path path, @NotNull final RevisionVector rootRevision, @NotNull final RevisionVector rev) { //Check secondary cache first return nodeStateCache.getDocumentNodeState(path, rootRevision, rev); } - PropertyState createPropertyState(String name, String value){ + @NotNull + public PropertyState createPropertyState(String name, String value){ return new DocumentPropertyState(this, name, checkNotNull(value)); } @@ -1139,8 +1137,8 @@ public final class DocumentNodeStore * given revision. */ @Nullable - public DocumentNodeState getNode(@NotNull final String path, - @NotNull final RevisionVector rev) { + public DocumentNodeState getNode(@NotNull final Path path, + @NotNull final RevisionVector rev) { checkNotNull(rev); checkNotNull(path); final long start = PERFLOG.start(); @@ -1173,16 +1171,16 @@ public final class DocumentNodeStore @NotNull DocumentNodeState.Children getChildren(@NotNull final AbstractDocumentNodeState parent, - @Nullable final String name, - final int limit) + @NotNull final String name, + final int limit) throws DocumentStoreException { if (checkNotNull(parent).hasNoChildren()) { return DocumentNodeState.NO_CHILDREN; } - final String path = checkNotNull(parent).getPath(); + final Path path = checkNotNull(parent).getPath(); final RevisionVector readRevision = parent.getLastRevision(); try { - PathRev key = childNodeCacheKey(path, readRevision, name); + NamePathRev key = childNodeCacheKey(path, readRevision, name); DocumentNodeState.Children children = nodeChildrenCache.get(key, new Callable() { @Override public DocumentNodeState.Children call() throws Exception { @@ -1216,15 +1214,15 @@ public final class DocumentNodeStore * ascending order. * * @param parent the parent node. - * @param name the name of the lower bound child node (exclusive) or - * {@code null} if no lower bound is given. + * @param name the name of the lower bound child node (exclusive) or the + * empty {@code String} if no lower bound is given. * @param limit the maximum number of child nodes to return. * @return the children of {@code parent}. */ - DocumentNodeState.Children readChildren(AbstractDocumentNodeState parent, - String name, int limit) { + DocumentNodeState.Children readChildren(@NotNull AbstractDocumentNodeState parent, + @NotNull String name, int limit) { String queriedName = name; - String path = parent.getPath(); + Path path = parent.getPath(); RevisionVector rev = parent.getLastRevision(); LOG.trace("Reading children for [{}] at rev [{}]", path, rev); Iterable docs; @@ -1238,10 +1236,10 @@ public final class DocumentNodeStore int numReturned = 0; for (NodeDocument doc : docs) { numReturned++; - String p = doc.getPath(); + Path p = doc.getPath(); // remember name of last returned document for // potential next round of readChildDocs() - name = PathUtils.getName(p); + name = p.getName(); // filter out deleted children DocumentNodeState child = getNode(p, rev); if (child == null) { @@ -1249,7 +1247,7 @@ public final class DocumentNodeStore } if (c.children.size() < limit) { // add to children until limit is reached - c.children.add(PathUtils.getName(p)); + c.children.add(p.getName()); } else { // enough collected and we know there are more c.hasMore = true; @@ -1261,7 +1259,7 @@ public final class DocumentNodeStore // fewer documents returned than requested // -> no more documents c.hasMore = false; - if (queriedName == null) { + if (queriedName.isEmpty()) { //we've got to the end of list and we started from the top //This list is complete and can be sorted Collections.sort(c.children); @@ -1279,20 +1277,21 @@ public final class DocumentNodeStore * lower exclusive bound. * * @param path the path of the parent document. - * @param name the lower exclusive bound or {@code null}. + * @param name the name of the lower bound child node (exclusive) or the + * empty {@code String} if no lower bound is given. * @param limit the maximum number of child documents to return. * @return the child documents. */ @NotNull - private Iterable readChildDocs(@NotNull final String path, - @Nullable String name, + private Iterable readChildDocs(@NotNull final Path path, + @NotNull String name, final int limit) { final String to = Utils.getKeyUpperLimit(checkNotNull(path)); final String from; - if (name != null) { - from = Utils.getIdFromPath(concat(path, name)); - } else { + if (name.isEmpty()) { from = Utils.getKeyLowerLimit(path); + } else { + from = Utils.getIdFromPath(new Path(path, name)); } return store.query(Collection.NODES, from, to, limit); } @@ -1302,15 +1301,15 @@ public final class DocumentNodeStore * {@code name} (exclusive). * * @param parent the parent node. - * @param name the name of the lower bound child node (exclusive) or - * {@code null}, if the method should start with the first known - * child node. + * @param name the name of the lower bound child node (exclusive) or the + * empty {@code String}, if the method should start with the + * first known child node. * @param limit the maximum number of child nodes to return. * @return the child nodes. */ @NotNull Iterable getChildNodes(@NotNull final DocumentNodeState parent, - @Nullable final String name, + @NotNull final String name, final int limit) { // Preemptive check. If we know there are no children then // return straight away @@ -1322,7 +1321,7 @@ public final class DocumentNodeStore return transform(getChildren(parent, name, limit).children, new Function() { @Override public DocumentNodeState apply(String input) { - String p = concat(parent.getPath(), input); + Path p = new Path(parent.getPath(), input); DocumentNodeState result = getNode(p, readRevision); if (result == null) { // This is very unexpected situation - parent's child list @@ -1366,7 +1365,7 @@ public final class DocumentNodeStore } @Nullable - DocumentNodeState readNode(String path, RevisionVector readRevision) { + private DocumentNodeState readNode(Path path, RevisionVector readRevision) { final long start = PERFLOG.start(); String id = Utils.getIdFromPath(path); Revision lastRevision = getPendingModifications().get(path); @@ -1400,9 +1399,9 @@ public final class DocumentNodeStore * */ void applyChanges(RevisionVector before, RevisionVector after, - Revision rev, String path, - boolean isNew, List added, - List removed, List changed) { + Revision rev, Path path, + boolean isNew, List added, + List removed, List changed) { if (isNew) { // determine the revision for the nodeChildrenCache entry when // the node is new. Fallback to after revision in case document @@ -1417,18 +1416,18 @@ public final class DocumentNodeStore // this is a leaf node. // check if it has the children flag set if (doc != null && doc.hasChildren()) { - PathRev key = childNodeCacheKey(path, afterLastRev, null); + NamePathRev key = childNodeCacheKey(path, afterLastRev, ""); LOG.debug("nodeChildrenCache.put({},{})", key, "NO_CHILDREN"); nodeChildrenCache.put(key, DocumentNodeState.NO_CHILDREN); } } else { DocumentNodeState.Children c = new DocumentNodeState.Children(); Set set = Sets.newTreeSet(); - for (String p : added) { - set.add(PathUtils.getName(p)); + for (Path p : added) { + set.add(p.getName()); } c.children.addAll(set); - PathRev key = childNodeCacheKey(path, afterLastRev, null); + NamePathRev key = childNodeCacheKey(path, afterLastRev, ""); LOG.debug("nodeChildrenCache.put({},{})", key, c); nodeChildrenCache.put(key, c); } @@ -1437,9 +1436,9 @@ public final class DocumentNodeStore DocumentNodeState beforeState = getRoot(before); // do we have a cached before state that can be used // to calculate the new children? - int depth = PathUtils.getDepth(path); + int depth = path.getDepth(); for (int i = 1; i <= depth && beforeState != null; i++) { - String p = PathUtils.getAncestorPath(path, depth - i); + Path p = path.getAncestor(depth - i); RevisionVector lastRev = beforeState.getLastRevision(); PathRev key = new PathRev(p, lastRev); beforeState = nodeCache.getIfPresent(key); @@ -1447,10 +1446,10 @@ public final class DocumentNodeStore // This is unexpected. The before state should exist. // Invalidate the relevant cache entries. (OAK-6294) LOG.warn("Before state is missing {}. Invalidating " + - "affected cache entries.", key.asString()); + "affected cache entries.", key); store.invalidateCache(NODES, Utils.getIdFromPath(p)); nodeCache.invalidate(key); - nodeChildrenCache.invalidate(childNodeCacheKey(path, lastRev, null)); + nodeChildrenCache.invalidate(childNodeCacheKey(path, lastRev, "")); beforeState = null; } } @@ -1459,12 +1458,12 @@ public final class DocumentNodeStore if (beforeState.hasNoChildren()) { children = DocumentNodeState.NO_CHILDREN; } else { - PathRev key = childNodeCacheKey(path, beforeState.getLastRevision(), null); + NamePathRev key = childNodeCacheKey(path, beforeState.getLastRevision(), ""); children = nodeChildrenCache.getIfPresent(key); } } if (children != null) { - PathRev afterKey = new PathRev(path, beforeState.getLastRevision().update(rev)); + NamePathRev afterKey = childNodeCacheKey(path, beforeState.getLastRevision().update(rev), ""); // are there any added or removed children? if (added.isEmpty() && removed.isEmpty()) { // simply use the same list @@ -1473,11 +1472,11 @@ public final class DocumentNodeStore } else if (!children.hasMore){ // list is complete. use before children as basis Set afterChildren = Sets.newTreeSet(children.children); - for (String p : added) { - afterChildren.add(PathUtils.getName(p)); + for (Path p : added) { + afterChildren.add(p.getName()); } - for (String p : removed) { - afterChildren.remove(PathUtils.getName(p)); + for (Path p : removed) { + afterChildren.remove(p.getName()); } DocumentNodeState.Children c = new DocumentNodeState.Children(); c.children.addAll(afterChildren); @@ -1492,8 +1491,8 @@ public final class DocumentNodeStore // incomplete list, but we only removed nodes // use linked hash set to retain order Set afterChildren = Sets.newLinkedHashSet(children.children); - for (String p : removed) { - afterChildren.remove(PathUtils.getName(p)); + for (Path p : removed) { + afterChildren.remove(p.getName()); } DocumentNodeState.Children c = new DocumentNodeState.Children(); c.children.addAll(afterChildren); @@ -1617,7 +1616,7 @@ public final class DocumentNodeStore */ @NotNull DocumentNodeState getRoot(@NotNull RevisionVector revision) { - DocumentNodeState root = getNode("/", revision); + DocumentNodeState root = getNode(ROOT, revision); if (root == null) { throw new IllegalStateException( "root node does not exist at revision " + revision); @@ -1681,9 +1680,9 @@ public final class DocumentNodeStore revs.add(ancestorRev); } revs.addAll(b.getCommits().tailSet(ancestorRev)); - UpdateOp rootOp = new UpdateOp(Utils.getIdFromPath("/"), false); + UpdateOp rootOp = new UpdateOp(Utils.getIdFromPath(ROOT), false); // reset each branch commit in reverse order - Map operations = Maps.newHashMap(); + Map operations = Maps.newHashMap(); AtomicReference currentRev = new AtomicReference<>(); for (Revision r : reverse(revs)) { operations.clear(); @@ -1733,7 +1732,7 @@ public final class DocumentNodeStore MergeCommit commit = newMergeCommit(base, numBranchCommits); try { // make branch commits visible - UpdateOp op = new UpdateOp(Utils.getIdFromPath("/"), false); + UpdateOp op = new UpdateOp(Utils.getIdFromPath(ROOT), false); NodeDocument.setModified(op, commit.getRevision()); if (b != null) { // check the branch age and fail the commit @@ -1817,7 +1816,7 @@ public final class DocumentNodeStore } else { return new LastRevTracker() { @Override - public void track(String path) { + public void track(Path path) { unsavedLastRevisions.put(path, r); } }; @@ -2278,7 +2277,7 @@ public final class DocumentNodeStore // was successful -> apply them to the diff cache try { JournalEntry.applyTo(changedPaths, diffCache, - PathUtils.ROOT_PATH, oldHead, newHead); + ROOT, oldHead, newHead); } catch (Exception e1) { LOG.error("backgroundRead: Exception while processing external changes from journal: " + e1, e1); } @@ -2323,7 +2322,7 @@ public final class DocumentNodeStore while ((b = branches.pollOrphanedBranch()) != null) { LOG.debug("Cleaning up orphaned branch with base revision: {}, " + "commits: {}", b.getBase(), b.getCommits()); - UpdateOp op = new UpdateOp(Utils.getIdFromPath("/"), false); + UpdateOp op = new UpdateOp(Utils.getIdFromPath(ROOT), false); for (Revision r : b.getCommits()) { r = r.asTrunkRevision(); NodeDocument.removeRevision(op, r); @@ -2334,7 +2333,7 @@ public final class DocumentNodeStore } private void cleanRootCollisions() { - String id = Utils.getIdFromPath("/"); + String id = Utils.getIdFromPath(ROOT); NodeDocument root = store.find(NODES, id); if (root != null) { cleanCollisions(root, Integer.MAX_VALUE); @@ -2496,7 +2495,7 @@ public final class DocumentNodeStore Revision newSweepRev = sweeper.sweep(docs, new NodeDocumentSweepListener() { @Override - public void sweepUpdate(final Map updates) + public void sweepUpdate(final Map updates) throws DocumentStoreException { // create a synthetic commit. this commit does not have any // changes, we just use it to create a journal entry for @@ -2523,7 +2522,7 @@ public final class DocumentNodeStore } } - private void writeUpdates(Map updates, + private void writeUpdates(Map updates, Revision revision) throws DocumentStoreException { // create journal entry @@ -2537,7 +2536,7 @@ public final class DocumentNodeStore throw new DocumentStoreException(msg); } changes.invalidate(Collections.singleton(r)); - unsavedLastRevisions.put(ROOT_PATH, revision); + unsavedLastRevisions.put(ROOT, revision); RevisionVector newHead = getHeadRevision().update(revision); setRoot(newHead); commitQueue.headRevisionChanged(); @@ -2774,22 +2773,22 @@ public final class DocumentNodeStore /** * Search for presence of child node as denoted by path in the children cache of parent * - * @param path + * @param path the path of the child node * @param rev revision at which check is performed * @return true if and only if the children cache entry for parent path is complete * and that list does not have the given child node. A false indicates that node might * exist */ - private boolean checkNodeNotExistsFromChildrenCache(String path, + private boolean checkNodeNotExistsFromChildrenCache(Path path, RevisionVector rev) { - if (PathUtils.denotesRoot(path)) { + final Path parentPath = path.getParent(); + if (parentPath == null) { return false; } - final String parentPath = PathUtils.getParentPath(path); - PathRev key = childNodeCacheKey(parentPath, rev, null);//read first child cache entry + NamePathRev key = childNodeCacheKey(parentPath, rev, "");//read first child cache entry DocumentNodeState.Children children = nodeChildrenCache.getIfPresent(key); - String lookupChildName = PathUtils.getName(path); + String lookupChildName = path.getName(); //Does not know about children so cannot say for sure if (children == null) { @@ -2855,8 +2854,8 @@ public final class DocumentNodeStore if (continueDiff) { DocumentNodeState.Children fromChildren, toChildren; - fromChildren = getChildren(from, null, max); - toChildren = getChildren(to, null, max); + fromChildren = getChildren(from, "", max); + toChildren = getChildren(to, "", max); getChildrenDoneIn = debug ? now() : 0; if (!fromChildren.hasMore && !toChildren.hasMore) { @@ -2872,8 +2871,8 @@ public final class DocumentNodeStore } else { diffAlgo = "diffAllChildren"; max = Integer.MAX_VALUE; - fromChildren = getChildren(from, null, max); - toChildren = getChildren(to, null, max); + fromChildren = getChildren(from, "", max); + toChildren = getChildren(to, "", max); diffFewChildren(w, from.getPath(), fromChildren, fromRev, toChildren, toRev); } @@ -2894,7 +2893,7 @@ public final class DocumentNodeStore return diff; } - private void diffManyChildren(JsopWriter w, String path, + private void diffManyChildren(JsopWriter w, Path path, RevisionVector fromRev, RevisionVector toRev) { long minTimestamp = Utils.getMinTimestampForDiff( @@ -2910,7 +2909,7 @@ public final class DocumentNodeStore long minValue = NodeDocument.getModifiedInSecs(minTimestamp); String fromKey = Utils.getKeyLowerLimit(path); String toKey = Utils.getKeyUpperLimit(path); - Set paths = Sets.newHashSet(); + Set paths = Sets.newHashSet(); LOG.debug("diffManyChildren: path: {}, fromRev: {}, toRev: {}", path, fromRev, toRev); @@ -2932,10 +2931,10 @@ public final class DocumentNodeStore } } } - for (String p : paths) { + for (Path p : paths) { DocumentNodeState fromNode = getNode(p, fromRev); DocumentNodeState toNode = getNode(p, toRev); - String name = PathUtils.getName(p); + String name = p.getName(); LOG.trace("diffManyChildren: Changed Path {}", path); @@ -2968,21 +2967,21 @@ public final class DocumentNodeStore } } - private static void addPathsForDiff(String path, - Set paths, - Iterable modified) { - for (String p : modified) { - if (PathUtils.denotesRoot(p)) { + private static void addPathsForDiff(Path path, + Set paths, + Iterable modified) { + for (Path p : modified) { + if (p.isRoot()) { continue; } - String parent = PathUtils.getParentPath(p); + Path parent = p.getParent(); if (path.equals(parent)) { paths.add(p); } } } - private void diffFewChildren(JsopWriter w, String parentPath, + private void diffFewChildren(JsopWriter w, Path parentPath, DocumentNodeState.Children fromChildren, RevisionVector fromRev, DocumentNodeState.Children toChildren, @@ -2992,7 +2991,7 @@ public final class DocumentNodeStore if (!childrenSet.contains(n)) { w.tag('-').value(n); } else { - String path = concat(parentPath, n); + Path path = new Path(parentPath, n); DocumentNodeState n1 = getNode(path, fromRev); DocumentNodeState n2 = getNode(path, toRev); // this is not fully correct: @@ -3014,11 +3013,10 @@ public final class DocumentNodeStore } } - private static PathRev childNodeCacheKey(@NotNull String path, - @NotNull RevisionVector readRevision, - @Nullable String name) { - String p = (name == null ? "" : name) + path; - return new PathRev(p, readRevision); + private static NamePathRev childNodeCacheKey(@NotNull Path path, + @NotNull RevisionVector readRevision, + @NotNull String name) { + return new NamePathRev(name, path, readRevision); } private static DocumentRootBuilder asDocumentRootBuilder(NodeBuilder builder) diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java index 0fbd15f3c9..b3abc6d745 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBuilder.java @@ -16,7 +16,7 @@ */ package org.apache.jackrabbit.oak.plugins.document; -import java.util.EnumMap; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -151,8 +151,7 @@ public class DocumentNodeStoreBuilder> { private CacheStats blobStoreCacheStats; private DocumentStoreStatsCollector documentStoreStatsCollector; private DocumentNodeStoreStatsCollector nodeStoreStatsCollector; - private Map persistentCacheStats = - new EnumMap(CacheType.class); + private Map persistentCacheStats = new HashMap<>(); private boolean bundlingDisabled; private JournalPropertyHandlerFactory journalPropertyHandlerFactory = new JournalPropertyHandlerFactory(); @@ -161,7 +160,7 @@ public class DocumentNodeStoreBuilder> { private long maxRevisionAgeMillis = DEFAULT_JOURNAL_GC_MAX_AGE_MILLIS; private GCMonitor gcMonitor = new LoggingGCMonitor( LoggerFactory.getLogger(VersionGarbageCollector.class)); - private Predicate nodeCachePredicate = Predicates.alwaysTrue(); + private Predicate nodeCachePredicate = Predicates.alwaysTrue(); /** * @return a new {@link DocumentNodeStoreBuilder}. @@ -479,7 +478,7 @@ public class DocumentNodeStoreBuilder> { } @NotNull - public Map getPersistenceCacheStats() { + public Map getPersistenceCacheStats() { return persistentCacheStats; } @@ -591,11 +590,11 @@ public class DocumentNodeStoreBuilder> { return buildCache(CacheType.NODE, getNodeCacheSize(), store, null); } - public Cache buildChildrenCache(DocumentNodeStore store) { + public Cache buildChildrenCache(DocumentNodeStore store) { return buildCache(CacheType.CHILDREN, getChildrenCacheSize(), store, null); } - public Cache buildMemoryDiffCache() { + public Cache buildMemoryDiffCache() { return buildCache(CacheType.DIFF, getMemoryDiffCacheSize(), null, null); } @@ -621,12 +620,29 @@ public class DocumentNodeStoreBuilder> { return new NodeDocumentCache(nodeDocumentsCache, nodeDocumentsCacheStats, prevDocumentsCache, prevDocumentsCacheStats, locks); } + /** + * @deprecated Use {@link #setNodeCachePathPredicate(Predicate)} instead. + */ + @Deprecated public T setNodeCachePredicate(Predicate p){ - this.nodeCachePredicate = p; + this.nodeCachePredicate = input -> input != null && p.apply(input.toString()); return thisBuilder(); } + /** + * @deprecated Use {@link #getNodeCachePathPredicate()} instead. + */ + @Deprecated public Predicate getNodeCachePredicate() { + return input -> input != null && nodeCachePredicate.apply(Path.fromString(input)); + } + + public T setNodeCachePathPredicate(Predicate p){ + this.nodeCachePredicate = p; + return thisBuilder(); + } + + public Predicate getNodeCachePathPredicate() { return nodeCachePredicate; } @@ -654,7 +670,7 @@ public class DocumentNodeStoreBuilder> { } PersistentCacheStats stats = PersistentCache.getPersistentCacheStats(cache); if (stats != null) { - persistentCacheStats.put(cacheType, stats); + persistentCacheStats.put(cacheType.name(), stats); } } return cache; diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java index 84b476248f..139d68dcfc 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreService.java @@ -20,7 +20,6 @@ package org.apache.jackrabbit.oak.plugins.document; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.newArrayList; -import static java.util.Collections.emptyList; import static org.apache.jackrabbit.oak.commons.IOUtils.closeQuietly; import static org.apache.jackrabbit.oak.commons.PropertiesUtil.toLong; import static org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder.DEFAULT_MEMORY_CACHE_SIZE; @@ -79,7 +78,6 @@ import org.apache.jackrabbit.oak.plugins.blob.BlobTrackingStore; import org.apache.jackrabbit.oak.plugins.blob.SharedDataStore; import org.apache.jackrabbit.oak.plugins.blob.datastore.BlobIdTracker; import org.apache.jackrabbit.oak.plugins.blob.datastore.SharedDataStoreUtils; -import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType; import org.apache.jackrabbit.oak.plugins.document.persistentCache.PersistentCacheStats; import org.apache.jackrabbit.oak.plugins.document.util.MongoConnection; import org.apache.jackrabbit.oak.spi.cluster.ClusterRepositoryInfo; @@ -88,7 +86,6 @@ import org.apache.jackrabbit.oak.spi.blob.BlobStoreWrapper; import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore; import org.apache.jackrabbit.oak.spi.blob.stats.BlobStoreStatsMBean; import org.apache.jackrabbit.oak.spi.commit.BackgroundObserverMBean; -import org.apache.jackrabbit.oak.spi.filter.PathFilter; import org.apache.jackrabbit.oak.spi.gc.DelegatingGCMonitor; import org.apache.jackrabbit.oak.spi.gc.GCMonitor; import org.apache.jackrabbit.oak.spi.gc.GCMonitorTracker; @@ -460,7 +457,7 @@ public class DocumentNodeStoreService { setPrefetchExternalChanges(config.prefetchExternalChanges()). setUpdateLimit(config.updateLimit()). setJournalGCMaxAge(config.journalGCMaxAge()). - setNodeCachePredicate(createCachePredicate()); + setNodeCachePathPredicate(createCachePredicate()); if (!Strings.isNullOrEmpty(persistentCache)) { builder.setPersistentCache(persistentCache); @@ -481,7 +478,7 @@ public class DocumentNodeStoreService { return customBlobStore && blobStore instanceof BlobStoreWrapper; } - private Predicate createCachePredicate() { + private Predicate createCachePredicate() { if (config.persistentCacheIncludes().length == 0) { return Predicates.alwaysTrue(); } @@ -489,16 +486,24 @@ public class DocumentNodeStoreService { return Predicates.alwaysTrue(); } - Set paths = new HashSet<>(); + Set paths = new HashSet<>(); for (String p : config.persistentCacheIncludes()) { p = p != null ? Strings.emptyToNull(p.trim()) : null; if (p != null) { - paths.add(p); + paths.add(Path.fromString(p)); } } - PathFilter pf = new PathFilter(paths, emptyList()); log.info("Configuring persistent cache to only cache nodes under paths {}", paths); - return path -> path != null && pf.filter(path) == PathFilter.Result.INCLUDE; + return input -> { + if (input != null) { + for (Path p : paths) { + if (p.isAncestorOf(input)) { + return true; + } + } + } + return false; + }; } private boolean isNodeStoreProvider() { @@ -731,7 +736,7 @@ public class DocumentNodeStoreService { } // register persistent cache stats - Map persistenceCacheStats = mkBuilder.getPersistenceCacheStats(); + Map persistenceCacheStats = mkBuilder.getPersistenceCacheStats(); for (PersistentCacheStats pcs: persistenceCacheStats.values()) { addRegistration( registerMBean(whiteboard, diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ExternalChange.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ExternalChange.java index 8a24cdbe3a..cbaa7a8426 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ExternalChange.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ExternalChange.java @@ -97,7 +97,7 @@ abstract class ExternalChange { Clock clock = store.getClock(); int clusterId = store.getClusterId(); long time = clock.getTime(); - String id = Utils.getIdFromPath("/"); + String id = Utils.getIdFromPath(Path.ROOT); NodeDocument doc = store.getDocumentStore().find(NODES, id, store.getAsyncDelay()); if (doc == null) { return stats; @@ -145,7 +145,7 @@ abstract class ExternalChange { // add changes for this particular clusterId to the externalSort try { fillExternalChanges(externalSort, invalidate, - PathUtils.ROOT_PATH, last, r, + Path.ROOT, last, r, store.getDocumentStore(), journalEntryConsumer, changeSetBuilder, journalPropertyHandler); } catch (Exception e1) { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java index f2091e516a..ff20bd9a48 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalDiffLoader.java @@ -65,13 +65,13 @@ class JournalDiffLoader implements DiffCache.Loader { @Override public String call() { - String path = node.getPath(); RevisionVector afterRev = node.getRootRevision(); RevisionVector beforeRev = base.getRootRevision(); - stats = new Stats(path, beforeRev, afterRev); + stats = new Stats(node.getPath(), beforeRev, afterRev); StringSort changes = JournalEntry.newSorter(); try { + Path path = node.getPath(); readTrunkChanges(path, beforeRev, afterRev, changes); readBranchChanges(path, beforeRev, changes); @@ -80,7 +80,7 @@ class JournalDiffLoader implements DiffCache.Loader { changes.sort(); DiffCache df = ns.getDiffCache(); WrappedDiffCache wrappedCache = new WrappedDiffCache(node.getPath(), df, stats); - JournalEntry.applyTo(changes, wrappedCache, path, beforeRev, afterRev); + JournalEntry.applyTo(changes, wrappedCache, node.getPath(), beforeRev, afterRev); return wrappedCache.changes; } catch (IOException e) { @@ -91,7 +91,7 @@ class JournalDiffLoader implements DiffCache.Loader { } } - private void readBranchChanges(String path, + private void readBranchChanges(Path path, RevisionVector rv, StringSort changes) throws IOException { if (!rv.isBranch() || ns.isDisableBranches()) { @@ -119,7 +119,7 @@ class JournalDiffLoader implements DiffCache.Loader { } } - private void readTrunkChanges(String path, + private void readTrunkChanges(Path path, RevisionVector beforeRev, RevisionVector afterRev, StringSort changes) throws IOException { @@ -214,14 +214,14 @@ class JournalDiffLoader implements DiffCache.Loader { private static class Stats { private final Stopwatch sw = Stopwatch.createStarted(); - private final String path; + private final Path path; private final RevisionVector from, to; private long numJournalEntries; private long numDiffEntries; private long keyMemory; private long valueMemory; - Stats(String path, RevisionVector from, RevisionVector to) { + Stats(Path path, RevisionVector from, RevisionVector to) { this.path = path; this.from = from; this.to = to; @@ -241,12 +241,12 @@ class JournalDiffLoader implements DiffCache.Loader { private static class WrappedDiffCache extends DiffCache { - private final String path; + private final Path path; private String changes = ""; private final DiffCache cache; private Stats stats; - WrappedDiffCache(String path, + WrappedDiffCache(Path path, DiffCache cache, Stats stats) { this.path = path; @@ -262,7 +262,7 @@ class JournalDiffLoader implements DiffCache.Loader { @Override String getChanges(@NotNull RevisionVector from, @NotNull RevisionVector to, - @NotNull String path, + @NotNull Path path, @Nullable Loader loader) { return cache.getChanges(from, to, path, loader); } @@ -275,7 +275,7 @@ class JournalDiffLoader implements DiffCache.Loader { final Entry entry = cache.newEntry(from, to, local); return new Entry() { @Override - public void append(@NotNull String path, + public void append(@NotNull Path path, @NotNull String changes) { trackStats(path, from, to, changes); entry.append(path, changes); @@ -291,12 +291,12 @@ class JournalDiffLoader implements DiffCache.Loader { }; } - private void trackStats(String path, + private void trackStats(Path path, RevisionVector from, RevisionVector to, String changes) { stats.numDiffEntries++; - stats.keyMemory += new StringValue(path).getMemory(); + stats.keyMemory += path.getMemory(); stats.keyMemory += from.getMemory(); stats.keyMemory += to.getMemory(); stats.valueMemory += new StringValue(changes).getMemory(); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java index a5949c29cb..c610d3df41 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/JournalEntry.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.function.Consumer; import com.google.common.collect.AbstractIterator; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -45,8 +46,6 @@ import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; -import static org.apache.jackrabbit.oak.commons.PathUtils.concat; import static org.apache.jackrabbit.oak.plugins.document.Collection.JOURNAL; /** @@ -128,7 +127,7 @@ public final class JournalEntry extends Document { static void applyTo(@NotNull Iterable changedPaths, @NotNull DiffCache diffCache, - @NotNull String path, + @NotNull Path path, @NotNull RevisionVector from, @NotNull RevisionVector to) throws IOException { LOG.debug("applyTo: starting for {} from {} to {}", path, from, to); @@ -206,12 +205,14 @@ public final class JournalEntry extends Document { LOG.debug("applyTo: done. totalCnt: {}, deDuplicatedCnt: {}", totalCnt, deDuplicatedCnt); } - private static boolean inScope(TreeNode node, String path) { - if (PathUtils.denotesRoot(path)) { + private static boolean inScope(TreeNode node, Path path) { + if (path.isRoot()) { return true; } - String p = node.getPath(); - return p.startsWith(path) && (p.length() == path.length() || p.charAt(path.length()) == '/'); + Path p = node.getPath(); + int depthDiff = p.getDepth() - path.getDepth(); + return depthDiff >= 0 + && Iterables.elementsEqual(path.elements(), p.getAncestor(depthDiff).elements()); } /** @@ -238,7 +239,7 @@ public final class JournalEntry extends Document { @NotNull Revision to, @NotNull DocumentStore store) throws IOException { - return fillExternalChanges(externalChanges, invalidate, ROOT_PATH, + return fillExternalChanges(externalChanges, invalidate, Path.ROOT, from, to, store, entry -> {}, null, null); } @@ -270,7 +271,7 @@ public final class JournalEntry extends Document { */ static int fillExternalChanges(@NotNull StringSort externalChanges, @NotNull StringSort invalidate, - @NotNull String path, + @NotNull Path path, @NotNull Revision from, @NotNull Revision to, @NotNull DocumentStore store, @@ -343,7 +344,7 @@ public final class JournalEntry extends Document { private static void fillFromJournalEntry(@NotNull StringSort externalChanges, @NotNull StringSort invalidate, - @NotNull String path, + @NotNull Path path, @Nullable ChangeSetBuilder changeSetBuilder, @Nullable JournalPropertyHandler journalPropertyHandler, @NotNull JournalEntry d, @@ -365,9 +366,9 @@ public final class JournalEntry extends Document { return Long.parseLong(parts[1], 16); } - void modified(String path) { + void modified(Path path) { TreeNode node = getChanges(); - for (String name : PathUtils.elements(path)) { + for (String name : path.elements()) { if (node.get(name) == null) { numChangedNodes++; } @@ -375,8 +376,8 @@ public final class JournalEntry extends Document { } } - void modified(Iterable paths) { - for (String p : paths) { + void modified(Iterable paths) { + for (Path p : paths) { modified(p); } } @@ -489,11 +490,11 @@ public final class JournalEntry extends Document { * @throws IOException if an exception occurs while adding a path to * {@code sort}. In this case only some paths may have been added. */ - void addTo(final StringSort sort, String path) throws IOException { + void addTo(final StringSort sort, Path path) throws IOException { TraversingVisitor v = new TraversingVisitor() { @Override - public void node(TreeNode node, String p) throws IOException { - sort.add(p); + public void node(TreeNode node, Path p) throws IOException { + sort.add(p.toString()); } }; TreeNode n = getNode(path); @@ -538,12 +539,12 @@ public final class JournalEntry extends Document { TraversingVisitor v = new TraversingVisitor() { @Override - public void node(TreeNode node, String path) throws IOException { - sort.add(path); + public void node(TreeNode node, Path path) throws IOException { + sort.add(path.toString()); } }; for (JournalEntry e : getInvalidateOnly()) { - e.getChanges().accept(v, "/"); + e.getChanges().accept(v, Path.ROOT); } } @@ -613,9 +614,9 @@ public final class JournalEntry extends Document { } @Nullable - private TreeNode getNode(String path) { + private TreeNode getNode(Path path) { TreeNode node = getChanges(); - for (String name : PathUtils.elements(path)) { + for (String name : path.elements()) { node = node.get(name); if (node == null) { return null; @@ -692,23 +693,15 @@ public final class JournalEntry extends Document { return n; } - private String getPath() { - return buildPath(new StringBuilder()).toString(); - } - - private StringBuilder buildPath(StringBuilder sb) { + private Path getPath() { + Path p; if (parent != null) { - parent.buildPath(sb); - if (parent.parent != null) { - // only add slash if parent is not the root - sb.append("/"); - } + p = new Path(parent.getPath(), name); } else { // this is the root - sb.append("/"); + p = Path.ROOT; } - sb.append(name); - return sb; + return p; } void parse(JsopReader reader) { @@ -741,10 +734,10 @@ public final class JournalEntry extends Document { return children.get(name); } - void accept(TraversingVisitor visitor, String path) throws IOException { + void accept(TraversingVisitor visitor, Path path) throws IOException { visitor.node(this, path); for (Map.Entry entry : children.entrySet()) { - entry.getValue().accept(visitor, concat(path, entry.getKey())); + entry.getValue().accept(visitor, new Path(path, entry.getKey())); } } @@ -773,7 +766,7 @@ public final class JournalEntry extends Document { private interface TraversingVisitor { - void node(TreeNode node, String path) throws IOException; + void node(TreeNode node, Path path) throws IOException; } private interface MapFactory { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java index 3f65fac95d..b4d63bb260 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevRecoveryAgent.java @@ -22,7 +22,6 @@ import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Maps.filterKeys; import static java.util.Collections.singletonList; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; import static org.apache.jackrabbit.oak.plugins.document.Collection.JOURNAL; import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES; import static org.apache.jackrabbit.oak.plugins.document.util.Utils.PROPERTY_OR_DELETED; @@ -40,7 +39,6 @@ import com.google.common.base.Supplier; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.document.util.MapFactory; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.apache.jackrabbit.oak.stats.Clock; @@ -239,7 +237,7 @@ public class LastRevRecoveryAgent { final NodeDocumentSweeper sweeper = new NodeDocumentSweeper(context, true); sweeper.sweep(suspects, new NodeDocumentSweepListener() { @Override - public void sweepUpdate(Map updates) + public void sweepUpdate(Map updates) throws DocumentStoreException { if (dryRun) { log.info("Dry run of sweeper identified [{}] documents for " + @@ -276,7 +274,7 @@ public class LastRevRecoveryAgent { UnsavedModifications unsavedParents = new UnsavedModifications(); //Map of known last rev of checked paths - Map knownLastRevOrModification = MapFactory.getInstance().create(); + Map knownLastRevOrModification = MapFactory.getInstance().create(); final JournalEntry changes = JOURNAL.newDocument(store); long count = 0; @@ -308,19 +306,19 @@ public class LastRevRecoveryAgent { //2. Update lastRev for parent paths aka rollup if (lastRevForParents != null) { - String path = doc.getPath(); + Path path = doc.getPath(); changes.modified(path); // track all changes while (true) { - if (PathUtils.denotesRoot(path)) { + path = path.getParent(); + if (path == null) { break; } - path = PathUtils.getParentPath(path); unsavedParents.put(path, lastRevForParents); } } } - for (String parentPath : unsavedParents.getPaths()) { + for (Path parentPath : unsavedParents.getPaths()) { Revision calcLastRev = unsavedParents.get(parentPath); Revision knownLastRev = knownLastRevOrModification.get(parentPath); if (knownLastRev == null) { @@ -348,11 +346,11 @@ public class LastRevRecoveryAgent { } if (sweepRev.get() != null) { - unsaved.put(ROOT_PATH, sweepRev.get()); + unsaved.put(Path.ROOT, sweepRev.get()); } // take the root's lastRev - final Revision lastRootRev = unsaved.get(ROOT_PATH); + final Revision lastRootRev = unsaved.get(Path.ROOT); //Note the size before persist as persist operation //would empty the internal state diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevTracker.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevTracker.java index 811ef05de5..1376d81660 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevTracker.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LastRevTracker.java @@ -27,5 +27,5 @@ public interface LastRevTracker { * * @param path the path of the document to update. */ - public void track(String path); + public void track(Path path); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java index 7b83c6f22a..458ab398bd 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCache.java @@ -58,7 +58,7 @@ public class LocalDiffCache extends DiffCache { @Override public String getChanges(@NotNull RevisionVector from, @NotNull RevisionVector to, - @NotNull String path, + @NotNull Path path, @Nullable Loader loader) { RevisionsKey key = new RevisionsKey(from, to); Diff diff = diffCache.getIfPresent(key); @@ -78,14 +78,14 @@ public class LocalDiffCache extends DiffCache { final @NotNull RevisionVector to, boolean local /*ignored*/) { return new Entry() { - private final Map changesPerPath = Maps.newHashMap(); + private final Map changesPerPath = Maps.newHashMap(); private long size; @Override - public void append(@NotNull String path, @NotNull String changes) { + public void append(@NotNull Path path, @NotNull String changes) { if (exceedsSize()){ return; } - size += size(path) + size(changes); + size += path.getMemory() + size(changes); changesPerPath.put(path, changes); } @@ -117,16 +117,16 @@ public class LocalDiffCache extends DiffCache { public static final class Diff implements CacheValue { - private final Map changes; + private final Map changes; private long memory; - public Diff(Map changes, long memory) { + public Diff(Map changes, long memory) { this.changes = changes; this.memory = memory; } public static Diff fromString(String value) { - Map map = Maps.newHashMap(); + Map map = Maps.newHashMap(); JsopReader reader = new JsopTokenizer(value); while (true) { if (reader.matches(JsopReader.END)) { @@ -135,7 +135,7 @@ public class LocalDiffCache extends DiffCache { String k = reader.readString(); reader.read(':'); String v = reader.readString(); - map.put(k, v); + map.put(Path.fromString(k), v); if (reader.matches(JsopReader.END)) { break; } @@ -146,14 +146,14 @@ public class LocalDiffCache extends DiffCache { public String asString(){ JsopBuilder builder = new JsopBuilder(); - for (Map.Entry entry : changes.entrySet()) { - builder.key(entry.getKey()); + for (Map.Entry entry : changes.entrySet()) { + builder.key(entry.getKey().toString()); builder.value(entry.getValue()); } return builder.toString(); } - public Map getChanges() { + public Map getChanges() { return Collections.unmodifiableMap(changes); } @@ -161,8 +161,8 @@ public class LocalDiffCache extends DiffCache { public int getMemory() { if (memory == 0) { long m = 0; - for (Map.Entry e : changes.entrySet()){ - m += size(e.getKey()) + size(e.getValue()); + for (Map.Entry e : changes.entrySet()){ + m += e.getKey().getMemory() + size(e.getValue()); } memory = m; } @@ -174,7 +174,7 @@ public class LocalDiffCache extends DiffCache { } } - String get(String path) { + String get(Path path) { return changes.get(path); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java index 38946e433a..a95587cec7 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCache.java @@ -21,6 +21,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import org.apache.jackrabbit.oak.cache.CacheStats; +import org.apache.jackrabbit.oak.cache.CacheValue; import org.apache.jackrabbit.oak.plugins.document.util.StringValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,9 +31,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot; -import static org.apache.jackrabbit.oak.commons.PathUtils.getName; -import static org.apache.jackrabbit.oak.commons.PathUtils.getParentPath; /** * An in-memory diff cache implementation. @@ -53,7 +51,7 @@ public class MemoryDiffCache extends DiffCache { * * Key: PathRev, value: StringValue */ - protected final Cache diffCache; + protected final Cache diffCache; protected final CacheStats diffCacheStats; @@ -67,9 +65,9 @@ public class MemoryDiffCache extends DiffCache { @Override public String getChanges(@NotNull final RevisionVector from, @NotNull final RevisionVector to, - @NotNull final String path, + @NotNull final Path path, @Nullable final Loader loader) { - PathRev key = diffCacheKey(path, from, to); + Key key = new Key(path, from, to); StringValue diff; if (loader == null) { diff = diffCache.getIfPresent(key); @@ -121,8 +119,8 @@ public class MemoryDiffCache extends DiffCache { } @Override - public void append(@NotNull String path, @NotNull String changes) { - PathRev key = diffCacheKey(path, from, to); + public void append(@NotNull Path path, @NotNull String changes) { + Key key = new Key(path, from, to); if (changes.length() > CACHE_VALUE_LIMIT) { LOG.warn("Not caching entry for {} from {} to {}. Length of changes is {}.", path, from, to, changes.length()); @@ -138,12 +136,6 @@ public class MemoryDiffCache extends DiffCache { } } - private static PathRev diffCacheKey(@NotNull String path, - @NotNull RevisionVector from, - @NotNull RevisionVector to) { - return new PathRev(from + path, to); - } - /** * Returns {@code true} if it can be inferred from cache entries on * ancestors of the given {@code path} that the node was not changed between @@ -159,27 +151,28 @@ public class MemoryDiffCache extends DiffCache { */ private boolean isUnchanged(@NotNull final RevisionVector from, @NotNull final RevisionVector to, - @NotNull final String path) { - return !denotesRoot(path) - && isChildUnchanged(from, to, getParentPath(path), getName(path)); + @NotNull final Path path) { + Path parent = path.getParent(); + return parent != null + && isChildUnchanged(from, to, parent, path.getName()); } private boolean isChildUnchanged(@NotNull final RevisionVector from, @NotNull final RevisionVector to, - @NotNull final String parent, + @NotNull final Path parent, @NotNull final String name) { - PathRev parentKey = diffCacheKey(parent, from, to); + Key parentKey = new Key(parent, from, to); StringValue parentCachedEntry = diffCache.getIfPresent(parentKey); boolean unchanged; if (parentCachedEntry == null) { - if (denotesRoot(parent)) { + if (parent.getParent() == null) { // reached root and we don't know whether name // changed between from and to unchanged = false; } else { // recurse down unchanged = isChildUnchanged(from, to, - getParentPath(parent), getName(parent)); + parent.getParent(), parent.getName()); } } else { unchanged = parseJsopDiff(parentCachedEntry.asString(), new Diff() { @@ -201,4 +194,107 @@ public class MemoryDiffCache extends DiffCache { } return unchanged; } + + public static final class Key implements CacheValue, Comparable { + + private final Path path; + + private final RevisionVector from; + + private final RevisionVector to; + + public Key(@NotNull Path path, + @NotNull RevisionVector from, + @NotNull RevisionVector to) { + this.path = checkNotNull(path); + this.from = checkNotNull(from); + this.to = checkNotNull(to); + } + + @NotNull + public Path getPath() { + return path; + } + + @NotNull + public RevisionVector getFromRevision() { + return from; + } + + @NotNull + public RevisionVector getToRevision() { + return to; + } + + public String asString() { + return toString(); + } + + public static Key fromString(@NotNull String s) { + int idx1 = s.indexOf('/'); + int idx2 = s.lastIndexOf('@'); + if (idx1 == -1 || idx2 == -1) { + throw new IllegalArgumentException("Malformed " + + MemoryDiffCache.Key.class.getSimpleName() + ": " + s); + } + return new Key( + Path.fromString(s.substring(idx1, idx2)), + RevisionVector.fromString(s.substring(0, idx1)), + RevisionVector.fromString(s.substring(idx2 + 1)) + ); + } + + @Override + public int getMemory() { + return 32 + path.getMemory() + from.getMemory() + to.getMemory(); + } + + @Override + public int compareTo(@NotNull MemoryDiffCache.Key other) { + if (this == other) { + return 0; + } + int compare = this.from.compareTo(other.from); + if (compare != 0) { + return compare; + } + compare = this.path.compareTo(other.path); + if (compare != 0) { + return compare; + } + return this.to.compareTo(other.to); + } + + @Override + public String toString() { + int dim = from.getDimensions() + to.getDimensions(); + StringBuilder sb = new StringBuilder(path.length() + (Revision.REV_STRING_APPROX_SIZE + 1) * dim); + from.toStringBuilder(sb); + path.toStringBuilder(sb).append('@'); + to.toStringBuilder(sb); + return sb.toString(); + } + + @Override + public int hashCode() { + int h = 17; + h = 37 * h + path.hashCode(); + h = 37 * h + from.hashCode(); + h = 37 * h + to.hashCode(); + return h; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof Key) { + Key other = (Key) obj; + return from.equals(other.from) + && to.equals(other.to) + && path.equals(other.path); + } + return false; + } + } } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java index 32cad8c983..f12bd56ecf 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/MissingLastRevSeeker.java @@ -39,8 +39,6 @@ import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getSelectedD */ public class MissingLastRevSeeker { - protected final String ROOT_PATH = "/"; - private final DocumentStore store; protected final Clock clock; @@ -125,7 +123,7 @@ public class MissingLastRevSeeker { } public NodeDocument getRoot() { - return store.find(Collection.NODES, Utils.getIdFromPath(ROOT_PATH)); + return store.find(Collection.NODES, Utils.getIdFromPath(Path.ROOT)); } /** diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NamePathRev.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NamePathRev.java new file mode 100644 index 0000000000..345d077931 --- /dev/null +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NamePathRev.java @@ -0,0 +1,131 @@ +/* + * 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.plugins.document; + +import org.apache.jackrabbit.oak.cache.CacheValue; +import org.apache.jackrabbit.oak.commons.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A cache key implementation, which is a combination of a name, path and a + * revision vector. + */ +public final class NamePathRev implements CacheValue, Comparable { + + private static final Logger LOG = LoggerFactory.getLogger(NamePathRev.class); + + @NotNull + private final String name; + + @NotNull + private final Path path; + + @NotNull + private final RevisionVector revision; + + public NamePathRev(@NotNull String name, + @NotNull Path path, + @NotNull RevisionVector revision) { + this.name = checkNotNull(name); + this.path = checkNotNull(path); + this.revision = checkNotNull(revision); + } + + @NotNull + public Path getPath() { + return path; + } + + @NotNull + public String getName() { + return name; + } + + @NotNull + public RevisionVector getRevision() { + return revision; + } + + @Override + public int getMemory() { + long size = 24L // shallow size + + path.getMemory() + + StringUtils.estimateMemoryUsage(name) + + revision.getMemory(); + if (size > Integer.MAX_VALUE) { + LOG.debug("Estimated memory footprint larger than Integer.MAX_VALUE: {}.", size); + size = Integer.MAX_VALUE; + } + return (int) size; + } + + //---------------------------< Comparable >--------------------------------- + + public int compareTo(@NotNull NamePathRev other) { + if (this == other) { + return 0; + } + int compare = this.name.compareTo(other.name); + if (compare != 0) { + return compare; + } + compare = this.path.compareTo(other.path); + if (compare != 0) { + return compare; + } + return this.revision.compareTo(other.revision); + } + + //----------------------------< Object >------------------------------------ + + @Override + public int hashCode() { + int h = 17; + h = 37 * h + name.hashCode(); + h = 37 * h + path.hashCode(); + h = 37 * h + revision.hashCode(); + return h; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof NamePathRev) { + NamePathRev other = (NamePathRev) obj; + return revision.equals(other.revision) + && name.equals(other.name) + && path.equals(other.path); + } + return false; + } + + @Override + public String toString() { + int dim = revision.getDimensions(); + int len = path.length() + name.length(); + StringBuilder sb = new StringBuilder(len + (Revision.REV_STRING_APPROX_SIZE + 1) * dim); + sb.append(name); + path.toStringBuilder(sb).append('@'); + revision.toStringBuilder(sb); + return sb.toString(); + } +} diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java index 4fac646518..a27926fa39 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java @@ -248,6 +248,11 @@ public final class NodeDocument extends Document { */ public static final String SD_MAX_REV_TIME_IN_SECS = "_sdMaxRevTime"; + /** + * The path prefix for previous documents. + */ + private static final Path PREVIOUS_PREFIX = new Path("p"); + /** * Return time in seconds with 5 second resolution * @@ -496,18 +501,17 @@ public final class NodeDocument extends Document { * @return the path of the main document. */ @NotNull - public String getMainPath() { - String p = getPath(); + public Path getMainPath() { + String p = getPathString(); if (p.startsWith("p")) { p = PathUtils.getAncestorPath(p, 2); if (p.length() == 1) { - return "/"; + return Path.ROOT; } else { - return p.substring(1); + p = p.substring(1); } - } else { - return p; } + return Path.fromString(p); } /** @@ -647,7 +651,7 @@ public final class NodeDocument extends Document { * @return the commit root path or null. */ @Nullable - public String getCommitRootPath(Revision revision) { + public Path getCommitRootPath(Revision revision) { String depth = getCommitRootDepth(revision); if (depth != null) { return getPathAtDepth(depth); @@ -917,7 +921,7 @@ public final class NodeDocument extends Document { // deleted return null; } - String path = getPath(); + Path path = getPath(); List props = Lists.newArrayList(); for (String key : keySet()) { if (!Utils.isPropertyName(key)) { @@ -1119,7 +1123,7 @@ public final class NodeDocument extends Document { * @return if conflicting change in {@link #DELETED} property is allowed */ private boolean allowConflictingDeleteChange(UpdateOp op) { - String path = getPath(); + String path = getPathString(); if (!Utils.isHiddenPath(path)) { return false; } @@ -1255,7 +1259,7 @@ public final class NodeDocument extends Document { if (revision == null) { return new PropertyHistory(this, property); } else { - final String mainPath = getMainPath(); + final Path mainPath = getMainPath(); // first try to lookup revision directly Map.Entry entry = getPreviousRanges().floorEntry(revision); if (entry != null) { @@ -1863,7 +1867,7 @@ public final class NodeDocument extends Document { // main document may be stale, evict it from the cache if it is // older than one minute. We don't want to invalidate a document // too frequently if the document structure is really broken. - String path = getMainPath(); + Path path = getMainPath(); String id = Utils.getIdFromPath(path); NodeDocument doc = store.getIfCached(NODES, id); long now = Revision.getCurrentTimestamp(); @@ -1974,7 +1978,7 @@ public final class NodeDocument extends Document { if (getLocalRevisions().containsKey(rev)) { return this; } - String commitRootPath; + Path commitRootPath; String depth = getLocalCommitRoot().get(rev); if (depth != null) { commitRootPath = getPathAtDepth(depth); @@ -2004,12 +2008,12 @@ public final class NodeDocument extends Document { * integer. */ @NotNull - private String getPathAtDepth(@NotNull String depth) { + private Path getPathAtDepth(@NotNull String depth) { if (checkNotNull(depth).equals("0")) { - return "/"; + return Path.ROOT; } - String p = getPath(); - return PathUtils.getAncestorPath(p, PathUtils.getDepth(p) - Integer.parseInt(depth)); + Path p = getPath(); + return p.getAncestor(p.getDepth() - Integer.parseInt(depth)); } /** @@ -2173,7 +2177,13 @@ public final class NodeDocument extends Document { return null; } - public String getPath() { + @NotNull + public Path getPath() { + return Path.fromString(getPathString()); + } + + @NotNull + private String getPathString() { String p = (String) get(PATH); if (p != null) { return p; diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweepListener.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweepListener.java index a75ee916a5..a3e915f2ef 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweepListener.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweepListener.java @@ -32,6 +32,6 @@ interface NodeDocumentSweepListener { * of the documents to update. * @throws DocumentStoreException if the operation fails. */ - void sweepUpdate(Map updates) throws DocumentStoreException; + void sweepUpdate(Map updates) throws DocumentStoreException; } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeper.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeper.java index 5930affb2e..1587d0b532 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeper.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeper.java @@ -137,10 +137,10 @@ final class NodeDocumentSweeper { return null; } - Iterable> ops = sweepOperations(documents); - for (List> batch : partition(ops, INVALIDATE_BATCH_SIZE)) { - Map updates = newHashMap(); - for (Map.Entry entry : batch) { + Iterable> ops = sweepOperations(documents); + for (List> batch : partition(ops, INVALIDATE_BATCH_SIZE)) { + Map updates = newHashMap(); + for (Map.Entry entry : batch) { updates.put(entry.getKey(), entry.getValue()); } listener.sweepUpdate(updates); @@ -149,17 +149,17 @@ final class NodeDocumentSweeper { return head; } - private Iterable> sweepOperations( + private Iterable> sweepOperations( final Iterable docs) { return filter(transform(docs, - new Function>() { + new Function>() { @Override - public Map.Entry apply(NodeDocument doc) { + public Map.Entry apply(NodeDocument doc) { return immutableEntry(doc.getPath(), sweepOne(doc)); } - }), new Predicate>() { + }), new Predicate>() { @Override - public boolean apply(Map.Entry input) { + public boolean apply(Map.Entry input) { return input.getValue() != null; } }); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Path.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Path.java new file mode 100644 index 0000000000..adc8b63aeb --- /dev/null +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/Path.java @@ -0,0 +1,368 @@ +/* + * 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.plugins.document; + +import java.util.Arrays; +import java.util.Objects; + +import org.apache.jackrabbit.oak.cache.CacheValue; +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.commons.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.Iterables.elementsEqual; + +/** + * The {@code Path} class is closely modeled after the semantics of + * {@code PathUtils} in oak-commons. Corresponding methods in this class can + * be used as a replacement for the methods in {@code PathUtils} on {@code Path} + * objects. + */ +public final class Path implements CacheValue, Comparable { + + public static final Path ROOT = new Path(null, "", "".hashCode()); + + @Nullable + private final Path parent; + + @NotNull + private final String name; + + private int hash; + + private Path(@Nullable Path parent, + @NotNull String name, + int hash) { + this.parent = parent; + this.name = name; + this.hash = hash; + } + + /** + * Creates a new {@code Path} from the given parent {@code Path}. The name + * of the new {@code Path} cannot be the empty {@code String}. + * + * @param parent the parent {@code Path}. + * @param name the name of the new {@code Path}. + * @throws IllegalArgumentException if the {@code name} is empty. + */ + public Path(@NotNull Path parent, @NotNull String name) { + this(checkNotNull(parent), checkNotNull(name), -1); + checkArgument(!name.isEmpty(), "name cannot be the empty String"); + } + + /** + * Creates a relative path with a single name element. The name cannot be + * the empty {@code String}. + * + * @param name the name of the first path element. + * @throws IllegalArgumentException if the {@code name} is empty. + */ + public Path(@NotNull String name) { + this(null, checkNotNull(name), -1); + checkArgument(!name.isEmpty(), "name cannot be the empty String"); + } + + /** + * Returns the name of this path. The {@link #ROOT} is the only path with + * an empty name. That is a String with length zero. + * + * @return the name of this path. + */ + @NotNull + public String getName() { + return name; + } + + /** + * Returns the names of the path elements with increasing {@link #getDepth()} + * starting at depth 1. + * + * @return the names of the path elements. + */ + @NotNull + public Iterable elements() { + return elements(false); + } + + /** + * Returns {@code true} if this is the {@link #ROOT} path; {@code false} + * otherwise. + * + * @return whether this is the {@link #ROOT} path. + */ + public boolean isRoot() { + return name.isEmpty(); + } + + /** + * The parent of this path or {@code null} if this path does not have a + * parent. The {@link #ROOT} path and the first path element of a relative + * path do not have a parent. + * + * @return the parent of this path or {@code null} if this path does not + * have a parent. + */ + @Nullable + public Path getParent() { + return parent; + } + + /** + * @return the number of characters of the {@code String} representation of + * this path. + */ + public int length() { + if (isRoot()) { + return 1; + } + int length = 0; + Path p = this; + while (p != null) { + length += p.name.length(); + if (p.parent != null) { + length++; + } + p = p.parent; + } + return length; + } + + /** + * The depth of this path. The {@link #ROOT} has a depth of 0. The path + * {@code /foo/bar} as well as {@code bar/baz} have depth 2. + * + * @return the depth of the path. + */ + public int getDepth() { + return getNumberOfPathElements(false); + } + + /** + * Get the nth ancestor of a path. The 1st ancestor is the parent path, + * 2nd ancestor the grandparent path, and so on... + *

+ * If {@code nth <= 0}, then this path is returned. + * + * @param nth indicates the ancestor level for which the path should be + * calculated. + * @return the ancestor path + */ + @NotNull + public Path getAncestor(int nth) { + Path p = this; + while (nth-- > 0 && p.parent != null) { + p = p.parent; + } + return p; + } + + /** + * Return {@code true} if {@code this} path is an ancestor of the + * {@code other} path, otherwise {@code false}. + * + * @param other the other path. + * @return whether this path is an ancestor of the other path. + */ + public boolean isAncestorOf(@NotNull Path other) { + checkNotNull(other); + int depthDiff = other.getDepth() - getDepth(); + return depthDiff > 0 + && elementsEqual(elements(true), other.getAncestor(depthDiff).elements(true)); + } + + /** + * @return {@code true} if this is an absolute path; {@code false} otherwise. + */ + public boolean isAbsolute() { + Path p = this; + while (p.parent != null) { + p = p.parent; + } + return p.isRoot(); + } + + /** + * Creates a {@code Path} from a {@code String}. + * + * @param path the {@code String} to parse. + * @return the {@code Path} from the {@code String}. + * @throws IllegalArgumentException if the {@code path} is the empty + * {@code String}. + */ + @NotNull + public static Path fromString(@NotNull String path) throws IllegalArgumentException { + checkNotNull(path); + Path p = null; + if (PathUtils.isAbsolute(path)) { + p = ROOT; + } + for (String name : PathUtils.elements(path)) { + name = StringCache.get(name); + if (p == null) { + p = new Path(name); + } else { + p = new Path(p, StringCache.get(name)); + } + } + if (p == null) { + throw new IllegalArgumentException("path must not be empty"); + } + return p; + } + + /** + * Appends the {@code String} representation of this {@code Path} to the + * passed {@code StringBuilder}. See also {@link #toString()}. + * + * @param sb the {@code StringBuilder} this {@code Path} is appended to. + * @return the passed {@code StringBuilder}. + */ + @NotNull + public StringBuilder toStringBuilder(@NotNull StringBuilder sb) { + if (isRoot()) { + sb.append('/'); + } else { + buildPath(sb); + } + return sb; + } + + @Override + public int getMemory() { + int memory = 0; + Path p = this; + while (p.parent != null) { + memory += 24; // shallow size + memory += StringUtils.estimateMemoryUsage(name); + p = p.parent; + } + return memory; + } + + @Override + public int compareTo(@NotNull Path other) { + if (this == other) { + return 0; + } + Path t = this; + int off = t.getNumberOfPathElements(true) - + checkNotNull(other).getNumberOfPathElements(true); + int corrected = off; + while (corrected > 0) { + t = t.parent; + corrected--; + } + while (corrected < 0) { + other = other.parent; + corrected++; + } + int cp = comparePath(t, other); + if (cp != 0) { + return cp; + } + return Integer.signum(off); + } + + @Override + public String toString() { + if (isRoot()) { + return "/"; + } else { + return buildPath(new StringBuilder(length())).toString(); + } + } + + @Override + public int hashCode() { + int h = hash; + if (h == -1 && parent != null) { + h = 17; + h = 37 * h + parent.hashCode(); + h = 37 * h + name.hashCode(); + hash = h; + } + return h; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof Path) { + Path other = (Path) obj; + return this.name.equals(other.name) + && Objects.equals(this.parent, other.parent); + } + return false; + } + + //-------------------------< internal >------------------------------------- + + private Iterable elements(boolean withRoot) { + int size = getNumberOfPathElements(withRoot); + String[] elements = new String[size]; + Path p = this; + for (int i = size - 1; p != null; i--) { + if (withRoot || !p.isRoot()) { + elements[i] = p.name; + } + p = p.parent; + } + return Arrays.asList(elements); + } + + private StringBuilder buildPath(StringBuilder sb) { + if (parent != null) { + parent.buildPath(sb).append("/"); + } + sb.append(name); + return sb; + } + + /** + * Returns the number of path elements. Depending on {@code withRoot} the + * root of an absolute path is also taken into account. + * + * @param withRoot whether the root of an absolute path is also counted. + * @return the number of path elements. + */ + private int getNumberOfPathElements(boolean withRoot) { + int depth = 0; + for (Path p = this; p != null; p = p.parent) { + if (withRoot || !p.isRoot()) { + depth++; + } + } + return depth; + } + + private static int comparePath(Path a, Path b) { + if (a.parent != b.parent) { + int cp = comparePath(a.parent, b.parent); + if (cp != 0) { + return cp; + } + } + return a.name.compareTo(b.name); + } +} diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathComparator.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathComparator.java index 09988d0235..28eaf28378 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathComparator.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathComparator.java @@ -24,25 +24,25 @@ import java.util.Comparator; import org.apache.jackrabbit.oak.plugins.document.util.Utils; /** - * Implements a comparator, which sorts path string according to 1) their - * depth (highest first) and 2) the path string itself. + * Implements a comparator, which sorts paths according to 1) their depth + * (highest first) and 2) the paths natural ordering. */ -public class PathComparator implements Comparator, Serializable { +public class PathComparator implements Comparator, Serializable { private static final long serialVersionUID = -1523171906146067782L; - public static final Comparator INSTANCE = new PathComparator(); + public static final Comparator INSTANCE = new PathComparator(); private PathComparator() { } @Override - public int compare(String o1, String o2) { - int d1 = Utils.pathDepth(o1); - int d2 = Utils.pathDepth(o2); + public int compare(Path p1, Path p2) { + int d1 = p1.getDepth(); + int d2 = p2.getDepth(); if (d1 != d2) { return Integer.signum(d2 - d1); } - return o1.compareTo(o2); + return p1.compareTo(p2); } } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java index bc60fa21df..b410078512 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PathRev.java @@ -19,7 +19,6 @@ package org.apache.jackrabbit.oak.plugins.document; import org.apache.jackrabbit.oak.cache.CacheValue; -import org.apache.jackrabbit.oak.commons.StringUtils; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,31 +26,37 @@ import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; /** - * A cache key implementation, which is a combination of a path string and a - * revision. + * A cache key implementation, which is a combination of a path and a revision + * vector. */ -public final class PathRev implements CacheValue { +public final class PathRev implements CacheValue, Comparable { private static final Logger LOG = LoggerFactory.getLogger(PathRev.class); - private final String path; + private final Path path; private final RevisionVector revision; - public PathRev(@NotNull String path, @NotNull RevisionVector revision) { + private int hash; + + public PathRev(@NotNull Path path, @NotNull RevisionVector revision) { this.path = checkNotNull(path); this.revision = checkNotNull(revision); } - public String getPath() { + public Path getPath() { return path; } + public RevisionVector getRevision() { + return revision; + } + @Override public int getMemory() { - long size = 24 // shallow size - + (long)StringUtils.estimateMemoryUsage(path) // path - + revision.getMemory(); // revision + long size = 24L // shallow size + + path.getMemory() // path + + revision.getMemory(); // revision if (size > Integer.MAX_VALUE) { LOG.debug("Estimated memory footprint larger than Integer.MAX_VALUE: {}.", size); size = Integer.MAX_VALUE; @@ -59,12 +64,31 @@ public final class PathRev implements CacheValue { return (int) size; } - //----------------------------< Object >------------------------------------ + //---------------------------< Comparable >--------------------------------- + + public int compareTo(@NotNull PathRev other) { + if (this == other) { + return 0; + } + int compare = this.path.compareTo(other.path); + if (compare != 0) { + return compare; + } + return this.revision.compareTo(other.revision); + } + //----------------------------< Object >------------------------------------ @Override public int hashCode() { - return path.hashCode() ^ revision.hashCode(); + int h = this.hash; + if (h == 0) { + h = 17; + h = 37 * h + path.hashCode(); + h = 37 * h + revision.hashCode(); + this.hash = h; + } + return h; } @Override @@ -88,25 +112,4 @@ public final class PathRev implements CacheValue { return sb.toString(); } - public String asString() { - return toString(); - } - - public static PathRev fromString(String s) { - int index = s.lastIndexOf('@'); - return new PathRev(s.substring(0, index), - RevisionVector.fromString(s.substring(index + 1))); - } - - public int compareTo(PathRev b) { - if (this == b) { - return 0; - } - int compare = path.compareTo(b.path); - if (compare == 0) { - compare = revision.compareTo(b.revision); - } - return compare; - } - } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java index 6ee24cff83..9c15192ffe 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/PropertyHistory.java @@ -47,7 +47,7 @@ class PropertyHistory implements Iterable { private final NodeDocument doc; private final String property; // path of the main document - private final String mainPath; + private final Path mainPath; public PropertyHistory(@NotNull NodeDocument doc, @NotNull String property) { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ResetDiff.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ResetDiff.java index a60fecb712..0714a62103 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ResetDiff.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/ResetDiff.java @@ -19,7 +19,6 @@ package org.apache.jackrabbit.oak.plugins.document; import java.util.Map; import org.apache.jackrabbit.oak.api.PropertyState; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStateDiff; @@ -38,19 +37,19 @@ class ResetDiff implements NodeStateDiff { private final ResetDiff parent; private final Revision revision; - private final String path; - private final Map operations; + private final Path path; + private final Map operations; private UpdateOp update; ResetDiff(@NotNull Revision revision, - @NotNull Map operations) { - this(null, revision, "/", operations); + @NotNull Map operations) { + this(null, revision, Path.ROOT, operations); } private ResetDiff(@Nullable ResetDiff parent, @NotNull Revision revision, - @NotNull String path, - @NotNull Map operations) { + @NotNull Path path, + @NotNull Map operations) { this.parent = parent; this.revision = checkNotNull(revision); this.path = checkNotNull(path); @@ -78,7 +77,7 @@ class ResetDiff implements NodeStateDiff { @Override public boolean childNodeAdded(String name, NodeState after) { NodeDocument.removeCommitRoot(getUpdateOp(), revision); - String p = PathUtils.concat(path, name); + Path p = new Path(path, name); ResetDiff diff = new ResetDiff(this, revision, p, operations); UpdateOp op = diff.getUpdateOp(); NodeDocument.removeDeleted(op, revision); @@ -94,20 +93,20 @@ class ResetDiff implements NodeStateDiff { public boolean childNodeChanged(String name, NodeState before, NodeState after) { - String p = PathUtils.concat(path, name); + Path p = new Path(path, name); return after.compareAgainstBaseState(before, new ResetDiff(this, revision, p, operations)); } @Override public boolean childNodeDeleted(String name, NodeState before) { - String p = PathUtils.concat(path, name); + Path p = new Path(path, name); ResetDiff diff = new ResetDiff(this, revision, p, operations); NodeDocument.removeDeleted(diff.getUpdateOp(), revision); return MISSING_NODE.compareAgainstBaseState(before, diff); } - Map getOperations() { + Map getOperations() { return operations; } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitDocumentCleanUp.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitDocumentCleanUp.java index 45ad2f89d1..14c5766f83 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitDocumentCleanUp.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitDocumentCleanUp.java @@ -114,11 +114,9 @@ public class SplitDocumentCleanUp implements Closeable { return; } - String splitDocPath = splitDoc.getPath(); - int slashIdx = splitDocPath.lastIndexOf('/'); - int height = Integer.parseInt(splitDocPath.substring(slashIdx + 1)); - Revision rev = Revision.fromString( - splitDocPath.substring(splitDocPath.lastIndexOf('/', slashIdx - 1) + 1, slashIdx)); + Path splitDocPath = splitDoc.getPath(); + int height = Integer.parseInt(splitDocPath.getName()); + Revision rev = Revision.fromString(splitDocPath.getParent().getName()); doc = doc.findPrevReferencingDoc(rev, height); if (doc == null) { LOG.warn("Split document {} for path {} not referenced anymore. Main document is {}", diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitOperations.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitOperations.java index 3059be8e06..4bca73bb25 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitOperations.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/SplitOperations.java @@ -77,7 +77,7 @@ class SplitOperations { private static final DocumentStore STORE = new MemoryDocumentStore(); private final NodeDocument doc; - private final String path; + private final Path path; private final String id; private final Revision headRevision; private final RevisionContext context; @@ -314,11 +314,11 @@ class SplitOperations { if (h == null || l == null) { throw new IllegalStateException(); } - String prevPath = Utils.getPreviousPathFor(path, h, entry.getKey() + 1); + Path prevPath = Utils.getPreviousPathFor(path, h, entry.getKey() + 1); String prevId = Utils.getIdFromPath(prevPath); UpdateOp intermediate = new UpdateOp(prevId, true); - if (Utils.isLongPath(prevPath)) { - intermediate.set(NodeDocument.PATH, prevPath); + if (Utils.isIdFromLongPath(prevId)) { + intermediate.set(NodeDocument.PATH, prevPath.toString()); } setPrevious(main, new Range(h, l, entry.getKey() + 1)); for (Range r : entry.getValue()) { @@ -351,10 +351,11 @@ class SplitOperations { // move to another document main = new UpdateOp(id, false); setPrevious(main, new Range(high, low, 0)); - String oldPath = Utils.getPreviousPathFor(path, high, 0); - UpdateOp old = new UpdateOp(Utils.getIdFromPath(oldPath), true); - if (Utils.isLongPath(oldPath)) { - old.set(NodeDocument.PATH, oldPath); + Path oldPath = Utils.getPreviousPathFor(path, high, 0); + String oldId = Utils.getIdFromPath(oldPath); + UpdateOp old = new UpdateOp(oldId, true); + if (Utils.isIdFromLongPath(oldId)) { + old.set(NodeDocument.PATH, oldPath.toString()); } for (String property : committedChanges.keySet()) { NavigableMap splitMap = committedChanges.get(property); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/StringCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/StringCache.java index 9f92ebaa04..2a5f014a64 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/StringCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/StringCache.java @@ -16,13 +16,13 @@ */ package org.apache.jackrabbit.oak.plugins.document; +import java.util.HashMap; import java.util.Map; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.spi.nodetype.NodeTypeConstants; import org.apache.jackrabbit.oak.spi.version.VersionConstants; -import com.google.common.collect.ImmutableMap; import org.apache.jackrabbit.oak.spi.state.ConflictType; /** @@ -139,11 +139,11 @@ public final class StringCache { } private static Map createStringMap(String... strings) { - ImmutableMap.Builder builder = ImmutableMap.builder(); + Map map = new HashMap<>(); for (String string : strings) { - builder.put(string, string); + map.put(string, string); } - return builder.build(); + return map; } // must be a power of 2 diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java index c9ad45c245..97b19d2eaa 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/TieredDiffCache.java @@ -53,7 +53,7 @@ class TieredDiffCache extends DiffCache { @Override public String getChanges(@NotNull RevisionVector from, @NotNull RevisionVector to, - @NotNull String path, + @NotNull Path path, @Nullable Loader loader) { // do not check local cache when changes are external if (isLocalChange(from, to, clusterId)) { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java index 3f7913f4af..b42923b044 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnmergedBranches.java @@ -71,7 +71,7 @@ class UnmergedBranches { if (!initialized.compareAndSet(false, true)) { throw new IllegalStateException("already initialized"); } - NodeDocument doc = store.find(Collection.NODES, Utils.getIdFromPath("/")); + NodeDocument doc = store.find(Collection.NODES, Utils.getIdFromPath(Path.ROOT)); if (doc == null) { return; } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java index 490d156d23..0289ada0e7 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsavedModifications.java @@ -24,7 +24,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.document.util.MapFactory; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.jetbrains.annotations.NotNull; @@ -41,7 +40,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; import static org.apache.jackrabbit.oak.plugins.document.Collection.CLUSTER_NODES; import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES; import static org.apache.jackrabbit.oak.plugins.document.Commit.createUpdateOp; @@ -59,7 +57,7 @@ class UnsavedModifications { */ static final int BACKGROUND_MULTI_UPDATE_LIMIT = 100; - private final ConcurrentMap map = MapFactory.getInstance().create(); + private final ConcurrentMap map = MapFactory.getInstance().create(); /** * Puts a revision for the given path. The revision for the given path is @@ -72,7 +70,7 @@ class UnsavedModifications { * was none or the current revision is newer. */ @Nullable - public Revision put(@NotNull String path, @NotNull Revision revision) { + public Revision put(@NotNull Path path, @NotNull Revision revision) { checkNotNull(path); checkNotNull(revision); for (;;) { @@ -95,12 +93,12 @@ class UnsavedModifications { } @Nullable - public Revision get(String path) { + public Revision get(Path path) { return map.get(path); } @NotNull - public Collection getPaths() { + public Collection getPaths() { return map.keySet(); } @@ -112,19 +110,19 @@ class UnsavedModifications { * @return matching paths with pending modifications. */ @NotNull - public Iterable getPaths(@NotNull final Revision start) { + public Iterable getPaths(@NotNull final Revision start) { if (map.isEmpty()) { return Collections.emptyList(); } else { return Iterables.transform(Iterables.filter(map.entrySet(), - new Predicate>() { + new Predicate>() { @Override - public boolean apply(Map.Entry input) { + public boolean apply(Map.Entry input) { return start.compareRevisionTime(input.getValue()) < 1; } - }), new Function, String>() { + }), new Function, Path>() { @Override - public String apply(Map.Entry input) { + public Path apply(Map.Entry input) { return input.getKey(); } }); @@ -163,7 +161,7 @@ class UnsavedModifications { stats.lock = sw.elapsed(TimeUnit.MILLISECONDS); sw.reset().start(); Revision sweepRev; - Map pending; + Map pending; try { snapshot.acquiring(getMostRecentRevision()); pending = Maps.newTreeMap(PathComparator.INSTANCE); @@ -174,23 +172,23 @@ class UnsavedModifications { } stats.num = pending.size(); List updates = Lists.newArrayList(); - Map pathToRevision = Maps.newHashMap(); - for (Iterable> batch : Iterables.partition( + Map pathToRevision = Maps.newHashMap(); + for (Iterable> batch : Iterables.partition( pending.entrySet(), BACKGROUND_MULTI_UPDATE_LIMIT)) { - for (Map.Entry entry : batch) { - String p = entry.getKey(); + for (Map.Entry entry : batch) { + Path p = entry.getKey(); Revision r = entry.getValue(); - if (PathUtils.denotesRoot(entry.getKey())) { + if (p.isRoot()) { // update root individually at the end continue; } updates.add(newUpdateOp(p, r)); - pathToRevision.put(p, r); + pathToRevision.put(entry.getKey(), r); } if (!updates.isEmpty()) { store.createOrUpdate(NODES, updates); stats.calls++; - for (Map.Entry entry : pathToRevision.entrySet()) { + for (Map.Entry entry : pathToRevision.entrySet()) { map.remove(entry.getKey(), entry.getValue()); LOG.debug("Updated _lastRev to {} on {}", entry.getValue(), entry.getKey()); } @@ -200,9 +198,9 @@ class UnsavedModifications { } } // finally update remaining root document - Revision rootRev = pending.get(ROOT_PATH); + Revision rootRev = pending.get(Path.ROOT); if (rootRev != null) { - UpdateOp rootUpdate = newUpdateOp(ROOT_PATH, rootRev); + UpdateOp rootUpdate = newUpdateOp(Path.ROOT, rootRev); // also update to most recent sweep revision if (sweepRev != null) { NodeDocument.setSweepRevision(rootUpdate, sweepRev); @@ -210,8 +208,8 @@ class UnsavedModifications { } store.findAndUpdate(NODES, rootUpdate); stats.calls++; - map.remove(ROOT_PATH, rootRev); - LOG.debug("Updated _lastRev to {} on {}", rootRev, ROOT_PATH); + map.remove(Path.ROOT, rootRev); + LOG.debug("Updated _lastRev to {} on {}", rootRev, Path.ROOT); int cid = rootRev.getClusterId(); UpdateOp update = new UpdateOp(String.valueOf(cid), false); @@ -228,7 +226,7 @@ class UnsavedModifications { return map.toString(); } - private static UpdateOp newUpdateOp(String path, Revision r) { + private static UpdateOp newUpdateOp(Path path, Revision r) { UpdateOp updateOp = createUpdateOp(path, r, false); NodeDocument.setLastRev(updateOp, r); return updateOp; @@ -236,7 +234,7 @@ class UnsavedModifications { private Revision getMostRecentRevision() { // use revision of root document - Revision rev = map.get(ROOT_PATH); + Revision rev = map.get(Path.ROOT); // otherwise find most recent if (rev == null) { for (Revision r : map.values()) { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java index 860c138051..37a0e50432 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollector.java @@ -764,7 +764,7 @@ public class VersionGarbageCollector { // all previous document ids can be constructed from the // previous ranges map. this works for first level previous // documents only. - final String path = doc.getPath(); + final Path path = doc.getPath(); return Iterators.transform(prevRanges.entrySet().iterator(), new Function, String>() { @Override diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundledDocumentDiffer.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundledDocumentDiffer.java index 71dfafc225..bde3b3862a 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundledDocumentDiffer.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundledDocumentDiffer.java @@ -26,6 +26,7 @@ import org.apache.jackrabbit.oak.commons.json.JsopWriter; import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; import static com.google.common.base.Preconditions.checkNotNull; @@ -103,7 +104,7 @@ public class BundledDocumentDiffer { //and from there traverse down to the actual child node checkState(BundlorUtils.isBundledChild(state)); String bundlingPath = state.getString(DocumentBundlor.META_PROP_BUNDLING_PATH); - String bundlingRootPath = PathUtils.getAncestorPath(state.getPath(), PathUtils.getDepth(bundlingPath)); + Path bundlingRootPath = state.getPath().getAncestor(PathUtils.getDepth(bundlingPath)); DocumentNodeState bundlingRoot = nodeStore.getNode(bundlingRootPath, state.getLastRevision()); result = (DocumentNodeState) NodeStateUtils.getNode(bundlingRoot, bundlingPath); } else { diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandler.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandler.java index e744a88da8..74a8313ebc 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandler.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandler.java @@ -24,25 +24,25 @@ import java.util.Set; import com.google.common.collect.Sets; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.spi.state.NodeState; import static com.google.common.base.Preconditions.checkNotNull; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; public class BundlingHandler { private final BundledTypesRegistry registry; - private final String path; + private final Path path; private final BundlingContext ctx; private final NodeState nodeState; public BundlingHandler(BundledTypesRegistry registry) { - this(checkNotNull(registry), BundlingContext.NULL, ROOT_PATH, EMPTY_NODE); + this(checkNotNull(registry), BundlingContext.NULL, Path.ROOT, EMPTY_NODE); } - private BundlingHandler(BundledTypesRegistry registry, BundlingContext ctx, String path, NodeState nodeState) { + private BundlingHandler(BundledTypesRegistry registry, BundlingContext ctx, Path path, NodeState nodeState) { this.registry = registry; this.path = path; this.ctx = ctx; @@ -67,7 +67,7 @@ public class BundlingHandler { /** * Returns absolute path of the current node */ - public String getNodeFullPath() { + public Path getNodeFullPath() { return path; } @@ -86,12 +86,12 @@ public class BundlingHandler { return ctx.removedProps; } - public String getRootBundlePath() { + public Path getRootBundlePath() { return ctx.isBundling() ? ctx.bundlingPath : path; } public BundlingHandler childAdded(String name, NodeState state){ - String childPath = childPath(name); + Path childPath = childPath(name); BundlingContext childContext; Matcher childMatcher = ctx.matcher.next(name); if (childMatcher.isMatch()) { @@ -111,7 +111,7 @@ public class BundlingHandler { } public BundlingHandler childDeleted(String name, NodeState state){ - String childPath = childPath(name); + Path childPath = childPath(name); BundlingContext childContext; Matcher childMatcher = ctx.matcher.next(name); if (childMatcher.isMatch()) { @@ -127,7 +127,7 @@ public class BundlingHandler { } public BundlingHandler childChanged(String name, NodeState before, NodeState after){ - String childPath = childPath(name); + Path childPath = childPath(name); BundlingContext childContext; Matcher childMatcher = ctx.matcher.next(name); if (childMatcher.isMatch()) { @@ -151,22 +151,22 @@ public class BundlingHandler { @Override public String toString() { - String result = path; + String result = path.toString(); if (isBundledNode()){ result = path + "( Bundling root - " + getRootBundlePath() + ")"; } return result; } - private String childPath(String name){ - return PathUtils.concat(path, name); + private Path childPath(String name){ + return new Path(path, name); } private BundlingContext createChildContext(Matcher childMatcher) { return ctx.child(childMatcher); } - private static BundlingContext getBundlorContext(String path, NodeState state) { + private static BundlingContext getBundlorContext(Path path, NodeState state) { BundlingContext result = BundlingContext.NULL; PropertyState bundlorConfig = state.getProperty(DocumentBundlor.META_PROP_PATTERN); if (bundlorConfig != null){ @@ -199,13 +199,13 @@ public class BundlingHandler { } private static class BundlingContext { - static final BundlingContext NULL = new BundlingContext("", Matcher.NON_MATCHING); - final String bundlingPath; + static final BundlingContext NULL = new BundlingContext(Path.ROOT, Matcher.NON_MATCHING); + final Path bundlingPath; final Matcher matcher; final Set metaProps = Sets.newHashSet(); final Set removedProps = Sets.newHashSet(); - public BundlingContext(String bundlingPath, Matcher matcher) { + public BundlingContext(Path bundlingPath, Matcher matcher) { this.bundlingPath = bundlingPath; this.matcher = matcher; } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java index e3ab63ea3d..6453179099 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/locks/StripedNodeDocumentLocks.java @@ -18,13 +18,14 @@ package org.apache.jackrabbit.oak.plugins.document.locks; import java.util.concurrent.locks.Lock; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import com.google.common.util.concurrent.Striped; public class StripedNodeDocumentLocks implements NodeDocumentLocks { - private static final String ROOT = Utils.getIdFromPath("/"); + private static final String ROOT = Utils.getIdFromPath(Path.ROOT); /** * Locks to ensure cache consistency on reads, writes and invalidation. diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java index a544354f6e..cadbbb51c7 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoVersionGCSupport.java @@ -38,6 +38,7 @@ import com.mongodb.client.model.Filters; import org.apache.jackrabbit.oak.plugins.document.Document; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.SplitDocumentCleanUp; @@ -189,7 +190,7 @@ public class MongoVersionGCSupport extends VersionGCSupport { // only remove those older than sweep rev List queries = Lists.newArrayList(); for (Revision r : sweepRevs) { - String idSuffix = Utils.getPreviousIdFor("/", r, 0); + String idSuffix = Utils.getPreviousIdFor(Path.ROOT, r, 0); idSuffix = idSuffix.substring(idSuffix.lastIndexOf('-')); // id/path constraint for previous documents diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java index a790df6a5d..5cf48529d2 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheType.java @@ -16,44 +16,60 @@ */ package org.apache.jackrabbit.oak.plugins.document.persistentCache; +import java.nio.ByteBuffer; + import org.apache.jackrabbit.oak.plugins.document.LocalDiffCache; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; +import org.apache.jackrabbit.oak.plugins.document.MemoryDiffCache; +import org.apache.jackrabbit.oak.plugins.document.NamePathRev; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.PathRev; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey; import org.apache.jackrabbit.oak.plugins.document.util.StringValue; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.StringDataType; public enum CacheType { NODE { + @Override - public String keyToString(K key) { - return ((PathRev) key).asString(); + public void writeKey(WriteBuffer buffer, K key) { + PathRev pr = (PathRev) key; + DataTypeUtil.pathRevToBuffer(pr, buffer); } + @SuppressWarnings("unchecked") @Override - public K keyFromString(String key) { - return (K) PathRev.fromString(key); + public K readKey(ByteBuffer buffer) { + return (K) DataTypeUtil.pathRevFromBuffer(buffer); } + @Override public int compareKeys(K a, K b) { return ((PathRev) a).compareTo((PathRev) b); } + @Override - public String valueToString(V value) { - return ((DocumentNodeState) value).asString(); + public void writeValue(WriteBuffer buffer, V value) { + DataTypeUtil.stateToBuffer((DocumentNodeState) value, buffer); } + @SuppressWarnings("unchecked") @Override - public V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) DocumentNodeState.fromString(store, value); + public V readValue(DocumentNodeStore store, + DocumentStore docStore, + ByteBuffer buffer) { + return (V) DataTypeUtil.stateFromBuffer(store, buffer); } + @Override public boolean shouldCache(DocumentNodeStore store, K key) { - String path = ((PathRev) key).getPath(); + Path path = ((PathRev) key).getPath(); if (!store.getNodeCachePredicate().apply(path)){ return false; } @@ -62,33 +78,40 @@ public enum CacheType { }, CHILDREN { + @Override - public String keyToString(K key) { - return ((PathRev) key).asString(); + public void writeKey(WriteBuffer buffer, K key) { + DataTypeUtil.namePathRevToBuffer((NamePathRev) key, buffer); } + @SuppressWarnings("unchecked") @Override - public K keyFromString(String key) { - return (K) PathRev.fromString(key); + public K readKey(ByteBuffer buffer) { + return (K) DataTypeUtil.namePathRevFromBuffer(buffer); } + @Override public int compareKeys(K a, K b) { - return ((PathRev) a).compareTo((PathRev) b); + return ((NamePathRev) a).compareTo((NamePathRev) b); } + @Override - public String valueToString(V value) { - return ((DocumentNodeState.Children) value).asString(); + public void writeValue(WriteBuffer buffer, V value) { + String s = ((DocumentNodeState.Children) value).asString(); + StringDataType.INSTANCE.write(buffer, s); } + @SuppressWarnings("unchecked") @Override - public V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) DocumentNodeState.Children.fromString(value); + public V readValue(DocumentNodeStore store, + DocumentStore docStore, + ByteBuffer buffer) { + return (V) DocumentNodeState.Children.fromString(readString(buffer)); } @Override public boolean shouldCache(DocumentNodeStore store, K key) { - String path = ((PathRev) key).getPath(); + Path path = ((NamePathRev) key).getPath(); if (!store.getNodeCachePredicate().apply(path)){ return false; } @@ -97,29 +120,44 @@ public enum CacheType { }, DIFF { + @Override - public String keyToString(K key) { - return ((PathRev) key).asString(); + public void writeKey(WriteBuffer buffer, K key) { + MemoryDiffCache.Key k = ((MemoryDiffCache.Key) key); + DataTypeUtil.pathToBuffer(k.getPath(), buffer); + DataTypeUtil.revisionVectorToBuffer(k.getFromRevision(), buffer); + DataTypeUtil.revisionVectorToBuffer(k.getToRevision(), buffer); } + @SuppressWarnings("unchecked") @Override - public K keyFromString(String key) { - return (K) PathRev.fromString(key); + public K readKey(ByteBuffer buffer) { + Path p = DataTypeUtil.pathFromBuffer(buffer); + RevisionVector from = DataTypeUtil.revisionVectorFromBuffer(buffer); + RevisionVector to = DataTypeUtil.revisionVectorFromBuffer(buffer); + return (K) new MemoryDiffCache.Key(p, from, to); } + + @Override public int compareKeys(K a, K b) { - return ((PathRev) a).compareTo((PathRev) b); - } + return ((MemoryDiffCache.Key) a).compareTo((MemoryDiffCache.Key) b); + } + @Override - public String valueToString(V value) { - return ((StringValue) value).asString(); + public void writeValue(WriteBuffer buffer, V value) { + String s = ((StringValue) value).asString(); + StringDataType.INSTANCE.write(buffer, s); } + @SuppressWarnings("unchecked") @Override - public V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) StringValue.fromString(value); + public V readValue(DocumentNodeStore store, + DocumentStore docStore, + ByteBuffer buffer) { + return (V) StringValue.fromString(readString(buffer)); } + @Override public boolean shouldCache(DocumentNodeStore store, K key) { return true; @@ -127,27 +165,34 @@ public enum CacheType { }, DOCUMENT { + @Override - public String keyToString(K key) { + public void writeKey(WriteBuffer buffer, K key) { throw new UnsupportedOperationException(); } + @Override - public K keyFromString(String key) { + public K readKey(ByteBuffer buffer) { throw new UnsupportedOperationException(); } + @Override public int compareKeys(K a, K b) { throw new UnsupportedOperationException(); - } + } + @Override - public String valueToString(V value) { + public void writeValue(WriteBuffer buffer, V value) { throw new UnsupportedOperationException(); } + @Override - public V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value) { + public V readValue(DocumentNodeStore store, + DocumentStore docStore, + ByteBuffer buffer) { throw new UnsupportedOperationException(); } + @Override public boolean shouldCache(DocumentNodeStore store, K key) { return false; @@ -155,29 +200,38 @@ public enum CacheType { }, PREV_DOCUMENT { + @Override - public String keyToString(K key) { - return ((StringValue) key).asString(); + public void writeKey(WriteBuffer buffer, K key) { + String s = ((StringValue) key).asString(); + StringDataType.INSTANCE.write(buffer, s); } + @SuppressWarnings("unchecked") @Override - public K keyFromString(String key) { - return (K) StringValue.fromString(key); + public K readKey(ByteBuffer buffer) { + return (K) StringValue.fromString(readString(buffer)); } + @Override public int compareKeys(K a, K b) { return ((StringValue) a).asString().compareTo(((StringValue) b).asString()); } + @Override - public String valueToString(V value) { - return ((NodeDocument) value).asString(); + public void writeValue(WriteBuffer buffer, V value) { + String s = ((NodeDocument) value).asString(); + StringDataType.INSTANCE.write(buffer, s); } + @SuppressWarnings("unchecked") @Override - public V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) NodeDocument.fromString(docStore, value); + public V readValue(DocumentNodeStore store, + DocumentStore docStore, + ByteBuffer buffer) { + return (V) NodeDocument.fromString(docStore, readString(buffer)); } + @Override public boolean shouldCache(DocumentNodeStore store, K key) { return true; @@ -185,29 +239,41 @@ public enum CacheType { }, LOCAL_DIFF { + @Override - public String keyToString(K key) { - return ((RevisionsKey) key).asString(); + public void writeKey(WriteBuffer buffer, K key) { + RevisionsKey revisionsKey = ((RevisionsKey) key); + DataTypeUtil.revisionVectorToBuffer(revisionsKey.getRev1(), buffer); + DataTypeUtil.revisionVectorToBuffer(revisionsKey.getRev2(), buffer); } + @SuppressWarnings("unchecked") @Override - public K keyFromString(String key) { - return (K) RevisionsKey.fromString(key); + public K readKey(ByteBuffer buffer) { + RevisionVector rv1 = DataTypeUtil.revisionVectorFromBuffer(buffer); + RevisionVector rv2 = DataTypeUtil.revisionVectorFromBuffer(buffer); + return (K) new RevisionsKey(rv1, rv2); } + @Override public int compareKeys(K a, K b) { return ((RevisionsKey) a).compareTo((RevisionsKey) b); } + @Override - public String valueToString(V value) { - return ((LocalDiffCache.Diff) value).asString(); + public void writeValue(WriteBuffer buffer, V value) { + String s = ((LocalDiffCache.Diff) value).asString(); + StringDataType.INSTANCE.write(buffer, s); } + @SuppressWarnings("unchecked") @Override - public V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value) { - return (V) LocalDiffCache.Diff.fromString(value); + public V readValue(DocumentNodeStore store, + DocumentStore docStore, + ByteBuffer buffer) { + return (V) LocalDiffCache.Diff.fromString(readString(buffer)); } + @Override public boolean shouldCache(DocumentNodeStore store, K key) { return true; @@ -216,13 +282,22 @@ public enum CacheType { public static final CacheType[] VALUES = CacheType.values(); - public abstract String keyToString(K key); - public abstract K keyFromString(String key); + public String getMapName() { + // add a version suffix to the name and increment the version + // whenever the serialization for a key or value changes + return name() + "-v1"; + } + + public abstract void writeKey(WriteBuffer buffer, K key); + public abstract K readKey(ByteBuffer buffer); public abstract int compareKeys(K a, K b); - public abstract String valueToString(V value); - public abstract V valueFromString( - DocumentNodeStore store, DocumentStore docStore, String value); + public abstract void writeValue(WriteBuffer buffer, V value); + public abstract V readValue( + DocumentNodeStore store, DocumentStore docStore, ByteBuffer buffer); public abstract boolean shouldCache(DocumentNodeStore store, K key); + private static String readString(ByteBuffer buffer) { + return StringDataType.INSTANCE.read(buffer); + } } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/DataTypeUtil.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/DataTypeUtil.java new file mode 100644 index 0000000000..c704bf4475 --- /dev/null +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/DataTypeUtil.java @@ -0,0 +1,169 @@ +/* + * 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.plugins.document.persistentCache; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.NamePathRev; +import org.apache.jackrabbit.oak.plugins.document.Path; +import org.apache.jackrabbit.oak.plugins.document.PathRev; +import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.StringDataType; + +/** + * Utility class to write various types to a buffer and read it back again. + */ +class DataTypeUtil { + + static void booleanToBuffer(boolean b, WriteBuffer buffer) { + buffer.put((byte) (b ? 1 : 0)); + } + + static boolean booleanFromBuffer(ByteBuffer buffer) { + return buffer.get() != 0; + } + + static void revisionVectorToBuffer(RevisionVector rv, WriteBuffer buffer) { + buffer.putVarInt(rv.getDimensions()); + for (Revision r : rv) { + buffer.putLong(r.getTimestamp()); + buffer.putVarInt(r.getCounter()); + buffer.putVarInt(r.getClusterId()); + booleanToBuffer(r.isBranch(), buffer); + } + } + + static RevisionVector revisionVectorFromBuffer(ByteBuffer buffer) { + int dim = DataUtils.readVarInt(buffer); + List revisions = new ArrayList<>(); + for (int i = 0; i < dim; i++) { + revisions.add(new Revision( + buffer.getLong(), + DataUtils.readVarInt(buffer), + DataUtils.readVarInt(buffer), + booleanFromBuffer(buffer)) + ); + } + return new RevisionVector(revisions); + } + + static void pathToBuffer(Path p, WriteBuffer buffer) { + int len = p.getDepth() + (p.isAbsolute() ? 1 : 0); + buffer.putVarInt(len); + // write path elements backwards + while (p != null) { + StringDataType.INSTANCE.write(buffer, p.getName()); + p = p.getParent(); + } + } + + static Path pathFromBuffer(ByteBuffer buffer) { + int numElements = DataUtils.readVarInt(buffer); + List elements = new ArrayList<>(numElements); + for (int i = 0; i < numElements; i++) { + elements.add(StringDataType.INSTANCE.read(buffer)); + } + // elements are written backwards + String firstElement = elements.get(elements.size() - 1); + Path p; + if (firstElement.isEmpty()) { + p = Path.ROOT; + } else { + p = new Path(firstElement); + } + // construct path with remaining elements + for (int i = elements.size() - 2; i >= 0; i--) { + p = new Path(p, elements.get(i)); + } + return p; + } + + static void pathRevToBuffer(PathRev pr, WriteBuffer buffer) { + pathToBuffer(pr.getPath(), buffer); + revisionVectorToBuffer(pr.getRevision(), buffer); + } + + static PathRev pathRevFromBuffer(ByteBuffer buffer) { + return new PathRev( + pathFromBuffer(buffer), + revisionVectorFromBuffer(buffer) + ); + } + + static void namePathRevToBuffer(NamePathRev pnr, WriteBuffer buffer) { + StringDataType.INSTANCE.write(buffer, pnr.getName()); + pathToBuffer(pnr.getPath(), buffer); + revisionVectorToBuffer(pnr.getRevision(), buffer); + } + + static NamePathRev namePathRevFromBuffer(ByteBuffer buffer) { + return new NamePathRev( + StringDataType.INSTANCE.read(buffer), + pathFromBuffer(buffer), + revisionVectorFromBuffer(buffer) + ); + } + + static void stateToBuffer(DocumentNodeState state, WriteBuffer buffer) { + pathToBuffer(state.getPath(), buffer); + revisionVectorToBuffer(state.getRootRevision(), buffer); + RevisionVector lastRevision = state.getLastRevision(); + if (lastRevision == null) { + lastRevision = RevisionVector.fromString(""); + } + revisionVectorToBuffer(lastRevision, buffer); + buffer.putVarInt(state.getMemory()); + booleanToBuffer(state.hasNoChildren(), buffer); + Map props = state.getAllBundledProperties(); + buffer.putVarInt(props.size()); + for (Map.Entry e : props.entrySet()) { + StringDataType.INSTANCE.write(buffer, e.getKey()); + StringDataType.INSTANCE.write(buffer, e.getValue()); + } + } + + static DocumentNodeState stateFromBuffer(DocumentNodeStore store, + ByteBuffer buffer) { + Path p = pathFromBuffer(buffer); + RevisionVector rootRevision = revisionVectorFromBuffer(buffer); + RevisionVector lastRevision = revisionVectorFromBuffer(buffer); + if (lastRevision.getDimensions() == 0) { + lastRevision = null; + } + int mem = DataUtils.readVarInt(buffer); + boolean noChildren = booleanFromBuffer(buffer); + int numProps = DataUtils.readVarInt(buffer); + Map props = new HashMap<>(numProps); + for (int i = 0; i < numProps; i++) { + String name = StringDataType.INSTANCE.read(buffer); + String value = StringDataType.INSTANCE.read(buffer); + props.put(name, store.createPropertyState(name, value)); + } + return new DocumentNodeState(store, p, rootRevision, props, + !noChildren, mem, lastRevision, false); + } +} diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java index d6a37892a6..9d2b356ae1 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/KeyDataType.java @@ -21,7 +21,6 @@ import java.nio.ByteBuffer; import org.apache.jackrabbit.oak.cache.CacheValue; import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.type.DataType; -import org.h2.mvstore.type.StringDataType; public class KeyDataType implements DataType { @@ -43,14 +42,12 @@ public class KeyDataType implements DataType { @Override public void write(WriteBuffer buff, Object obj) { - String s = type.keyToString(obj); - StringDataType.INSTANCE.write(buff, s); + type.writeKey(buff, obj); } @Override public Object read(ByteBuffer buff) { - String s = StringDataType.INSTANCE.read(buff); - return type.keyFromString(s); + return type.readKey(buff); } @Override diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java index f3e7cc4595..4df170f508 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCache.java @@ -112,8 +112,7 @@ class NodeCache public void addGeneration(int generation, boolean readOnly) { MVMap.Builder b = new MVMap.Builder(). keyType(keyType).valueType(valueType); - String mapName = type.name(); - CacheMap m = cache.openMap(generation, mapName, b); + CacheMap m = cache.openMap(generation, type.getMapName(), b); map.addReadMap(generation, m); if (!readOnly) { map.setWriteMap(m); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java index a0080eb324..36442c2951 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/ValueDataType.java @@ -23,7 +23,9 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.type.DataType; -import org.h2.mvstore.type.StringDataType; +import org.jetbrains.annotations.NotNull; + +import static com.google.common.base.Preconditions.checkNotNull; public class ValueDataType implements DataType { @@ -31,9 +33,9 @@ public class ValueDataType implements DataType { private final DocumentStore docStore; private final CacheType type; - public ValueDataType( - DocumentNodeStore docNodeStore, - DocumentStore docStore, CacheType type) { + ValueDataType(DocumentNodeStore docNodeStore, + DocumentStore docStore, + CacheType type) { this.docNodeStore = docNodeStore; this.docStore = docStore; this.type = type; @@ -51,14 +53,12 @@ public class ValueDataType implements DataType { @Override public void write(WriteBuffer buff, Object obj) { - String s = type.valueToString(obj); - StringDataType.INSTANCE.write(buff, s); + type.writeValue(buff, obj); } @Override public Object read(ByteBuffer buff) { - String s = StringDataType.INSTANCE.read(buff); - return type.valueFromString(docNodeStore, docStore, s); + return type.readValue(docNodeStore, docStore, buff); } @Override diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java index 78dd9b69b2..c9558901bb 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/DelegatingDocumentNodeState.java @@ -25,6 +25,7 @@ import com.google.common.collect.Iterables; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry; import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeBuilder; @@ -36,9 +37,6 @@ import org.jetbrains.annotations.Nullable; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; -import static org.apache.jackrabbit.oak.commons.PathUtils.concat; -import static org.apache.jackrabbit.oak.commons.PathUtils.denotesRoot; /** * NodeState wrapper which wraps another NodeState (mostly SegmentNodeState) @@ -61,7 +59,7 @@ public class DelegatingDocumentNodeState extends AbstractDocumentNodeState { private final NodeState delegate; private final RevisionVector rootRevision; private final boolean fromExternalChange; - private final String path; + private final Path path; private RevisionVector lastRevision; @@ -75,7 +73,7 @@ public class DelegatingDocumentNodeState extends AbstractDocumentNodeState { public static NodeState wrapIfPossible(NodeState delegate, NodeStateDiffer differ) { if (hasMetaProps(delegate)) { String revVector = getRequiredProp(delegate, PROP_REVISION); - return new DelegatingDocumentNodeState(delegate, ROOT_PATH, RevisionVector.fromString(revVector), false, differ); + return new DelegatingDocumentNodeState(delegate, Path.ROOT, RevisionVector.fromString(revVector), false, differ); } return delegate; } @@ -86,10 +84,10 @@ public class DelegatingDocumentNodeState extends AbstractDocumentNodeState { public static AbstractDocumentNodeState wrap(NodeState delegate, NodeStateDiffer differ) { String revVector = getRequiredProp(delegate, PROP_REVISION); - return new DelegatingDocumentNodeState(delegate, ROOT_PATH, RevisionVector.fromString(revVector), false, differ); + return new DelegatingDocumentNodeState(delegate, Path.ROOT, RevisionVector.fromString(revVector), false, differ); } - private DelegatingDocumentNodeState(NodeState delegate, String path, RevisionVector rootRevision, + private DelegatingDocumentNodeState(NodeState delegate, Path path, RevisionVector rootRevision, boolean fromExternalChange, NodeStateDiffer differ) { this.differ = differ; this.delegate = delegate; @@ -111,7 +109,7 @@ public class DelegatingDocumentNodeState extends AbstractDocumentNodeState { //~----------------------------------< AbstractDocumentNodeState > @Override - public String getPath() { + public Path getPath() { return path; } @@ -192,7 +190,7 @@ public class DelegatingDocumentNodeState extends AbstractDocumentNodeState { @NotNull @Override public NodeBuilder builder() { - checkState(!denotesRoot(getPath()), "Builder cannot be opened for root " + + checkState(!getPath().isRoot(), "Builder cannot be opened for root " + "path for state of type [%s]", delegate.getClass()); return new MemoryNodeBuilder(this); } @@ -261,7 +259,7 @@ public class DelegatingDocumentNodeState extends AbstractDocumentNodeState { private NodeState decorate(String nodeName, NodeState childNode) { if (childNode.exists()) { - return new DelegatingDocumentNodeState(childNode, concat(path, nodeName), rootRevision, + return new DelegatingDocumentNodeState(childNode, new Path(path, nodeName), rootRevision, fromExternalChange, differ); } return childNode; diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java index aaa4d2ce40..19b1917f21 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/PathFilteringDiff.java @@ -22,7 +22,6 @@ package org.apache.jackrabbit.oak.plugins.document.secondary; import java.util.List; import org.apache.jackrabbit.oak.api.PropertyState; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.spi.filter.PathFilter; @@ -56,7 +55,7 @@ class PathFilteringDiff extends ApplyDiff { @Override public boolean childNodeAdded(String name, NodeState after) { AbstractDocumentNodeState afterDoc = asDocumentState(after, name); - String nextPath = afterDoc.getPath(); + String nextPath = afterDoc.getPath().toString(); PathFilter.Result result = ctx.pathFilter.filter(nextPath); if (result == PathFilter.Result.EXCLUDE){ return true; @@ -75,7 +74,7 @@ class PathFilteringDiff extends ApplyDiff { @Override public boolean childNodeChanged(String name, NodeState before, NodeState after) { AbstractDocumentNodeState afterDoc = asDocumentState(after, name); - String nextPath = afterDoc.getPath(); + String nextPath = afterDoc.getPath().toString(); if (ctx.pathFilter.filter(nextPath) != PathFilter.Result.EXCLUDE) { ctx.traversingNode(nextPath); NodeBuilder childBuilder = builder.getChildNode(name); @@ -88,7 +87,7 @@ class PathFilteringDiff extends ApplyDiff { @Override public boolean childNodeDeleted(String name, NodeState before) { - String path = asDocumentState(before, name).getPath(); + String path = asDocumentState(before, name).getPath().toString(); if (ctx.pathFilter.filter(path) != PathFilter.Result.EXCLUDE) { return super.childNodeDeleted(name, before); } @@ -104,7 +103,7 @@ class PathFilteringDiff extends ApplyDiff { static void copyMetaProperties(AbstractDocumentNodeState state, NodeBuilder builder, List metaPropNames) { //Only set root revision on root node - if (PathUtils.denotesRoot(state.getPath())) { + if (state.getPath().isRoot()) { builder.setProperty(asPropertyState(PROP_REVISION, state.getRootRevision())); } diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java index 34575f25f1..5edd5f8e2a 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCache.java @@ -20,10 +20,10 @@ package org.apache.jackrabbit.oak.plugins.document.secondary; import com.google.common.collect.EvictingQueue; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.document.AbstractDocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache; import org.apache.jackrabbit.oak.plugins.document.NodeStateDiffer; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.spi.filter.PathFilter; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -73,11 +73,12 @@ public class SecondaryStoreCache implements DocumentNodeStateCache, SecondarySto @Nullable @Override - public AbstractDocumentNodeState getDocumentNodeState(String path, RevisionVector rootRevision, - RevisionVector lastRev) { + public AbstractDocumentNodeState getDocumentNodeState(Path path, RevisionVector rootRevision, + RevisionVector lastRev) { //TODO We might need skip the calls if they occur due to SecondaryStoreObserver //doing the diff or in the startup when we try to sync the state - PathFilter.Result result = pathFilter.filter(path); + String p = path.toString(); + PathFilter.Result result = pathFilter.filter(p); if (result != PathFilter.Result.INCLUDE) { unknownPaths.mark(); return null; @@ -103,7 +104,7 @@ public class SecondaryStoreCache implements DocumentNodeStateCache, SecondarySto AbstractDocumentNodeState matchingRoot = findMatchingRoot(rootRevision); if (matchingRoot != null){ - NodeState state = NodeStateUtils.getNode(matchingRoot, path); + NodeState state = NodeStateUtils.getNode(matchingRoot, p); if (state.exists()){ AbstractDocumentNodeState docState = asDocState(state); prevRevMatched.mark(); @@ -116,16 +117,16 @@ public class SecondaryStoreCache implements DocumentNodeStateCache, SecondarySto } @Override - public boolean isCached(String path) { - return pathFilter.filter(path) == PathFilter.Result.INCLUDE; + public boolean isCached(Path path) { + return pathFilter.filter(path.toString()) == PathFilter.Result.INCLUDE; } @Nullable - private AbstractDocumentNodeState findByMatchingLastRev(AbstractDocumentNodeState root, String path, + private AbstractDocumentNodeState findByMatchingLastRev(AbstractDocumentNodeState root, Path path, RevisionVector lastRev){ NodeState state = root; - for (String name : PathUtils.elements(path)) { + for (String name : path.elements()) { state = state.getChildNode(name); if (!state.exists()){ diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java index bdae982297..d07831a51a 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/MapFactory.java @@ -22,6 +22,7 @@ package org.apache.jackrabbit.oak.plugins.document.util; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; /** @@ -31,12 +32,12 @@ import org.apache.jackrabbit.oak.plugins.document.Revision; public abstract class MapFactory { private static MapFactory DEFAULT = new MapFactory() { @Override - public ConcurrentMap create() { - return new ConcurrentHashMap(); + public ConcurrentMap create() { + return new ConcurrentHashMap<>(); } }; - public abstract ConcurrentMap create(); + public abstract ConcurrentMap create(); private static MapFactory instance = DEFAULT; diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java index 78531b35b2..9434ef2287 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/RevisionsKey.java @@ -39,6 +39,14 @@ public final class RevisionsKey implements CacheValue, Comparable this.r2 = checkNotNull(r2); } + public RevisionVector getRev1() { + return r1; + } + + public RevisionVector getRev2() { + return r2; + } + @Override public int getMemory() { long size = 32 + (long)r1.getMemory() + (long)r2.getMemory(); diff --git a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java index 0a01787621..f73299df33 100644 --- a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java +++ b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java @@ -43,6 +43,7 @@ import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.StableRevisionComparator; @@ -116,6 +117,22 @@ public class Utils { return depth; } + /** + * Calculates the depth prefix of the id for the given {@code path}. The is + * the same as {@link #pathDepth(String)}, but takes a {@link Path} + * argument. + * + * @param path a path. + * @return the id depth prefix for the given {@code path}. + */ + public static int getIdDepth(Path path) { + int depth = path.getDepth(); + if (!path.isAbsolute()) { + depth--; + } + return depth; + } + @SuppressWarnings("unchecked") public static int estimateMemoryUsage(Map map) { if (map == null) { @@ -232,27 +249,42 @@ public class Utils { return !key.startsWith("_") || key.startsWith("__") || key.startsWith("_$"); } - public static String getIdFromPath(String path) { + public static String getIdFromPath(@NotNull String path) { + int depth = Utils.pathDepth(path); if (isLongPath(path)) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - int depth = Utils.pathDepth(path); String parent = PathUtils.getParentPath(path); - byte[] hash = digest.digest(parent.getBytes(UTF_8)); - String name = PathUtils.getName(path); - StringBuilder sb = new StringBuilder(digest.getDigestLength() * 2 + name.length() + 6); - sb.append(depth).append(":h"); - encodeHexString(hash, sb).append("/").append(name); - return sb.toString(); + byte[] hash = createSHA256Digest(parent); + return createHashedId(depth, hash, PathUtils.getName(path)); + } + return depth + ":" + path; + } + + public static String getIdFromPath(@NotNull Path path) { + checkNotNull(path); + int depth = getIdDepth(path); + Path parent = path.getParent(); + if (parent != null && isLongPath(path)) { + byte[] hash = createSHA256Digest(parent.toString()); + return createHashedId(depth, hash, path.getName()); } - int depth = Utils.pathDepth(path); return depth + ":" + path; } + private static String createHashedId(int depth, byte[] hash, String name) { + StringBuilder sb = new StringBuilder(hash.length * 2 + name.length() + 6); + sb.append(depth).append(":h"); + encodeHexString(hash, sb).append("/").append(name); + return sb.toString(); + } + + private static byte[] createSHA256Digest(String input) { + try { + return MessageDigest.getInstance("SHA-256").digest(input.getBytes(UTF_8)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + /** * Encodes the given data as hexadecimal string representation and appends * it to the {@code StringBuilder}. The hex digits are in lower case. @@ -298,7 +330,7 @@ public class Utils { return Utils.getIdFromPath(parentPath); } - public static boolean isLongPath(String path) { + private static boolean isLongPath(String path) { // the most common case: a short path // avoid calculating the parent path if (path.length() < PATH_SHORT) { @@ -315,12 +347,33 @@ public class Utils { } return true; } - + + public static boolean isLongPath(Path path) { + // the most common case: a short path + // avoid calculating the parent path + if (path.length() < PATH_SHORT) { + return false; + } + // check if the parent path is long + Path parent = path.getParent(); + if (parent == null) { + return false; + } + if (parent.toString().getBytes(UTF_8).length < PATH_LONG) { + return false; + } + if (path.getName().getBytes(UTF_8).length > NODE_NAME_LIMIT) { + throw new IllegalArgumentException("Node name is too long: " + path); + } + return true; + } + public static boolean isIdFromLongPath(String id) { int index = id.indexOf(':'); return index != -1 && index < id.length() - 1 && id.charAt(index + 1) == 'h'; } + // TODO deprecate? public static String getPathFromId(String id) { if (isIdFromLongPath(id)) { throw new IllegalArgumentException("Id is hashed: " + id); @@ -341,20 +394,19 @@ public class Utils { throw new IllegalArgumentException("Invalid id: " + id); } - public static String getPreviousPathFor(String path, Revision r, int height) { - if (!PathUtils.isAbsolute(path)) { + public static Path getPreviousPathFor(Path path, Revision r, int height) { + if (!path.isAbsolute()) { throw new IllegalArgumentException("path must be absolute: " + path); } - StringBuilder sb = new StringBuilder(path.length() + REVISION_LENGTH + 3); - sb.append("p").append(path); - if (sb.charAt(sb.length() - 1) != '/') { - sb.append('/'); + Path prev = new Path("p"); + for (String name : path.elements()) { + prev = new Path(prev, name); } - r.toStringBuilder(sb).append("/").append(height); - return sb.toString(); + prev = new Path(prev, r.toString()); + return new Path(prev, String.valueOf(height)); } - public static String getPreviousIdFor(String path, Revision r, int height) { + public static String getPreviousIdFor(Path path, Revision r, int height) { return getIdFromPath(getPreviousPathFor(path, r, height)); } @@ -416,9 +468,8 @@ public class Utils { * @param path a path. * @return the lower key limit. */ - public static String getKeyLowerLimit(String path) { - String from = PathUtils.concat(path, "a"); - from = getIdFromPath(from); + public static String getKeyLowerLimit(Path path) { + String from = getIdFromPath(new Path(path, "a")); from = from.substring(0, from.length() - 1); return from; } @@ -430,9 +481,8 @@ public class Utils { * @param path a path. * @return the upper key limit. */ - public static String getKeyUpperLimit(String path) { - String to = PathUtils.concat(path, "z"); - to = getIdFromPath(to); + public static String getKeyUpperLimit(Path path) { + String to = getIdFromPath(new Path(path, "z")); to = to.substring(0, to.length() - 2) + "0"; return to; } @@ -608,7 +658,7 @@ public class Utils { */ @NotNull public static NodeDocument getRootDocument(@NotNull DocumentStore store) { - String rootId = Utils.getIdFromPath("/"); + String rootId = Utils.getIdFromPath(Path.ROOT); NodeDocument root = store.find(Collection.NODES, rootId); if (root == null) { throw new IllegalStateException("missing root document"); @@ -950,7 +1000,7 @@ public class Utils { ClusterNodeInfo info, Clock clock) throws DocumentStoreException { - NodeDocument root = store.find(Collection.NODES, getIdFromPath("/")); + NodeDocument root = store.find(Collection.NODES, getIdFromPath(Path.ROOT)); if (root == null) { return; } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java index 9c56ecf10f..6be9feedc2 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AmnesiaDiffCache.java @@ -36,7 +36,7 @@ class AmnesiaDiffCache extends DiffCache { @Override public String getChanges(@NotNull RevisionVector from, @NotNull RevisionVector to, - @NotNull String path, + @NotNull Path path, @Nullable Loader loader) { if (loader != null) { return loader.call(); @@ -49,7 +49,7 @@ class AmnesiaDiffCache extends DiffCache { public Entry newEntry(@NotNull RevisionVector from, @NotNull RevisionVector to, boolean local) { return new Entry() { @Override - public void append(@NotNull String path, @NotNull String changes) { + public void append(@NotNull Path path, @NotNull String changes) { } @Override diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AsyncCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AsyncCacheTest.java index c99af187ce..43c97a5603 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AsyncCacheTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/AsyncCacheTest.java @@ -37,14 +37,15 @@ public class AsyncCacheTest { DocumentMK.Builder builder = builderProvider.newBuilder(); builder.setPersistentCache("target/cacheTest"); DocumentNodeStore nodeStore = builder.getNodeStore(); - Cache cache = builder.buildChildrenCache(nodeStore); + Cache cache = builder.buildChildrenCache(nodeStore); DocumentNodeState.Children c = new DocumentNodeState.Children(); for (int i = 0; i < 100; i++) { c.children.add("node-" + i); } - PathRev key = null; + Path path = Path.fromString("/foo/bar"); + NamePathRev key = null; for (int i = 0; i < 1000; i++) { - key = new PathRev("/foo/bar", new RevisionVector(new Revision(i, 0, 1))); + key = new NamePathRev("", path, new RevisionVector(new Revision(i, 0, 1))); cache.put(key, c); } cache.invalidate(key); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundWriteTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundWriteTest.java index e83d5f9bea..6b65cf7a36 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundWriteTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BackgroundWriteTest.java @@ -53,9 +53,9 @@ public class BackgroundWriteTest { mk.runBackgroundOperations(); Revision r = mk.getNodeStore().newRevision(); UnsavedModifications pending = mk.getNodeStore().getPendingModifications(); - pending.put("/", r); + pending.put(Path.ROOT, r); for (String p : paths) { - pending.put(p, r); + pending.put(Path.fromString(p), r); } mk.runBackgroundOperations(); mk.dispose(); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java index ebb25cf67d..de346ffb8b 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java @@ -1104,7 +1104,7 @@ public class BasicDocumentStoreTest extends AbstractDocumentStoreTest { @Test public void removeWithCondition() throws Exception { - Set existingDocs = new HashSet(); + Set existingDocs = new HashSet<>(); for (NodeDocument doc : Utils.getAllDocuments(ds)) { existingDocs.add(doc.getPath()); } @@ -1132,8 +1132,9 @@ public class BasicDocumentStoreTest extends AbstractDocumentStoreTest { assertEquals(2, removed); assertNotNull(ds.find(Collection.NODES, Utils.getIdFromPath("/bar"))); + Path bar = Path.fromString("/bar"); for (NodeDocument doc : Utils.getAllDocuments(ds)) { - if (!doc.getPath().equals("/bar") && !existingDocs.contains(doc.getPath())) { + if (!doc.getPath().equals(bar) && !existingDocs.contains(doc.getPath())) { fail("document must not exist: " + doc.getId()); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchTest.java index 6f88da389f..91dbb62ba9 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/BranchTest.java @@ -56,12 +56,12 @@ public class BranchTest { Branch b = branches.create(base, c1, null); BranchCommit bc1 = b.getCommit(c1); - bc1.track("/foo"); + bc1.track(Path.fromString("/foo")); Revision c2 = Revision.newRevision(1).asBranchRevision(); b.addCommit(c2); BranchCommit bc2 = b.getCommit(c2); - bc2.track("/bar"); + bc2.track(Path.fromString("/bar")); Revision c3 = Revision.newRevision(1).asBranchRevision(); b.rebase(c3, new RevisionVector(Revision.newRevision(1))); @@ -69,7 +69,7 @@ public class BranchTest { Revision c4 = Revision.newRevision(1).asBranchRevision(); b.addCommit(c4); BranchCommit bc4 = b.getCommit(c4); - bc4.track("/baz"); + bc4.track(Path.fromString("/baz")); Revision c5 = Revision.newRevision(1).asBranchRevision(); @@ -138,7 +138,11 @@ public class BranchTest { assertEquals("a", state.getString("p")); } - private void assertModifiedPaths(Iterable actual, String... expected) { - assertEquals(Sets.newHashSet(expected), Sets.newHashSet(actual)); + private void assertModifiedPaths(Iterable actual, String... expected) { + Set expectedSet = Sets.newHashSet(); + for (String p : expected) { + expectedSet.add(Path.fromString(p)); + } + assertEquals(expectedSet, Sets.newHashSet(actual)); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyRDBTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyRDBTest.java index 038b36e591..244420184b 100755 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyRDBTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyRDBTest.java @@ -65,7 +65,7 @@ public class CacheConsistencyRDBTest extends AbstractRDBConnectionTest { Thread t = new Thread(new Runnable() { @Override public void run() { - store.query(NODES, Utils.getKeyLowerLimit("/"), Utils.getKeyUpperLimit("/"), 10); + store.query(NODES, Utils.getKeyLowerLimit(Path.ROOT), Utils.getKeyUpperLimit(Path.ROOT), 10); } }, "query"); // block thread when it tries to convert db objects diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyTest.java index 6846609829..85be44bc31 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CacheConsistencyTest.java @@ -71,8 +71,8 @@ public class CacheConsistencyTest extends AbstractMongoConnectionTest { @Override public void run() { store.query(NODES, - Utils.getKeyLowerLimit("/"), - Utils.getKeyUpperLimit("/"), 10); + Utils.getKeyLowerLimit(Path.ROOT), + Utils.getKeyUpperLimit(Path.ROOT), 10); } }); // block thread when it tries to convert db objects diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ClusterTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ClusterTest.java index d08e65a280..aab3fba170 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ClusterTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ClusterTest.java @@ -225,9 +225,9 @@ public class ClusterTest { mk3.runBackgroundOperations(); // pick up changes from mk2 - DocumentNodeState base = ns3.getNode("/", RevisionVector.fromString(base3)); + DocumentNodeState base = ns3.getNode(Path.ROOT, RevisionVector.fromString(base3)); assertNotNull(base); - NodeState branchHead = ns3.getNode("/", RevisionVector.fromString(b3)); + NodeState branchHead = ns3.getNode(Path.ROOT, RevisionVector.fromString(b3)); assertNotNull(branchHead); TrackingDiff diff = new TrackingDiff(); branchHead.compareAgainstBaseState(base, diff); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilderTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilderTest.java index be001b6db3..c6428ffd1e 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilderTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitBuilderTest.java @@ -92,8 +92,9 @@ public class CommitBuilderTest { @Test public void addNode() { RevisionVector baseRev = ns.getHeadRevision(); + Path foo = Path.fromString("/foo"); CommitBuilder builder = new CommitBuilder(ns, baseRev); - builder.addNode("/foo"); + builder.addNode(foo); Commit c = builder.build(ns.newRevision()); assertNotNull(c); assertFalse(c.isEmpty()); @@ -102,10 +103,11 @@ public class CommitBuilderTest { @Test public void addNodeTwice() { RevisionVector baseRev = ns.getHeadRevision(); + Path foo = Path.fromString("/foo"); CommitBuilder builder = new CommitBuilder(ns, baseRev); - builder.addNode("/foo"); + builder.addNode(foo); try { - builder.addNode("/foo"); + builder.addNode(foo); fail("Must fail with DocumentStoreException"); } catch (DocumentStoreException e) { assertThat(e.getMessage(), containsString("already added")); @@ -116,7 +118,7 @@ public class CommitBuilderTest { public void addNodePathNull() { CommitBuilder builder = new CommitBuilder(ns, null); try { - builder.addNode((String) null); + builder.addNode((Path) null); expectNPE(); } catch (NullPointerException e) { // expected @@ -136,7 +138,7 @@ public class CommitBuilderTest { @Test public void addNodeState() { - String path = "/foo"; + Path path = Path.fromString("/foo"); DocumentNodeState foo = addNode("foo"); CommitBuilder builder = new CommitBuilder(ns, null); @@ -153,10 +155,11 @@ public class CommitBuilderTest { RevisionVector baseRev = ns.getHeadRevision() .update(ns.newRevision().asBranchRevision()); CommitBuilder builder = new CommitBuilder(ns, baseRev); - builder.addNode("/foo"); + Path path = Path.fromString("/foo"); + builder.addNode(path); Revision commitRev = ns.newRevision(); Commit c = builder.build(commitRev); - UpdateOp up = c.getUpdateOperationForNode("/foo"); + UpdateOp up = c.getUpdateOperationForNode(path); UpdateOp.Operation op = up.getChanges().get( new UpdateOp.Key("_bc", commitRev)); assertNotNull(op); @@ -167,7 +170,7 @@ public class CommitBuilderTest { DocumentNodeState bar = addNode("bar"); CommitBuilder builder = new CommitBuilder(ns, null); - String path = "/bar"; + Path path = Path.fromString("/bar"); builder.removeNode(path, bar); Commit c = builder.build(ns.newRevision()); @@ -182,7 +185,7 @@ public class CommitBuilderTest { DocumentNodeState bar = addNode("bar"); CommitBuilder builder = new CommitBuilder(ns, null); - String path = "/bar"; + Path path = Path.fromString("/bar"); builder.removeNode(path, bar); try { @@ -210,7 +213,7 @@ public class CommitBuilderTest { public void removeNodeStateNull() { CommitBuilder builder = new CommitBuilder(ns, null); try { - builder.removeNode("/bar", null); + builder.removeNode(Path.fromString("/bar"), null); expectNPE(); } catch (NullPointerException e) { // expected @@ -219,10 +222,11 @@ public class CommitBuilderTest { @Test public void updateProperty() { + Path path = Path.fromString("/foo"); CommitBuilder builder = new CommitBuilder(ns, null); - builder.updateProperty("/foo", "p", "v"); + builder.updateProperty(path, "p", "v"); Commit c = builder.build(ns.newRevision()); - UpdateOp up = c.getUpdateOperationForNode("/foo"); + UpdateOp up = c.getUpdateOperationForNode(path); UpdateOp.Operation op = up.getChanges().get( new UpdateOp.Key("p", c.getRevision())); assertNotNull(op); @@ -230,10 +234,11 @@ public class CommitBuilderTest { @Test public void updatePropertyValueNull() { + Path path = Path.fromString("/foo"); CommitBuilder builder = new CommitBuilder(ns, null); - builder.updateProperty("/foo", "p", null); + builder.updateProperty(path, "p", null); Commit c = builder.build(ns.newRevision()); - UpdateOp up = c.getUpdateOperationForNode("/foo"); + UpdateOp up = c.getUpdateOperationForNode(path); UpdateOp.Operation op = up.getChanges().get( new UpdateOp.Key("p", c.getRevision())); assertNotNull(op); @@ -243,7 +248,7 @@ public class CommitBuilderTest { public void updatePropertyPathNull() { CommitBuilder builder = new CommitBuilder(ns, null); try { - builder.updateProperty(null, "p", "v"); + builder.updateProperty((Path) null, "p", "v"); expectNPE(); } catch (NullPointerException e) { // expected @@ -254,7 +259,7 @@ public class CommitBuilderTest { public void updatePropertyPropertyNull() { CommitBuilder builder = new CommitBuilder(ns, null); try { - builder.updateProperty("/foo", null, "v"); + builder.updateProperty(Path.fromString("/foo"), null, "v"); expectNPE(); } catch (NullPointerException e) { // expected diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitRootUpdateTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitRootUpdateTest.java index 118cdf864c..f47edd9b6f 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitRootUpdateTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitRootUpdateTest.java @@ -68,8 +68,8 @@ public class CommitRootUpdateTest { throwAfterUpdate.set(true); boolean success = false; Commit c = ns.newCommit(changes -> { - changes.addNode("/foo/node"); - changes.addNode("/bar/node"); + changes.addNode(Path.fromString("/foo/node")); + changes.addNode(Path.fromString("/bar/node")); }, ns.getHeadRevision(), null); try { c.apply(); @@ -125,7 +125,7 @@ public class CommitRootUpdateTest { throwAfterUpdate.set(true); boolean success = false; Commit c = ns.newCommit( - changes -> changes.updateProperty("/foo", "p", "v"), + changes -> changes.updateProperty(Path.fromString("/foo"), "p", "v"), ns.getHeadRevision(), null); try { c.apply(); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitTest.java index d34e51d149..152e050b63 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CommitTest.java @@ -59,10 +59,10 @@ public class CommitTest { // this commit should fail Commit c = ns.newCommit(changes -> { - changes.addNode("/foo/baz"); + changes.addNode(Path.fromString("/foo/baz")); }, ns.getHeadRevision(), null); try { - UpdateOp op = c.getUpdateOperationForNode("/bar"); + UpdateOp op = c.getUpdateOperationForNode(Path.fromString("/bar")); op.setMapEntry("p", c.getRevision(), "v"); try { c.apply(); @@ -94,7 +94,7 @@ public class CommitTest { // this commit should fail Commit c = ns.newCommit(changes -> { - changes.addNode("/foo"); + changes.addNode(Path.fromString("/foo")); }, ns.getHeadRevision(), null); try { c.apply(); @@ -117,7 +117,7 @@ public class CommitTest { // this branch commit must fail with a DocumentStoreException Commit c = ns.newCommit(changes -> { - changes.removeNode("/foo", EMPTY_NODE); + changes.removeNode(Path.fromString("/foo"), EMPTY_NODE); }, ns.getHeadRevision().asBranchRevision(ns.getClusterId()), null); try { try { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndInvalidateIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndInvalidateIT.java index 85608a6e18..8a3e20f61b 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndInvalidateIT.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndInvalidateIT.java @@ -122,7 +122,7 @@ public class ConcurrentQueryAndInvalidateIT extends AbstractMultiDocumentStoreTe } private void queryDocuments() { - ds1.query(NODES, getKeyLowerLimit("/"), getKeyUpperLimit("/"), 100); + ds1.query(NODES, getKeyLowerLimit(Path.ROOT), getKeyUpperLimit(Path.ROOT), 100); } private Iterable updateDocuments() { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdate2IT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdate2IT.java index 62788469c6..d892568193 100755 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdate2IT.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdate2IT.java @@ -105,7 +105,7 @@ public class ConcurrentQueryAndUpdate2IT extends AbstractDocumentStoreTest { Thread.sleep(0, ThreadLocalRandom.current().nextInt(1000, 10000)); } catch (InterruptedException ignore) { } - ds.query(NODES, getKeyLowerLimit("/"), getKeyUpperLimit("/"), 100); + ds.query(NODES, getKeyLowerLimit(Path.ROOT), getKeyUpperLimit(Path.ROOT), 100); } private void updateDocuments() { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdateIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdateIT.java index c5fe807066..951c698607 100755 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdateIT.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ConcurrentQueryAndUpdateIT.java @@ -87,7 +87,7 @@ public class ConcurrentQueryAndUpdateIT extends AbstractDocumentStoreTest { } private void queryDocuments() { - ds.query(NODES, getKeyLowerLimit("/"), getKeyUpperLimit("/"), 100); + ds.query(NODES, getKeyLowerLimit(Path.ROOT), getKeyUpperLimit(Path.ROOT), 100); } private void updateDocuments() { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDiffCache.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDiffCache.java index 35a1f9fd5c..931e0eaec7 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDiffCache.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDiffCache.java @@ -58,7 +58,7 @@ public class CountingDiffCache extends MemoryDiffCache { @Override public String getChanges(@NotNull RevisionVector from, @NotNull RevisionVector to, - @NotNull String path, + @NotNull Path path, @Nullable Loader loader) { return super.getChanges(from, to, path, new CountingLoader(loader)); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java index cf5223917d..eaed5d543e 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentMK.java @@ -126,8 +126,9 @@ public class DocumentMK { } RevisionVector fromRev = RevisionVector.fromString(fromRevisionId); RevisionVector toRev = RevisionVector.fromString(toRevisionId); - final DocumentNodeState before = nodeStore.getNode(path, fromRev); - final DocumentNodeState after = nodeStore.getNode(path, toRev); + Path p = Path.fromString(path); + final DocumentNodeState before = nodeStore.getNode(p, fromRev); + final DocumentNodeState after = nodeStore.getNode(p, toRev); if (before == null || after == null) { String msg = String.format("Diff is only supported if the node exists in both cases. " + "Node [%s], fromRev [%s] -> %s, toRev [%s] -> %s", @@ -149,7 +150,7 @@ public class DocumentMK { RevisionVector rev = RevisionVector.fromString(revisionId); DocumentNodeState n; try { - n = nodeStore.getNode(path, rev); + n = nodeStore.getNode(Path.fromString(path), rev); } catch (DocumentStoreException e) { throw new DocumentStoreException(e); } @@ -165,7 +166,7 @@ public class DocumentMK { revisionId = revisionId != null ? revisionId : nodeStore.getHeadRevision().toString(); RevisionVector rev = RevisionVector.fromString(revisionId); try { - DocumentNodeState n = nodeStore.getNode(path, rev); + DocumentNodeState n = nodeStore.getNode(Path.fromString(path), rev); if (n == null) { return null; } @@ -183,7 +184,7 @@ public class DocumentMK { long m = ((long) maxChildNodes) + offset; max = (int) Math.min(m, Integer.MAX_VALUE); } - Children c = nodeStore.getChildren(n, null, max); + Children c = nodeStore.getChildren(n, "", max); for (long i = offset; i < c.children.size(); i++) { if (maxChildNodes-- <= 0) { break; @@ -330,7 +331,7 @@ public class DocumentMK { added.add(path); break; case '-': - DocumentNodeState toRemove = nodeStore.getNode(path, commit.getBaseRevision()); + DocumentNodeState toRemove = nodeStore.getNode(Path.fromString(path), commit.getBaseRevision()); if (toRemove == null) { throw new DocumentStoreException("Node not found: " + path + " in revision " + baseRevId); } @@ -345,11 +346,11 @@ public class DocumentMK { value = t.readRawValue().trim(); } String p = PathUtils.getParentPath(path); - if (!added.contains(p) && nodeStore.getNode(p, commit.getBaseRevision()) == null) { + if (!added.contains(p) && nodeStore.getNode(Path.fromString(p), commit.getBaseRevision()) == null) { throw new DocumentStoreException("Node not found: " + path + " in revision " + baseRevId); } String propertyName = PathUtils.getName(path); - commit.updateProperty(p, propertyName, value); + commit.updateProperty(Path.fromString(p), propertyName, value); break; case '>': { t.read(':'); @@ -357,7 +358,7 @@ public class DocumentMK { if (!PathUtils.isAbsolute(targetPath)) { targetPath = PathUtils.concat(rootPath, targetPath); } - DocumentNodeState source = nodeStore.getNode(path, baseRev); + DocumentNodeState source = nodeStore.getNode(Path.fromString(path), baseRev); if (source == null) { throw new DocumentStoreException("Node not found: " + path + " in revision " + baseRevId); } else if (nodeExists(targetPath, baseRevId)) { @@ -372,7 +373,7 @@ public class DocumentMK { if (!PathUtils.isAbsolute(targetPath)) { targetPath = PathUtils.concat(rootPath, targetPath); } - DocumentNodeState source = nodeStore.getNode(path, baseRev); + DocumentNodeState source = nodeStore.getNode(Path.fromString(path), baseRev); if (source == null) { throw new DocumentStoreException("Node not found: " + path + " in revision " + baseRevId); } else if (nodeExists(targetPath, baseRevId)) { @@ -403,7 +404,7 @@ public class DocumentMK { } while (t.matches(',')); t.read('}'); } - DocumentNodeState n = new DocumentNodeState(nodeStore, path, + DocumentNodeState n = new DocumentNodeState(nodeStore, Path.fromString(path), new RevisionVector(commit.getRevision()), props, false, null); commit.addNode(n); } @@ -421,7 +422,7 @@ public class DocumentMK { if (subTreeAlso) { // recurse down the tree - for (DocumentNodeState child : nodeStore.getChildNodes(node, null, Integer.MAX_VALUE)) { + for (DocumentNodeState child : nodeStore.getChildNodes(node, "", Integer.MAX_VALUE)) { markAsDeleted(child, commit, true); } } @@ -432,15 +433,15 @@ public class DocumentMK { String targetPath, CommitBuilder commit) { RevisionVector destRevision = commit.getBaseRevision().update(commit.getRevision()); - DocumentNodeState newNode = new DocumentNodeState(nodeStore, targetPath, destRevision, + DocumentNodeState newNode = new DocumentNodeState(nodeStore, Path.fromString(targetPath), destRevision, source.getProperties(), false, null); commit.addNode(newNode); if (move) { markAsDeleted(source, commit, false); } - for (DocumentNodeState child : nodeStore.getChildNodes(source, null, Integer.MAX_VALUE)) { - String childName = PathUtils.getName(child.getPath()); + for (DocumentNodeState child : nodeStore.getChildNodes(source, "", Integer.MAX_VALUE)) { + String childName = child.getPath().getName(); String destChildPath = concat(targetPath, childName); moveOrCopyNode(move, child, destChildPath, commit); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateTest.java index 2cf72d3ac5..1497f50cb3 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStateTest.java @@ -35,8 +35,8 @@ public class DocumentNodeStateTest { public void getMemory() { DocumentNodeStore store = builderProvider.newBuilder().getNodeStore(); RevisionVector rv = new RevisionVector(Revision.newRevision(1)); - DocumentNodeState state = new DocumentNodeState(store, "/foo", rv); - assertEquals(176, state.getMemory()); + DocumentNodeState state = new DocumentNodeState(store, Path.fromString("/foo"), rv); + assertEquals(198, state.getMemory()); } @Test diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranchesTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranchesTest.java index c4ac7d7732..4c70e4827c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranchesTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreBranchesTest.java @@ -190,10 +190,12 @@ public class DocumentNodeStoreBranchesTest { long numCreateOrUpdate = store.getNumCreateOrUpdateCalls(NODES); assertThat(numCreateOrUpdate, lessThanOrEqualTo(branchCommits + 1)); + Path bar = Path.fromString("/bar"); // verify reset cleaned up properly for (NodeDocument doc : Utils.getAllDocuments(store)) { - String path = doc.getPath(); - if (path.startsWith("/bar")) { + Path p = doc.getPath(); + if (bar.isAncestorOf(p) || bar.equals(p)) { + String path = p.toString(); assertThat(path, doc.getLocalRevisions().keySet(), is(empty())); assertThat(path, doc.getLocalCommitRoot().keySet(), is(empty())); assertThat(path, doc.getDeleted().keySet(), is(empty())); @@ -238,10 +240,12 @@ public class DocumentNodeStoreBranchesTest { // expected } + Path bar = Path.fromString("/bar"); // verify reset cleaned up properly for (NodeDocument doc : Utils.getAllDocuments(ns.getDocumentStore())) { - String path = doc.getPath(); - if (path.startsWith("/bar")) { + Path p = doc.getPath(); + if (bar.isAncestorOf(p) || bar.equals(p)) { + String path = p.toString(); assertThat(path, doc.getLocalRevisions().keySet(), is(empty())); assertThat(path, doc.getLocalCommitRoot().keySet(), is(empty())); assertThat(path, doc.getDeleted().keySet(), is(empty())); @@ -295,10 +299,12 @@ public class DocumentNodeStoreBranchesTest { // otherwise expected } + Path bar = Path.fromString("/bar"); // verify reset cleaned up properly for (NodeDocument doc : Utils.getAllDocuments(ns.getDocumentStore())) { - String path = doc.getPath(); - if (path.startsWith("/bar")) { + Path p = doc.getPath(); + if (bar.isAncestorOf(p) || bar.equals(p)) { + String path = p.toString(); assertThat(path, doc.getLocalRevisions().keySet(), is(empty())); assertThat(path, doc.getLocalCommitRoot().keySet(), is(empty())); assertThat(path, doc.getDeleted().keySet(), is(empty())); @@ -353,10 +359,12 @@ public class DocumentNodeStoreBranchesTest { // otherwise expected } + Path bar = Path.fromString("/bar"); // verify reset cleaned up properly for (NodeDocument doc : Utils.getAllDocuments(ns.getDocumentStore())) { - String path = doc.getPath(); - if (path.startsWith("/bar")) { + Path p = doc.getPath(); + if (bar.isAncestorOf(p) || bar.equals(p)) { + String path = p.toString(); assertThat(path, doc.getLocalRevisions().keySet(), is(empty())); assertThat(path, doc.getLocalCommitRoot().keySet(), is(empty())); assertThat(path, doc.getDeleted().keySet(), is(empty())); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java index 0e475e2157..820bce9196 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreServiceTest.java @@ -195,10 +195,10 @@ public class DocumentNodeStoreServiceTest { MockOsgi.activate(service, context.bundleContext()); DocumentNodeStore dns = context.getService(DocumentNodeStore.class); - assertTrue(dns.getNodeCachePredicate().apply("/a/b/c")); - assertTrue(dns.getNodeCachePredicate().apply("/c/d/e")); + assertTrue(dns.getNodeCachePredicate().apply(Path.fromString("/a/b/c"))); + assertTrue(dns.getNodeCachePredicate().apply(Path.fromString("/c/d/e"))); - assertFalse(dns.getNodeCachePredicate().apply("/x")); + assertFalse(dns.getNodeCachePredicate().apply(Path.fromString("/x"))); } @Test diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepIT.java index 6d9fa75e38..df3356a190 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepIT.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepIT.java @@ -164,7 +164,7 @@ public class DocumentNodeStoreSweepIT extends AbstractTwoNodeTest { // store must now contain uncommitted changes NodeDocument doc = null; for (NodeDocument d : Utils.getAllDocuments(store)) { - if (d.getPath().startsWith("/node-")) { + if (d.getPath().toString().startsWith("/node-")) { doc = d; break; } @@ -175,7 +175,7 @@ public class DocumentNodeStoreSweepIT extends AbstractTwoNodeTest { assertEquals(1, deleted.size()); assertNull(ns.getCommitValue(deleted.firstKey(), doc)); - return doc.getPath(); + return doc.getPath().toString(); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java index ef27f50324..c1eb8571d7 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreSweepTest.java @@ -37,7 +37,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import static org.apache.jackrabbit.oak.commons.PathUtils.ROOT_PATH; import static org.apache.jackrabbit.oak.plugins.document.TestUtils.NO_BINARY; import static org.apache.jackrabbit.oak.plugins.document.TestUtils.merge; import static org.apache.jackrabbit.oak.plugins.document.util.Utils.getAllDocuments; @@ -216,7 +215,7 @@ public class DocumentNodeStoreSweepTest { crashDocumentNodeStore(); // and remove the sweep revision for clusterId // this will look like an upgraded and crashed pre 1.8 node store - UpdateOp op = new UpdateOp(getIdFromPath(ROOT_PATH), false); + UpdateOp op = new UpdateOp(getIdFromPath(Path.ROOT), false); op.removeMapEntry("_sweepRev", new Revision(0, 0, clusterId)); assertNotNull(store.findAndUpdate(Collection.NODES, op)); NodeDocument rootDoc = getRootDocument(store); @@ -260,7 +259,7 @@ public class DocumentNodeStoreSweepTest { // get the revision of the uncommitted changes Revision r = null; for (NodeDocument d : Utils.getAllDocuments(store)) { - if (d.getPath().startsWith("/node-")) { + if (d.getPath().toString().startsWith("/node-")) { r = Iterables.getFirst(d.getAllChanges(), null); break; } @@ -308,7 +307,7 @@ public class DocumentNodeStoreSweepTest { // store must now contain uncommitted changes NodeDocument doc = null; for (NodeDocument d : Utils.getAllDocuments(store)) { - if (d.getPath().startsWith("/node-")) { + if (d.getPath().toString().startsWith("/node-")) { doc = d; break; } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java index 753366268a..64e341799c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentNodeStoreTest.java @@ -295,9 +295,9 @@ public class DocumentNodeStoreTest { @Override public void run() { Commit c = new CommitBuilder(store, store.newRevision(), head) - .addNode("/newConflictingNode") - .addNode("/deletedNode") - .updateProperty("/updateNode", "foo", "baz") + .addNode(Path.fromString("/newConflictingNode")) + .addNode(Path.fromString("/deletedNode")) + .updateProperty(Path.fromString("/updateNode"), "foo", "baz") .build(); try { c.apply(); @@ -316,8 +316,8 @@ public class DocumentNodeStoreTest { // commit will succeed and add collision marker to writer commit Revision r = store.newRevision(); Commit c = new CommitBuilder(store, r, head) - .addNode("/newConflictingNode") - .addNode("/newNonConflictingNode") + .addNode(Path.fromString("/newConflictingNode")) + .addNode(Path.fromString("/newNonConflictingNode")) .build(); c.apply(); // allow writer to continue @@ -2144,7 +2144,7 @@ public class DocumentNodeStoreTest { RevisionVector to = ns.getHeadRevision(); DiffCache.Entry entry = ns.getDiffCache().newEntry(from, to, true); - entry.append("/", "-\"foo\""); + entry.append(Path.ROOT, "-\"foo\""); entry.done(); ns.compare(ns.getRoot(), ns.getRoot(from), new DefaultNodeStateDiff() { @@ -2928,7 +2928,7 @@ public class DocumentNodeStoreTest { RevisionVector headRev = ns.getHeadRevision(); Iterable nodes = ns.getChildNodes( - asDocumentNodeState(ns.getRoot().getChildNode("foo")), null, 10); + asDocumentNodeState(ns.getRoot().getChildNode("foo")), "", 10); assertEquals(2, Iterables.size(nodes)); for (DocumentNodeState c : nodes) { assertEquals(headRev, c.getRootRevision()); @@ -3259,7 +3259,7 @@ public class DocumentNodeStoreTest { Revision rev = ns.newRevision(); RevisionVector after = new RevisionVector(ns.newRevision()); - String path = "/foo"; + Path path = Path.fromString("/foo"); ns.getNode(path, before); assertNotNull(ns.getNodeCache().getIfPresent(new PathRev(path, before))); @@ -3282,7 +3282,7 @@ public class DocumentNodeStoreTest { RevisionVector head = ns.getHeadRevision(); // simulate an incorrect cache entry - PathRev key = new PathRev("/", head); + NamePathRev key = new NamePathRev("", Path.ROOT, head); DocumentNodeState.Children c = new DocumentNodeState.Children(); c.children.add("a"); c.children.add("b"); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java index cb9d1dcd9a..ac7d616155 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentSplitTest.java @@ -107,7 +107,7 @@ public class DocumentSplitTest extends BaseDocumentMKTest { assertTrue(isCommitted(ns.getCommitValue(rev, doc))); } // check if document is still there - assertNotNull(ns.getNode("/", RevisionVector.fromString(head))); + assertNotNull(ns.getNode(Path.ROOT, RevisionVector.fromString(head))); NodeDocument prevDoc = Iterators.getOnlyElement(doc.getAllPreviousDocs()); assertThat(prevDoc.getSplitDocType(), either(is(SplitDocType.DEFAULT)).or(is(SplitDocType.DEFAULT_NO_BRANCH))); @@ -147,7 +147,7 @@ public class DocumentSplitTest extends BaseDocumentMKTest { || doc.getCommitRootPath(rev) != null); assertTrue(isCommitted(ns.getCommitValue(rev, doc))); } - DocumentNodeState node = ns.getNode("/foo", RevisionVector.fromString(head)); + DocumentNodeState node = ns.getNode(Path.fromString("/foo"), RevisionVector.fromString(head)); // check status of node if (create) { assertNull(node); @@ -305,7 +305,7 @@ public class DocumentSplitTest extends BaseDocumentMKTest { NodeDocument doc = ds.find(NODES, Utils.getIdFromPath("/test")); assertNotNull(doc); RevisionVector head = ns.getHeadRevision(); - Revision lastRev = ns.getPendingModifications().get("/test"); + Revision lastRev = ns.getPendingModifications().get(Path.fromString("/test")); DocumentNodeState n = doc.getNodeAtRevision(mk.getNodeStore(), head, lastRev); assertNotNull(n); String value = n.getPropertyAsString(name); @@ -416,11 +416,11 @@ public class DocumentSplitTest extends BaseDocumentMKTest { @Test public void cascadingSplitLongPath() { - String p = "/"; + Path p = Path.ROOT; while (!Utils.isLongPath(p)) { - p = PathUtils.concat(p, "long-path-element"); + p = new Path(p, "long-path-element"); } - cascadingSplit(p); + cascadingSplit(p.toString()); } private void cascadingSplit(String path) { @@ -502,7 +502,8 @@ public class DocumentSplitTest extends BaseDocumentMKTest { @Test public void mainPath() { Revision r = Revision.fromString("r1-0-1"); - for (String path : new String[]{"/", "/test", "/test/path"}) { + for (String p : new String[]{"/", "/test", "/test/path"}) { + Path path = Path.fromString(p); DocumentStore store = mk.getDocumentStore(); NodeDocument doc = new NodeDocument(store); String id = Utils.getPreviousIdFor(path, r, 0); @@ -550,7 +551,7 @@ public class DocumentSplitTest extends BaseDocumentMKTest { assertEquals(2, splitOps.size()); // first update op is for the new intermediate doc op = splitOps.get(0); - String newPrevId = Utils.getPreviousIdFor("/test", prev.last(), 1); + String newPrevId = Utils.getPreviousIdFor(Path.fromString("/test"), prev.last(), 1); assertEquals(newPrevId, op.getId()); // second update op is for the main document op = splitOps.get(1); @@ -563,7 +564,7 @@ public class DocumentSplitTest extends BaseDocumentMKTest { if (entry.getValue().type == REMOVE_MAP_ENTRY) { assertTrue(prev.contains(r)); } else if (entry.getValue().type == SET_MAP_ENTRY) { - assertEquals(newPrevId, Utils.getPreviousIdFor("/test", r, 1)); + assertEquals(newPrevId, Utils.getPreviousIdFor(Path.fromString("/test"), r, 1)); } else { fail("unexpected update operation " + entry); } @@ -605,7 +606,7 @@ public class DocumentSplitTest extends BaseDocumentMKTest { mk.getNodeStore(), mk.getNodeStore().getHeadRevision(), NO_BINARY)); assertEquals(2, splitOps.size()); - String prevId = Utils.getPreviousIdFor("/test", revs.get(revs.size() - 2), 0); + String prevId = Utils.getPreviousIdFor(Path.fromString("/test"), revs.get(revs.size() - 2), 0); assertEquals(prevId, splitOps.get(0).getId()); assertEquals(id, splitOps.get(1).getId()); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java index 0eccb69bdd..6e681818f2 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/JournalEntryTest.java @@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.commons.json.JsopReader; import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; import org.apache.jackrabbit.oak.commons.sort.StringSort; @@ -52,20 +51,20 @@ public class JournalEntryTest { @Test public void applyTo() throws Exception { DiffCache cache = new MemoryDiffCache(new DocumentMK.Builder()); - List paths = Lists.newArrayList(); + List paths = Lists.newArrayList(); addRandomPaths(paths); StringSort sort = JournalEntry.newSorter(); add(sort, paths); RevisionVector from = new RevisionVector(new Revision(1, 0, 1)); RevisionVector to = new RevisionVector(new Revision(2, 0, 1)); sort.sort(); - JournalEntry.applyTo(sort, cache, "/", from, to); + JournalEntry.applyTo(sort, cache, Path.ROOT, from, to); - for (String p : paths) { + for (Path p : paths) { String changes = cache.getChanges(from, to, p, null); assertNotNull("missing changes for " + p, changes); for (String c : getChildren(changes)) { - assertTrue(paths.contains(PathUtils.concat(p, c))); + assertTrue(paths.contains(new Path(p, c))); } } sort.close(); @@ -85,13 +84,13 @@ public class JournalEntryTest { RevisionVector from = new RevisionVector(Revision.newRevision(1)); RevisionVector to = new RevisionVector(Revision.newRevision(1)); sort.sort(); - JournalEntry.applyTo(sort, cache, "/foo", from, to); - assertNotNull(cache.getChanges(from, to, "/foo", null)); - assertNotNull(cache.getChanges(from, to, "/foo/a", null)); - assertNotNull(cache.getChanges(from, to, "/foo/b", null)); - assertNull(cache.getChanges(from, to, "/bar", null)); - assertNull(cache.getChanges(from, to, "/bar/a", null)); - assertNull(cache.getChanges(from, to, "/bar/b", null)); + JournalEntry.applyTo(sort, cache, p("/foo"), from, to); + assertNotNull(cache.getChanges(from, to, p("/foo"), null)); + assertNotNull(cache.getChanges(from, to, p("/foo/a"), null)); + assertNotNull(cache.getChanges(from, to, p("/foo/b"), null)); + assertNull(cache.getChanges(from, to, p("/bar"), null)); + assertNull(cache.getChanges(from, to, p("/bar/a"), null)); + assertNull(cache.getChanges(from, to, p("/bar/b"), null)); } //OAK-3494 @@ -104,7 +103,7 @@ public class JournalEntryTest { //Put one entry for (from, to, "/a/b")->["c1", "c2"] manually DiffCache.Entry entry = cache.newEntry(from, to, false); - entry.append("/a/b", "^\"c1\":{}^\"c2\":{}"); + entry.append(p("/a/b"), "^\"c1\":{}^\"c2\":{}"); entry.done(); //NOTE: calling validateCacheUsage fills the cache with an empty diff for the path being validated. @@ -120,11 +119,14 @@ public class JournalEntryTest { validateCacheUsage(cache, from, to, "/c", false);//there is no cache entry for the whole hierarchy //Fill cache using journal - List paths = Lists.newArrayList("/content/changed", "/content/changed1/child1"); + List paths = Lists.newArrayList( + p("/content/changed"), + p("/content/changed1/child1") + ); StringSort sort = JournalEntry.newSorter(); add(sort, paths); sort.sort(); - JournalEntry.applyTo(sort, cache, "/", from, to); + JournalEntry.applyTo(sort, cache, Path.ROOT, from, to); validateCacheUsage(cache, from, to, "/topUnchanged", true); validateCacheUsage(cache, from, to, "/content/changed/unchangedLeaf", true); @@ -138,7 +140,7 @@ public class JournalEntryTest { public void fillExternalChanges() throws Exception { DocumentStore store = new MemoryDocumentStore(); JournalEntry entry = JOURNAL.newDocument(store); - Set paths = Sets.newHashSet(); + Set paths = Sets.newHashSet(); addRandomPaths(paths); entry.modified(paths); Revision r1 = new Revision(1, 0, 1); @@ -170,7 +172,7 @@ public class JournalEntryTest { public void invalidateOnly() throws Exception { DocumentStore store = new MemoryDocumentStore(); JournalEntry invalidateEntry = JOURNAL.newDocument(store); - Set paths = Sets.newHashSet(); + Set paths = Sets.newHashSet(); addRandomPaths(paths); invalidateEntry.modified(paths); Revision r1 = new Revision(1, 0, 1); @@ -213,14 +215,14 @@ public class JournalEntryTest { Revision r4 = new Revision(4, 0, 1); DocumentStore store = new MemoryDocumentStore(); JournalEntry entry = JOURNAL.newDocument(store); - entry.modified("/"); - entry.modified("/foo"); + entry.modified(p("/")); + entry.modified(p("/foo")); UpdateOp op = entry.asUpdateOp(r2); assertTrue(store.create(JOURNAL, Collections.singletonList(op))); entry = JOURNAL.newDocument(store); - entry.modified("/"); - entry.modified("/bar"); + entry.modified(p("/")); + entry.modified(p("/bar")); op = entry.asUpdateOp(r4); assertTrue(store.create(JOURNAL, Collections.singletonList(op))); @@ -271,22 +273,22 @@ public class JournalEntryTest { Revision r2 = new Revision(2, 0, 1); DocumentStore store = new MemoryDocumentStore(); JournalEntry entry = JOURNAL.newDocument(store); - entry.modified("/"); - entry.modified("/foo"); - entry.modified("/foo/a"); - entry.modified("/foo/b"); - entry.modified("/foo/c"); - entry.modified("/bar"); - entry.modified("/bar/a"); - entry.modified("/bar/b"); - entry.modified("/bar/c"); + entry.modified(p("/")); + entry.modified(p("/foo")); + entry.modified(p("/foo/a")); + entry.modified(p("/foo/b")); + entry.modified(p("/foo/c")); + entry.modified(p("/bar")); + entry.modified(p("/bar/a")); + entry.modified(p("/bar/b")); + entry.modified(p("/bar/c")); UpdateOp op = entry.asUpdateOp(r2); assertTrue(store.create(JOURNAL, Collections.singletonList(op))); StringSort sort = JournalEntry.newSorter(); StringSort inv = JournalEntry.newSorter(); - JournalEntry.fillExternalChanges(sort, inv, "/foo", r1, r2, store, e -> {}, null, null); + JournalEntry.fillExternalChanges(sort, inv, p("/foo"), r1, r2, store, e -> {}, null, null); assertEquals(4, sort.getSize()); assertEquals(0, inv.getSize()); sort.close(); @@ -297,7 +299,7 @@ public class JournalEntryTest { public void getRevisionTimestamp() throws Exception { DocumentStore store = new MemoryDocumentStore(); JournalEntry entry = JOURNAL.newDocument(store); - entry.modified("/foo"); + entry.modified(p("/foo")); Revision r = Revision.newRevision(1); assertTrue(store.create(JOURNAL, Collections.singletonList(entry.asUpdateOp(r)))); @@ -315,14 +317,14 @@ public class JournalEntryTest { @Override public void run() { for (int i = 0; i < 100000; i++) { - entry.modified("/node-" + i); + entry.modified(p("/node-" + i)); } } }); t.start(); StringSort sort = JournalEntry.newSorter(); try { - entry.addTo(sort, PathUtils.ROOT_PATH); + entry.addTo(sort, Path.ROOT); } finally { sort.close(); } @@ -336,17 +338,17 @@ public class JournalEntryTest { public void addToWithPath() throws Exception { DocumentStore store = new MemoryDocumentStore(); JournalEntry entry = JOURNAL.newDocument(store); - entry.modified("/"); - entry.modified("/foo"); - entry.modified("/foo/a"); - entry.modified("/foo/b"); - entry.modified("/foo/c"); - entry.modified("/bar"); - entry.modified("/bar/a"); - entry.modified("/bar/b"); - entry.modified("/bar/c"); + entry.modified(p("/")); + entry.modified(p("/foo")); + entry.modified(p("/foo/a")); + entry.modified(p("/foo/b")); + entry.modified(p("/foo/c")); + entry.modified(p("/bar")); + entry.modified(p("/bar/a")); + entry.modified(p("/bar/b")); + entry.modified(p("/bar/c")); StringSort sort = JournalEntry.newSorter(); - entry.addTo(sort, "/foo"); + entry.addTo(sort, p("/foo")); assertEquals(4, sort.getSize()); sort.close(); } @@ -359,24 +361,24 @@ public class JournalEntryTest { assertEquals("Incorrect number of initial paths", 0, entry.getNumChangedNodes()); assertFalse("Incorrect hasChanges", entry.hasChanges()); - entry.modified("/foo"); - entry.modified("/bar"); + entry.modified(p("/foo")); + entry.modified(p("/bar")); assertEquals("Incorrect number of paths", 2, entry.getNumChangedNodes()); assertTrue("Incorrect hasChanges", entry.hasChanges()); - entry.modified(Arrays.asList("/foo1", "/bar1")); + entry.modified(Arrays.asList(p("/foo1"), p("/bar1"))); assertEquals("Incorrect number of paths", 4, entry.getNumChangedNodes()); assertTrue("Incorrect hasChanges", entry.hasChanges()); - entry.modified("/foo/bar2"); + entry.modified(p("/foo/bar2")); assertEquals("Incorrect number of paths", 5, entry.getNumChangedNodes()); assertTrue("Incorrect hasChanges", entry.hasChanges()); - entry.modified("/foo3/bar3"); + entry.modified(p("/foo3/bar3")); assertEquals("Incorrect number of paths", 7, entry.getNumChangedNodes()); assertTrue("Incorrect hasChanges", entry.hasChanges()); - entry.modified(Arrays.asList("/foo/bar4", "/foo5/bar5")); + entry.modified(Arrays.asList(p("/foo/bar4"), p("/foo5/bar5"))); assertEquals("Incorrect number of paths", 10, entry.getNumChangedNodes()); assertTrue("Incorrect hasChanges", entry.hasChanges()); } @@ -405,24 +407,24 @@ public class JournalEntryTest { assertNull(entry.get(JournalEntry.BRANCH_COMMITS)); } - private static void addRandomPaths(java.util.Collection paths) throws IOException { - paths.add("/"); + private static void addRandomPaths(java.util.Collection paths) throws IOException { + paths.add(Path.ROOT); Random random = new Random(42); for (int i = 0; i < 1000; i++) { - String path = "/"; + Path path = Path.ROOT; int depth = random.nextInt(6); for (int j = 0; j < depth; j++) { char name = (char) ('a' + random.nextInt(26)); - path = PathUtils.concat(path, String.valueOf(name)); + path = new Path(path, String.valueOf(name)); paths.add(path); } } } - private static void add(StringSort sort, List paths) + private static void add(StringSort sort, List paths) throws IOException { - for (String p : paths) { - sort.add(p); + for (Path p : paths) { + sort.add(p.toString()); } } @@ -453,9 +455,10 @@ public class JournalEntryTest { RevisionVector to, String path, boolean cacheExpected) { - String nonLoaderDiff = cache.getChanges(from, to, path, null); + Path p = p(path); + String nonLoaderDiff = cache.getChanges(from, to, p, null); final AtomicBoolean loaderCalled = new AtomicBoolean(false); - cache.getChanges(from, to, path, new DiffCache.Loader() { + cache.getChanges(from, to, p, new DiffCache.Loader() { @Override public String call() { loaderCalled.set(true); @@ -484,4 +487,8 @@ public class JournalEntryTest { invalidate.close(); } return changes; } + + private static Path p(String path) { + return Path.fromString(path); + } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java index 11fed28c3e..885454e38c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/LocalDiffCacheTest.java @@ -34,8 +34,6 @@ import org.apache.jackrabbit.oak.spi.state.NodeStore; import org.junit.After; import org.junit.Test; -import static com.google.common.collect.Maps.newHashMap; -import static com.google.common.collect.Sets.newHashSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -85,11 +83,11 @@ public class LocalDiffCacheTest { @Test public void diffFromAsString() { - Map changes = Maps.newHashMap(); - changes.put("/", "+\"foo\":{}^\"bar\":{}-\"baz\""); - changes.put("/foo", ""); - changes.put("/bar", "+\"qux\""); - changes.put("/bar/qux", ""); + Map changes = Maps.newHashMap(); + changes.put(Path.ROOT, "+\"foo\":{}^\"bar\":{}-\"baz\""); + changes.put(Path.fromString("/foo"), ""); + changes.put(Path.fromString("/bar"), "+\"qux\""); + changes.put(Path.fromString("/bar/qux"), ""); Diff diff = new Diff(changes, 0); assertEquals(changes, Diff.fromString(diff.asString()).getChanges()); @@ -97,7 +95,7 @@ public class LocalDiffCacheTest { @Test public void emptyDiff() throws Exception{ - Map changes = new HashMap(); + Map changes = new HashMap<>(); Diff diff = new Diff(changes, 100); String asString = diff.asString(); Diff diff2 = Diff.fromString(asString); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java index b2e524c49f..51ed11855b 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MeasureMemory.java @@ -23,12 +23,14 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; import com.google.common.collect.Lists; import com.mongodb.BasicDBObject; import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.api.PropertyState; +import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.document.util.RevisionsKey; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState; @@ -43,7 +45,7 @@ import org.junit.Test; */ public class MeasureMemory { - static final boolean TRACE = false; + static final boolean TRACE = true; static final int TEST_COUNT = 10000; static final int OVERHEAD = 24; @@ -178,6 +180,19 @@ public class MeasureMemory { }); } + @Test + public void revisionsKey2() throws Exception { + measureMemory(new Callable() { + @Override + public Object[] call() { + RevisionsKey k = new RevisionsKey( + new RevisionVector(Revision.newRevision(0)), + new RevisionVector(Revision.newRevision(0))); + return new Object[]{k, k.getMemory() + OVERHEAD}; + } + }); + } + @Test public void revisionVector() throws Exception { measureMemory(new Callable() { @@ -215,6 +230,84 @@ public class MeasureMemory { }); } + @Test + public void memoryDiffCacheKey() throws Exception { + measureMemory(new Callable() { + private int counter = 0; + @Override + public Object[] call() { + Path p = Path.fromString(generatePath()); + RevisionVector rv1 = new RevisionVector( + Revision.newRevision(0), + Revision.newRevision(1)); + RevisionVector rv2 = new RevisionVector( + Revision.newRevision(0), + Revision.newRevision(1)); + MemoryDiffCache.Key k = new MemoryDiffCache.Key(p, rv1, rv2); + return new Object[]{k, k.getMemory() + OVERHEAD}; + } + + private String generatePath() { + String p = "/"; + for (int i = 0; i < 5; i++) { + p = PathUtils.concat(p, generateName()); + } + return p; + } + + private String generateName() { + return String.format("node-%05d", counter++); + } + }); + } + + @Test + public void path() throws Exception { + measureMemory(new Callable() { + private AtomicInteger counter = new AtomicInteger(); + @Override + public Object[] call() { + Path p = Path.fromString(generatePath(counter)); + return new Object[]{p, p.getMemory() + OVERHEAD}; + } + }); + } + + @Test + public void pathRev() throws Exception { + measureMemory(new Callable() { + private AtomicInteger counter = new AtomicInteger(); + @Override + public Object[] call() { + Path p = Path.fromString(generatePath(counter)); + RevisionVector r = new RevisionVector( + Revision.newRevision(1), + Revision.newRevision(2) + ); + PathRev pr = new PathRev(p, r); + return new Object[]{pr, pr.getMemory() + OVERHEAD}; + } + }); + } + + @Test + public void namePathRev() throws Exception { + measureMemory(new Callable() { + private AtomicInteger counter = new AtomicInteger(); + @Override + public Object[] call() { + String name = generateName(counter); + Path p = Path.fromString(generatePath(counter)); + RevisionVector r = new RevisionVector( + Revision.newRevision(1), + Revision.newRevision(2) + ); + NamePathRev npr = new NamePathRev(name, p, r); + return new Object[]{npr, npr.getMemory() + OVERHEAD}; + } + }); + } + private static void measureMemory(Callable c) throws Exception { LinkedList list = new LinkedList(); long base = getMemoryUsed(); @@ -252,7 +345,7 @@ public class MeasureMemory { String key = "property" + i; props.add(STORE.createPropertyState(key, "\"values " + i + "\"")); } - return new DocumentNodeState(STORE, new String("/hello/world"), + return new DocumentNodeState(STORE, Path.fromString("/hello/world"), new RevisionVector(new Revision(1, 2, 3)), props, false, new RevisionVector(new Revision(1, 2, 3))); } @@ -289,4 +382,15 @@ public class MeasureMemory { - Runtime.getRuntime().freeMemory(); } + private static String generatePath(AtomicInteger counter) { + String p = "/"; + for (int i = 0; i < 5; i++) { + p = PathUtils.concat(p, generateName(counter)); + } + return p; + } + + private static String generateName(AtomicInteger counter) { + return String.format("node-%05d", counter.getAndIncrement()); + } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheKeyTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheKeyTest.java new file mode 100644 index 0000000000..85635214be --- /dev/null +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheKeyTest.java @@ -0,0 +1,50 @@ +/* + * 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.plugins.document; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class MemoryDiffCacheKeyTest { + + @Test + public void asString() { + Path p = Path.fromString("/foo/bar"); + RevisionVector from = RevisionVector.fromString("r3-0-1"); + RevisionVector to = RevisionVector.fromString("r7-0-1"); + MemoryDiffCache.Key key = new MemoryDiffCache.Key(p, from, to); + assertEquals("r3-0-1/foo/bar@r7-0-1", key.asString()); + } + + @Test + public void fromString() { + Path p = Path.fromString("/foo/bar"); + RevisionVector from = RevisionVector.fromString("r3-0-1"); + RevisionVector to = RevisionVector.fromString("r7-0-1"); + MemoryDiffCache.Key expected = new MemoryDiffCache.Key(p, from, to); + + String s = "r3-0-1/foo/bar@r7-0-1"; + MemoryDiffCache.Key key = MemoryDiffCache.Key.fromString(s); + assertEquals(expected, key); + } + + @Test(expected = IllegalArgumentException.class) + public void fromStringIllegalArgumentException() { + MemoryDiffCache.Key.fromString("foo@r7-0-1"); + } +} diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java index fb317bd4c9..fdffb47e2d 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MemoryDiffCacheTest.java @@ -37,11 +37,11 @@ public class MemoryDiffCacheTest { RevisionVector from = new RevisionVector(Revision.newRevision(1)); RevisionVector to = new RevisionVector(Revision.newRevision(1)); DiffCache.Entry entry = cache.newEntry(from, to, false); - entry.append("/", "^\"foo\":{}"); - entry.append("/foo", changes(MemoryDiffCache.CACHE_VALUE_LIMIT)); + entry.append(Path.ROOT, "^\"foo\":{}"); + entry.append(Path.fromString("/foo"), changes(MemoryDiffCache.CACHE_VALUE_LIMIT)); entry.done(); - assertNotNull(cache.getChanges(from, to, "/", null)); - assertNull(cache.getChanges(from, to, "/foo", null)); + assertNotNull(cache.getChanges(from, to, Path.ROOT, null)); + assertNull(cache.getChanges(from, to, Path.fromString("/foo"), null)); } private static String changes(int minLength) { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoDocumentStoreTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoDocumentStoreTest.java index aeafe0e9e4..5d95a805dd 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoDocumentStoreTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/MongoDocumentStoreTest.java @@ -229,13 +229,13 @@ public class MongoDocumentStoreTest { Revision rev = Revision.newRevision(0); List inserts = new ArrayList(); for (int i = 0; i < DocumentMK.MANY_CHILDREN_THRESHOLD * 2; i++) { - DocumentNodeState n = new DocumentNodeState(store, "/node-" + i, + DocumentNodeState n = new DocumentNodeState(store, Path.fromString("/node-" + i), new RevisionVector(rev)); inserts.add(n.asOperation(rev)); } docStore.create(Collection.NODES, inserts); List docs = docStore.query(Collection.NODES, - Utils.getKeyLowerLimit("/"), Utils.getKeyUpperLimit("/"), + Utils.getKeyLowerLimit(Path.ROOT), Utils.getKeyUpperLimit(Path.ROOT), DocumentMK.MANY_CHILDREN_THRESHOLD); assertEquals(DocumentMK.MANY_CHILDREN_THRESHOLD, docs.size()); store.dispose(); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperTest.java index fadc9707d4..0601513bfe 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/NodeDocumentSweeperTest.java @@ -198,7 +198,7 @@ public class NodeDocumentSweeperTest { Iterable docs = seeker.getCandidates(startRev.getTimestamp()); return sweeper.sweep(docs, new NodeDocumentSweepListener() { @Override - public void sweepUpdate(Map updates) + public void sweepUpdate(Map updates) throws DocumentStoreException { ops.addAll(updates.values()); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathComparatorTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathComparatorTest.java index 2b434f3e50..3eb2000152 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathComparatorTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathComparatorTest.java @@ -35,18 +35,22 @@ public class PathComparatorTest { @Test public void sort() { - List paths = new ArrayList(); - paths.add("/foo"); - paths.add("/foo/bar"); - paths.add("/bar/qux"); - paths.add("/"); - paths.add("/bar"); + List paths = new ArrayList<>(); + paths.add(p("/foo")); + paths.add(p("/foo/bar")); + paths.add(p("/bar/qux")); + paths.add(p("/")); + paths.add(p("/bar")); - Collections.sort(paths, PathComparator.INSTANCE); + paths.sort(PathComparator.INSTANCE); - List expected = Lists.newArrayList( - "/bar/qux", "/foo/bar", "/bar", "/foo", "/"); + List expected = Lists.newArrayList( + p("/bar/qux"), p("/foo/bar"), p("/bar"), p("/foo"), p("/")); assertEquals(expected, paths); } + + private static Path p(String path) { + return Path.fromString(path); + } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathRevTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathRevTest.java index 45347169e6..b2b7c702a0 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathRevTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathRevTest.java @@ -25,11 +25,11 @@ import static org.junit.Assert.assertEquals; public class PathRevTest { @Test - public void roundTrip() { - String path = "/this/is/a/test/path"; + public void equals() { + Path path = Path.fromString("/this/is/a/test/path"); RevisionVector rv = new RevisionVector( Revision.newRevision(1), Revision.newRevision(2)); - PathRev key = new PathRev(path, rv); - assertEquals(key, PathRev.fromString(key.asString())); + PathRev expected = new PathRev(path, rv); + assertEquals(expected, new PathRev(path, rv)); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathTest.java new file mode 100644 index 0000000000..c665861f77 --- /dev/null +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/PathTest.java @@ -0,0 +1,265 @@ +/* + * 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.plugins.document; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.jackrabbit.oak.commons.PathUtils; +import org.junit.Test; + +import static org.apache.jackrabbit.oak.plugins.document.Path.ROOT; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.emptyIterable; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class PathTest { + + private final Path root = ROOT; + private final Path foo = new Path(root, "foo"); + private final Path fooBar = new Path(foo, "bar"); + private final Path fooBarQuux = new Path(fooBar, "quux"); + private final Path relFoo = new Path("foo"); + private final Path relFooBar = new Path(relFoo, "bar"); + private final Path relFooBarQuux = new Path(relFooBar, "quux"); + + @Test + public void equals() { + assertEquals(ROOT, Path.fromString("/")); + assertEquals(foo, Path.fromString("/foo")); + assertEquals(fooBar, Path.fromString("/foo/bar")); + assertEquals(relFoo, Path.fromString("foo")); + assertEquals(relFooBar, Path.fromString("foo/bar")); + assertNotEquals(fooBar, Path.fromString("foo/bar")); + assertNotEquals(relFooBar, Path.fromString("/foo/bar")); + } + + @Test + public void pathToString() { + assertEquals("/", root.toString()); + assertEquals("/foo", foo.toString()); + assertEquals("/foo/bar", fooBar.toString()); + assertEquals("/foo/bar/quux", fooBarQuux.toString()); + assertEquals("foo", relFoo.toString()); + assertEquals("foo/bar", relFooBar.toString()); + assertEquals("foo/bar/quux", relFooBarQuux.toString()); + } + + @Test + public void toStringBuilder() { + StringBuilder sb = new StringBuilder(); + root.toStringBuilder(sb); + assertEquals(root.toString(), sb.toString()); + sb.setLength(0); + foo.toStringBuilder(sb); + assertEquals(foo.toString(), sb.toString()); + sb.setLength(0); + fooBar.toStringBuilder(sb); + assertEquals(fooBar.toString(), sb.toString()); + sb.setLength(0); + fooBarQuux.toStringBuilder(sb); + assertEquals(fooBarQuux.toString(), sb.toString()); + sb.setLength(0); + relFoo.toStringBuilder(sb); + assertEquals(relFoo.toString(), sb.toString()); + sb.setLength(0); + relFooBar.toStringBuilder(sb); + assertEquals(relFooBar.toString(), sb.toString()); + sb.setLength(0); + relFooBarQuux.toStringBuilder(sb); + assertEquals(relFooBarQuux.toString(), sb.toString()); + } + + @Test + public void fromString() { + assertEquals(root, Path.fromString(root.toString())); + assertEquals(foo, Path.fromString(foo.toString())); + assertEquals(fooBar, Path.fromString(fooBar.toString())); + assertEquals(fooBarQuux, Path.fromString(fooBarQuux.toString())); + assertEquals(relFoo, Path.fromString(relFoo.toString())); + assertEquals(relFooBar, Path.fromString(relFooBar.toString())); + assertEquals(relFooBarQuux, Path.fromString(relFooBarQuux.toString())); + } + + @Test + public void length() { + assertEquals(root.toString().length(), root.length()); + assertEquals(foo.toString().length(), foo.length()); + assertEquals(fooBar.toString().length(), fooBar.length()); + assertEquals(fooBarQuux.toString().length(), fooBarQuux.length()); + assertEquals(relFoo.toString().length(), relFoo.length()); + assertEquals(relFooBar.toString().length(), relFooBar.length()); + assertEquals(relFooBarQuux.toString().length(), relFooBarQuux.length()); + } + + @Test + public void isRoot() { + assertTrue(root.isRoot()); + assertFalse(foo.isRoot()); + assertFalse(fooBar.isRoot()); + assertFalse(fooBarQuux.isRoot()); + assertFalse(relFoo.isRoot()); + assertFalse(relFooBar.isRoot()); + assertFalse(relFooBarQuux.isRoot()); + } + + @Test + public void getParent() { + assertNull(root.getParent()); + assertEquals(foo.getParent(), root); + assertEquals(fooBar.getParent(), foo); + assertEquals(fooBarQuux.getParent(), fooBar); + assertNull(relFoo.getParent()); + assertEquals(relFooBar.getParent(), relFoo); + assertEquals(relFooBarQuux.getParent(), relFooBar); + } + + @Test + public void getDepth() { + assertEquals(PathUtils.getDepth(root.toString()), root.getDepth()); + assertEquals(PathUtils.getDepth(foo.toString()), foo.getDepth()); + assertEquals(PathUtils.getDepth(fooBar.toString()), fooBar.getDepth()); + assertEquals(PathUtils.getDepth(fooBarQuux.toString()), fooBarQuux.getDepth()); + assertEquals(PathUtils.getDepth(relFoo.toString()), relFoo.getDepth()); + assertEquals(PathUtils.getDepth(relFooBar.toString()), relFooBar.getDepth()); + assertEquals(PathUtils.getDepth(relFooBarQuux.toString()), relFooBarQuux.getDepth()); + } + + @Test + public void getAncestor() { + assertEquals(root, root.getAncestor(-1)); + assertEquals(root, root.getAncestor(0)); + assertEquals(root, root.getAncestor(1)); + assertEquals(foo, foo.getAncestor(0)); + assertEquals(root, foo.getAncestor(1)); + assertEquals(root, foo.getAncestor(2)); + assertEquals(fooBar, fooBar.getAncestor(0)); + assertEquals(foo, fooBar.getAncestor(1)); + assertEquals(root, fooBar.getAncestor(2)); + assertEquals(root, fooBar.getAncestor(3)); + assertEquals(fooBar, fooBarQuux.getAncestor(1)); + + assertEquals(relFoo, relFoo.getAncestor(-1)); + assertEquals(relFoo, relFoo.getAncestor(0)); + assertEquals(relFoo, relFoo.getAncestor(1)); + assertEquals(relFooBar, relFooBar.getAncestor(0)); + assertEquals(relFoo, relFooBar.getAncestor(1)); + assertEquals(relFoo, relFooBar.getAncestor(2)); + assertEquals(relFoo, relFooBar.getAncestor(3)); + assertEquals(relFooBar, relFooBarQuux.getAncestor(1)); + } + + @Test + public void getName() { + assertEquals("", root.getName()); + assertEquals("foo", foo.getName()); + assertEquals("bar", fooBar.getName()); + assertEquals("quux", fooBarQuux.getName()); + assertEquals("foo", relFoo.getName()); + assertEquals("bar", relFooBar.getName()); + assertEquals("quux", relFooBarQuux.getName()); + } + + @Test + public void elements() { + assertThat(root.elements(), emptyIterable()); + assertThat(foo.elements(), contains("foo")); + assertThat(fooBar.elements(), contains("foo", "bar")); + assertThat(fooBarQuux.elements(), contains("foo", "bar", "quux")); + assertThat(relFoo.elements(), contains("foo")); + assertThat(relFooBar.elements(), contains("foo", "bar")); + assertThat(relFooBarQuux.elements(), contains("foo", "bar", "quux")); + } + + @Test + public void isAncestorOf() { + assertTrue(root.isAncestorOf(foo)); + assertTrue(root.isAncestorOf(fooBar)); + assertTrue(foo.isAncestorOf(fooBar)); + assertTrue(fooBar.isAncestorOf(fooBarQuux)); + assertFalse(root.isAncestorOf(root)); + assertFalse(foo.isAncestorOf(root)); + assertFalse(foo.isAncestorOf(foo)); + assertFalse(fooBar.isAncestorOf(fooBar)); + assertFalse(fooBar.isAncestorOf(foo)); + assertFalse(fooBar.isAncestorOf(root)); + + assertFalse(root.isAncestorOf(relFoo)); + assertFalse(root.isAncestorOf(relFooBar)); + assertFalse(relFoo.isAncestorOf(relFoo)); + assertFalse(relFooBar.isAncestorOf(relFoo)); + assertFalse(relFooBar.isAncestorOf(relFooBar)); + assertFalse(relFooBarQuux.isAncestorOf(relFooBar)); + assertFalse(relFooBarQuux.isAncestorOf(relFooBarQuux)); + assertTrue(relFoo.isAncestorOf(relFooBar)); + assertTrue(relFooBar.isAncestorOf(relFooBarQuux)); + + assertFalse(foo.isAncestorOf(relFooBar)); + assertFalse(foo.isAncestorOf(relFooBarQuux)); + assertFalse(relFoo.isAncestorOf(fooBar)); + assertFalse(relFoo.isAncestorOf(fooBarQuux)); + } + + @Test + public void isAbsolute() { + assertTrue(ROOT.isAbsolute()); + assertTrue(foo.isAbsolute()); + assertTrue(fooBar.isAbsolute()); + assertTrue(fooBarQuux.isAbsolute()); + assertFalse(relFoo.isAbsolute()); + assertFalse(relFooBar.isAbsolute()); + assertFalse(relFooBarQuux.isAbsolute()); + } + + @Test(expected = IllegalArgumentException.class) + public void emptyName() { + new Path(ROOT, ""); + } + + @Test(expected = IllegalArgumentException.class) + public void fromStringWithEmptyString() { + Path.fromString(""); + } + + @Test + public void compareTo() { + Path baz = Path.fromString("/baz"); + Path fooBaz = Path.fromString("/foo/baz"); + Path relFooBaz = Path.fromString("foo/baz"); + List paths = new ArrayList<>(); + paths.add(root); + paths.add(baz); + paths.add(foo); + paths.add(fooBar); + paths.add(fooBaz); + paths.add(fooBarQuux); + paths.add(relFoo); + paths.add(relFooBar); + paths.add(relFooBaz); + for (int i = 0; i < 20; i++) { + Collections.shuffle(paths); + Collections.sort(paths); + assertThat(paths, contains(root, baz, foo, fooBar, fooBarQuux, fooBaz, relFoo, relFooBar, relFooBaz)); + } + } +} diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java index 4c54d9c406..c90086065c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/SimpleTest.java @@ -72,6 +72,7 @@ public class SimpleTest { assertEquals(2, Utils.pathDepth("/a/")); assertEquals(2, Utils.pathDepth("/a/b")); assertEquals(3, Utils.pathDepth("/a/b/c")); + assertEquals(2, Utils.pathDepth("a/b/c")); } @Test @@ -80,13 +81,13 @@ public class SimpleTest { DocumentStore s = mk.getDocumentStore(); DocumentNodeStore ns = mk.getNodeStore(); RevisionVector rev = RevisionVector.fromString(mk.getHeadRevision()); - DocumentNodeState n = new DocumentNodeState(ns, "/test", rev, + DocumentNodeState n = new DocumentNodeState(ns, Path.fromString("/test"), rev, Collections.singleton(ns.createPropertyState("name", "\"Hello\"")), false, null); UpdateOp op = n.asOperation(rev.getRevision(ns.getClusterId())); // mark as commit root NodeDocument.setRevision(op, rev.getRevision(ns.getClusterId()), "c"); assertTrue(s.create(Collection.NODES, Lists.newArrayList(op))); - DocumentNodeState n2 = ns.getNode("/test", rev); + DocumentNodeState n2 = ns.getNode(Path.fromString("/test"), rev); assertNotNull(n2); PropertyState p = n2.getProperty("name"); assertNotNull(p); @@ -249,13 +250,13 @@ public class SimpleTest { String r0 = mk.commit("/test", "+\"a\":{\"name\": \"World\"}", null, null); String r1 = mk.commit("/test", "+\"b\":{\"name\": \"!\"}", null, null); test = mk.getNodes("/test", r0, 0, 0, Integer.MAX_VALUE, null); - DocumentNodeState n = ns.getNode("/", RevisionVector.fromString(r0)); + DocumentNodeState n = ns.getNode(Path.ROOT, RevisionVector.fromString(r0)); assertNotNull(n); - Children c = ns.getChildren(n, null, Integer.MAX_VALUE); + Children c = ns.getChildren(n, "", Integer.MAX_VALUE); assertEquals("[test]", c.toString()); - n = ns.getNode("/test", RevisionVector.fromString(r1)); + n = ns.getNode(Path.fromString("/test"), RevisionVector.fromString(r1)); assertNotNull(n); - c = ns.getChildren(n, null, Integer.MAX_VALUE); + c = ns.getChildren(n, "", Integer.MAX_VALUE); assertEquals("[a, b]", c.toString()); rev = mk.commit("", "^\"/test\":1", null, null); @@ -275,19 +276,19 @@ public class SimpleTest { mk.commit("/testDel", "+\"b\":{\"name\": \"!\"}", null, null); String r1 = mk.commit("/testDel", "+\"c\":{\"name\": \"!\"}", null, null); - DocumentNodeState n = ns.getNode("/testDel", RevisionVector.fromString(r1)); + DocumentNodeState n = ns.getNode(Path.fromString("/testDel"), RevisionVector.fromString(r1)); assertNotNull(n); - Children c = ns.getChildren(n, null, Integer.MAX_VALUE); + Children c = ns.getChildren(n, "", Integer.MAX_VALUE); assertEquals(3, c.children.size()); String r2 = mk.commit("/testDel", "-\"c\"", null, null); - n = ns.getNode("/testDel", RevisionVector.fromString(r2)); + n = ns.getNode(Path.fromString("/testDel"), RevisionVector.fromString(r2)); assertNotNull(n); - c = ns.getChildren(n, null, Integer.MAX_VALUE); + c = ns.getChildren(n, "", Integer.MAX_VALUE); assertEquals(2, c.children.size()); String r3 = mk.commit("/", "-\"testDel\"", null, null); - n = ns.getNode("/testDel", RevisionVector.fromString(r3)); + n = ns.getNode(Path.fromString("/testDel"), RevisionVector.fromString(r3)); assertNull(n); } @@ -412,7 +413,7 @@ public class SimpleTest { NodeDocument foo = store.find(Collection.NODES, "2:/test/foo"); assertNotNull(foo); assertFalse(foo.containsRevision(head)); - assertEquals("/", foo.getCommitRootPath(head)); + assertEquals(Path.ROOT, foo.getCommitRootPath(head)); head = Revision.fromString(mk.commit("", "+\"/bar\":{}+\"/test/foo/bar\":{}", head.toString(), null)); @@ -424,12 +425,12 @@ public class SimpleTest { // /bar refers to root nodes a commit root NodeDocument bar = store.find(Collection.NODES, "1:/bar"); assertNotNull(bar); - assertEquals("/", bar.getCommitRootPath(head)); + assertEquals(Path.ROOT, bar.getCommitRootPath(head)); // /test/foo/bar refers to root nodes a commit root bar = store.find(Collection.NODES, "3:/test/foo/bar"); assertNotNull(bar); - assertEquals("/", bar.getCommitRootPath(head)); + assertEquals(Path.ROOT, bar.getCommitRootPath(head)); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/StringCachePerformance.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/StringCachePerformance.java new file mode 100644 index 0000000000..258e40a787 --- /dev/null +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/StringCachePerformance.java @@ -0,0 +1,34 @@ +/* + * 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.plugins.document; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +public class StringCachePerformance { + + @Test + public void get() { + long time = System.nanoTime(); + for (int i = 0; i < 1000000; i++) { + StringCache.get("some string"); + } + time = System.nanoTime() - time; + System.out.println("time: " + TimeUnit.NANOSECONDS.toMicros(time) + " us."); + } +} diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java index 9ee18c62e8..dc05e3d41d 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/ValueMapTest.java @@ -33,6 +33,7 @@ import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES; +import static org.apache.jackrabbit.oak.plugins.document.Path.ROOT; import static org.apache.jackrabbit.oak.plugins.document.TestUtils.persistToBranch; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -45,18 +46,17 @@ public class ValueMapTest { @Test public void previousDocs1() { - String rootPath = "/"; - String rootId = Utils.getIdFromPath(rootPath); + String rootId = Utils.getIdFromPath(ROOT); Revision r0 = new Revision(0, 0, 1); MemoryDocumentStore store = new MemoryDocumentStore(); // create previous docs - UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r0, 0), true); + UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(ROOT, r0, 0), true); op.setMapEntry("prop", r0, "0"); NodeDocument.setRevision(op, r0, "c"); store.createOrUpdate(NODES, op); Revision r1low = new Revision(1, 0, 1); Revision r1high = new Revision(1, 10, 1); - op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r1high, 0), true); + op = new UpdateOp(Utils.getPreviousIdFor(ROOT, r1high, 0), true); for (int i = r1low.getCounter(); i <= r1high.getCounter(); i++) { Revision r = new Revision(1, i, 1); op.setMapEntry("foo", r, String.valueOf(i)); @@ -88,8 +88,7 @@ public class ValueMapTest { @Test public void previousDocs2() { MemoryDocumentStore store = new MemoryDocumentStore(); - String rootPath = "/"; - String rootId = Utils.getIdFromPath(rootPath); + String rootId = Utils.getIdFromPath(ROOT); Revision r01 = new Revision(0, 0, 1); Revision r12 = new Revision(1, 0, 2); Revision r22 = new Revision(2, 0, 2); @@ -97,14 +96,14 @@ public class ValueMapTest { Revision r42 = new Revision(4, 0, 2); Revision r51 = new Revision(5, 0, 1); // create previous docs - UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r31, 0), true); + UpdateOp op = new UpdateOp(Utils.getPreviousIdFor(ROOT, r31, 0), true); op.setMapEntry("p0", r01, "0"); NodeDocument.setRevision(op, r01, "c"); op.setMapEntry("p1", r31, "1"); NodeDocument.setRevision(op, r31, "c"); store.createOrUpdate(NODES, op); - op = new UpdateOp(Utils.getPreviousIdFor(rootPath, r42, 0), true); + op = new UpdateOp(Utils.getPreviousIdFor(ROOT, r42, 0), true); op.setMapEntry("p1", r12, "0"); NodeDocument.setRevision(op, r12, "c"); op.setMapEntry("p1", r22, "1"); @@ -127,8 +126,8 @@ public class ValueMapTest { List prevDocs = Lists.newArrayList( doc.getPreviousDocs("p1", null)); assertEquals(2, prevDocs.size()); - assertEquals(Utils.getPreviousIdFor(rootPath, r31, 0), prevDocs.get(0).getId()); - assertEquals(Utils.getPreviousIdFor(rootPath, r42, 0), prevDocs.get(1).getId()); + assertEquals(Utils.getPreviousIdFor(ROOT, r31, 0), prevDocs.get(0).getId()); + assertEquals(Utils.getPreviousIdFor(ROOT, r42, 0), prevDocs.get(1).getId()); List revs = new ArrayList(); for (Revision r : doc.getValueMap("p1").keySet()) { @@ -189,12 +188,12 @@ public class ValueMapTest { Range range1 = new Range(r7, r5, 0); Range range2 = new Range(r4, r1, 0); - String prevId1 = Utils.getPreviousIdFor("/", range1.high, 0); + String prevId1 = Utils.getPreviousIdFor(ROOT, range1.high, 0); UpdateOp prevOp1 = new UpdateOp(prevId1, true); NodeDocument.setRevision(prevOp1, r5, "c"); NodeDocument.setRevision(prevOp1, r7, "c"); - String prevId2 = Utils.getPreviousIdFor("/", range2.high, 0); + String prevId2 = Utils.getPreviousIdFor(ROOT, range2.high, 0); UpdateOp prevOp2 = new UpdateOp(prevId2, true); NodeDocument.setRevision(prevOp2, r1, "c"); NodeDocument.setRevision(prevOp2, r2, "c"); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java index c2cc5356de..11b863e00f 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGCDeletionTest.java @@ -428,7 +428,7 @@ public class VersionGCDeletionTest { * /x /x/y /x/y/z */ private static class NodeDocComparator implements Comparator { - private static Comparator reverse = Collections.reverseOrder(PathComparator.INSTANCE); + private static Comparator reverse = Collections.reverseOrder(PathComparator.INSTANCE); @Override public int compare(NodeDocument o1, NodeDocument o2) { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java index c5a62d7572..d3e462b9ff 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/VersionGarbageCollectorIT.java @@ -510,7 +510,7 @@ public class VersionGarbageCollectorIT { }); NodeDocument doc = docs.take(); - String name = PathUtils.getName(doc.getPath()); + String name = doc.getPath().getName(); // recreate node, which hasn't been removed yet name = name.equals("foo") ? "bar" : "foo"; builder = store.getRoot().builder(); @@ -523,7 +523,7 @@ public class VersionGarbageCollectorIT { } // invalidate cached DocumentNodeState DocumentNodeState state = (DocumentNodeState) store.getRoot().getChildNode(name); - store.invalidateNodeCache(state.getPath(), store.getRoot().getLastRevision()); + store.invalidateNodeCache(state.getPath().toString(), store.getRoot().getLastRevision()); while (!f.isDone()) { docs.poll(); @@ -892,8 +892,11 @@ public class VersionGarbageCollectorIT { store.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); } - private NodeDocument getDoc(String path){ - return store.getDocumentStore().find(NODES, Utils.getIdFromPath(path), 0); + private NodeDocument getDoc(String path) { + return getDoc(Path.fromString(path)); } + private NodeDocument getDoc(Path path) { + return store.getDocumentStore().find(NODES, Utils.getIdFromPath(path), 0); + } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandlerTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandlerTest.java index 20d103c755..0138753dd9 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandlerTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/BundlingHandlerTest.java @@ -20,6 +20,7 @@ package org.apache.jackrabbit.oak.plugins.document.bundlor; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.junit.Test; @@ -42,17 +43,17 @@ public class BundlingHandlerTest { childBuilder(builder, "/x/y/z"); NodeState state = builder.getNodeState(); - assertEquals("/", handler.getRootBundlePath()); + assertEquals(Path.ROOT, handler.getRootBundlePath()); assertTrue(handler.isBundlingRoot()); assertEquals("foo", handler.getPropertyPath("foo")); BundlingHandler xh = childHandler(handler, state, "/x"); - assertEquals("/x", xh.getRootBundlePath()); + assertEquals(Path.fromString("/x"), xh.getRootBundlePath()); assertTrue(xh.isBundlingRoot()); assertEquals("foo", xh.getPropertyPath("foo")); BundlingHandler xz = childHandler(handler, state, "/x/y/z"); - assertEquals("/x/y/z", xz.getRootBundlePath()); + assertEquals(Path.fromString("/x/y/z"), xz.getRootBundlePath()); assertTrue(xz.isBundlingRoot()); assertEquals("foo", xz.getPropertyPath("foo")); } @@ -70,26 +71,26 @@ public class BundlingHandlerTest { BundlingHandler handler = new BundlingHandler(registry); BundlingHandler fileHandler = childHandler(handler, state, "/sunrise.jpg"); - assertEquals("/sunrise.jpg", fileHandler.getRootBundlePath()); + assertEquals(Path.fromString("/sunrise.jpg"), fileHandler.getRootBundlePath()); assertTrue(fileHandler.isBundlingRoot()); assertFalse(fileHandler.isBundledNode()); assertEquals("foo", fileHandler.getPropertyPath("foo")); BundlingHandler jcrContentHandler = childHandler(handler, state, "/sunrise.jpg/jcr:content"); - assertEquals("/sunrise.jpg", jcrContentHandler.getRootBundlePath()); + assertEquals(Path.fromString("/sunrise.jpg"), jcrContentHandler.getRootBundlePath()); assertFalse(jcrContentHandler.isBundlingRoot()); assertTrue(jcrContentHandler.isBundledNode()); assertEquals("jcr:content/foo", jcrContentHandler.getPropertyPath("foo")); BundlingHandler metadataHandler = childHandler(handler, state, "/sunrise.jpg/metadata"); - assertEquals("/sunrise.jpg/metadata", metadataHandler.getRootBundlePath()); + assertEquals(Path.fromString("/sunrise.jpg/metadata"), metadataHandler.getRootBundlePath()); assertTrue(metadataHandler.isBundlingRoot()); assertFalse(metadataHandler.isBundledNode()); assertEquals("foo", metadataHandler.getPropertyPath("foo")); // /sunrise.jpg/jcr:content/bar should have bundle root reset BundlingHandler barHandler = childHandler(handler, state, "/sunrise.jpg/jcr:content/bar"); - assertEquals("/sunrise.jpg/jcr:content/bar", barHandler.getRootBundlePath()); + assertEquals(Path.fromString("/sunrise.jpg/jcr:content/bar"), barHandler.getRootBundlePath()); assertTrue(barHandler.isBundlingRoot()); assertEquals("foo", barHandler.getPropertyPath("foo")); } @@ -104,7 +105,7 @@ public class BundlingHandlerTest { NodeState state = builder.getNodeState(); BundlingHandler fileHandler = handler.childAdded("sunrise.jpg", state.getChildNode("sunrise.jpg")); - assertEquals("/sunrise.jpg", fileHandler.getRootBundlePath()); + assertEquals(Path.fromString("/sunrise.jpg"), fileHandler.getRootBundlePath()); assertTrue(fileHandler.isBundlingRoot()); assertEquals("foo", fileHandler.getPropertyPath("foo")); assertEquals(1, fileHandler.getMetaProps().size()); @@ -118,7 +119,7 @@ public class BundlingHandlerTest { NodeState state = builder.getNodeState(); BundlingHandler fileHandler = handler.childAdded("sunrise.jpg", state.getChildNode("sunrise.jpg")); - assertEquals("/sunrise.jpg", fileHandler.getRootBundlePath()); + assertEquals(Path.fromString("/sunrise.jpg"), fileHandler.getRootBundlePath()); assertTrue(fileHandler.isBundlingRoot()); assertEquals("foo", fileHandler.getPropertyPath("foo")); assertEquals(0, fileHandler.getMetaProps().size()); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/DocumentBundlingTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/DocumentBundlingTest.java index 46a3452d2c..02da1e1faf 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/DocumentBundlingTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/bundlor/DocumentBundlingTest.java @@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.plugins.document.bundlor; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -38,11 +39,12 @@ import org.apache.jackrabbit.oak.plugins.document.Document; import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.NamePathRev; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; -import org.apache.jackrabbit.oak.plugins.document.PathRev; import org.apache.jackrabbit.oak.plugins.document.RandomStream; import org.apache.jackrabbit.oak.plugins.document.TestNodeObserver; import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore; +import org.apache.jackrabbit.oak.plugins.document.persistentCache.CacheType; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.apache.jackrabbit.oak.InitialContent; import org.apache.jackrabbit.oak.spi.commit.CommitInfo; @@ -57,6 +59,7 @@ import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.spi.state.NodeStateDiff; import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; +import org.h2.mvstore.WriteBuffer; import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Before; @@ -186,7 +189,7 @@ public class DocumentBundlingTest { int nonBundledMem = nonBundledFile.getMemory() + nonBundledContent.getMemory(); int bundledMem = bundledFile.getMemory(); - assertEquals(1410, bundledMem); + assertEquals(1510, bundledMem); assertThat(bundledMem, is(greaterThan(nonBundledMem))); } @@ -274,7 +277,7 @@ public class DocumentBundlingTest { List bundledPaths = new ArrayList<>(); for (DocumentNodeState bs : appNode.getAllBundledNodesStates()) { - bundledPaths.add(bs.getPath()); + bundledPaths.add(bs.getPath().toString()); } assertThat(bundledPaths, containsInAnyOrder( "/test/book.jpg/jcr:content", @@ -613,7 +616,7 @@ public class DocumentBundlingTest { } @Test - public void jsonSerialization() throws Exception{ + public void bufferSerialization() throws Exception { NodeBuilder builder = store.getRoot().builder(); NodeBuilder appNB = newNode("app:Asset"); createChild(appNB, @@ -629,8 +632,11 @@ public class DocumentBundlingTest { merge(builder); DocumentNodeState appNode = (DocumentNodeState) getNode(store.getRoot(), "test/book.jpg"); - String json = appNode.asString(); - NodeState appNode2 = DocumentNodeState.fromString(store, json); + WriteBuffer wb = new WriteBuffer(1024); + CacheType.NODE.writeValue(wb, appNode); + ByteBuffer rb = wb.getBuffer(); + rb.rewind(); + NodeState appNode2 = CacheType.NODE.readValue(store, ds, rb); AssertingDiff.assertEquals(appNode, appNode2); } @@ -723,9 +729,9 @@ public class DocumentBundlingTest { merge(builder); - Set cachedPaths = store.getNodeChildrenCache().asMap().keySet(); - for (PathRev pr : cachedPaths){ - assertFalse(pr.getPath().contains("jcr:content/renditions")); + Set cachedPaths = store.getNodeChildrenCache().asMap().keySet(); + for (NamePathRev pr : cachedPaths){ + assertFalse(pr.getPath().toString().contains("jcr:content/renditions")); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/CacheChangesTrackerTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/CacheChangesTrackerTest.java index f684676785..499d67df5c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/CacheChangesTrackerTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/cache/CacheChangesTrackerTest.java @@ -25,6 +25,7 @@ import org.apache.jackrabbit.oak.cache.CacheValue; import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.locks.NodeDocumentLocks; import org.apache.jackrabbit.oak.plugins.document.locks.StripedNodeDocumentLocks; import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore; @@ -82,7 +83,8 @@ public class CacheChangesTrackerTest { @Test public void testRegisterChildrenTracker() { NodeDocumentCache cache = createCache(); - CacheChangesTracker tracker = cache.registerTracker(getKeyLowerLimit("/parent"), getKeyUpperLimit("/parent")); + Path parent = Path.fromString("/parent"); + CacheChangesTracker tracker = cache.registerTracker(getKeyLowerLimit(parent), getKeyUpperLimit(parent)); assertFalse(tracker.mightBeenAffected("2:/parent/xyz")); assertFalse(tracker.mightBeenAffected("2:/parent/abc")); @@ -105,7 +107,8 @@ public class CacheChangesTrackerTest { @Test public void testGetLoaderAffectsTracker() throws ExecutionException { NodeDocumentCache cache = createCache(); - CacheChangesTracker tracker = cache.registerTracker(getKeyLowerLimit("/parent"), getKeyUpperLimit("/parent")); + Path parent = Path.fromString("/parent"); + CacheChangesTracker tracker = cache.registerTracker(getKeyLowerLimit(parent), getKeyUpperLimit(parent)); assertFalse(tracker.mightBeenAffected("2:/parent/xyz")); @@ -149,7 +152,8 @@ public class CacheChangesTrackerTest { @Test public void testOnlyExternalChanges() { NodeDocumentCache cache = createCache(); - CacheChangesTracker tracker = cache.registerTracker(getKeyLowerLimit("/parent"), getKeyUpperLimit("/parent")); + Path parent = Path.fromString("/parent"); + CacheChangesTracker tracker = cache.registerTracker(getKeyLowerLimit(parent), getKeyUpperLimit(parent)); cache.putNonConflictingDocs(tracker, ImmutableSet.of(createDoc("2:/parent/local"))); assertFalse(tracker.mightBeenAffected("2:/parent/local")); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDBExceptionTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDBExceptionTest.java index 5a9877732e..73a1d9d49d 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDBExceptionTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDBExceptionTest.java @@ -21,6 +21,7 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentMK; import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException; import org.apache.jackrabbit.oak.plugins.document.MongoUtils; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.UpdateOp; import org.apache.jackrabbit.oak.plugins.document.util.Utils; @@ -114,8 +115,9 @@ public class MongoDBExceptionTest { e.getMessage().contains(id)); } - String fromKey = Utils.getKeyLowerLimit("/foo"); - String toKey = Utils.getKeyUpperLimit("/foo"); + Path foo = Path.fromString("/foo"); + String fromKey = Utils.getKeyLowerLimit(foo); + String toKey = Utils.getKeyUpperLimit(foo); exceptionMsg = "query failed"; setExceptionMsg(); try { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoMissingLastRevSeekerTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoMissingLastRevSeekerTest.java index dc4f861dd4..166781b727 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoMissingLastRevSeekerTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoMissingLastRevSeekerTest.java @@ -103,7 +103,7 @@ public class MongoMissingLastRevSeekerTest { assertNotNull(store.findAndUpdate(NODES, op)); updated = true; } - if (doc.getPath().startsWith("/node-")) { + if (doc.getPath().toString().startsWith("/node-")) { ids.add(doc.getId()); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/QueryHintTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/QueryHintTest.java index b5b20af12d..33f0f94d31 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/QueryHintTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/QueryHintTest.java @@ -32,6 +32,7 @@ import org.apache.jackrabbit.oak.plugins.document.AbstractMongoConnectionTest; import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentMK; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.apache.jackrabbit.oak.stats.Clock; import org.junit.After; @@ -75,8 +76,8 @@ public class QueryHintTest extends AbstractMongoConnectionTest { assertTrue(mongoDS.canUseModifiedTimeIdx(10)); mongoDS.query(Collection.NODES, - Utils.getKeyLowerLimit("/"), - Utils.getKeyUpperLimit("/"), + Utils.getKeyLowerLimit(Path.ROOT), + Utils.getKeyUpperLimit(Path.ROOT), NodeDocument.MODIFIED_IN_SECS, 50, 10); diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/RetryReadIT.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/RetryReadIT.java index 3d6ec676fd..c70376b1d9 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/RetryReadIT.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/mongo/RetryReadIT.java @@ -25,6 +25,7 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentMK; import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException; import org.apache.jackrabbit.oak.plugins.document.MongoUtils; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.util.Utils; import org.jetbrains.annotations.NotNull; import org.junit.Test; @@ -78,15 +79,17 @@ public class RetryReadIT extends AbstractMongoConnectionTest { @Test public void retryQuery() { - String fromKey = Utils.getKeyLowerLimit("/foo"); - String toKey = Utils.getKeyUpperLimit("/foo"); + Path foo = Path.fromString("/foo"); + String fromKey = Utils.getKeyLowerLimit(foo); + String toKey = Utils.getKeyUpperLimit(foo); // must survive two consecutive failures. -> 2 retries store.failRead = 2; List docs = store.query(NODES, fromKey, toKey, 100); assertThat(docs, is(empty())); - fromKey = Utils.getKeyLowerLimit("/bar"); - toKey = Utils.getKeyUpperLimit("/bar"); + Path bar = Path.fromString("/bar"); + fromKey = Utils.getKeyLowerLimit(bar); + toKey = Utils.getKeyUpperLimit(bar); // must fail with three consecutive failures store.failRead = 3; try { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/AsyncQueueTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/AsyncQueueTest.java index 994eca0e4d..0445b3b0dc 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/AsyncQueueTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/AsyncQueueTest.java @@ -22,6 +22,7 @@ import com.google.common.cache.RemovalCause; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.oak.cache.CacheLIRS; import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.PathRev; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; @@ -124,7 +125,7 @@ public class AsyncQueueTest { } private PathRev generatePathRev() { - return new PathRev("/" + id++, new RevisionVector(new Revision(0, 0, 0))); + return new PathRev(Path.fromString("/" + id++), new RevisionVector(new Revision(0, 0, 0))); } private void flush() { diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BroadcastTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BroadcastTest.java index 7e162df7ef..48df5e991c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BroadcastTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/BroadcastTest.java @@ -30,8 +30,8 @@ import java.util.concurrent.Callable; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.oak.cache.CacheLIRS; -import org.apache.jackrabbit.oak.plugins.document.PathRev; -import org.apache.jackrabbit.oak.plugins.document.Revision; +import org.apache.jackrabbit.oak.plugins.document.MemoryDiffCache.Key; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.Broadcaster; import org.apache.jackrabbit.oak.plugins.document.persistentCache.broadcast.TCPBroadcaster; @@ -63,9 +63,11 @@ public class BroadcastTest { ArrayList nodeList = new ArrayList(); for (int nodes = 1; nodes < 20; nodes++) { PersistentCache pc = new PersistentCache("target/broadcastTest/p" + nodes + ",broadcast=" + type); - Cache cache = openCache(pc); - String key = "/test" + Math.random(); - PathRev k = new PathRev(key, new RevisionVector(new Revision(0, 0, 0))); + Cache cache = openCache(pc); + Path key = Path.fromString("/test" + Math.random()); + RevisionVector from = RevisionVector.fromString("r1-0-1"); + RevisionVector to = RevisionVector.fromString("r2-0-1"); + Key k = new Key(key, from, to); long time = System.currentTimeMillis(); for (int i = 0; i < 2000; i++) { cache.put(k, new StringValue("Hello World " + i)); @@ -204,10 +206,12 @@ public class BroadcastTest { new File("target/broadcastTest").mkdirs(); PersistentCache p1 = new PersistentCache("target/broadcastTest/p1,broadcast=" + type); PersistentCache p2 = new PersistentCache("target/broadcastTest/p2,broadcast=" + type); - Cache c1 = openCache(p1); - Cache c2 = openCache(p2); - String key = "/test" + Math.random(); - PathRev k = new PathRev(key, new RevisionVector(new Revision(0, 0, 0))); + Cache c1 = openCache(p1); + Cache c2 = openCache(p2); + Path key = Path.fromString("/test" + Math.random()); + RevisionVector from = RevisionVector.fromString("r1-0-1"); + RevisionVector to = RevisionVector.fromString("r2-0-1"); + Key k = new Key(key, from, to); int correct = 0; for (int i = 0; i < 50; i++) { c1.put(k, new StringValue("Hello World " + i)); @@ -280,8 +284,8 @@ public class BroadcastTest { }, timeoutInMilliseconds); } - private static Cache openCache(PersistentCache p) { - CacheLIRS cache = new CacheLIRS.Builder(). + private static Cache openCache(PersistentCache p) { + CacheLIRS cache = new CacheLIRS.Builder(). maximumSize(1).build(); return p.wrap(null, null, cache, CacheType.DIFF); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java index 6983a21745..8f1d3f4c64 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/CacheTest.java @@ -30,6 +30,7 @@ import com.google.common.cache.Cache; import org.apache.commons.io.FileUtils; import org.apache.jackrabbit.oak.cache.CacheLIRS; import org.apache.jackrabbit.oak.commons.junit.LogCustomizer; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.PathRev; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; @@ -63,7 +64,7 @@ public class CacheTest { Thread.yield(); } for (int i = 0; i < 100; i++) { - PathRev k = new PathRev("/" + counter, new RevisionVector(new Revision(0, 0, i))); + PathRev k = new PathRev(Path.fromString("/" + counter), new RevisionVector(new Revision(0, 0, i))); map.getIfPresent(k); map.put(k, new StringValue(largeString)); } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/DataTypeUtilTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/DataTypeUtilTest.java new file mode 100644 index 0000000000..b37af9d386 --- /dev/null +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/DataTypeUtilTest.java @@ -0,0 +1,167 @@ +/* + * 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.plugins.document.persistentCache; + +import java.nio.ByteBuffer; +import java.util.Collections; + +import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; +import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; +import org.apache.jackrabbit.oak.plugins.document.Path; +import org.apache.jackrabbit.oak.plugins.document.NamePathRev; +import org.apache.jackrabbit.oak.plugins.document.PathRev; +import org.apache.jackrabbit.oak.plugins.document.RevisionVector; +import org.h2.mvstore.WriteBuffer; +import org.junit.Rule; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class DataTypeUtilTest { + + @Rule + public DocumentMKBuilderProvider builderProvider = new DocumentMKBuilderProvider(); + + private WriteBuffer wb = new WriteBuffer(1024); + + @Test + public void booleanToBufferTrue() { + DataTypeUtil.booleanToBuffer(true, wb); + ByteBuffer rb = readBufferFrom(wb); + assertTrue(DataTypeUtil.booleanFromBuffer(rb)); + } + + @Test + public void booleanToBufferFalse() { + DataTypeUtil.booleanToBuffer(false, wb); + ByteBuffer rb = readBufferFrom(wb); + assertFalse(DataTypeUtil.booleanFromBuffer(rb)); + } + + @Test + public void revisionVectorToBufferEmpty() { + RevisionVector empty = new RevisionVector(); + DataTypeUtil.revisionVectorToBuffer(empty, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(empty, DataTypeUtil.revisionVectorFromBuffer(rb)); + } + + @Test + public void revisionVectorToBuffer() { + RevisionVector revisions = RevisionVector.fromString("r9-0-1,br7-0-2"); + DataTypeUtil.revisionVectorToBuffer(revisions, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(revisions, DataTypeUtil.revisionVectorFromBuffer(rb)); + } + + @Test + public void pathToBufferRoot() { + Path p = Path.ROOT; + DataTypeUtil.pathToBuffer(p, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(p, DataTypeUtil.pathFromBuffer(rb)); + } + + @Test + public void pathToBuffer() { + Path p = Path.fromString("/foo/bar/quux"); + DataTypeUtil.pathToBuffer(p, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(p, DataTypeUtil.pathFromBuffer(rb)); + } + + @Test + public void relPathToBuffer() { + Path p = Path.fromString("foo/bar/quux"); + DataTypeUtil.pathToBuffer(p, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(p, DataTypeUtil.pathFromBuffer(rb)); + } + + @Test + public void relPathSingleElementToBuffer() { + Path p = Path.fromString("foo"); + DataTypeUtil.pathToBuffer(p, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(p, DataTypeUtil.pathFromBuffer(rb)); + } + + @Test + public void relPathMultipleToBuffer() { + Path fooBar = Path.fromString("foo/bar"); + Path barBaz = Path.fromString("bar/baz"); + DataTypeUtil.pathToBuffer(fooBar, wb); + DataTypeUtil.pathToBuffer(barBaz, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(fooBar, DataTypeUtil.pathFromBuffer(rb)); + assertEquals(barBaz, DataTypeUtil.pathFromBuffer(rb)); + } + + @Test + public void pathRevToBuffer() { + Path p = Path.fromString("/foo/bar/quux"); + RevisionVector rv = RevisionVector.fromString("r3-4-1,br4-9-2"); + PathRev expected = new PathRev(p, rv); + DataTypeUtil.pathRevToBuffer(expected, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(expected, DataTypeUtil.pathRevFromBuffer(rb)); + } + + @Test + public void pathNameRevToBuffer() { + Path p = Path.fromString("/foo/bar/quux"); + String name = "baz"; + RevisionVector rv = RevisionVector.fromString("r3-4-1,br4-9-2"); + NamePathRev expected = new NamePathRev(name, p, rv); + DataTypeUtil.namePathRevToBuffer(expected, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(expected, DataTypeUtil.namePathRevFromBuffer(rb)); + } + + @Test + public void stateToBufferLastRevNull() { + DocumentNodeStore ns = builderProvider.newBuilder().build(); + Path p = Path.fromString("/foo/bar"); + RevisionVector rootRev = ns.getHeadRevision(); + DocumentNodeState expected = new DocumentNodeState(ns, p, rootRev, + Collections.emptyMap(), true, 0, null, false); + DataTypeUtil.stateToBuffer(expected, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(expected, DataTypeUtil.stateFromBuffer(ns, rb)); + } + + @Test + public void stateToBuffer() { + DocumentNodeStore ns = builderProvider.newBuilder().build(); + Path p = Path.fromString("/foo/bar"); + RevisionVector rootRev = ns.getHeadRevision(); + DocumentNodeState expected = new DocumentNodeState(ns, p, rootRev, + Collections.emptyMap(), true, 0, rootRev, false); + DataTypeUtil.stateToBuffer(expected, wb); + ByteBuffer rb = readBufferFrom(wb); + assertEquals(expected, DataTypeUtil.stateFromBuffer(ns, rb)); + } + + private static ByteBuffer readBufferFrom(WriteBuffer wb) { + ByteBuffer rb = wb.getBuffer(); + rb.rewind(); + return rb; + } +} diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCacheTest.java index 8ddba41098..3c3690640c 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCacheTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/persistentCache/NodeCacheTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; -import com.google.common.base.Predicate; import com.google.common.cache.RemovalCause; import com.google.common.collect.Lists; @@ -39,12 +38,13 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentNodeState; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStateCache; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; +import org.apache.jackrabbit.oak.plugins.document.NamePathRev; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.PathRev; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.memory.MemoryDocumentStore; import org.apache.jackrabbit.oak.spi.commit.CommitInfo; import org.apache.jackrabbit.oak.spi.commit.EmptyHook; -import org.apache.jackrabbit.oak.spi.filter.PathFilter; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.apache.jackrabbit.oak.stats.Counting; @@ -57,9 +57,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -74,7 +71,7 @@ public class NodeCacheTest { private DocumentStore store; private DocumentNodeStore ns; private NodeCache nodeCache; - private NodeCache nodeChildren; + private NodeCache nodeChildren; private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private StatisticsProvider statsProvider = new DefaultStatisticsProvider(executor); @@ -93,8 +90,8 @@ public class NodeCacheTest { builder.child("c").child("d"); AbstractDocumentNodeState root = (AbstractDocumentNodeState) ns.merge(builder, EmptyHook.INSTANCE, CommitInfo.EMPTY); - PathRev prc = new PathRev("/c", root.getRootRevision()); - PathRev pra = new PathRev("/a", root.getRootRevision()); + PathRev prc = new PathRev(Path.fromString("/c"), root.getRootRevision()); + PathRev pra = new PathRev(Path.fromString("/a"), root.getRootRevision()); Counting counter = nodeCache.getPersistentCacheStats().getPutRejectedAsCachedInSecCounter(); long count0 = counter.getCount(); @@ -121,7 +118,7 @@ public class NodeCacheTest { assertContains(nodeCache, "/a/b"); assertContains(nodeCache, "/a"); - assertContains(nodeChildren, "/a"); + assertPathNameRevs(nodeChildren, "/a", true); ns.setNodeStateCache(new PathExcludingCache("/c")); @@ -131,14 +128,15 @@ public class NodeCacheTest { ns.getRoot().getChildNode("c").getChildNode("d"); assertNotContains(nodeCache, "/c/d"); assertNotContains(nodeCache, "/c"); - assertNotContains(nodeChildren, "/c"); + assertPathNameRevs(nodeChildren, "/c", false); } @Test public void cachePredicateSync() throws Exception{ - PathFilter pf = new PathFilter(asList("/a"), emptyList()); - Predicate p = path -> pf.filter(path) == PathFilter.Result.INCLUDE; - initializeNodeStore(false, b -> b.setNodeCachePredicate(p)); + Path a = Path.fromString("/a"); + initializeNodeStore(false, b -> b.setNodeCachePathPredicate( + path -> path != null && (a.equals(path) || a.isAncestorOf(path)) + )); NodeBuilder builder = ns.getRoot().builder(); builder.child("a").child("c1"); @@ -158,9 +156,10 @@ public class NodeCacheTest { // OAK-7153 @Test public void persistentCacheAccessForIncludedPathOnly() throws Exception { - PathFilter pf = new PathFilter(singletonList("/a"), emptyList()); - Predicate p = path -> pf.filter(path) == PathFilter.Result.INCLUDE; - initializeNodeStore(false, b -> b.setNodeCachePredicate(p)); + Path a = Path.fromString("/a"); + initializeNodeStore(false, b -> b.setNodeCachePathPredicate( + path -> path != null && (a.equals(path) || a.isAncestorOf(path)) + )); NodeBuilder builder = ns.getRoot().builder(); builder.child("x"); @@ -234,7 +233,7 @@ public class NodeCacheTest { ns = builder.getNodeStore(); nodeCache = (NodeCache) ns.getNodeCache(); - nodeChildren = (NodeCache) ns.getNodeChildrenCache(); + nodeChildren = (NodeCache) ns.getNodeChildrenCache(); } @@ -264,10 +263,38 @@ public class NodeCacheTest { } } + private static void assertPathNameRevs(NodeCache cache, String path, boolean contains) { + List revs = getPathNameRevs(cache, path); + List matchingRevs = Lists.newArrayList(); + for (NamePathRev pr : revs) { + if (cache.getGenerationalMap().containsKey(pr)) { + matchingRevs.add(pr); + } + } + + if (contains && matchingRevs.isEmpty()) { + fail(String.format("Expecting entry for [%s]. Did not found in %s", path, matchingRevs)); + } + + if (!contains && !matchingRevs.isEmpty()) { + fail(String.format("Expecting entry for [%s]. Found %s", path, revs)); + } + } + private static List getPathRevs(NodeCache cache, String path) { List revs = Lists.newArrayList(); for (PathRev pr : cache.asMap().keySet()) { - if (pr.getPath().equals(path)) { + if (pr.getPath().toString().equals(path)) { + revs.add(pr); + } + } + return revs; + } + + private static List getPathNameRevs(NodeCache cache, String path) { + List revs = Lists.newArrayList(); + for (NamePathRev pr : cache.asMap().keySet()) { + if (pr.getPath().toString().equals(path)) { revs.add(pr); } } @@ -282,14 +309,14 @@ public class NodeCacheTest { } @Override - public AbstractDocumentNodeState getDocumentNodeState(String path, RevisionVector rootRevision, + public AbstractDocumentNodeState getDocumentNodeState(Path path, RevisionVector rootRevision, RevisionVector lastRev) { return null; } @Override - public boolean isCached(String path) { - if (path.startsWith(excludeRoot)) { + public boolean isCached(Path path) { + if (path.toString().startsWith(excludeRoot)) { return true; } return false; diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreTest.java index d34d61b4ed..ecb1034654 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreTest.java @@ -38,6 +38,7 @@ import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentStoreFixture; import org.apache.jackrabbit.oak.plugins.document.MissingLastRevSeeker; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.UpdateOp; import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentStore.QueryCondition; @@ -183,7 +184,8 @@ public class RDBDocumentStoreTest extends AbstractDocumentStoreTest { super.ds.create(Collection.NODES, Collections.singletonList(up2)); // query - List results = super.ds.query(Collection.NODES, Utils.getKeyLowerLimit("/testRDBJDBCPerfLog"), Utils.getKeyUpperLimit("/testRDBJDBCPerfLog"), 10); + Path p = Path.fromString("/testRDBJDBCPerfLog"); + List results = super.ds.query(Collection.NODES, Utils.getKeyLowerLimit(p), Utils.getKeyUpperLimit(p), 10); assertEquals(1, results.size()); assertEquals(2, logCustomizerQuery.getLogs().size()); } finally { @@ -222,7 +224,7 @@ public class RDBDocumentStoreTest extends AbstractDocumentStoreTest { assertNotNull(ds.findAndUpdate(NODES, op)); updated = true; } - if (doc.getPath().startsWith("/lastRevnode-")) { + if (doc.getPath().toString().startsWith("/lastRevnode-")) { ids.add(doc.getId()); } } diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java index 7c64a7dc55..0822f93afd 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/secondary/SecondaryStoreCacheTest.java @@ -31,6 +31,7 @@ import org.apache.jackrabbit.oak.plugins.document.Collection; import org.apache.jackrabbit.oak.plugins.document.DocumentMKBuilderProvider; import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.bundlor.BundledTypesRegistry; @@ -85,8 +86,8 @@ public class SecondaryStoreCacheTest { RevisionVector rv1 = new RevisionVector(new Revision(1,0,1)); RevisionVector rv2 = new RevisionVector(new Revision(1,0,3)); - assertNull(cache.getDocumentNodeState("/a/b", rv1, rv2)); - assertNull(cache.getDocumentNodeState("/x", rv1, rv2)); + assertNull(cache.getDocumentNodeState(Path.fromString("/a/b"), rv1, rv2)); + assertNull(cache.getDocumentNodeState(Path.fromString("/x"), rv1, rv2)); } @Test @@ -105,7 +106,7 @@ public class SecondaryStoreCacheTest { //Lookup should work fine AbstractDocumentNodeState a_r2 = documentState(r2, "/a/c"); AbstractDocumentNodeState result - = cache.getDocumentNodeState("/a/c", r2.getRootRevision(), a_r2.getLastRevision()); + = cache.getDocumentNodeState(Path.fromString("/a/c"), r2.getRootRevision(), a_r2.getLastRevision()); assertTrue(EqualsDiff.equals(a_r2, result)); //Child docs should only have lastRev and not root rev @@ -121,7 +122,7 @@ public class SecondaryStoreCacheTest { AbstractDocumentNodeState r3 = merge(nb); //Now look from older revision - result = cache.getDocumentNodeState("/a/c", r3.getRootRevision(), a_r2.getLastRevision()); + result = cache.getDocumentNodeState(Path.fromString("/a/c"), r3.getRootRevision(), a_r2.getLastRevision()); //now as its not visible from head it would not be visible assertNull(result); @@ -143,11 +144,11 @@ public class SecondaryStoreCacheTest { AbstractDocumentNodeState a_c_1 = documentState(primary.getRoot(), "/a/c"); AbstractDocumentNodeState result - = cache.getDocumentNodeState("/a/c", r1.getRootRevision(), a_c_1.getLastRevision()); + = cache.getDocumentNodeState(Path.fromString("/a/c"), r1.getRootRevision(), a_c_1.getLastRevision()); assertTrue(EqualsDiff.equals(a_c_1, result)); //Read from older revision - result = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), a_c_0.getLastRevision()); + result = cache.getDocumentNodeState(Path.fromString("/a/c"), r0.getRootRevision(), a_c_0.getLastRevision()); assertTrue(EqualsDiff.equals(a_c_0, result)); } @@ -195,7 +196,7 @@ public class SecondaryStoreCacheTest { observer.contentChanged(r0, CommitInfo.EMPTY); - AbstractDocumentNodeState result = cache.getDocumentNodeState("/a/c", r0.getRootRevision(), a_c_0 + AbstractDocumentNodeState result = cache.getDocumentNodeState(Path.fromString("/a/c"), r0.getRootRevision(), a_c_0 .getLastRevision()); assertTrue(EqualsDiff.equals(a_c_0, result)); @@ -206,13 +207,13 @@ public class SecondaryStoreCacheTest { //Change is yet not pushed to secondary i.e. observer not invoked //but lookup with latest root should still work fine if lastRev matches - result = cache.getDocumentNodeState("/a/c", r1.getRootRevision(), a_c_0 + result = cache.getDocumentNodeState(Path.fromString("/a/c"), r1.getRootRevision(), a_c_0 .getLastRevision()); assertTrue(EqualsDiff.equals(a_c_0, result)); //Change which is not pushed would though not be visible AbstractDocumentNodeState a_e_1 = documentState(primary.getRoot(), "/a/e"); - result = cache.getDocumentNodeState("/a/e", r1.getRootRevision(), a_e_1 + result = cache.getDocumentNodeState(Path.fromString("/a/e"), r1.getRootRevision(), a_e_1 .getLastRevision()); assertNull(result); } @@ -221,9 +222,9 @@ public class SecondaryStoreCacheTest { public void isCached() throws Exception{ SecondaryStoreCache cache = createCache(new PathFilter(of("/a"), empty)); - assertTrue(cache.isCached("/a")); - assertTrue(cache.isCached("/a/b")); - assertFalse(cache.isCached("/x")); + assertTrue(cache.isCached(Path.fromString("/a"))); + assertTrue(cache.isCached(Path.fromString("/a/b"))); + assertFalse(cache.isCached(Path.fromString("/x"))); } @Test diff --git a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java index 426618e0b5..01a01868bd 100644 --- a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java +++ b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/util/UtilsTest.java @@ -35,6 +35,7 @@ import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStore; import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException; import org.apache.jackrabbit.oak.plugins.document.NodeDocument; +import org.apache.jackrabbit.oak.plugins.document.Path; import org.apache.jackrabbit.oak.plugins.document.Revision; import org.apache.jackrabbit.oak.plugins.document.RevisionVector; import org.apache.jackrabbit.oak.plugins.document.UpdateOp; @@ -71,18 +72,18 @@ public class UtilsTest { public void getPreviousIdFor() { Revision r = new Revision(System.currentTimeMillis(), 0, 0); assertEquals("2:p/" + r.toString() + "/0", - Utils.getPreviousIdFor("/", r, 0)); + Utils.getPreviousIdFor(Path.ROOT, r, 0)); assertEquals("3:p/test/" + r.toString() + "/1", - Utils.getPreviousIdFor("/test", r, 1)); + Utils.getPreviousIdFor(Path.fromString("/test"), r, 1)); assertEquals("15:p/a/b/c/d/e/f/g/h/i/j/k/l/m/" + r.toString() + "/3", - Utils.getPreviousIdFor("/a/b/c/d/e/f/g/h/i/j/k/l/m", r, 3)); + Utils.getPreviousIdFor(Path.fromString("/a/b/c/d/e/f/g/h/i/j/k/l/m"), r, 3)); } @Test public void previousDoc() throws Exception{ Revision r = new Revision(System.currentTimeMillis(), 0, 0); - assertTrue(Utils.isPreviousDocId(Utils.getPreviousIdFor("/", r, 0))); - assertTrue(Utils.isPreviousDocId(Utils.getPreviousIdFor("/a/b/c/d/e/f/g/h/i/j/k/l/m", r, 3))); + assertTrue(Utils.isPreviousDocId(Utils.getPreviousIdFor(Path.ROOT, r, 0))); + assertTrue(Utils.isPreviousDocId(Utils.getPreviousIdFor(Path.fromString("/a/b/c/d/e/f/g/h/i/j/k/l/m"), r, 3))); assertFalse(Utils.isPreviousDocId(Utils.getIdFromPath("/a/b"))); assertFalse(Utils.isPreviousDocId("foo")); assertFalse(Utils.isPreviousDocId("0:")); @@ -91,9 +92,9 @@ public class UtilsTest { @Test public void leafPreviousDoc() throws Exception { Revision r = new Revision(System.currentTimeMillis(), 0, 0); - assertTrue(Utils.isLeafPreviousDocId(Utils.getPreviousIdFor("/", r, 0))); - assertTrue(Utils.isLeafPreviousDocId(Utils.getPreviousIdFor("/a/b/c/d/e/f/g/h/i/j/k/l/m", r, 0))); - assertFalse(Utils.isLeafPreviousDocId(Utils.getPreviousIdFor("/a/b/c/d/e/f/g/h/i/j/k/l/m", r, 3))); + assertTrue(Utils.isLeafPreviousDocId(Utils.getPreviousIdFor(Path.ROOT, r, 0))); + assertTrue(Utils.isLeafPreviousDocId(Utils.getPreviousIdFor(Path.fromString("/a/b/c/d/e/f/g/h/i/j/k/l/m"), r, 0))); + assertFalse(Utils.isLeafPreviousDocId(Utils.getPreviousIdFor(Path.fromString("/a/b/c/d/e/f/g/h/i/j/k/l/m"), r, 3))); assertFalse(Utils.isLeafPreviousDocId(Utils.getIdFromPath("/a/b"))); assertFalse(Utils.isLeafPreviousDocId("foo")); assertFalse(Utils.isLeafPreviousDocId("0:")); @@ -102,19 +103,19 @@ public class UtilsTest { @Test public void getParentIdFromLowerLimit() throws Exception{ - assertEquals("1:/foo",Utils.getParentIdFromLowerLimit(Utils.getKeyLowerLimit("/foo"))); + assertEquals("1:/foo",Utils.getParentIdFromLowerLimit(Utils.getKeyLowerLimit(Path.fromString("/foo")))); assertEquals("1:/foo",Utils.getParentIdFromLowerLimit("2:/foo/bar")); } @Test public void getParentId() throws Exception{ - String longPath = PathUtils.concat("/"+Strings.repeat("p", Utils.PATH_LONG + 1), "foo"); + Path longPath = Path.fromString(PathUtils.concat("/"+Strings.repeat("p", Utils.PATH_LONG + 1), "foo")); assertTrue(Utils.isLongPath(longPath)); assertNull(Utils.getParentId(Utils.getIdFromPath(longPath))); - assertNull(Utils.getParentId(Utils.getIdFromPath("/"))); - assertEquals("1:/foo",Utils.getParentId("2:/foo/bar")); + assertNull(Utils.getParentId(Utils.getIdFromPath(Path.ROOT))); + assertEquals("1:/foo", Utils.getParentId("2:/foo/bar")); } @Test @@ -128,7 +129,7 @@ public class UtilsTest { @Test public void performance_getPreviousIdFor() { Revision r = new Revision(System.currentTimeMillis(), 0, 0); - String path = "/some/test/path/foo"; + Path path = Path.fromString("/some/test/path/foo"); // warm up for (int i = 0; i < 1 * 1000 * 1000; i++) { Utils.getPreviousIdFor(path, r, 0); @@ -300,9 +301,9 @@ public class UtilsTest { @Test public void isIdFromLongPath() { - String path = "/test"; + Path path = Path.fromString("/test"); while (!Utils.isLongPath(path)) { - path += path; + path = new Path(path, path.getName()); } String idFromLongPath = Utils.getIdFromPath(path); assertTrue(Utils.isIdFromLongPath(idFromLongPath)); @@ -312,6 +313,16 @@ public class UtilsTest { assertFalse(Utils.isIdFromLongPath(":")); } + @Test + public void idDepth() { + assertEquals(0, Utils.getIdDepth(Path.ROOT)); + assertEquals(0, Utils.getIdDepth(Path.fromString("a"))); + assertEquals(1, Utils.getIdDepth(Path.fromString("/a"))); + assertEquals(2, Utils.getIdDepth(Path.fromString("/a/b"))); + assertEquals(3, Utils.getIdDepth(Path.fromString("/a/b/c"))); + assertEquals(2, Utils.getIdDepth(Path.fromString("a/b/c"))); + } + @Test public void encodeHexString() { Random r = new Random(42); diff --git a/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/MetadataExposingNodeState.java b/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/MetadataExposingNodeState.java index 2a53104c45..f493667f11 100644 --- a/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/MetadataExposingNodeState.java +++ b/oak-upgrade/src/main/java/org/apache/jackrabbit/oak/upgrade/nodestate/MetadataExposingNodeState.java @@ -37,7 +37,7 @@ public class MetadataExposingNodeState extends AbstractDecoratedNodeState { super(documentNodeState, true); metadataProperties = new ArrayList<>(2); - if (PathUtils.denotesRoot(documentNodeState.getPath())) { + if (documentNodeState.getPath().isRoot()) { metadataProperties.add(createProperty(PROP_REVISION, documentNodeState.getRootRevision().asString())); } metadataProperties.add(createProperty(PROP_LAST_REV, documentNodeState.getLastRevision().asString()));