AccountsUpdate: Provide AccountState instead of Account for account updates

Some callers need access to the external IDs in order to decide which
account updates should be done. At the moment these callers first read
the external IDs and then read and update the account. This way there is
a risk of a race that the external IDs are updated after they have been
read and before the account is read and updated. By providing an
AccountState that was consistently read for the account updates callers
have access to both the account and the external IDs (and also general
preferences and project watches). Adapting the callers to take advantage
of the provided AccountState will be done in a follow-up change.

Change-Id: Ia2bcd7df61bd8450e9f228afd01ffe7836a3270a
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2018-01-09 17:22:21 +01:00
parent 8f9793676d
commit faf942807a
8 changed files with 70 additions and 26 deletions

View File

@@ -378,7 +378,7 @@ public class AccountManager {
(a, u) -> { (a, u) -> {
u.addExternalId( u.addExternalId(
ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress())); ExternalId.createWithEmail(who.getExternalIdKey(), to, who.getEmailAddress()));
if (who.getEmailAddress() != null && a.getPreferredEmail() == null) { if (who.getEmailAddress() != null && a.getAccount().getPreferredEmail() == null) {
u.setPreferredEmail(who.getEmailAddress()); u.setPreferredEmail(who.getEmailAddress());
} }
}); });
@@ -468,8 +468,10 @@ public class AccountManager {
from, from,
(a, u) -> { (a, u) -> {
u.deleteExternalIds(extIds); u.deleteExternalIds(extIds);
if (a.getPreferredEmail() != null if (a.getAccount().getPreferredEmail() != null
&& extIds.stream().anyMatch(e -> a.getPreferredEmail().equals(e.email()))) { && extIds
.stream()
.anyMatch(e -> a.getAccount().getPreferredEmail().equals(e.email()))) {
u.setPreferredEmail(null); u.setPreferredEmail(null);
} }
}); });

View File

