blob: 9b9c055ccced30f1ebccab6a05d4e78c8a9a6b8c [file] [log] [blame]
/*
* 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.List;
import com.google.common.base.Supplier;
import com.google.common.cache.Cache;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.apache.jackrabbit.oak.stats.Clock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.google.common.cache.CacheBuilder.newBuilder;
import static com.google.common.collect.ImmutableList.of;
/**
* Resolves the commit value for a given change revision on a document.
*/
final class CachingCommitValueResolver implements CommitValueResolver {
private static final List<String> COMMIT_ROOT_OR_REVISIONS
= of(NodeDocument.COMMIT_ROOT, NodeDocument.REVISIONS);
private final Cache<Revision, String> commitValueCache;
private final int cacheSize;
private final Supplier<RevisionVector> sweepRevisions;
CachingCommitValueResolver(int cacheSize, Supplier<RevisionVector> sweepRevisions) {
this.commitValueCache = newBuilder().maximumSize(cacheSize).build();
this.cacheSize = cacheSize;
this.sweepRevisions = sweepRevisions;
}
CommitValueResolver withEmptyCommitValueCache(boolean enable,
Clock clock,
long maxAgeMillis) {
if (enable) {
return new CommitValueResolver() {
private final Cache<Revision, String> emptyCommitValueCache
= newBuilder().maximumSize(cacheSize).build();
@Override
public String resolve(@NotNull Revision changeRevision,
@NotNull NodeDocument doc) {
// check cache first
String value = commitValueCache.getIfPresent(changeRevision);
if (value != null) {
return value;
}
// check negative cache
if (emptyCommitValueCache.getIfPresent(changeRevision) != null) {
return null;
}
value = CachingCommitValueResolver.this.resolve(changeRevision, doc);
if (value == null && isOld(changeRevision, clock, maxAgeMillis)) {
// remember
emptyCommitValueCache.put(changeRevision, "");
}
return value;
}
private boolean isOld(Revision r, Clock c, long maxAgeMillis) {
return r.getTimestamp() + maxAgeMillis < c.getTime();
}
};
} else {
return this;
}
}
@Override
public String resolve(@NotNull Revision changeRevision,
@NotNull NodeDocument doc) {
// check cache first
String value = commitValueCache.getIfPresent(changeRevision);
if (value != null) {
return value;
}
// need to determine the commit value
doc = resolveDocument(doc, changeRevision);
if (doc == null) {
// the document including its history does not contain a change
// for the given revision
return null;
}
// at this point 'doc' is guaranteed to have a local entry
// for the given change revision
if (sweepRevisions.get().isRevisionNewer(changeRevision)) {
// change revision is newer than sweep revision
// resolve the commit value without any short cuts
value = doc.resolveCommitValue(changeRevision);
} else {
// change revision is equal or older than sweep revision
// there are different cases:
// - doc is a main document and we have a guarantee that a
// potential branch commit is marked accordingly
// - doc is a split document and the revision is guaranteed
// to be committed. the remaining question is whether the
// revision is from a branch commit
NodeDocument.SplitDocType sdt = doc.getSplitDocType();
if (sdt == NodeDocument.SplitDocType.NONE) {
// sweeper ensures that all changes on main document
// before the sweep revision are properly marked with
// branch commit entry if applicable
if (doc.getLocalBranchCommits().contains(changeRevision)) {
// resolve the commit value the classic way
value = doc.resolveCommitValue(changeRevision);
} else {
value = "c";
}
} else if (sdt == NodeDocument.SplitDocType.DEFAULT_NO_BRANCH) {
// split document without branch commits, we don't have
// to check the commit root, the commit value is always 'c'
value = "c";
} else {
// some other split document type introduced
// before Oak 1.8 and we don't know if this is a branch
// commit. first try to resolve
value = doc.resolveCommitValue(changeRevision);
if (value == null) {
// then it must be a non-branch commit and the
// split document with the commit value was
// already garbage collected
value = "c";
}
}
}
if (Utils.isCommitted(value)) {
// only cache committed states
// e.g. branch commits may be merged later and
// the commit value will change
commitValueCache.put(changeRevision, value);
}
return value;
}
/**
* Resolves to the document that contains the change with the given
* revision. If the given document contains a local change for the given
* revision, then the passed document is returned. Otherwise this method
* looks up previous documents and returns one with a change for the given
* revision. This method returns {@code null} if neither the passed document
* nor any of its previous documents contains a change for the given
* revision.
*
* @param doc the document to resolve for the given change revision.
* @param changeRevision the revision of a change.
* @return the document with the change or {@code null} if there is no
* document with such a change.
*/
@Nullable
private NodeDocument resolveDocument(@NotNull NodeDocument doc,
@NotNull Revision changeRevision) {
// check if the document contains the change or we need to
// look up previous documents for the actual change
if (doc.getLocalCommitRoot().containsKey(changeRevision)
|| doc.getLocalRevisions().containsKey(changeRevision)) {
return doc;
}
// find the previous document with this change
// first check if there is a commit root entry for this revision
NodeDocument d = null;
for (String p : COMMIT_ROOT_OR_REVISIONS) {
for (NodeDocument prev : doc.getPreviousDocs(p, changeRevision)) {
d = prev;
break;
}
if (d != null) {
break;
}
}
return d;
}
}