blob: 71fa1f9e440d7f637d0572bfe983e72f403517db [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.common.flogger.FluentLogger;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.Emails;
import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
/** Save OwnersDb in a cache for multiple calls to submit_filter. */
class Cache {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// 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.
// Since a Java runtime can host multiple Gerrit sites, each site should have
// its own cache. We assume that each site has its own GitRepositoryManager.
// We use a WeakHashMap to avoid leaking objects in the map.
private static final Map<GitRepositoryManager, Cache> cacheMap =
Collections.synchronizedMap(new WeakHashMap<GitRepositoryManager, Cache>());
// dbCache key is generated by makeKey.
private com.google.common.cache.Cache<String, OwnersDb> dbCache;
private Cache(int maxSeconds, int maxSize) {
init(maxSeconds, maxSize);
}
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) {
logger.atInfo().log("Initialize Cache with maxSeconds=%d maxSize=%d", maxSeconds, maxSize);
dbCache =
CacheBuilder.newBuilder()
.maximumSize(maxSize)
.expireAfterWrite(maxSeconds, SECONDS)
.build();
} else {
logger.atInfo().log("Cache disabled.");
dbCache = null;
}
return this;
}
/** Returns a cached or new OwnersDb, for the current patchset. */
OwnersDb get(
Boolean useCache,
ProjectState projectState,
AccountCache accountCache,
Emails emails,
GitRepositoryManager repoManager,
PluginConfigFactory configFactory,
ChangeData changeData)
throws StorageException, IOException {
return get(
useCache,
projectState,
accountCache,
emails,
repoManager,
configFactory,
changeData,
changeData.currentPatchSet().getId().get());
}
/** Returns a cached or new OwnersDb, for the specified patchset. */
OwnersDb get(
Boolean useCache,
ProjectState projectState,
AccountCache accountCache,
Emails emails,
GitRepositoryManager repoManager,
PluginConfigFactory configFactory,
ChangeData changeData,
int patchset)
throws StorageException, IOException {
String branch = changeData.change().getDest().get();
String dbKey = Cache.makeKey(changeData.getId().get(), patchset, repoManager);
// TODO: get changed files of the given patchset?
return get(
useCache,
projectState,
accountCache,
emails,
dbKey,
repoManager,
configFactory,
changeData,
branch,
changeData.currentFilePaths());
}
/** Returns a cached or new OwnersDb, for the specified branch and changed files. */
OwnersDb get(
Boolean useCache,
ProjectState projectState,
AccountCache accountCache,
Emails emails,
String key,
GitRepositoryManager repoManager,
PluginConfigFactory configFactory,
ChangeData changeData,
String branch,
Collection<String> files) {
if (dbCache == null || !useCache) { // Do not cache OwnersDb
logger.atFiner().log("Create new OwnersDb, key=%s", key);
return new OwnersDb(
projectState,
accountCache,
emails,
key,
repoManager,
configFactory,
changeData,
branch,
files);
}
try {
logger.atFiner().log(
"Get from cache %s, key=%s, cache size=%d", dbCache, key, dbCache.size());
return dbCache.get(
key,
new Callable<OwnersDb>() {
@Override
public OwnersDb call() {
logger.atFiner().log("Create new OwnersDb, key=%s", key);
return new OwnersDb(
projectState,
accountCache,
emails,
key,
repoManager,
configFactory,
changeData,
branch,
files);
}
});
} catch (ExecutionException e) {
logger.atSevere().withCause(e).log(
"Cache.get has exception for %s", Config.getChangeId(changeData));
return new OwnersDb(
projectState,
accountCache,
emails,
key,
repoManager,
configFactory,
changeData,
branch,
files);
}
}
public static String makeKey(int change, int patch, GitRepositoryManager repoManager) {
return String.format("%d:%d:%H", change, patch, repoManager);
}
public static Cache getInstance(
PluginConfigFactory configFactory, GitRepositoryManager repoManager) {
Cache cache = cacheMap.get(repoManager);
if (cache == null) {
Config config = new Config(configFactory);
cache = new Cache(config.getMaxCacheAge(), config.getMaxCacheSize());
cacheMap.put(repoManager, cache);
}
return cache;
}
}