Serialize AccountCache
Data on googlesource.com suggests that we spend a significant amount of time loading accounts from NoteDb. This is true for all Gerrit installations, but especially for distributed setups or setups that restart often. This commit serializes the AccountCache using established mechanisms. To do that, we decompose AccountState - the entity that we currently cache - into smaller chunks that can be cached individually: 1) External IDs + user name (cached in ExternalIdCache) 2) CachedAccountDetails (newly cached) 3) Gerrit's default settings (we start caching this in a follow-up change) CachedAccountDetails - a new class representing all information stored under the user's ref (refs/users/<sharded-id>) is now cached in the 'accounts' cache instead of AccountState. AccountState is contructed when requested from the sources 1-3 and not cached itself as it's just a plain wrapper around other state, that we already cache. This has the following advantages: 1) CachedAccountDetails contains only details from refs/users/<sharded-id>. By that, we can use the SHA1 of that ref as cache key and start serializing the cache to eliminate cold start penalty as well as router assignment change penalty (for distributed setups). It also means that we don't have to do invalidation ourselves anymore. 2) When the server's default preferences change, we don't have to invalidate all accounts anymore. This is a shortcoming of the current approach. 3) The projected speed improvements that come from persisting the cache makes it so that we can remove the logic to load accounts in parallel. The new aproach also means that: 1) We now need to get the SHA1 from refs/users/<sharded-id> for every account that we look up. Data suggests that this is not an issue for latency as ref lookups are cheap. We retain the method in AccountCacheImpl that allows the caller to load a Set<AccountState> so that in the cases where we want many many accounts (change queries, ...) we have to open All-Users only once. In case we discover that - against our assumptions - this is a bottleneck we can add a small in-memory cache for AccountState. Related prework: The new aproach shows that the way we handle user preferences is suboptimal, because: 1) We pipe through API data types to the storage 2) We overlay defaults directly in the storage 3) Use reflection to get/set fields. I considered and prototyped a rewrite of this and initially thought I could get it done before serializing the account cache. However it turned out to be significantly more work and the impact of that work (besides being a much desired cleanup) is rather low. So I decided to get the cache serialized independently. Change-Id: I61ae57802f37c62ee9e3552e4a0f19fe3d8d762b
This commit is contained in:
parent
9ab6603065
commit
e945da3880
|
@ -380,8 +380,7 @@ public abstract class AbstractDaemonTest {
|
|||
initSsh();
|
||||
}
|
||||
|
||||
protected void evictAndReindexAccount(Account.Id accountId) {
|
||||
accountCache.evict(accountId);
|
||||
protected void reindexAccount(Account.Id accountId) {
|
||||
accountIndexer.index(accountId);
|
||||
}
|
||||
|
||||
|
@ -436,8 +435,8 @@ public abstract class AbstractDaemonTest {
|
|||
user = accountCreator.user();
|
||||
|
||||
// Evict and reindex accounts in case tests modify them.
|
||||
evictAndReindexAccount(admin.id());
|
||||
evictAndReindexAccount(user.id());
|
||||
reindexAccount(admin.id());
|
||||
reindexAccount(user.id());
|
||||
|
||||
adminRestSession = new RestSession(server, admin);
|
||||
userRestSession = new RestSession(server, user);
|
||||
|
|
|
@ -339,8 +339,8 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
|
|||
return description.getClassName();
|
||||
}
|
||||
|
||||
private TestAccount evictAndCopy(TestAccount account) {
|
||||
evictAndReindexAccount(account.id());
|
||||
private TestAccount reindexAndCopy(TestAccount account) {
|
||||
reindexAccount(account.id());
|
||||
return account;
|
||||
}
|
||||
|
||||
|
@ -348,14 +348,14 @@ public abstract class AbstractNotificationTest extends AbstractDaemonTest {
|
|||
synchronized (stagedUsers) {
|
||||
if (stagedUsers.containsKey(usersCacheKey())) {
|
||||
StagedUsers existing = stagedUsers.get(usersCacheKey());
|
||||
owner = evictAndCopy(existing.owner);
|
||||
author = evictAndCopy(existing.author);
|
||||
uploader = evictAndCopy(existing.uploader);
|
||||
reviewer = evictAndCopy(existing.reviewer);
|
||||
ccer = evictAndCopy(existing.ccer);
|
||||
starrer = evictAndCopy(existing.starrer);
|
||||
assignee = evictAndCopy(existing.assignee);
|
||||
watchingProjectOwner = evictAndCopy(existing.watchingProjectOwner);
|
||||
owner = reindexAndCopy(existing.owner);
|
||||
author = reindexAndCopy(existing.author);
|
||||
uploader = reindexAndCopy(existing.uploader);
|
||||
reviewer = reindexAndCopy(existing.reviewer);
|
||||
ccer = reindexAndCopy(existing.ccer);
|
||||
starrer = reindexAndCopy(existing.starrer);
|
||||
assignee = reindexAndCopy(existing.assignee);
|
||||
watchingProjectOwner = reindexAndCopy(existing.watchingProjectOwner);
|
||||
watchers.putAll(existing.watchers);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -335,18 +335,18 @@ public class ProjectResetter implements AutoCloseable {
|
|||
// Make sure all accounts are evicted and reindexed.
|
||||
try (Repository repo = repoManager.openRepository(allUsersName)) {
|
||||
for (Account.Id id : accountIds(repo)) {
|
||||
evictAndReindexAccount(id);
|
||||
reindexAccount(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted accounts from the cache and index.
|
||||
for (Account.Id id : deletedAccounts) {
|
||||
evictAndReindexAccount(id);
|
||||
reindexAccount(id);
|
||||
}
|
||||
} else {
|
||||
// Evict and reindex all modified and deleted accounts.
|
||||
for (Account.Id id : Sets.union(modifiedAccounts, deletedAccounts)) {
|
||||
evictAndReindexAccount(id);
|
||||
reindexAccount(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -367,10 +367,7 @@ public class ProjectResetter implements AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
private void evictAndReindexAccount(Account.Id accountId) {
|
||||
if (accountCache != null) {
|
||||
accountCache.evict(accountId);
|
||||
}
|
||||
private void reindexAccount(Account.Id accountId) {
|
||||
if (groupIncludeCache != null) {
|
||||
groupIncludeCache.evictGroupsWithMember(accountId);
|
||||
}
|
||||
|
|
|
@ -19,15 +19,10 @@ import static com.google.gerrit.entities.RefNames.REFS_STARRED_CHANGES;
|
|||
import static com.google.gerrit.entities.RefNames.REFS_USERS;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.proto.Protos;
|
||||
import com.google.gerrit.server.cache.proto.Cache.AccountProto;
|
||||
import com.google.gerrit.server.cache.serialize.CacheSerializer;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
|
@ -125,42 +120,6 @@ public abstract class Account {
|
|||
}
|
||||
}
|
||||
|
||||
enum Serializer implements CacheSerializer<Account> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Account account) {
|
||||
// We don't care about the difference of empty strings and null in the Account entity.
|
||||
AccountProto.Builder proto =
|
||||
AccountProto.newBuilder()
|
||||
.setId(account.id().get())
|
||||
.setRegisteredOn(account.registeredOn().toInstant().toEpochMilli())
|
||||
.setInactive(account.inactive())
|
||||
.setFullName(Strings.nullToEmpty(account.fullName()))
|
||||
.setDisplayName(Strings.nullToEmpty(account.displayName()))
|
||||
.setPreferredEmail(Strings.nullToEmpty(account.preferredEmail()))
|
||||
.setStatus(Strings.nullToEmpty(account.status()))
|
||||
.setMetaId(Strings.nullToEmpty(account.metaId()));
|
||||
return Protos.toByteArray(proto.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Account deserialize(byte[] in) {
|
||||
// We don't care about the difference of empty strings and null in the Account entity.
|
||||
AccountProto proto = Protos.parseUnchecked(AccountProto.parser(), in);
|
||||
return Account.builder(
|
||||
Account.id(proto.getId()),
|
||||
Timestamp.from(Instant.ofEpochMilli(proto.getRegisteredOn())))
|
||||
.setFullName(Strings.emptyToNull(proto.getFullName()))
|
||||
.setDisplayName(Strings.emptyToNull(proto.getDisplayName()))
|
||||
.setPreferredEmail(Strings.emptyToNull(proto.getPreferredEmail()))
|
||||
.setInactive(proto.getInactive())
|
||||
.setStatus(Strings.emptyToNull(proto.getStatus()))
|
||||
.setMetaId(Strings.emptyToNull(proto.getMetaId()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Id id();
|
||||
|
||||
/** Date and time the user registered with the review server. */
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
@ -73,14 +72,4 @@ public interface AccountCache {
|
|||
* exists or if loading the external ID fails {@link Optional#empty()} is returned
|
||||
*/
|
||||
Optional<AccountState> getByUsername(String username);
|
||||
|
||||
/**
|
||||
* Evicts the account from the cache.
|
||||
*
|
||||
* @param accountId account ID of the account that should be evicted
|
||||
*/
|
||||
void evict(@Nullable Account.Id accountId);
|
||||
|
||||
/** Evict all accounts from the cache. */
|
||||
void evictAll();
|
||||
}
|
||||
|
|
|
@ -20,12 +20,15 @@ import com.google.common.cache.CacheLoader;
|
|||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.server.FanOutExecutor;
|
||||
import com.google.gerrit.entities.RefNames;
|
||||
import com.google.gerrit.exceptions.StorageException;
|
||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.config.CachedPreferences;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.logging.Metadata;
|
||||
import com.google.gerrit.server.logging.TraceContext;
|
||||
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
|
||||
|
@ -33,34 +36,34 @@ import com.google.gerrit.server.util.time.TimeUtil;
|
|||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
/** Caches important (but small) account state to avoid database hits. */
|
||||
@Singleton
|
||||
public class AccountCacheImpl implements AccountCache {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private static final String BYID_NAME = "accounts";
|
||||
private static final String BYID_AND_REV_NAME = "accounts";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
cache(BYID_NAME, Account.Id.class, new TypeLiteral<AccountState>() {})
|
||||
.loader(ByIdLoader.class);
|
||||
persist(BYID_AND_REV_NAME, CachedAccountDetails.Key.class, CachedAccountDetails.class)
|
||||
.version(1)
|
||||
.keySerializer(CachedAccountDetails.Key.Serializer.INSTANCE)
|
||||
.valueSerializer(CachedAccountDetails.Serializer.INSTANCE)
|
||||
.loader(Loader.class);
|
||||
|
||||
bind(AccountCacheImpl.class);
|
||||
bind(AccountCache.class).to(AccountCacheImpl.class);
|
||||
|
@ -69,76 +72,60 @@ public class AccountCacheImpl implements AccountCache {
|
|||
}
|
||||
|
||||
private final ExternalIds externalIds;
|
||||
private final LoadingCache<Account.Id, AccountState> byId;
|
||||
private final ExecutorService executor;
|
||||
private final LoadingCache<CachedAccountDetails.Key, CachedAccountDetails> accountDetailsCache;
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
|
||||
@Inject
|
||||
AccountCacheImpl(
|
||||
ExternalIds externalIds,
|
||||
@Named(BYID_NAME) LoadingCache<Account.Id, AccountState> byId,
|
||||
@FanOutExecutor ExecutorService executor) {
|
||||
@Named(BYID_AND_REV_NAME)
|
||||
LoadingCache<CachedAccountDetails.Key, CachedAccountDetails> accountDetailsCache,
|
||||
GitRepositoryManager repoManager,
|
||||
AllUsersName allUsersName) {
|
||||
this.externalIds = externalIds;
|
||||
this.byId = byId;
|
||||
this.executor = executor;
|
||||
this.accountDetailsCache = accountDetailsCache;
|
||||
this.repoManager = repoManager;
|
||||
this.allUsersName = allUsersName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountState getEvenIfMissing(Account.Id accountId) {
|
||||
try {
|
||||
return byId.get(accountId);
|
||||
} catch (ExecutionException e) {
|
||||
if (!(e.getCause() instanceof AccountNotFoundException)) {
|
||||
logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
|
||||
}
|
||||
return missing(accountId);
|
||||
}
|
||||
return get(accountId).orElse(missing(accountId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AccountState> get(Account.Id accountId) {
|
||||
try {
|
||||
return Optional.ofNullable(byId.get(accountId));
|
||||
} catch (ExecutionException e) {
|
||||
if (!(e.getCause() instanceof AccountNotFoundException)) {
|
||||
logger.atWarning().withCause(e).log("Cannot load AccountState for %s", accountId);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(get(Collections.singleton(accountId)).getOrDefault(accountId, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Account.Id, AccountState> get(Set<Account.Id> accountIds) {
|
||||
Map<Account.Id, AccountState> accountStates = new HashMap<>(accountIds.size());
|
||||
List<Callable<Optional<AccountState>>> callables = new ArrayList<>();
|
||||
for (Account.Id accountId : accountIds) {
|
||||
AccountState state = byId.getIfPresent(accountId);
|
||||
if (state != null) {
|
||||
// The value is in-memory, so we just get the state
|
||||
accountStates.put(accountId, state);
|
||||
} else {
|
||||
// Queue up a callable so that we can load accounts in parallel
|
||||
callables.add(() -> get(accountId));
|
||||
}
|
||||
}
|
||||
if (callables.isEmpty()) {
|
||||
return accountStates;
|
||||
}
|
||||
|
||||
List<Future<Optional<AccountState>>> futures;
|
||||
try {
|
||||
futures = executor.invokeAll(callables);
|
||||
} catch (InterruptedException e) {
|
||||
logger.atSevere().withCause(e).log("Cannot load AccountStates");
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
for (Future<Optional<AccountState>> f : futures) {
|
||||
try {
|
||||
f.get().ifPresent(s -> accountStates.put(s.account().id(), s));
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.atSevere().withCause(e).log("Cannot load AccountState");
|
||||
try (Repository allUsers = repoManager.openRepository(allUsersName)) {
|
||||
// TODO(hiesel): Cache the server's default config
|
||||
Config defaultConfig = StoredPreferences.readDefaultConfig(allUsersName, allUsers);
|
||||
|
||||
ImmutableMap.Builder<Account.Id, AccountState> result = ImmutableMap.builder();
|
||||
for (Account.Id id : accountIds) {
|
||||
Ref userRef = allUsers.exactRef(RefNames.refsUsers(id));
|
||||
if (userRef == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.put(
|
||||
id,
|
||||
AccountState.forCachedAccount(
|
||||
accountDetailsCache.get(
|
||||
CachedAccountDetails.Key.create(id, userRef.getObjectId())),
|
||||
CachedPreferences.fromConfig(defaultConfig),
|
||||
externalIds));
|
||||
}
|
||||
return result.build();
|
||||
}
|
||||
} catch (IOException | ExecutionException | ConfigInvalidException e) {
|
||||
throw new StorageException(e);
|
||||
}
|
||||
return accountStates;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -154,42 +141,37 @@ public class AccountCacheImpl implements AccountCache {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(@Nullable Account.Id accountId) {
|
||||
if (accountId != null) {
|
||||
logger.atFine().log("Evict account %d", accountId.get());
|
||||
byId.invalidate(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictAll() {
|
||||
logger.atFine().log("Evict all accounts");
|
||||
byId.invalidateAll();
|
||||
}
|
||||
|
||||
private AccountState missing(Account.Id accountId) {
|
||||
Account.Builder account = Account.builder(accountId, TimeUtil.nowTs());
|
||||
account.setActive(false);
|
||||
return AccountState.forAccount(account.build());
|
||||
}
|
||||
|
||||
static class ByIdLoader extends CacheLoader<Account.Id, AccountState> {
|
||||
private final Accounts accounts;
|
||||
@Singleton
|
||||
static class Loader extends CacheLoader<CachedAccountDetails.Key, CachedAccountDetails> {
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
|
||||
@Inject
|
||||
ByIdLoader(Accounts accounts) {
|
||||
this.accounts = accounts;
|
||||
Loader(GitRepositoryManager repoManager, AllUsersName allUsersName) {
|
||||
this.repoManager = repoManager;
|
||||
this.allUsersName = allUsersName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountState load(Account.Id who) throws Exception {
|
||||
try (TraceTimer timer =
|
||||
TraceContext.newTimer(
|
||||
"Loading account", Metadata.builder().accountId(who.get()).build())) {
|
||||
return accounts
|
||||
.get(who)
|
||||
.orElseThrow(() -> new AccountNotFoundException(who + " not found"));
|
||||
public CachedAccountDetails load(CachedAccountDetails.Key key) throws Exception {
|
||||
try (TraceTimer ignored =
|
||||
TraceContext.newTimer(
|
||||
"Loading account", Metadata.builder().accountId(key.accountId().get()).build());
|
||||
Repository repo = repoManager.openRepository(allUsersName)) {
|
||||
AccountConfig cfg = new AccountConfig(key.accountId(), allUsersName, repo).load(key.id());
|
||||
Account account =
|
||||
cfg.getLoadedAccount()
|
||||
.orElseThrow(() -> new AccountNotFoundException(key.accountId() + " not found"));
|
||||
return CachedAccountDetails.create(
|
||||
account,
|
||||
cfg.getProjectWatches(),
|
||||
CachedPreferences.fromConfig(cfg.getRawPreferences()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,9 +24,6 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.entities.RefNames;
|
||||
import com.google.gerrit.exceptions.DuplicateKeyException;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.EditPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.server.account.ProjectWatches.NotifyType;
|
||||
import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
|
@ -106,6 +103,11 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
|
|||
return this;
|
||||
}
|
||||
|
||||
public AccountConfig load(ObjectId rev) throws IOException, ConfigInvalidException {
|
||||
load(allUsersName, repo, rev);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loaded account.
|
||||
*
|
||||
|
@ -142,36 +144,6 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
|
|||
return projectWatches.getProjectWatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the general preferences of the loaded account.
|
||||
*
|
||||
* @return the general preferences of the loaded account
|
||||
*/
|
||||
public GeneralPreferencesInfo getGeneralPreferences() {
|
||||
checkLoaded();
|
||||
return preferences.getGeneralPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the diff preferences of the loaded account.
|
||||
*
|
||||
* @return the diff preferences of the loaded account
|
||||
*/
|
||||
public DiffPreferencesInfo getDiffPreferences() {
|
||||
checkLoaded();
|
||||
return preferences.getDiffPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edit preferences of the loaded account.
|
||||
*
|
||||
* @return the edit preferences of the loaded account
|
||||
*/
|
||||
public EditPreferencesInfo getEditPreferences() {
|
||||
checkLoaded();
|
||||
return preferences.getEditPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the account. This means the loaded account will be overwritten with the given account.
|
||||
*
|
||||
|
@ -228,6 +200,21 @@ public class AccountConfig extends VersionedMetaData implements ValidationError.
|
|||
return this;
|
||||
}
|
||||
|
||||
/** Returns the content of the {@code preferences.config} file. */
|
||||
Config getRawPreferences() {
|
||||
checkLoaded();
|
||||
return preferences.getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the {@code preferences.config} file on {@code
|
||||
* refs/preferences/defaults}.
|
||||
*/
|
||||
Config getRawDefaultPreferences() {
|
||||
checkLoaded();
|
||||
return preferences.getRawDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLoad() throws IOException, ConfigInvalidException {
|
||||
if (revision != null) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
|
|||
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
import com.google.gerrit.server.config.CachedPreferences;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
@ -92,12 +93,6 @@ public abstract class AccountState {
|
|||
// an open Repository instance.
|
||||
ImmutableMap<ProjectWatchKey, ImmutableSet<NotifyType>> projectWatches =
|
||||
accountConfig.getProjectWatches();
|
||||
Preferences.General generalPreferences =
|
||||
Preferences.General.fromInfo(accountConfig.getGeneralPreferences());
|
||||
Preferences.Diff diffPreferences =
|
||||
Preferences.Diff.fromInfo(accountConfig.getDiffPreferences());
|
||||
Preferences.Edit editPreferences =
|
||||
Preferences.Edit.fromInfo(accountConfig.getEditPreferences());
|
||||
|
||||
return Optional.of(
|
||||
new AutoValue_AccountState(
|
||||
|
@ -105,9 +100,8 @@ public abstract class AccountState {
|
|||
extIds,
|
||||
ExternalId.getUserName(extIds),
|
||||
projectWatches,
|
||||
generalPreferences,
|
||||
diffPreferences,
|
||||
editPreferences));
|
||||
Optional.of(CachedPreferences.fromConfig(accountConfig.getRawDefaultPreferences())),
|
||||
Optional.of(CachedPreferences.fromConfig(accountConfig.getRawPreferences()))));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,6 +115,26 @@ public abstract class AccountState {
|
|||
return forAccount(account, ImmutableSet.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccountState for a given account and external IDs.
|
||||
*
|
||||
* @param account the account
|
||||
* @return the account state
|
||||
*/
|
||||
public static AccountState forCachedAccount(
|
||||
CachedAccountDetails account, CachedPreferences defaultConfig, ExternalIds externalIds)
|
||||
throws IOException {
|
||||
ImmutableSet<ExternalId> extIds =
|
||||
ImmutableSet.copyOf(externalIds.byAccount(account.account().id()));
|
||||
return new AutoValue_AccountState(
|
||||
account.account(),
|
||||
extIds,
|
||||
ExternalId.getUserName(extIds),
|
||||
account.projectWatches(),
|
||||
Optional.of(defaultConfig),
|
||||
Optional.of(account.preferences()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an AccountState for a given account with no project watches and default preferences.
|
||||
*
|
||||
|
@ -134,9 +148,8 @@ public abstract class AccountState {
|
|||
ImmutableSet.copyOf(extIds),
|
||||
ExternalId.getUserName(extIds),
|
||||
ImmutableMap.of(),
|
||||
Preferences.General.fromInfo(GeneralPreferencesInfo.defaults()),
|
||||
Preferences.Diff.fromInfo(DiffPreferencesInfo.defaults()),
|
||||
Preferences.Edit.fromInfo(EditPreferencesInfo.defaults()));
|
||||
Optional.empty(),
|
||||
Optional.empty());
|
||||
}
|
||||
|
||||
/** Get the cached account metadata. */
|
||||
|
@ -158,17 +171,20 @@ public abstract class AccountState {
|
|||
|
||||
/** The general preferences of the account. */
|
||||
public GeneralPreferencesInfo generalPreferences() {
|
||||
return immutableGeneralPreferences().toInfo();
|
||||
return CachedPreferences.general(
|
||||
defaultPreferences(), userPreferences().orElse(CachedPreferences.EMPTY));
|
||||
}
|
||||
|
||||
/** The diff preferences of the account. */
|
||||
public DiffPreferencesInfo diffPreferences() {
|
||||
return immutableDiffPreferences().toInfo();
|
||||
return CachedPreferences.diff(
|
||||
defaultPreferences(), userPreferences().orElse(CachedPreferences.EMPTY));
|
||||
}
|
||||
|
||||
/** The edit preferences of the account. */
|
||||
public EditPreferencesInfo editPreferences() {
|
||||
return immutableEditPreferences().toInfo();
|
||||
return CachedPreferences.edit(
|
||||
defaultPreferences(), userPreferences().orElse(CachedPreferences.EMPTY));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -178,9 +194,9 @@ public abstract class AccountState {
|
|||
return h.toString();
|
||||
}
|
||||
|
||||
protected abstract Preferences.General immutableGeneralPreferences();
|
||||
/** Gerrit's default preferences as stored in {@code preferences.config}. */
|
||||
protected abstract Optional<CachedPreferences> defaultPreferences();
|
||||
|
||||
protected abstract Preferences.Diff immutableDiffPreferences();
|
||||
|
||||
protected abstract Preferences.Edit immutableEditPreferences();
|
||||
/** User preferences as stored in {@code preferences.config}. */
|
||||
protected abstract Optional<CachedPreferences> userPreferences();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
// Copyright (C) 2020 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.google.gerrit.server.account;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Enums;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.entities.Project;
|
||||
import com.google.gerrit.proto.Protos;
|
||||
import com.google.gerrit.server.cache.proto.Cache;
|
||||
import com.google.gerrit.server.cache.serialize.CacheSerializer;
|
||||
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
|
||||
import com.google.gerrit.server.config.CachedPreferences;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
|
||||
/** Details of an account that are cached persistently in {@link AccountCache}. */
|
||||
@AutoValue
|
||||
abstract class CachedAccountDetails {
|
||||
@AutoValue
|
||||
abstract static class Key {
|
||||
static Key create(Account.Id accountId, ObjectId id) {
|
||||
return new AutoValue_CachedAccountDetails_Key(accountId, id.copy());
|
||||
}
|
||||
|
||||
/** Identifier of the account. */
|
||||
abstract Account.Id accountId();
|
||||
|
||||
/**
|
||||
* Git revision at which the account was loaded. Corresponds to a revision on the account ref
|
||||
* ({@code refs/users/<sharded-id>}).
|
||||
*/
|
||||
abstract ObjectId id();
|
||||
|
||||
/** Serializer used to read this entity from and write it to a persistent storage. */
|
||||
enum Serializer implements CacheSerializer<Key> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public byte[] serialize(Key object) {
|
||||
return Protos.toByteArray(
|
||||
Cache.AccountKeyProto.newBuilder()
|
||||
.setAccountId(object.accountId().get())
|
||||
.setId(ObjectIdConverter.create().toByteString(object.id()))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key deserialize(byte[] in) {
|
||||
Cache.AccountKeyProto proto = Protos.parseUnchecked(Cache.AccountKeyProto.parser(), in);
|
||||
return Key.create(
|
||||
Account.id(proto.getAccountId()),
|
||||
ObjectIdConverter.create().fromByteString(proto.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Essential attributes of the account, such as name or registration time. */
|
||||
abstract Account account();
|
||||
|
||||
/** Projects that the user has configured to watch. */
|
||||
abstract ImmutableMap<ProjectWatches.ProjectWatchKey, ImmutableSet<ProjectWatches.NotifyType>>
|
||||
projectWatches();
|
||||
|
||||
/** Preferences that this user has. Serialized as Git-config style string. */
|
||||
abstract CachedPreferences preferences();
|
||||
|
||||
static CachedAccountDetails create(
|
||||
Account account,
|
||||
ImmutableMap<ProjectWatches.ProjectWatchKey, ImmutableSet<ProjectWatches.NotifyType>>
|
||||
projectWatches,
|
||||
CachedPreferences preferences) {
|
||||
return new AutoValue_CachedAccountDetails(account, projectWatches, preferences);
|
||||
}
|
||||
|
||||
/** Serializer used to read this entity from and write it to a persistent storage. */
|
||||
enum Serializer implements CacheSerializer<CachedAccountDetails> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public byte[] serialize(CachedAccountDetails cachedAccountDetails) {
|
||||
Cache.AccountDetailsProto.Builder serialized = Cache.AccountDetailsProto.newBuilder();
|
||||
// We don't care about the difference of empty strings and null in the Account entity.
|
||||
Account account = cachedAccountDetails.account();
|
||||
Cache.AccountProto.Builder accountProto =
|
||||
Cache.AccountProto.newBuilder()
|
||||
.setId(account.id().get())
|
||||
.setRegisteredOn(account.registeredOn().toInstant().toEpochMilli())
|
||||
.setInactive(account.inactive())
|
||||
.setFullName(Strings.nullToEmpty(account.fullName()))
|
||||
.setDisplayName(Strings.nullToEmpty(account.displayName()))
|
||||
.setPreferredEmail(Strings.nullToEmpty(account.preferredEmail()))
|
||||
.setStatus(Strings.nullToEmpty(account.status()))
|
||||
.setMetaId(Strings.nullToEmpty(account.metaId()));
|
||||
serialized.setAccount(accountProto);
|
||||
|
||||
for (Map.Entry<ProjectWatches.ProjectWatchKey, ImmutableSet<ProjectWatches.NotifyType>>
|
||||
watch : cachedAccountDetails.projectWatches().entrySet()) {
|
||||
Cache.ProjectWatchProto.Builder proto =
|
||||
Cache.ProjectWatchProto.newBuilder().setProject(watch.getKey().project().get());
|
||||
if (watch.getKey().filter() != null) {
|
||||
proto.setFilter(watch.getKey().filter());
|
||||
}
|
||||
watch
|
||||
.getValue()
|
||||
.forEach(
|
||||
n ->
|
||||
proto.addNotifyType(
|
||||
Enums.stringConverter(ProjectWatches.NotifyType.class)
|
||||
.reverse()
|
||||
.convert(n)));
|
||||
serialized.addProjectWatchProto(proto);
|
||||
}
|
||||
|
||||
serialized.setUserPreferences(cachedAccountDetails.preferences().config());
|
||||
return Protos.toByteArray(serialized.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CachedAccountDetails deserialize(byte[] in) {
|
||||
Cache.AccountDetailsProto proto =
|
||||
Protos.parseUnchecked(Cache.AccountDetailsProto.parser(), in);
|
||||
Account account =
|
||||
Account.builder(
|
||||
Account.id(proto.getAccount().getId()),
|
||||
Timestamp.from(Instant.ofEpochMilli(proto.getAccount().getRegisteredOn())))
|
||||
.setFullName(Strings.emptyToNull(proto.getAccount().getFullName()))
|
||||
.setDisplayName(Strings.emptyToNull(proto.getAccount().getDisplayName()))
|
||||
.setPreferredEmail(Strings.emptyToNull(proto.getAccount().getPreferredEmail()))
|
||||
.setInactive(proto.getAccount().getInactive())
|
||||
.setStatus(Strings.emptyToNull(proto.getAccount().getStatus()))
|
||||
.setMetaId(Strings.emptyToNull(proto.getAccount().getMetaId()))
|
||||
.build();
|
||||
|
||||
ImmutableMap.Builder<ProjectWatches.ProjectWatchKey, ImmutableSet<ProjectWatches.NotifyType>>
|
||||
projectWatches = ImmutableMap.builder();
|
||||
proto.getProjectWatchProtoList().stream()
|
||||
.forEach(
|
||||
p ->
|
||||
projectWatches.put(
|
||||
ProjectWatches.ProjectWatchKey.create(
|
||||
Project.nameKey(p.getProject()), p.getFilter()),
|
||||
p.getNotifyTypeList().stream()
|
||||
.map(
|
||||
e ->
|
||||
Enums.stringConverter(ProjectWatches.NotifyType.class).convert(e))
|
||||
.collect(toImmutableSet())));
|
||||
|
||||
return CachedAccountDetails.create(
|
||||
account,
|
||||
projectWatches.build(),
|
||||
CachedPreferences.fromString(proto.getUserPreferences()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,429 +0,0 @@
|
|||
// Copyright (C) 2019 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.google.gerrit.server.account;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
|
||||
import com.google.gerrit.extensions.client.EditPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DateFormat;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DefaultBase;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
|
||||
import com.google.gerrit.extensions.client.MenuItem;
|
||||
import java.util.Optional;
|
||||
|
||||
@AutoValue
|
||||
public abstract class Preferences {
|
||||
@AutoValue
|
||||
public abstract static class General {
|
||||
public abstract Optional<Integer> changesPerPage();
|
||||
|
||||
public abstract Optional<String> downloadScheme();
|
||||
|
||||
public abstract Optional<DateFormat> dateFormat();
|
||||
|
||||
public abstract Optional<TimeFormat> timeFormat();
|
||||
|
||||
public abstract Optional<Boolean> expandInlineDiffs();
|
||||
|
||||
public abstract Optional<Boolean> highlightAssigneeInChangeTable();
|
||||
|
||||
public abstract Optional<Boolean> relativeDateInChangeTable();
|
||||
|
||||
public abstract Optional<DiffView> diffView();
|
||||
|
||||
public abstract Optional<Boolean> sizeBarInChangeTable();
|
||||
|
||||
public abstract Optional<Boolean> legacycidInChangeTable();
|
||||
|
||||
public abstract Optional<Boolean> muteCommonPathPrefixes();
|
||||
|
||||
public abstract Optional<Boolean> signedOffBy();
|
||||
|
||||
public abstract Optional<EmailStrategy> emailStrategy();
|
||||
|
||||
public abstract Optional<EmailFormat> emailFormat();
|
||||
|
||||
public abstract Optional<DefaultBase> defaultBaseForMerges();
|
||||
|
||||
public abstract Optional<Boolean> publishCommentsOnPush();
|
||||
|
||||
public abstract Optional<Boolean> workInProgressByDefault();
|
||||
|
||||
public abstract Optional<ImmutableList<MenuItem>> my();
|
||||
|
||||
public abstract Optional<ImmutableList<String>> changeTable();
|
||||
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
abstract Builder changesPerPage(@Nullable Integer val);
|
||||
|
||||
abstract Builder downloadScheme(@Nullable String val);
|
||||
|
||||
abstract Builder dateFormat(@Nullable DateFormat val);
|
||||
|
||||
abstract Builder timeFormat(@Nullable TimeFormat val);
|
||||
|
||||
abstract Builder expandInlineDiffs(@Nullable Boolean val);
|
||||
|
||||
abstract Builder highlightAssigneeInChangeTable(@Nullable Boolean val);
|
||||
|
||||
abstract Builder relativeDateInChangeTable(@Nullable Boolean val);
|
||||
|
||||
abstract Builder diffView(@Nullable DiffView val);
|
||||
|
||||
abstract Builder sizeBarInChangeTable(@Nullable Boolean val);
|
||||
|
||||
abstract Builder legacycidInChangeTable(@Nullable Boolean val);
|
||||
|
||||
abstract Builder muteCommonPathPrefixes(@Nullable Boolean val);
|
||||
|
||||
abstract Builder signedOffBy(@Nullable Boolean val);
|
||||
|
||||
abstract Builder emailStrategy(@Nullable EmailStrategy val);
|
||||
|
||||
abstract Builder emailFormat(@Nullable EmailFormat val);
|
||||
|
||||
abstract Builder defaultBaseForMerges(@Nullable DefaultBase val);
|
||||
|
||||
abstract Builder publishCommentsOnPush(@Nullable Boolean val);
|
||||
|
||||
abstract Builder workInProgressByDefault(@Nullable Boolean val);
|
||||
|
||||
abstract Builder my(@Nullable ImmutableList<MenuItem> val);
|
||||
|
||||
abstract Builder changeTable(@Nullable ImmutableList<String> val);
|
||||
|
||||
abstract General build();
|
||||
}
|
||||
|
||||
public static General fromInfo(GeneralPreferencesInfo info) {
|
||||
return (new AutoValue_Preferences_General.Builder())
|
||||
.changesPerPage(info.changesPerPage)
|
||||
.downloadScheme(info.downloadScheme)
|
||||
.dateFormat(info.dateFormat)
|
||||
.timeFormat(info.timeFormat)
|
||||
.expandInlineDiffs(info.expandInlineDiffs)
|
||||
.highlightAssigneeInChangeTable(info.highlightAssigneeInChangeTable)
|
||||
.relativeDateInChangeTable(info.relativeDateInChangeTable)
|
||||
.diffView(info.diffView)
|
||||
.sizeBarInChangeTable(info.sizeBarInChangeTable)
|
||||
.legacycidInChangeTable(info.legacycidInChangeTable)
|
||||
.muteCommonPathPrefixes(info.muteCommonPathPrefixes)
|
||||
.signedOffBy(info.signedOffBy)
|
||||
.emailStrategy(info.emailStrategy)
|
||||
.emailFormat(info.emailFormat)
|
||||
.defaultBaseForMerges(info.defaultBaseForMerges)
|
||||
.publishCommentsOnPush(info.publishCommentsOnPush)
|
||||
.workInProgressByDefault(info.workInProgressByDefault)
|
||||
.my(info.my == null ? null : ImmutableList.copyOf(info.my))
|
||||
.changeTable(info.changeTable == null ? null : ImmutableList.copyOf(info.changeTable))
|
||||
.build();
|
||||
}
|
||||
|
||||
public GeneralPreferencesInfo toInfo() {
|
||||
GeneralPreferencesInfo info = new GeneralPreferencesInfo();
|
||||
info.changesPerPage = changesPerPage().orElse(null);
|
||||
info.downloadScheme = downloadScheme().orElse(null);
|
||||
info.dateFormat = dateFormat().orElse(null);
|
||||
info.timeFormat = timeFormat().orElse(null);
|
||||
info.expandInlineDiffs = expandInlineDiffs().orElse(null);
|
||||
info.highlightAssigneeInChangeTable = highlightAssigneeInChangeTable().orElse(null);
|
||||
info.relativeDateInChangeTable = relativeDateInChangeTable().orElse(null);
|
||||
info.diffView = diffView().orElse(null);
|
||||
info.sizeBarInChangeTable = sizeBarInChangeTable().orElse(null);
|
||||
info.legacycidInChangeTable = legacycidInChangeTable().orElse(null);
|
||||
info.muteCommonPathPrefixes = muteCommonPathPrefixes().orElse(null);
|
||||
info.signedOffBy = signedOffBy().orElse(null);
|
||||
info.emailStrategy = emailStrategy().orElse(null);
|
||||
info.emailFormat = emailFormat().orElse(null);
|
||||
info.defaultBaseForMerges = defaultBaseForMerges().orElse(null);
|
||||
info.publishCommentsOnPush = publishCommentsOnPush().orElse(null);
|
||||
info.workInProgressByDefault = workInProgressByDefault().orElse(null);
|
||||
info.my = my().orElse(null);
|
||||
info.changeTable = changeTable().orElse(null);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
public abstract static class Edit {
|
||||
public abstract Optional<Integer> tabSize();
|
||||
|
||||
public abstract Optional<Integer> lineLength();
|
||||
|
||||
public abstract Optional<Integer> indentUnit();
|
||||
|
||||
public abstract Optional<Integer> cursorBlinkRate();
|
||||
|
||||
public abstract Optional<Boolean> hideTopMenu();
|
||||
|
||||
public abstract Optional<Boolean> showTabs();
|
||||
|
||||
public abstract Optional<Boolean> showWhitespaceErrors();
|
||||
|
||||
public abstract Optional<Boolean> syntaxHighlighting();
|
||||
|
||||
public abstract Optional<Boolean> hideLineNumbers();
|
||||
|
||||
public abstract Optional<Boolean> matchBrackets();
|
||||
|
||||
public abstract Optional<Boolean> lineWrapping();
|
||||
|
||||
public abstract Optional<Boolean> indentWithTabs();
|
||||
|
||||
public abstract Optional<Boolean> autoCloseBrackets();
|
||||
|
||||
public abstract Optional<Boolean> showBase();
|
||||
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
abstract Builder tabSize(@Nullable Integer val);
|
||||
|
||||
abstract Builder lineLength(@Nullable Integer val);
|
||||
|
||||
abstract Builder indentUnit(@Nullable Integer val);
|
||||
|
||||
abstract Builder cursorBlinkRate(@Nullable Integer val);
|
||||
|
||||
abstract Builder hideTopMenu(@Nullable Boolean val);
|
||||
|
||||
abstract Builder showTabs(@Nullable Boolean val);
|
||||
|
||||
abstract Builder showWhitespaceErrors(@Nullable Boolean val);
|
||||
|
||||
abstract Builder syntaxHighlighting(@Nullable Boolean val);
|
||||
|
||||
abstract Builder hideLineNumbers(@Nullable Boolean val);
|
||||
|
||||
abstract Builder matchBrackets(@Nullable Boolean val);
|
||||
|
||||
abstract Builder lineWrapping(@Nullable Boolean val);
|
||||
|
||||
abstract Builder indentWithTabs(@Nullable Boolean val);
|
||||
|
||||
abstract Builder autoCloseBrackets(@Nullable Boolean val);
|
||||
|
||||
abstract Builder showBase(@Nullable Boolean val);
|
||||
|
||||
abstract Edit build();
|
||||
}
|
||||
|
||||
public static Edit fromInfo(EditPreferencesInfo info) {
|
||||
return (new AutoValue_Preferences_Edit.Builder())
|
||||
.tabSize(info.tabSize)
|
||||
.lineLength(info.lineLength)
|
||||
.indentUnit(info.indentUnit)
|
||||
.cursorBlinkRate(info.cursorBlinkRate)
|
||||
.hideTopMenu(info.hideTopMenu)
|
||||
.showTabs(info.showTabs)
|
||||
.showWhitespaceErrors(info.showWhitespaceErrors)
|
||||
.syntaxHighlighting(info.syntaxHighlighting)
|
||||
.hideLineNumbers(info.hideLineNumbers)
|
||||
.matchBrackets(info.matchBrackets)
|
||||
.lineWrapping(info.lineWrapping)
|
||||
.indentWithTabs(info.indentWithTabs)
|
||||
.autoCloseBrackets(info.autoCloseBrackets)
|
||||
.showBase(info.showBase)
|
||||
.build();
|
||||
}
|
||||
|
||||
public EditPreferencesInfo toInfo() {
|
||||
EditPreferencesInfo info = new EditPreferencesInfo();
|
||||
info.tabSize = tabSize().orElse(null);
|
||||
info.lineLength = lineLength().orElse(null);
|
||||
info.indentUnit = indentUnit().orElse(null);
|
||||
info.cursorBlinkRate = cursorBlinkRate().orElse(null);
|
||||
info.hideTopMenu = hideTopMenu().orElse(null);
|
||||
info.showTabs = showTabs().orElse(null);
|
||||
info.showWhitespaceErrors = showWhitespaceErrors().orElse(null);
|
||||
info.syntaxHighlighting = syntaxHighlighting().orElse(null);
|
||||
info.hideLineNumbers = hideLineNumbers().orElse(null);
|
||||
info.matchBrackets = matchBrackets().orElse(null);
|
||||
info.lineWrapping = lineWrapping().orElse(null);
|
||||
info.indentWithTabs = indentWithTabs().orElse(null);
|
||||
info.autoCloseBrackets = autoCloseBrackets().orElse(null);
|
||||
info.showBase = showBase().orElse(null);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
public abstract static class Diff {
|
||||
public abstract Optional<Integer> context();
|
||||
|
||||
public abstract Optional<Integer> tabSize();
|
||||
|
||||
public abstract Optional<Integer> fontSize();
|
||||
|
||||
public abstract Optional<Integer> lineLength();
|
||||
|
||||
public abstract Optional<Integer> cursorBlinkRate();
|
||||
|
||||
public abstract Optional<Boolean> expandAllComments();
|
||||
|
||||
public abstract Optional<Boolean> intralineDifference();
|
||||
|
||||
public abstract Optional<Boolean> manualReview();
|
||||
|
||||
public abstract Optional<Boolean> showLineEndings();
|
||||
|
||||
public abstract Optional<Boolean> showTabs();
|
||||
|
||||
public abstract Optional<Boolean> showWhitespaceErrors();
|
||||
|
||||
public abstract Optional<Boolean> syntaxHighlighting();
|
||||
|
||||
public abstract Optional<Boolean> hideTopMenu();
|
||||
|
||||
public abstract Optional<Boolean> autoHideDiffTableHeader();
|
||||
|
||||
public abstract Optional<Boolean> hideLineNumbers();
|
||||
|
||||
public abstract Optional<Boolean> renderEntireFile();
|
||||
|
||||
public abstract Optional<Boolean> hideEmptyPane();
|
||||
|
||||
public abstract Optional<Boolean> matchBrackets();
|
||||
|
||||
public abstract Optional<Boolean> lineWrapping();
|
||||
|
||||
public abstract Optional<Whitespace> ignoreWhitespace();
|
||||
|
||||
public abstract Optional<Boolean> retainHeader();
|
||||
|
||||
public abstract Optional<Boolean> skipDeleted();
|
||||
|
||||
public abstract Optional<Boolean> skipUnchanged();
|
||||
|
||||
public abstract Optional<Boolean> skipUncommented();
|
||||
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
abstract Builder context(@Nullable Integer val);
|
||||
|
||||
abstract Builder tabSize(@Nullable Integer val);
|
||||
|
||||
abstract Builder fontSize(@Nullable Integer val);
|
||||
|
||||
abstract Builder lineLength(@Nullable Integer val);
|
||||
|
||||
abstract Builder cursorBlinkRate(@Nullable Integer val);
|
||||
|
||||
abstract Builder expandAllComments(@Nullable Boolean val);
|
||||
|
||||
abstract Builder intralineDifference(@Nullable Boolean val);
|
||||
|
||||
abstract Builder manualReview(@Nullable Boolean val);
|
||||
|
||||
abstract Builder showLineEndings(@Nullable Boolean val);
|
||||
|
||||
abstract Builder showTabs(@Nullable Boolean val);
|
||||
|
||||
abstract Builder showWhitespaceErrors(@Nullable Boolean val);
|
||||
|
||||
abstract Builder syntaxHighlighting(@Nullable Boolean val);
|
||||
|
||||
abstract Builder hideTopMenu(@Nullable Boolean val);
|
||||
|
||||
abstract Builder autoHideDiffTableHeader(@Nullable Boolean val);
|
||||
|
||||
abstract Builder hideLineNumbers(@Nullable Boolean val);
|
||||
|
||||
abstract Builder renderEntireFile(@Nullable Boolean val);
|
||||
|
||||
abstract Builder hideEmptyPane(@Nullable Boolean val);
|
||||
|
||||
abstract Builder matchBrackets(@Nullable Boolean val);
|
||||
|
||||
abstract Builder lineWrapping(@Nullable Boolean val);
|
||||
|
||||
abstract Builder ignoreWhitespace(@Nullable Whitespace val);
|
||||
|
||||
abstract Builder retainHeader(@Nullable Boolean val);
|
||||
|
||||
abstract Builder skipDeleted(@Nullable Boolean val);
|
||||
|
||||
abstract Builder skipUnchanged(@Nullable Boolean val);
|
||||
|
||||
abstract Builder skipUncommented(@Nullable Boolean val);
|
||||
|
||||
abstract Diff build();
|
||||
}
|
||||
|
||||
public static Diff fromInfo(DiffPreferencesInfo info) {
|
||||
return (new AutoValue_Preferences_Diff.Builder())
|
||||
.context(info.context)
|
||||
.tabSize(info.tabSize)
|
||||
.fontSize(info.fontSize)
|
||||
.lineLength(info.lineLength)
|
||||
.cursorBlinkRate(info.cursorBlinkRate)
|
||||
.expandAllComments(info.expandAllComments)
|
||||
.intralineDifference(info.intralineDifference)
|
||||
.manualReview(info.manualReview)
|
||||
.showLineEndings(info.showLineEndings)
|
||||
.showTabs(info.showTabs)
|
||||
.showWhitespaceErrors(info.showWhitespaceErrors)
|
||||
.syntaxHighlighting(info.syntaxHighlighting)
|
||||
.hideTopMenu(info.hideTopMenu)
|
||||
.autoHideDiffTableHeader(info.autoHideDiffTableHeader)
|
||||
.hideLineNumbers(info.hideLineNumbers)
|
||||
.renderEntireFile(info.renderEntireFile)
|
||||
.hideEmptyPane(info.hideEmptyPane)
|
||||
.matchBrackets(info.matchBrackets)
|
||||
.lineWrapping(info.lineWrapping)
|
||||
.ignoreWhitespace(info.ignoreWhitespace)
|
||||
.retainHeader(info.retainHeader)
|
||||
.skipDeleted(info.skipDeleted)
|
||||
.skipUnchanged(info.skipUnchanged)
|
||||
.skipUncommented(info.skipUncommented)
|
||||
.build();
|
||||
}
|
||||
|
||||
public DiffPreferencesInfo toInfo() {
|
||||
DiffPreferencesInfo info = new DiffPreferencesInfo();
|
||||
info.context = context().orElse(null);
|
||||
info.tabSize = tabSize().orElse(null);
|
||||
info.fontSize = fontSize().orElse(null);
|
||||
info.lineLength = lineLength().orElse(null);
|
||||
info.cursorBlinkRate = cursorBlinkRate().orElse(null);
|
||||
info.expandAllComments = expandAllComments().orElse(null);
|
||||
info.intralineDifference = intralineDifference().orElse(null);
|
||||
info.manualReview = manualReview().orElse(null);
|
||||
info.showLineEndings = showLineEndings().orElse(null);
|
||||
info.showTabs = showTabs().orElse(null);
|
||||
info.showWhitespaceErrors = showWhitespaceErrors().orElse(null);
|
||||
info.syntaxHighlighting = syntaxHighlighting().orElse(null);
|
||||
info.hideTopMenu = hideTopMenu().orElse(null);
|
||||
info.autoHideDiffTableHeader = autoHideDiffTableHeader().orElse(null);
|
||||
info.hideLineNumbers = hideLineNumbers().orElse(null);
|
||||
info.renderEntireFile = renderEntireFile().orElse(null);
|
||||
info.hideEmptyPane = hideEmptyPane().orElse(null);
|
||||
info.matchBrackets = matchBrackets().orElse(null);
|
||||
info.lineWrapping = lineWrapping().orElse(null);
|
||||
info.ignoreWhitespace = ignoreWhitespace().orElse(null);
|
||||
info.retainHeader = retainHeader().orElse(null);
|
||||
info.skipDeleted = skipDeleted().orElse(null);
|
||||
info.skipUnchanged = skipUnchanged().orElse(null);
|
||||
info.skipUncommented = skipUncommented().orElse(null);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,9 +32,6 @@ import com.google.common.collect.Sets;
|
|||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.entities.Project;
|
||||
import com.google.gerrit.proto.Protos;
|
||||
import com.google.gerrit.server.cache.proto.Cache.ProjectWatchKeyProto;
|
||||
import com.google.gerrit.server.cache.serialize.CacheSerializer;
|
||||
import com.google.gerrit.server.git.ValidationError;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -90,26 +87,6 @@ public class ProjectWatches {
|
|||
public abstract Project.NameKey project();
|
||||
|
||||
public abstract @Nullable String filter();
|
||||
|
||||
enum Serializer implements CacheSerializer<ProjectWatchKey> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public byte[] serialize(ProjectWatchKey key) {
|
||||
ProjectWatchKeyProto.Builder proto =
|
||||
ProjectWatchKeyProto.newBuilder().setProject(key.project().get());
|
||||
if (key.filter() != null) {
|
||||
proto.setFilter(key.filter());
|
||||
}
|
||||
return Protos.toByteArray(proto.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectWatchKey deserialize(byte[] in) {
|
||||
ProjectWatchKeyProto proto = Protos.parseUnchecked(ProjectWatchKeyProto.parser(), in);
|
||||
return ProjectWatchKey.create(Project.nameKey(proto.getProject()), proto.getFilter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum NotifyType {
|
||||
|
|
|
@ -184,6 +184,19 @@ public class StoredPreferences {
|
|||
return cfg;
|
||||
}
|
||||
|
||||
/** Returns the content of the {@code preferences.config} file as {@link Config}. */
|
||||
Config getRaw() {
|
||||
return cfg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the {@code preferences.config} file on {@code
|
||||
* refs/preferences/defaults}.
|
||||
*/
|
||||
Config getRawDefault() {
|
||||
return defaultCfg;
|
||||
}
|
||||
|
||||
private GeneralPreferencesInfo parseGeneralPreferences(@Nullable GeneralPreferencesInfo input) {
|
||||
try {
|
||||
return parseGeneralPreferences(cfg, defaultCfg, input);
|
||||
|
@ -224,7 +237,12 @@ public class StoredPreferences {
|
|||
}
|
||||
}
|
||||
|
||||
private static GeneralPreferencesInfo parseGeneralPreferences(
|
||||
/**
|
||||
* Returns a {@link GeneralPreferencesInfo} that is the result of parsing {@code defaultCfg} for
|
||||
* the server's default configs and {@code cfg} for the user's config. These configs are then
|
||||
* overlaid to inherit values (default -> user -> input (if provided).
|
||||
*/
|
||||
public static GeneralPreferencesInfo parseGeneralPreferences(
|
||||
Config cfg, @Nullable Config defaultCfg, @Nullable GeneralPreferencesInfo input)
|
||||
throws ConfigInvalidException {
|
||||
GeneralPreferencesInfo r =
|
||||
|
@ -247,7 +265,12 @@ public class StoredPreferences {
|
|||
return r;
|
||||
}
|
||||
|
||||
private static DiffPreferencesInfo parseDiffPreferences(
|
||||
/**
|
||||
* Returns a {@link DiffPreferencesInfo} that is the result of parsing {@code defaultCfg} for the
|
||||
* server's default configs and {@code cfg} for the user's config. These configs are then overlaid
|
||||
* to inherit values (default -> user -> input (if provided).
|
||||
*/
|
||||
public static DiffPreferencesInfo parseDiffPreferences(
|
||||
Config cfg, @Nullable Config defaultCfg, @Nullable DiffPreferencesInfo input)
|
||||
throws ConfigInvalidException {
|
||||
return loadSection(
|
||||
|
@ -261,7 +284,12 @@ public class StoredPreferences {
|
|||
input);
|
||||
}
|
||||
|
||||
private static EditPreferencesInfo parseEditPreferences(
|
||||
/**
|
||||
* Returns a {@link EditPreferencesInfo} that is the result of parsing {@code defaultCfg} for the
|
||||
* server's default configs and {@code cfg} for the user's config. These configs are then overlaid
|
||||
* to inherit values (default -> user -> input (if provided).
|
||||
*/
|
||||
public static EditPreferencesInfo parseEditPreferences(
|
||||
Config cfg, @Nullable Config defaultCfg, @Nullable EditPreferencesInfo input)
|
||||
throws ConfigInvalidException {
|
||||
return loadSection(
|
||||
|
|
|
@ -745,9 +745,6 @@ public class ExternalIdNotes extends VersionedMetaData {
|
|||
.map(ExternalId::accountId)
|
||||
.filter(i -> !accountsToSkip.contains(i))
|
||||
.collect(toSet())) {
|
||||
if (accountCache != null) {
|
||||
accountCache.evict(id);
|
||||
}
|
||||
if (accountIndexer != null) {
|
||||
accountIndexer.get().index(id);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (C) 2020 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.google.gerrit.server.config;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.exceptions.StorageException;
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.EditPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.server.account.StoredPreferences;
|
||||
import java.util.Optional;
|
||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
|
||||
/**
|
||||
* Container class for preferences serialized as Git-style config files. Keeps the values as {@link
|
||||
* String}s as they are immutable and thread-safe.
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract class CachedPreferences {
|
||||
|
||||
public static CachedPreferences EMPTY = fromString("");
|
||||
|
||||
public abstract String config();
|
||||
|
||||
/** Returns a cache-able representation of the config. */
|
||||
public static CachedPreferences fromConfig(Config cfg) {
|
||||
return new AutoValue_CachedPreferences(cfg.toText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache-able representation of the config. To be used only when constructing a {@link
|
||||
* CachedPreferences} from a serialized, cached value.
|
||||
*/
|
||||
public static CachedPreferences fromString(String cfg) {
|
||||
return new AutoValue_CachedPreferences(cfg);
|
||||
}
|
||||
|
||||
public static GeneralPreferencesInfo general(
|
||||
Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) {
|
||||
try {
|
||||
return StoredPreferences.parseGeneralPreferences(
|
||||
asConfig(userPreferences), configOrNull(defaultPreferences), null);
|
||||
} catch (ConfigInvalidException e) {
|
||||
return GeneralPreferencesInfo.defaults();
|
||||
}
|
||||
}
|
||||
|
||||
public static EditPreferencesInfo edit(
|
||||
Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) {
|
||||
try {
|
||||
return StoredPreferences.parseEditPreferences(
|
||||
asConfig(userPreferences), configOrNull(defaultPreferences), null);
|
||||
} catch (ConfigInvalidException e) {
|
||||
return EditPreferencesInfo.defaults();
|
||||
}
|
||||
}
|
||||
|
||||
public static DiffPreferencesInfo diff(
|
||||
Optional<CachedPreferences> defaultPreferences, CachedPreferences userPreferences) {
|
||||
try {
|
||||
return StoredPreferences.parseDiffPreferences(
|
||||
asConfig(userPreferences), configOrNull(defaultPreferences), null);
|
||||
} catch (ConfigInvalidException e) {
|
||||
return DiffPreferencesInfo.defaults();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Config configOrNull(Optional<CachedPreferences> cachedPreferences) {
|
||||
return cachedPreferences.map(CachedPreferences::asConfig).orElse(null);
|
||||
}
|
||||
|
||||
private static Config asConfig(CachedPreferences cachedPreferences) {
|
||||
Config cfg = new Config();
|
||||
try {
|
||||
cfg.fromText(cachedPreferences.config());
|
||||
} catch (ConfigInvalidException e) {
|
||||
// Programmer error: We have parsed this config before and are unable to parse it now.
|
||||
throw new StorageException(e);
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
}
|
|
@ -83,7 +83,6 @@ public class AccountIndexerImpl implements AccountIndexer {
|
|||
|
||||
@Override
|
||||
public void index(Account.Id id) {
|
||||
byIdCache.evict(id);
|
||||
Optional<AccountState> accountState = byIdCache.get(id);
|
||||
|
||||
if (accountState.isPresent()) {
|
||||
|
|
|
@ -90,7 +90,6 @@ public class AllAccountsIndexer extends SiteIndexer<Account.Id, AccountState, Ac
|
|||
executor.submit(
|
||||
() -> {
|
||||
try {
|
||||
accountCache.evict(id);
|
||||
Optional<AccountState> a = accountCache.get(id);
|
||||
if (a.isPresent()) {
|
||||
index.replace(a.get());
|
||||
|
|
|
@ -94,7 +94,6 @@ public class ReindexAfterRefUpdate implements GitReferenceUpdatedListener {
|
|||
if (allUsersName.get().equals(event.getProjectName())) {
|
||||
Account.Id accountId = Account.Id.fromRef(event.getRefName());
|
||||
if (accountId != null && !event.getRefName().startsWith(RefNames.REFS_STARRED_CHANGES)) {
|
||||
accountCache.evict(accountId);
|
||||
indexer.get().index(accountId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,6 @@ public class SetDiffPreferences implements RestModifyView<ConfigResource, DiffPr
|
|||
|
||||
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
|
||||
DiffPreferencesInfo updatedPrefs = StoredPreferences.updateDefaultDiffPreferences(md, input);
|
||||
accountCache.evictAll();
|
||||
return Response.ok(updatedPrefs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,6 @@ public class SetEditPreferences implements RestModifyView<ConfigResource, EditPr
|
|||
|
||||
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
|
||||
EditPreferencesInfo updatedPrefs = StoredPreferences.updateDefaultEditPreferences(md, input);
|
||||
accountCache.evictAll();
|
||||
return Response.ok(updatedPrefs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,6 @@ public class SetPreferences implements RestModifyView<ConfigResource, GeneralPre
|
|||
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
|
||||
GeneralPreferencesInfo updatedPrefs =
|
||||
StoredPreferences.updateDefaultGeneralPreferences(md, input);
|
||||
accountCache.evictAll();
|
||||
return Response.ok(updatedPrefs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ package com.google.gerrit.testing;
|
|||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
|
@ -61,18 +60,6 @@ public class FakeAccountCache implements AccountCache {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void evict(@Nullable Account.Id accountId) {
|
||||
if (byId != null) {
|
||||
byId.remove(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void evictAll() {
|
||||
byId.clear();
|
||||
}
|
||||
|
||||
public synchronized void put(Account account) {
|
||||
AccountState state = newState(account);
|
||||
byId.put(account.id(), state);
|
||||
|
|
|
@ -20,9 +20,7 @@ import static org.mockito.Mockito.only;
|
|||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.entities.AccountGroup;
|
||||
import com.google.gerrit.entities.Project;
|
||||
import com.google.gerrit.entities.RefNames;
|
||||
|
@ -253,130 +251,6 @@ public class ProjectResetterTest {
|
|||
verify(projectCache, only()).evict(project2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accountEvictionIfUserBranchIsReset() throws Exception {
|
||||
Account.Id accountId = Account.id(1);
|
||||
Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
|
||||
Repository allUsersRepo = repoManager.createRepository(allUsers);
|
||||
Ref userBranch = createRef(allUsersRepo, RefNames.refsUsers(accountId));
|
||||
|
||||
AccountCache accountCache = mock(AccountCache.class);
|
||||
AccountIndexer accountIndexer = mock(AccountIndexer.class);
|
||||
|
||||
// Non-user branch because it's not in All-Users.
|
||||
Ref nonUserBranch = createRef(RefNames.refsUsers(Account.id(2)));
|
||||
|
||||
try (ProjectResetter resetProject =
|
||||
builder(null, accountCache, accountIndexer, null, null, null, null)
|
||||
.build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
|
||||
updateRef(nonUserBranch);
|
||||
updateRef(allUsersRepo, userBranch);
|
||||
}
|
||||
|
||||
verify(accountCache, only()).evict(accountId);
|
||||
verify(accountIndexer, only()).index(accountId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accountEvictionIfUserBranchIsDeleted() throws Exception {
|
||||
Account.Id accountId = Account.id(1);
|
||||
Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
|
||||
Repository allUsersRepo = repoManager.createRepository(allUsers);
|
||||
|
||||
AccountCache accountCache = mock(AccountCache.class);
|
||||
AccountIndexer accountIndexer = mock(AccountIndexer.class);
|
||||
|
||||
try (ProjectResetter resetProject =
|
||||
builder(null, accountCache, accountIndexer, null, null, null, null)
|
||||
.build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
|
||||
// Non-user branch because it's not in All-Users.
|
||||
createRef(RefNames.refsUsers(Account.id(2)));
|
||||
|
||||
createRef(allUsersRepo, RefNames.refsUsers(accountId));
|
||||
}
|
||||
|
||||
verify(accountCache, only()).evict(accountId);
|
||||
verify(accountIndexer, only()).index(accountId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accountEvictionIfExternalIdsBranchIsReset() throws Exception {
|
||||
Account.Id accountId = Account.id(1);
|
||||
Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
|
||||
Repository allUsersRepo = repoManager.createRepository(allUsers);
|
||||
Ref externalIds = createRef(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
|
||||
createRef(allUsersRepo, RefNames.refsUsers(accountId));
|
||||
|
||||
Account.Id accountId2 = Account.id(2);
|
||||
|
||||
AccountCache accountCache = mock(AccountCache.class);
|
||||
AccountIndexer accountIndexer = mock(AccountIndexer.class);
|
||||
|
||||
// Non-user branch because it's not in All-Users.
|
||||
Ref nonUserBranch = createRef(RefNames.refsUsers(Account.id(3)));
|
||||
|
||||
try (ProjectResetter resetProject =
|
||||
builder(null, accountCache, accountIndexer, null, null, null, null)
|
||||
.build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
|
||||
updateRef(nonUserBranch);
|
||||
updateRef(allUsersRepo, externalIds);
|
||||
createRef(allUsersRepo, RefNames.refsUsers(accountId2));
|
||||
}
|
||||
|
||||
verify(accountCache).evict(accountId);
|
||||
verify(accountCache).evict(accountId2);
|
||||
verify(accountIndexer).index(accountId);
|
||||
verify(accountIndexer).index(accountId2);
|
||||
verifyNoMoreInteractions(accountCache, accountIndexer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accountEvictionIfExternalIdsBranchIsDeleted() throws Exception {
|
||||
Account.Id accountId = Account.id(1);
|
||||
Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
|
||||
Repository allUsersRepo = repoManager.createRepository(allUsers);
|
||||
createRef(allUsersRepo, RefNames.refsUsers(accountId));
|
||||
|
||||
Account.Id accountId2 = Account.id(2);
|
||||
|
||||
AccountCache accountCache = mock(AccountCache.class);
|
||||
AccountIndexer accountIndexer = mock(AccountIndexer.class);
|
||||
|
||||
// Non-user branch because it's not in All-Users.
|
||||
Ref nonUserBranch = createRef(RefNames.refsUsers(Account.id(3)));
|
||||
|
||||
try (ProjectResetter resetProject =
|
||||
builder(null, accountCache, accountIndexer, null, null, null, null)
|
||||
.build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
|
||||
updateRef(nonUserBranch);
|
||||
createRef(allUsersRepo, RefNames.REFS_EXTERNAL_IDS);
|
||||
createRef(allUsersRepo, RefNames.refsUsers(accountId2));
|
||||
}
|
||||
|
||||
verify(accountCache).evict(accountId);
|
||||
verify(accountCache).evict(accountId2);
|
||||
verify(accountIndexer).index(accountId);
|
||||
verify(accountIndexer).index(accountId2);
|
||||
verifyNoMoreInteractions(accountCache, accountIndexer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void accountEvictionFromAccountCreatorIfUserBranchIsDeleted() throws Exception {
|
||||
Account.Id accountId = Account.id(1);
|
||||
Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
|
||||
Repository allUsersRepo = repoManager.createRepository(allUsers);
|
||||
|
||||
AccountCreator accountCreator = mock(AccountCreator.class);
|
||||
|
||||
try (ProjectResetter resetProject =
|
||||
builder(accountCreator, null, null, null, null, null, null)
|
||||
.build(new ProjectResetter.Config().reset(project).reset(allUsers))) {
|
||||
createRef(allUsersRepo, RefNames.refsUsers(accountId));
|
||||
}
|
||||
|
||||
verify(accountCreator, only()).evict(ImmutableSet.of(accountId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void groupEviction() throws Exception {
|
||||
AccountGroup.UUID uuid1 = AccountGroup.uuid("abcd1");
|
||||
|
|
|
@ -47,7 +47,6 @@ import static java.util.stream.Collectors.toSet;
|
|||
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
|
||||
|
||||
import com.github.rholder.retry.StopStrategies;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -143,7 +142,6 @@ import com.google.gerrit.testing.ConfigSuite;
|
|||
import com.google.gerrit.testing.FakeEmailSender.Message;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.name.Named;
|
||||
import com.jcraft.jsch.KeyPair;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -218,10 +216,6 @@ public class AccountIT extends AbstractDaemonTest {
|
|||
|
||||
@Inject protected Emails emails;
|
||||
|
||||
@Inject
|
||||
@Named("accounts")
|
||||
private LoadingCache<Account.Id, AccountState> accountsCache;
|
||||
|
||||
@Inject private AccountOperations accountOperations;
|
||||
|
||||
@Inject protected GroupOperations groupOperations;
|
||||
|
@ -2408,10 +2402,6 @@ public class AccountIT extends AbstractDaemonTest {
|
|||
}
|
||||
|
||||
private void assertStaleAccountAndReindex(Account.Id accountId) throws IOException {
|
||||
// Evict account from cache to be sure that we use the index state for staleness checks. This
|
||||
// has to happen directly on the accounts cache because AccountCacheImpl triggers a reindex for
|
||||
// the account.
|
||||
accountsCache.invalidate(accountId);
|
||||
assertThat(stalenessChecker.check(accountId).isStale()).isTrue();
|
||||
|
||||
// Reindex fixes staleness
|
||||
|
|
|
@ -85,18 +85,6 @@ public class AccountIndexerIT {
|
|||
assertThat(matchedAccountStates.get(0).account().id()).isEqualTo(accountId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexingUpdatesStaleCache() throws Exception {
|
||||
Account.Id accountId = createAccount("foo");
|
||||
loadAccountToCache(accountId);
|
||||
String status = "ooo";
|
||||
updateAccountWithoutCacheOrIndex(accountId, newAccountUpdate().setStatus(status).build());
|
||||
assertThat(accountCache.get(accountId).get().account().status()).isNull();
|
||||
|
||||
accountIndexer.index(accountId);
|
||||
assertThat(accountCache.get(accountId).get().account().status()).isEqualTo(status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reindexingStaleAccountUpdatesTheIndex() throws Exception {
|
||||
Account.Id accountId = createAccount("foo");
|
||||
|
@ -140,7 +128,6 @@ public class AccountIndexerIT {
|
|||
}
|
||||
|
||||
private void reloadAccountToCache(Account.Id accountId) {
|
||||
accountCache.evict(accountId);
|
||||
loadAccountToCache(accountId);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (C) 2020 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.google.gerrit.entities;
|
||||
|
||||
import com.google.common.truth.Truth;
|
||||
import com.google.common.truth.extensions.proto.ProtoTruth;
|
||||
import com.google.gerrit.server.cache.proto.Cache.AccountProto;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test to ensure that we are serializing and deserializing {@link Account} correctly. This is part
|
||||
* of the {@code AccountCache}.
|
||||
*/
|
||||
public class AccountCacheTest {
|
||||
@Test
|
||||
public void roundTrip() throws Exception {
|
||||
Account account =
|
||||
Account.builder(Account.id(1), Timestamp.from(Instant.EPOCH))
|
||||
.setFullName("foo bar")
|
||||
.setDisplayName("foo")
|
||||
.setActive(false)
|
||||
.setMetaId("dead..beef")
|
||||
.setStatus("OOO")
|
||||
.setPreferredEmail("foo@bar.tld")
|
||||
.build();
|
||||
byte[] serialized = Account.Serializer.INSTANCE.serialize(account);
|
||||
ProtoTruth.assertThat(AccountProto.parseFrom(serialized))
|
||||
.isEqualTo(
|
||||
AccountProto.newBuilder()
|
||||
.setId(1)
|
||||
.setRegisteredOn(0)
|
||||
.setFullName("foo bar")
|
||||
.setDisplayName("foo")
|
||||
.setInactive(true)
|
||||
.setMetaId("dead..beef")
|
||||
.setStatus("OOO")
|
||||
.setPreferredEmail("foo@bar.tld")
|
||||
.build());
|
||||
Truth.assertThat(Account.Serializer.INSTANCE.deserialize(serialized)).isEqualTo(account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundTripNullFields() throws Exception {
|
||||
Account account = Account.builder(Account.id(1), Timestamp.from(Instant.EPOCH)).build();
|
||||
byte[] serialized = Account.Serializer.INSTANCE.serialize(account);
|
||||
ProtoTruth.assertThat(AccountProto.parseFrom(serialized))
|
||||
.isEqualTo(AccountProto.newBuilder().setId(1).setRegisteredOn(0).build());
|
||||
Truth.assertThat(Account.Serializer.INSTANCE.deserialize(serialized)).isEqualTo(account);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (C) 2020 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.google.gerrit.server.account;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.truth.Truth;
|
||||
import com.google.common.truth.extensions.proto.ProtoTruth;
|
||||
import com.google.gerrit.entities.Account;
|
||||
import com.google.gerrit.entities.Project;
|
||||
import com.google.gerrit.server.cache.proto.Cache;
|
||||
import com.google.gerrit.server.config.CachedPreferences;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Instant;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test to ensure that we are serializing and deserializing {@link Account} correctly. This is part
|
||||
* of the {@code AccountCache}.
|
||||
*/
|
||||
public class AccountCacheTest {
|
||||
private static final Account ACCOUNT =
|
||||
Account.builder(Account.id(1), Timestamp.from(Instant.EPOCH)).build();
|
||||
private static final Cache.AccountProto ACCOUNT_PROTO =
|
||||
Cache.AccountProto.newBuilder().setId(1).setRegisteredOn(0).build();
|
||||
private static final CachedAccountDetails.Serializer SERIALIZER =
|
||||
CachedAccountDetails.Serializer.INSTANCE;
|
||||
|
||||
@Test
|
||||
public void account_roundTrip() throws Exception {
|
||||
Account account =
|
||||
Account.builder(Account.id(1), Timestamp.from(Instant.EPOCH))
|
||||
.setFullName("foo bar")
|
||||
.setDisplayName("foo")
|
||||
.setActive(false)
|
||||
.setMetaId("dead..beef")
|
||||
.setStatus("OOO")
|
||||
.setPreferredEmail("foo@bar.tld")
|
||||
.build();
|
||||
CachedAccountDetails original =
|
||||
CachedAccountDetails.create(account, ImmutableMap.of(), CachedPreferences.fromString(""));
|
||||
byte[] serialized = SERIALIZER.serialize(original);
|
||||
Cache.AccountDetailsProto expected =
|
||||
Cache.AccountDetailsProto.newBuilder()
|
||||
.setAccount(
|
||||
Cache.AccountProto.newBuilder()
|
||||
.setId(1)
|
||||
.setRegisteredOn(0)
|
||||
.setFullName("foo bar")
|
||||
.setDisplayName("foo")
|
||||
.setInactive(true)
|
||||
.setMetaId("dead..beef")
|
||||
.setStatus("OOO")
|
||||
.setPreferredEmail("foo@bar.tld"))
|
||||
.build();
|
||||
ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected);
|
||||
Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void account_roundTripNullFields() throws Exception {
|
||||
CachedAccountDetails original =
|
||||
CachedAccountDetails.create(ACCOUNT, ImmutableMap.of(), CachedPreferences.fromString(""));
|
||||
byte[] serialized = SERIALIZER.serialize(original);
|
||||
Cache.AccountDetailsProto expected =
|
||||
Cache.AccountDetailsProto.newBuilder().setAccount(ACCOUNT_PROTO).build();
|
||||
ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected);
|
||||
Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void config_roundTrip() throws Exception {
|
||||
CachedAccountDetails original =
|
||||
CachedAccountDetails.create(
|
||||
ACCOUNT, ImmutableMap.of(), CachedPreferences.fromString("[general]\n\tfoo = bar"));
|
||||
|
||||
byte[] serialized = SERIALIZER.serialize(original);
|
||||
Cache.AccountDetailsProto expected =
|
||||
Cache.AccountDetailsProto.newBuilder()
|
||||
.setAccount(ACCOUNT_PROTO)
|
||||
.setUserPreferences("[general]\n\tfoo = bar")
|
||||
.build();
|
||||
ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected);
|
||||
Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectWatch_roundTrip() throws Exception {
|
||||
ProjectWatches.ProjectWatchKey key =
|
||||
ProjectWatches.ProjectWatchKey.create(Project.nameKey("pro/ject"), "*");
|
||||
CachedAccountDetails original =
|
||||
CachedAccountDetails.create(
|
||||
ACCOUNT,
|
||||
ImmutableMap.of(key, ImmutableSet.of(ProjectWatches.NotifyType.ALL_COMMENTS)),
|
||||
CachedPreferences.fromString(""));
|
||||
|
||||
byte[] serialized = SERIALIZER.serialize(original);
|
||||
Cache.AccountDetailsProto expected =
|
||||
Cache.AccountDetailsProto.newBuilder()
|
||||
.setAccount(ACCOUNT_PROTO)
|
||||
.addProjectWatchProto(
|
||||
Cache.ProjectWatchProto.newBuilder()
|
||||
.setProject("pro/ject")
|
||||
.setFilter("*")
|
||||
.addNotifyType("ALL_COMMENTS"))
|
||||
.build();
|
||||
ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected);
|
||||
Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void projectWatch_roundTripNullFilter() throws Exception {
|
||||
ProjectWatches.ProjectWatchKey key =
|
||||
ProjectWatches.ProjectWatchKey.create(Project.nameKey("pro/ject"), null);
|
||||
CachedAccountDetails original =
|
||||
CachedAccountDetails.create(
|
||||
ACCOUNT,
|
||||
ImmutableMap.of(key, ImmutableSet.of(ProjectWatches.NotifyType.ALL_COMMENTS)),
|
||||
CachedPreferences.fromString(""));
|
||||
|
||||
byte[] serialized = SERIALIZER.serialize(original);
|
||||
Cache.AccountDetailsProto expected =
|
||||
Cache.AccountDetailsProto.newBuilder()
|
||||
.setAccount(ACCOUNT_PROTO)
|
||||
.addProjectWatchProto(
|
||||
Cache.ProjectWatchProto.newBuilder()
|
||||
.setProject("pro/ject")
|
||||
.addNotifyType("ALL_COMMENTS"))
|
||||
.build();
|
||||
ProtoTruth.assertThat(Cache.AccountDetailsProto.parseFrom(serialized)).isEqualTo(expected);
|
||||
Truth.assertThat(SERIALIZER.deserialize(serialized)).isEqualTo(original);
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (C) 2019 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.google.gerrit.server.account;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.EditPreferencesInfo;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.json.OutputFormat;
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PreferencesTest {
|
||||
|
||||
private static final Gson GSON = OutputFormat.JSON_COMPACT.newGson();
|
||||
|
||||
@Test
|
||||
public void generalPreferencesRoundTrip() {
|
||||
GeneralPreferencesInfo original = GeneralPreferencesInfo.defaults();
|
||||
assertThat(GSON.toJson(original))
|
||||
.isEqualTo(GSON.toJson(Preferences.General.fromInfo(original).toInfo()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void diffPreferencesRoundTrip() {
|
||||
DiffPreferencesInfo original = DiffPreferencesInfo.defaults();
|
||||
assertThat(GSON.toJson(original))
|
||||
.isEqualTo(GSON.toJson(Preferences.Diff.fromInfo(original).toInfo()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void editPreferencesRoundTrip() {
|
||||
EditPreferencesInfo original = EditPreferencesInfo.defaults();
|
||||
assertThat(GSON.toJson(original))
|
||||
.isEqualTo(GSON.toJson(Preferences.Edit.fromInfo(original).toInfo()));
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright (C) 2020 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.google.gerrit.server.account;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.common.truth.extensions.proto.ProtoTruth;
|
||||
import com.google.gerrit.entities.Project;
|
||||
import com.google.gerrit.server.cache.proto.Cache.ProjectWatchKeyProto;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test to ensure that we are serializing and deserializing {@link ProjectWatches.ProjectWatchKey}
|
||||
* correctly. This is part of the {@code AccountCache}.
|
||||
*/
|
||||
public class ProjectWatchCacheTest {
|
||||
@Test
|
||||
public void keyRoundTrip() throws Exception {
|
||||
ProjectWatches.ProjectWatchKey key =
|
||||
ProjectWatches.ProjectWatchKey.create(Project.nameKey("pro/ject"), "*");
|
||||
byte[] serialized = ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.serialize(key);
|
||||
ProtoTruth.assertThat(ProjectWatchKeyProto.parseFrom(serialized))
|
||||
.isEqualTo(ProjectWatchKeyProto.newBuilder().setProject("pro/ject").setFilter("*").build());
|
||||
assertThat(ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.deserialize(serialized))
|
||||
.isEqualTo(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keyRoundTripNullFilter() throws Exception {
|
||||
ProjectWatches.ProjectWatchKey key =
|
||||
ProjectWatches.ProjectWatchKey.create(Project.nameKey("pro/ject"), null);
|
||||
byte[] serialized = ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.serialize(key);
|
||||
ProtoTruth.assertThat(ProjectWatchKeyProto.parseFrom(serialized))
|
||||
.isEqualTo(ProjectWatchKeyProto.newBuilder().setProject("pro/ject").build());
|
||||
assertThat(ProjectWatches.ProjectWatchKey.Serializer.INSTANCE.deserialize(serialized))
|
||||
.isEqualTo(key);
|
||||
}
|
||||
}
|
|
@ -790,7 +790,6 @@ public abstract class AbstractQueryAccountsTest extends GerritServerTests {
|
|||
for (String email : emails) {
|
||||
accountManager.link(id, AuthRequest.forEmail(email));
|
||||
}
|
||||
accountCache.evict(id);
|
||||
accountIndexer.index(id);
|
||||
}
|
||||
|
||||
|
|
|
@ -287,11 +287,12 @@ message PureRevertKeyProto {
|
|||
bytes claimed_revert = 3;
|
||||
}
|
||||
|
||||
// Key for com.google.gerrit.server.account.ProjectWatches.ProjectWatcheKey.
|
||||
// Next ID: 3
|
||||
message ProjectWatchKeyProto {
|
||||
// Key for com.google.gerrit.server.account.ProjectWatches.
|
||||
// Next ID: 4
|
||||
message ProjectWatchProto {
|
||||
string project = 1;
|
||||
string filter = 2;
|
||||
repeated string notify_type = 3;
|
||||
}
|
||||
|
||||
// Serialized form of
|
||||
|
@ -307,3 +308,18 @@ message AccountProto {
|
|||
string status = 7;
|
||||
string meta_id = 8;
|
||||
}
|
||||
|
||||
// Serialized form of com.google.gerrit.server.account.CachedAccountDetails.Key.
|
||||
// Next ID: 3
|
||||
message AccountKeyProto {
|
||||
int32 account_id = 1;
|
||||
bytes id = 2;
|
||||
}
|
||||
|
||||
// Serialized form of com.google.gerrit.server.account.CachedAccountDetails.
|
||||
// Next ID: 4
|
||||
message AccountDetailsProto {
|
||||
AccountProto account = 1;
|
||||
repeated ProjectWatchProto project_watch_proto = 2;
|
||||
string user_preferences = 3;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue