blob: 87e40f5449ca021899850934e82ad3718d34e8e7 [file] [log] [blame]
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed 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 com.googlesource.gerrit.plugins.findowners;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.cache.CacheBuilder;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.Emails;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Save OwnersDb in a cache for multiple calls to submit_filter. */
class Cache {
private static final Logger log = LoggerFactory.getLogger(Cache.class);
// The OwnersDb is created from OWNERS files in directories that
// contain changed files of a patch set, which belongs to a project
// and branch. OwnersDb can be cached if the head of a project branch
// and the patch set are not changed.
// Although the head of a project branch could be changed by other users,
// it is better to assume the same for a patch set during a short period
// of time. So multiple checks would have the same result. For example,
// one client UI action can trigger multiple HTTP requests.
// Each HTTP request has one StoredValues,
// and can trigger multiple Prolog submit_filter.
// Each submit_filter has one Prolog engine.
// It would not be enough to keep the cache in a Prolog engine environment
// or a StoredValues.
private static Cache instance = null; // a singleton
// When branch is "refs/heads/xyz" use only "xyz",
// to share cached OwnersDb between these two branch names.
private static final String REFS_HEADS = "refs/heads/";
// Until we have a way to inject Java singleton into Prolog PRED_* constructors,
// we keep the cache in a static object for all HTTP requests.
private com.google.common.cache.Cache<String, OwnersDb> dbCache;
private Cache() {
init(Config.getMaxCacheAge(), Config.getMaxCacheSize());
}
long size() {
return (dbCache == null) ? 0 : dbCache.size();
}
Cache init(int maxSeconds, int maxSize) {
// This should be called once in normal configuration,
// but could be called multiple times in unit or integration tests.
if (dbCache != null) {
dbCache.invalidateAll(); // release all cached objects
}
if (maxSeconds > 0) {
log.info("Initialize Cache with maxSeconds=" + maxSeconds + " maxSize=" + maxSize);
dbCache =
CacheBuilder.newBuilder()
.maximumSize(maxSize)
.expireAfterWrite(maxSeconds, SECONDS)
.build();
} else {
log.info("Cache disabled.");
dbCache = null;
}
return this;
}
/** Returns a cached or new OwnersDb, for the current patchset. */
OwnersDb get(AccountCache accountCache, Emails emails, Repository repo, ChangeData changeData)
throws OrmException, IOException {
return get(accountCache, emails, repo, changeData, changeData.currentPatchSet().getId().get());
}
/** Returns a cached or new OwnersDb, for the specified patchset. */
OwnersDb get(
AccountCache accountCache,
Emails emails,
Repository repository,
ChangeData changeData,
int patchset)
throws OrmException, IOException {
Project.NameKey project = changeData.change().getProject();
String branch = changeData.change().getDest().get();
String dbKey = Cache.makeKey(changeData.getId().get(), patchset, branch);
// TODO: get changed files of the given patchset?
return get(
accountCache,
emails,
dbKey,
repository,
changeData,
project,
branch,
changeData.currentFilePaths());
}
/** Returns a cached or new OwnersDb, for the specified branch and changed files. */
OwnersDb get(
AccountCache accountCache,
Emails emails,
String key,
Repository repository,
ChangeData changeData,
Project.NameKey project,
String branch,
Collection<String> files) {
if (dbCache == null) { // Do not cache OwnersDb
log.trace("Create new OwnersDb, key=" + key);
return new OwnersDb(
accountCache, emails, key, repository, changeData, project, branch, files);
}
try {
log.trace("Get from cash " + dbCache + ", key=" + key + ", cache size=" + dbCache.size());
return dbCache.get(
key,
new Callable<OwnersDb>() {
@Override
public OwnersDb call() {
log.trace("Create new OwnersDb, key=" + key);
return new OwnersDb(
accountCache, emails, key, repository, changeData, project, branch, files);
}
});
} catch (ExecutionException e) {
log.error("Cache.get has exception: " + e);
return new OwnersDb(
accountCache, emails, key, repository, changeData, project, branch, files);
}
}
public static String makeKey(int change, int patch, String branch) {
if (branch.indexOf(REFS_HEADS) == 0) {
branch = branch.substring(REFS_HEADS.length());
}
return change + ":" + patch + ":" + branch;
}
public static Cache getInstance() {
if (instance == null) {
instance = new Cache();
}
return instance;
}
}