@@ -30,7 +30,10 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig.NotifyType; import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey; import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.ExternalId; import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import java.io.IOException;
import java.util.Optional;
import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.DecoderException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -38,6 +41,9 @@ import org.slf4j.LoggerFactory;
/** /**
* Superset of all information related to an Account. This includes external IDs, project watches, * Superset of all information related to an Account. This includes external IDs, project watches,
* and properties from the account config file. AccountState maps one-to-one to Account. * and properties from the account config file. AccountState maps one-to-one to Account.
*
* <p>Most callers should not construct AccountStates directly but rather lookup accounts via the
* account cache (see {@link AccountCache#get(Account.Id)}).
*/ */
public class AccountState { public class AccountState {
private static final Logger logger = LoggerFactory.getLogger(AccountState.class); private static final Logger logger = LoggerFactory.getLogger(AccountState.class);
@@ -45,6 +51,24 @@ public class AccountState {
public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION = public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION =
a -> a.getAccount().getId(); a -> a.getAccount().getId();
public static Optional<AccountState> fromAccountConfig(
AllUsersName allUsersName, ExternalIds externalIds, AccountConfig accountConfig)
throws IOException {
if (!accountConfig.getLoadedAccount().isPresent()) {
return Optional.empty();
}
Account account = accountConfig.getLoadedAccount().get();
return Optional.of(
new AccountState(
allUsersName,
account,
accountConfig.getExternalIdsRev().isPresent()
? externalIds.byAccount(account.getId(), accountConfig.getExternalIdsRev().get())
: ImmutableSet.of(),
accountConfig.getProjectWatches(),
accountConfig.getGeneralPreferences()));
}
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final Account account; private final Account account;
private final ImmutableSet<ExternalId> externalIds; private final ImmutableSet<ExternalId> externalIds;

View File

@@ -18,7 +18,6 @@ import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet; import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RefNames;
@@ -136,20 +135,8 @@ public class Accounts {
private Optional<AccountState> read(Repository allUsersRepository, Account.Id accountId) private Optional<AccountState> read(Repository allUsersRepository, Account.Id accountId)
throws IOException, ConfigInvalidException { throws IOException, ConfigInvalidException {
AccountConfig accountConfig = new AccountConfig(accountId, allUsersRepository).load(); return AccountState.fromAccountConfig(
if (!accountConfig.getLoadedAccount().isPresent()) { allUsersName, externalIds, new AccountConfig(accountId, allUsersRepository).load());
return Optional.empty();
}
Account account = accountConfig.getLoadedAccount().get();
return Optional.of(
new AccountState(
allUsersName,
account,
accountConfig.getExternalIdsRev().isPresent()
? externalIds.byAccount(accountId, accountConfig.getExternalIdsRev().get())
: ImmutableSet.of(),
accountConfig.getProjectWatches(),
accountConfig.getGeneralPreferences()));
} }
public static Stream<Account.Id> readUserRefs(Repository repo) throws IOException { public static Stream<Account.Id> readUserRefs(Repository repo) throws IOException {

View File

@@ -19,15 +19,19 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Runnables; import com.google.common.util.concurrent.Runnables;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalIdNotes; import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.account.externalids.ExternalIdNotes.ExternalIdNotesLoader; import com.google.gerrit.server.account.externalids.ExternalIdNotes.ExternalIdNotesLoader;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated; import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
@@ -82,10 +86,10 @@ public class AccountsUpdate {
* <p>Use the provided account only to read the current state of the account. Don't do updates * <p>Use the provided account only to read the current state of the account. Don't do updates
* to the account. For updates use the provided account update builder. * to the account. For updates use the provided account update builder.
* *
* @param account the account that is being updated * @param accountState the account that is being updated
* @param update account update builder * @param update account update builder
*/ */
void update(Account account, InternalAccountUpdate.Builder update); void update(AccountState accountState, InternalAccountUpdate.Builder update);
static AccountUpdater join(List<AccountUpdater> updaters) { static AccountUpdater join(List<AccountUpdater> updaters) {
return (a, u) -> updaters.stream().forEach(updater -> updater.update(a, u)); return (a, u) -> updaters.stream().forEach(updater -> updater.update(a, u));
@@ -111,6 +115,7 @@ public class AccountsUpdate {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated; private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final ExternalIds externalIds;
private final Provider<PersonIdent> serverIdentProvider; private final Provider<PersonIdent> serverIdentProvider;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
private final RetryHelper retryHelper; private final RetryHelper retryHelper;
@@ -121,6 +126,7 @@ public class AccountsUpdate {
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated, GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName, AllUsersName allUsersName,
ExternalIds externalIds,
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider, @GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper, RetryHelper retryHelper,
@@ -128,6 +134,7 @@ public class AccountsUpdate {
this.repoManager = repoManager; this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated; this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName; this.allUsersName = allUsersName;
this.externalIds = externalIds;
this.serverIdentProvider = serverIdentProvider; this.serverIdentProvider = serverIdentProvider;
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory; this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
this.retryHelper = retryHelper; this.retryHelper = retryHelper;
@@ -141,6 +148,7 @@ public class AccountsUpdate {
gitRefUpdated, gitRefUpdated,
null, null,
allUsersName, allUsersName,
externalIds,
metaDataUpdateInternalFactory, metaDataUpdateInternalFactory,
retryHelper, retryHelper,
extIdNotesFactory, extIdNotesFactory,
@@ -163,6 +171,7 @@ public class AccountsUpdate {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated; private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final ExternalIds externalIds;
private final Provider<PersonIdent> serverIdentProvider; private final Provider<PersonIdent> serverIdentProvider;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
private final RetryHelper retryHelper; private final RetryHelper retryHelper;
@@ -173,6 +182,7 @@ public class AccountsUpdate {
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated, GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName, AllUsersName allUsersName,
ExternalIds externalIds,
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider, @GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper, RetryHelper retryHelper,
@@ -180,6 +190,7 @@ public class AccountsUpdate {
this.repoManager = repoManager; this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated; this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName; this.allUsersName = allUsersName;
this.externalIds = externalIds;
this.serverIdentProvider = serverIdentProvider; this.serverIdentProvider = serverIdentProvider;
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory; this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
this.retryHelper = retryHelper; this.retryHelper = retryHelper;
@@ -193,6 +204,7 @@ public class AccountsUpdate {
gitRefUpdated, gitRefUpdated,
null, null,
allUsersName, allUsersName,
externalIds,
metaDataUpdateInternalFactory, metaDataUpdateInternalFactory,
retryHelper, retryHelper,
extIdNotesFactory, extIdNotesFactory,
@@ -212,6 +224,7 @@ public class AccountsUpdate {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated; private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final ExternalIds externalIds;
private final Provider<PersonIdent> serverIdentProvider; private final Provider<PersonIdent> serverIdentProvider;
private final Provider<IdentifiedUser> identifiedUser; private final Provider<IdentifiedUser> identifiedUser;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
@@ -223,6 +236,7 @@ public class AccountsUpdate {
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated, GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName, AllUsersName allUsersName,
ExternalIds externalIds,
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider, @GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<IdentifiedUser> identifiedUser, Provider<IdentifiedUser> identifiedUser,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
@@ -231,6 +245,7 @@ public class AccountsUpdate {
this.repoManager = repoManager; this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated; this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName; this.allUsersName = allUsersName;
this.externalIds = externalIds;
this.serverIdentProvider = serverIdentProvider; this.serverIdentProvider = serverIdentProvider;
this.identifiedUser = identifiedUser; this.identifiedUser = identifiedUser;
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory; this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
@@ -247,6 +262,7 @@ public class AccountsUpdate {
gitRefUpdated, gitRefUpdated,
user, user,
allUsersName, allUsersName,
externalIds,
metaDataUpdateInternalFactory, metaDataUpdateInternalFactory,
retryHelper, retryHelper,
extIdNotesFactory, extIdNotesFactory,
@@ -263,6 +279,7 @@ public class AccountsUpdate {
private final GitReferenceUpdated gitRefUpdated; private final GitReferenceUpdated gitRefUpdated;
@Nullable private final IdentifiedUser currentUser; @Nullable private final IdentifiedUser currentUser;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final ExternalIds externalIds;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
private final RetryHelper retryHelper; private final RetryHelper retryHelper;
private final ExternalIdNotesLoader extIdNotesLoader; private final ExternalIdNotesLoader extIdNotesLoader;
@@ -275,6 +292,7 @@ public class AccountsUpdate {
GitReferenceUpdated gitRefUpdated, GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser currentUser, @Nullable IdentifiedUser currentUser,
AllUsersName allUsersName, AllUsersName allUsersName,
ExternalIds externalIds,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper, RetryHelper retryHelper,
ExternalIdNotesLoader extIdNotesLoader, ExternalIdNotesLoader extIdNotesLoader,
@@ -285,6 +303,7 @@ public class AccountsUpdate {
gitRefUpdated, gitRefUpdated,
currentUser, currentUser,
allUsersName, allUsersName,
externalIds,
metaDataUpdateInternalFactory, metaDataUpdateInternalFactory,
retryHelper, retryHelper,
extIdNotesLoader, extIdNotesLoader,
@@ -299,6 +318,7 @@ public class AccountsUpdate {
GitReferenceUpdated gitRefUpdated, GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser currentUser, @Nullable IdentifiedUser currentUser,
AllUsersName allUsersName, AllUsersName allUsersName,
ExternalIds externalIds,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper, RetryHelper retryHelper,
ExternalIdNotesLoader extIdNotesLoader, ExternalIdNotesLoader extIdNotesLoader,
@@ -309,6 +329,7 @@ public class AccountsUpdate {
this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated"); this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
this.currentUser = currentUser; this.currentUser = currentUser;
this.allUsersName = checkNotNull(allUsersName, "allUsersName"); this.allUsersName = checkNotNull(allUsersName, "allUsersName");
this.externalIds = checkNotNull(externalIds, "externalIds");
this.metaDataUpdateInternalFactory = this.metaDataUpdateInternalFactory =
checkNotNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory"); checkNotNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory");
this.retryHelper = checkNotNull(retryHelper, "retryHelper"); this.retryHelper = checkNotNull(retryHelper, "retryHelper");
@@ -355,8 +376,15 @@ public class AccountsUpdate {
AccountConfig accountConfig = read(r, accountId); AccountConfig accountConfig = read(r, accountId);
Account account = Account account =
accountConfig.getNewAccount(new Timestamp(committerIdent.getWhen().getTime())); accountConfig.getNewAccount(new Timestamp(committerIdent.getWhen().getTime()));
AccountState accountState =
new AccountState(
allUsersName,
account,
ImmutableSet.of(),
ImmutableMap.of(),
GeneralPreferencesInfo.defaults());
InternalAccountUpdate.Builder updateBuilder = InternalAccountUpdate.builder(); InternalAccountUpdate.Builder updateBuilder = InternalAccountUpdate.builder();
updater.update(account, updateBuilder); updater.update(accountState, updateBuilder);
InternalAccountUpdate update = updateBuilder.build(); InternalAccountUpdate update = updateBuilder.build();
accountConfig.setAccountUpdate(update); accountConfig.setAccountUpdate(update);
@@ -405,7 +433,8 @@ public class AccountsUpdate {
return updateAccount( return updateAccount(
r -> { r -> {
AccountConfig accountConfig = read(r, accountId); AccountConfig accountConfig = read(r, accountId);
Optional<Account> account = accountConfig.getLoadedAccount(); Optional<AccountState> account =
AccountState.fromAccountConfig(allUsersName, externalIds, accountConfig);
if (!account.isPresent()) { if (!account.isPresent()) {
return null; return null;
} }

View File

@@ -46,7 +46,7 @@ public class SetInactiveFlag {
"Deactivate Account via API", "Deactivate Account via API",
accountId, accountId,
(a, u) -> { (a, u) -> {
if (!a.isActive()) { if (!a.getAccount().isActive()) {
alreadyInactive.set(true); alreadyInactive.set(true);
} else { } else {
u.setActive(false); u.setActive(false);
@@ -71,7 +71,7 @@ public class SetInactiveFlag {
"Activate Account via API", "Activate Account via API",
accountId, accountId,
(a, u) -> { (a, u) -> {
if (a.isActive()) { if (a.getAccount().isActive()) {
alreadyActive.set(true); alreadyActive.set(true);
} else { } else {
u.setActive(true); u.setActive(true);

View File

@@ -2987,7 +2987,7 @@ class ReceiveCommits {
"Set Full Name on Receive Commits", "Set Full Name on Receive Commits",
user.getAccountId(), user.getAccountId(),
(a, u) -> { (a, u) -> {
if (Strings.isNullOrEmpty(a.getFullName())) { if (Strings.isNullOrEmpty(a.getAccount().getFullName())) {
u.setFullName(setFullNameTo); u.setFullName(setFullNameTo);
} }
}); });

View File

@@ -72,7 +72,7 @@ public class PutPreferred implements RestModifyView<AccountResource.Email, Input
"Set Preferred Email via API", "Set Preferred Email via API",
user.getAccountId(), user.getAccountId(),
(a, u) -> { (a, u) -> {
if (email.equals(a.getPreferredEmail())) { if (email.equals(a.getAccount().getPreferredEmail())) {
alreadyPreferred.set(true); alreadyPreferred.set(true);
} else { } else {
u.setPreferredEmail(email); u.setPreferredEmail(email);

View File

@@ -2021,6 +2021,7 @@ public class AccountIT extends AbstractDaemonTest {
gitReferenceUpdated, gitReferenceUpdated,
null, null,
allUsers, allUsers,
externalIds,
metaDataUpdateInternalFactory, metaDataUpdateInternalFactory,
new RetryHelper( new RetryHelper(
cfg, cfg,
@@ -2069,6 +2070,7 @@ public class AccountIT extends AbstractDaemonTest {
gitReferenceUpdated, gitReferenceUpdated,
null, null,
allUsers, allUsers,
externalIds,
metaDataUpdateInternalFactory, metaDataUpdateInternalFactory,
new RetryHelper( new RetryHelper(
cfg, cfg,