| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.server.account.storage.notedb; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.ACCOUNTS_UPDATE; |
| import static java.util.Objects.requireNonNull; |
| import static java.util.stream.Collectors.toList; |
| import static java.util.stream.Collectors.toSet; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Strings; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.entities.Account; |
| import com.google.gerrit.exceptions.StorageException; |
| import com.google.gerrit.git.LockFailureException; |
| import com.google.gerrit.git.RefUpdateUtil; |
| import com.google.gerrit.server.GerritPersonIdent; |
| import com.google.gerrit.server.IdentifiedUser; |
| import com.google.gerrit.server.account.AccountConfig; |
| import com.google.gerrit.server.account.AccountDelta; |
| import com.google.gerrit.server.account.AccountState; |
| import com.google.gerrit.server.account.AccountsUpdate; |
| import com.google.gerrit.server.account.ProjectWatches; |
| import com.google.gerrit.server.account.StoredPreferences; |
| import com.google.gerrit.server.account.externalids.ExternalId; |
| import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdNotes; |
| import com.google.gerrit.server.account.externalids.storage.notedb.ExternalIdsNoteDbImpl; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.gerrit.server.config.CachedPreferences; |
| import com.google.gerrit.server.config.VersionedDefaultPreferences; |
| import com.google.gerrit.server.extensions.events.GitReferenceUpdated; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.gerrit.server.git.meta.MetaDataUpdate; |
| import com.google.gerrit.server.index.change.ReindexAfterRefUpdate; |
| import com.google.gerrit.server.update.RetryHelper; |
| import com.google.gerrit.server.update.RetryableAction.Action; |
| import com.google.gerrit.server.update.context.RefUpdateContext; |
| import com.google.inject.Provider; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import javax.inject.Inject; |
| import javax.inject.Singleton; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.BatchRefUpdate; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.transport.ReceiveCommand; |
| |
| /** |
| * Creates and updates accounts which are stored in All-Users NoteDB repository. |
| * |
| * <p>Batch updates of multiple different accounts can be performed atomically, see {@link |
| * #updateBatch(List)}. Batch creation is not supported. |
| * |
| * <p>For any account update the caller must provide a commit message, the account ID and an {@link |
| * com.google.gerrit.server.account.AccountsUpdate.ConfigureDeltaFromState}. The account updater |
| * reads the current {@link AccountState} and prepares updates to the account by calling setters on |
| * the provided {@link com.google.gerrit.server.account.AccountDelta.Builder}. If the current |
| * account state is of no interest the caller may also provide a {@link Consumer} for {@link |
| * com.google.gerrit.server.account.AccountDelta.Builder} instead of the account updater. |
| * |
| * <p>The provided commit message is used for the update of the user branch. Using a precise and |
| * unique commit message allows to identify the code from which an update was made when looking at a |
| * commit in the user branch, and thus help debugging. |
| * |
| * <p>The account updates are written to NoteDb. In NoteDb accounts are represented as user branches |
| * in the {@code All-Users} repository. Optionally a user branch can contain a 'account.config' file |
| * that stores account properties, such as full name, display name, preferred email, status and the |
| * active flag. The timestamp of the first commit on a user branch denotes the registration date. |
| * The initial commit on the user branch may be empty (since having an 'account.config' is |
| * optional). See {@link AccountConfig} for details of the 'account.config' file format. In addition |
| * the user branch can contain a 'preferences.config' config file to store preferences (see {@link |
| * StoredPreferences}) and a 'watch.config' config file to store project watches (see {@link |
| * ProjectWatches}). External IDs are stored separately in the {@code refs/meta/external-ids} notes |
| * branch (see {@link ExternalIdNotes}). |
| * |
| * <p>On updating an account the account is evicted from the account cache and reindexed. The |
| * eviction from the account cache and the reindexing is done by the {@link ReindexAfterRefUpdate} |
| * class which receives the event about updating the user branch that is triggered by this class. |
| * |
| * <p>If external IDs are updated, the ExternalIdCache is automatically updated by {@link |
| * ExternalIdNotes}. In addition {@link ExternalIdNotes} takes care about evicting and reindexing |
| * corresponding accounts. This is needed because external ID updates don't touch the user branches. |
| * Hence in this case the accounts are not evicted and reindexed via {@link ReindexAfterRefUpdate}. |
| * |
| * <p>Reindexing and flushing accounts from the account cache can be disabled by |
| * |
| * <ul> |
| * <li>using {@link |
| * com.google.gerrit.server.account.storage.notedb.AccountsUpdateNoteDbImpl.FactoryNoReindex} |
| * and |
| * <li>binding {@link GitReferenceUpdated#DISABLED} |
| * </ul> |
| * |
| * <p>If there are concurrent account updates which updating the user branch in NoteDb may fail with |
| * {@link LockFailureException}. In this case the account update is automatically retried and the |
| * account updater is invoked once more with the updated account state. This means the whole |
| * read-modify-write sequence is atomic. Retrying is limited by a timeout. If the timeout is |
| * exceeded the account update can still fail with {@link LockFailureException}. |
| */ |
| public class AccountsUpdateNoteDbImpl extends AccountsUpdate { |
| private static class AbstractFactory implements AccountsUpdateLoader { |
| private final GitRepositoryManager repoManager; |
| private final GitReferenceUpdated gitRefUpdated; |
| private final AllUsersName allUsersName; |
| private final ExternalIdsNoteDbImpl externalIds; |
| private final ExternalIdNotes.ExternalIdNotesLoader extIdNotesFactory; |
| private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; |
| private final RetryHelper retryHelper; |
| private final Provider<PersonIdent> serverIdentProvider; |
| |
| private AbstractFactory( |
| GitRepositoryManager repoManager, |
| GitReferenceUpdated gitRefUpdated, |
| AllUsersName allUsersName, |
| ExternalIdsNoteDbImpl externalIds, |
| Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, |
| RetryHelper retryHelper, |
| @GerritPersonIdent Provider<PersonIdent> serverIdentProvider, |
| ExternalIdNotes.ExternalIdNotesLoader extIdNotesFactory) { |
| this.repoManager = repoManager; |
| this.gitRefUpdated = gitRefUpdated; |
| this.allUsersName = allUsersName; |
| this.externalIds = externalIds; |
| this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory; |
| this.retryHelper = retryHelper; |
| this.serverIdentProvider = serverIdentProvider; |
| this.extIdNotesFactory = extIdNotesFactory; |
| } |
| |
| @Override |
| public AccountsUpdate create(IdentifiedUser currentUser) { |
| PersonIdent serverIdent = serverIdentProvider.get(); |
| return new AccountsUpdateNoteDbImpl( |
| repoManager, |
| gitRefUpdated, |
| Optional.of(currentUser), |
| allUsersName, |
| externalIds, |
| extIdNotesFactory, |
| metaDataUpdateInternalFactory, |
| retryHelper, |
| serverIdent, |
| AccountsUpdateNoteDbImpl::doNothing, |
| AccountsUpdateNoteDbImpl::doNothing); |
| } |
| |
| @Override |
| public AccountsUpdate createWithServerIdent() { |
| PersonIdent serverIdent = serverIdentProvider.get(); |
| return new AccountsUpdateNoteDbImpl( |
| repoManager, |
| gitRefUpdated, |
| Optional.empty(), |
| allUsersName, |
| externalIds, |
| extIdNotesFactory, |
| metaDataUpdateInternalFactory, |
| retryHelper, |
| serverIdent, |
| AccountsUpdateNoteDbImpl::doNothing, |
| AccountsUpdateNoteDbImpl::doNothing); |
| } |
| } |
| |
| @Singleton |
| public static class Factory extends AbstractFactory { |
| @Inject |
| Factory( |
| GitRepositoryManager repoManager, |
| GitReferenceUpdated gitRefUpdated, |
| AllUsersName allUsersName, |
| ExternalIdsNoteDbImpl externalIds, |
| Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, |
| RetryHelper retryHelper, |
| @GerritPersonIdent Provider<PersonIdent> serverIdentProvider, |
| ExternalIdNotes.Factory extIdNotesFactory) { |
| super( |
| repoManager, |
| gitRefUpdated, |
| allUsersName, |
| externalIds, |
| metaDataUpdateInternalFactory, |
| retryHelper, |
| serverIdentProvider, |
| extIdNotesFactory); |
| } |
| } |
| |
| @Singleton |
| public static class FactoryNoReindex extends AbstractFactory { |
| @Inject |
| FactoryNoReindex( |
| GitRepositoryManager repoManager, |
| GitReferenceUpdated gitRefUpdated, |
| AllUsersName allUsersName, |
| ExternalIdsNoteDbImpl externalIds, |
| Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, |
| RetryHelper retryHelper, |
| @GerritPersonIdent Provider<PersonIdent> serverIdentProvider, |
| ExternalIdNotes.FactoryNoReindex extIdNotesFactory) { |
| super( |
| repoManager, |
| gitRefUpdated, |
| allUsersName, |
| externalIds, |
| metaDataUpdateInternalFactory, |
| retryHelper, |
| serverIdentProvider, |
| extIdNotesFactory); |
| } |
| } |
| |
| private final GitRepositoryManager repoManager; |
| private final GitReferenceUpdated gitRefUpdated; |
| private final AllUsersName allUsersName; |
| private final ExternalIdsNoteDbImpl externalIds; |
| |
| private final ExternalIdNotes.ExternalIdNotesLoader extIdNotesFactory; |
| private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory; |
| private final RetryHelper retryHelper; |
| |
| /** Invoked after reading the account config. */ |
| private final Runnable afterReadRevision; |
| |
| /** Invoked after updating the account but before committing the changes. */ |
| private final Runnable beforeCommit; |
| |
| /** Single instance that accumulates updates from the batch. */ |
| @Nullable private ExternalIdNotes externalIdNotes; |
| |
| @VisibleForTesting |
| public AccountsUpdateNoteDbImpl( |
| GitRepositoryManager repoManager, |
| GitReferenceUpdated gitRefUpdated, |
| Optional<IdentifiedUser> currentUser, |
| AllUsersName allUsersName, |
| ExternalIdsNoteDbImpl externalIds, |
| ExternalIdNotes.ExternalIdNotesLoader extIdNotesFactory, |
| Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory, |
| RetryHelper retryHelper, |
| PersonIdent committerIdent, |
| Runnable afterReadRevision, |
| Runnable beforeCommit) { |
| super(committerIdent, currentUser); |
| this.repoManager = requireNonNull(repoManager, "repoManager"); |
| this.gitRefUpdated = requireNonNull(gitRefUpdated, "gitRefUpdated"); |
| this.allUsersName = requireNonNull(allUsersName, "allUsersName"); |
| this.externalIds = requireNonNull(externalIds, "externalIds"); |
| this.extIdNotesFactory = extIdNotesFactory; |
| this.metaDataUpdateInternalFactory = |
| requireNonNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory"); |
| this.retryHelper = requireNonNull(retryHelper, "retryHelper"); |
| this.afterReadRevision = requireNonNull(afterReadRevision, "afterReadRevision"); |
| this.beforeCommit = requireNonNull(beforeCommit, "beforeCommit"); |
| } |
| |
| @Override |
| public AccountState insert(String message, Account.Id accountId, ConfigureDeltaFromState init) |
| throws IOException, ConfigInvalidException { |
| return execute( |
| ImmutableList.of( |
| repo -> { |
| AccountConfig accountConfig = read(repo, accountId); |
| Account account = accountConfig.getNewAccount(committerIdent.getWhenAsInstant()); |
| AccountState accountState = AccountState.forAccount(account); |
| AccountDelta.Builder deltaBuilder = AccountDelta.builder(); |
| init.configure(accountState, deltaBuilder); |
| |
| AccountDelta accountDelta = deltaBuilder.build(); |
| accountConfig.setAccountDelta(accountDelta); |
| updateExternalIdNotes( |
| repo, accountConfig.getExternalIdsRev(), accountId, accountDelta); |
| CachedPreferences defaultPreferences = |
| CachedPreferences.fromLegacyConfig( |
| VersionedDefaultPreferences.get(repo, allUsersName)); |
| |
| return new UpdatedAccount(message, accountConfig, defaultPreferences, true); |
| })) |
| .get(0) |
| .get(); |
| } |
| |
| @Override |
| public void delete(String message, Account.Id accountId) |
| throws IOException, ConfigInvalidException { |
| ImmutableSet<ExternalId> accountExternalIds = externalIds.byAccount(accountId); |
| Consumer<AccountDelta.Builder> delta = |
| deltaBuilder -> deltaBuilder.deleteAccount(accountExternalIds); |
| update(message, accountId, delta); |
| } |
| |
| private ExecutableUpdate createExecutableUpdate(UpdateArguments updateArguments) { |
| return repo -> { |
| AccountConfig accountConfig = read(repo, updateArguments.accountId); |
| CachedPreferences defaultPreferences = |
| CachedPreferences.fromLegacyConfig(VersionedDefaultPreferences.get(repo, allUsersName)); |
| Optional<AccountState> accountState = |
| AccountsNoteDbImpl.getFromAccountConfig(externalIds, accountConfig, defaultPreferences); |
| if (!accountState.isPresent()) { |
| return null; |
| } |
| |
| AccountDelta.Builder deltaBuilder = AccountDelta.builder(); |
| updateArguments.configureDeltaFromState.configure(accountState.get(), deltaBuilder); |
| |
| AccountDelta delta = deltaBuilder.build(); |
| updateExternalIdNotes( |
| repo, accountConfig.getExternalIdsRev(), updateArguments.accountId, delta); |
| |
| if (delta.getShouldDeleteAccount().orElse(false)) { |
| return new DeletedAccount(updateArguments.message, accountConfig.getRefName()); |
| } |
| |
| accountConfig.setAccountDelta(delta); |
| CachedPreferences cachedDefaultPreferences = |
| CachedPreferences.fromLegacyConfig(VersionedDefaultPreferences.get(repo, allUsersName)); |
| return new UpdatedAccount( |
| updateArguments.message, accountConfig, cachedDefaultPreferences, false); |
| }; |
| } |
| |
| private void updateExternalIdNotes( |
| Repository allUsersRepo, Optional<ObjectId> rev, Account.Id accountId, AccountDelta update) |
| throws IOException, ConfigInvalidException { |
| |
| if (update.hasExternalIdUpdates()) { |
| |
| // Only load the externalIds if they are going to be updated |
| // This makes e.g. preferences updates faster. |
| ExternalIdNotes.checkSameAccount( |
| Iterables.concat( |
| update.getCreatedExternalIds(), |
| update.getUpdatedExternalIds(), |
| update.getDeletedExternalIds()), |
| accountId); |
| if (externalIdNotes == null) { |
| externalIdNotes = extIdNotesFactory.load(allUsersRepo, rev.orElse(ObjectId.zeroId())); |
| } |
| externalIdNotes.replace(update.getDeletedExternalIds(), update.getCreatedExternalIds()); |
| externalIdNotes.upsert(update.getUpdatedExternalIds()); |
| } |
| } |
| |
| private AccountConfig read(Repository allUsersRepo, Account.Id accountId) |
| throws IOException, ConfigInvalidException { |
| AccountConfig accountConfig = new AccountConfig(accountId, allUsersName, allUsersRepo).load(); |
| afterReadRevision.run(); |
| return accountConfig; |
| } |
| |
| @Override |
| protected ImmutableList<Optional<AccountState>> executeUpdates(List<UpdateArguments> updates) |
| throws ConfigInvalidException, IOException { |
| return execute(updates.stream().map(this::createExecutableUpdate).collect(toImmutableList())); |
| } |
| |
| private ImmutableList<Optional<AccountState>> execute(List<ExecutableUpdate> executableUpdates) |
| throws IOException, ConfigInvalidException { |
| try (RefUpdateContext ctx = RefUpdateContext.open(ACCOUNTS_UPDATE)) { |
| List<Optional<AccountState>> accountState = new ArrayList<>(); |
| List<UpdatedAccount> updatedAccounts = new ArrayList<>(); |
| executeWithRetry( |
| () -> { |
| |
| // Reset state for retry. |
| externalIdNotes = null; |
| accountState.clear(); |
| updatedAccounts.clear(); |
| try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) { |
| for (ExecutableUpdate executableUpdate : executableUpdates) { |
| updatedAccounts.add(executableUpdate.execute(allUsersRepo)); |
| } |
| commit( |
| allUsersRepo, |
| updatedAccounts.stream().filter(Objects::nonNull).collect(toList())); |
| for (UpdatedAccount ua : updatedAccounts) { |
| accountState.add( |
| ua == null || ua.deleted ? Optional.empty() : ua.getAccountState()); |
| } |
| } |
| return null; |
| }); |
| |
| return ImmutableList.copyOf(accountState); |
| } |
| } |
| |
| private void executeWithRetry(Action<Void> action) throws IOException, ConfigInvalidException { |
| try { |
| retryHelper.accountUpdate("updateAccount", action).call(); |
| } catch (Exception e) { |
| Throwables.throwIfUnchecked(e); |
| Throwables.throwIfInstanceOf(e, IOException.class); |
| Throwables.throwIfInstanceOf(e, ConfigInvalidException.class); |
| throw new StorageException(e); |
| } |
| } |
| |
| private void commit(Repository allUsersRepo, List<UpdatedAccount> updatedAccounts) |
| throws IOException { |
| if (updatedAccounts.isEmpty()) { |
| return; |
| } |
| |
| beforeCommit.run(); |
| |
| BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate(); |
| Set<Account.Id> accountsToSkipForReindex = new HashSet<>(); |
| // External ids may be not updated if: |
| // * externalIdNotes is not loaded (there were no externalId updates in the delta) |
| // * new revCommit is identical to the previous externalId tip |
| boolean externalIdsUpdated = false; |
| if (externalIdNotes != null) { |
| String externalIdUpdateMessage = |
| updatedAccounts.size() == 1 |
| ? Iterables.getOnlyElement(updatedAccounts).message |
| : "Batch update for " + updatedAccounts.size() + " accounts"; |
| ObjectId oldExternalIdsRevision = externalIdNotes.getRevision(); |
| // These update the same ref, so they need to be stacked on top of one another using the same |
| // ExternalIdNotes instance. |
| RevCommit revCommit = |
| commitExternalIdUpdates(externalIdUpdateMessage, allUsersRepo, batchRefUpdate); |
| externalIdsUpdated = !Objects.equals(revCommit.getId(), oldExternalIdsRevision); |
| } |
| for (UpdatedAccount updatedAccount : updatedAccounts) { |
| if (updatedAccount.deleted) { |
| RefUpdate ru = RefUpdateUtil.deleteChecked(allUsersRepo, updatedAccount.refName); |
| gitRefUpdated.fire(allUsersName, ru, ReceiveCommand.Type.DELETE, null); |
| accountsToSkipForReindex.add(Account.Id.fromRef(updatedAccount.refName)); |
| continue; |
| } |
| // These updates are all for different refs (because batches never update the same account |
| // more than once), so there can be multiple commits in the same batch, all with the same base |
| // revision in their AccountConfig. |
| // We allow empty commits: |
| // 1) When creating a new account, so that the user branch gets created with an empty commit |
| // when no account properties are set and hence no |
| // 'account.config' file will be created. |
| // 2) When updating "refs/meta/external-ids", so that refs/users/* meta ref is updated too. |
| // This allows to schedule reindexing of account transactionally on refs/users/* meta |
| // updates. |
| boolean allowEmptyCommit = externalIdsUpdated || updatedAccount.created; |
| commitAccountConfig( |
| updatedAccount.message, |
| allUsersRepo, |
| batchRefUpdate, |
| updatedAccount.accountConfig, |
| allowEmptyCommit); |
| } |
| |
| RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo); |
| |
| if (externalIdsUpdated) { |
| accountsToSkipForReindex.addAll(getUpdatedAccountIds(batchRefUpdate)); |
| extIdNotesFactory.updateExternalIdCacheAndMaybeReindexAccounts( |
| externalIdNotes, accountsToSkipForReindex); |
| } |
| |
| gitRefUpdated.fire( |
| allUsersName, batchRefUpdate, currentUser.map(IdentifiedUser::state).orElse(null)); |
| } |
| |
| private static Set<Account.Id> getUpdatedAccountIds(BatchRefUpdate batchRefUpdate) { |
| return batchRefUpdate.getCommands().stream() |
| .map(c -> Account.Id.fromRef(c.getRefName())) |
| .filter(Objects::nonNull) |
| .collect(toSet()); |
| } |
| |
| private void commitAccountConfig( |
| String message, |
| Repository allUsersRepo, |
| BatchRefUpdate batchRefUpdate, |
| AccountConfig accountConfig, |
| boolean allowEmptyCommit) |
| throws IOException { |
| try (MetaDataUpdate md = createMetaDataUpdate(message, allUsersRepo, batchRefUpdate)) { |
| md.setAllowEmpty(allowEmptyCommit); |
| accountConfig.commit(md); |
| } |
| } |
| |
| private RevCommit commitExternalIdUpdates( |
| String message, Repository allUsersRepo, BatchRefUpdate batchRefUpdate) throws IOException { |
| try (MetaDataUpdate md = createMetaDataUpdate(message, allUsersRepo, batchRefUpdate)) { |
| return externalIdNotes.commit(md); |
| } |
| } |
| |
| private MetaDataUpdate createMetaDataUpdate( |
| String message, Repository allUsersRepo, BatchRefUpdate batchRefUpdate) { |
| MetaDataUpdate metaDataUpdate = |
| metaDataUpdateInternalFactory.get().create(allUsersName, allUsersRepo, batchRefUpdate); |
| if (!message.endsWith("\n")) { |
| message = message + "\n"; |
| } |
| |
| metaDataUpdate.getCommitBuilder().setMessage(message); |
| metaDataUpdate.getCommitBuilder().setCommitter(committerIdent); |
| metaDataUpdate.getCommitBuilder().setAuthor(authorIdent); |
| return metaDataUpdate; |
| } |
| |
| private static void doNothing() {} |
| |
| @FunctionalInterface |
| private interface ExecutableUpdate { |
| UpdatedAccount execute(Repository allUsersRepo) throws IOException, ConfigInvalidException; |
| } |
| |
| private class UpdatedAccount { |
| final String message; |
| final AccountConfig accountConfig; |
| final CachedPreferences defaultPreferences; |
| final String refName; |
| final boolean created; |
| final boolean deleted; |
| |
| UpdatedAccount( |
| String message, |
| AccountConfig accountConfig, |
| CachedPreferences defaultPreferences, |
| boolean created) { |
| this( |
| message, |
| requireNonNull(accountConfig), |
| defaultPreferences, |
| accountConfig.getRefName(), |
| created, |
| false); |
| } |
| |
| protected UpdatedAccount( |
| String message, |
| AccountConfig accountConfig, |
| CachedPreferences defaultPreferences, |
| String refName, |
| boolean created, |
| boolean deleted) { |
| checkState(!Strings.isNullOrEmpty(message), "message for account update must be set"); |
| this.message = requireNonNull(message); |
| this.accountConfig = accountConfig; |
| this.defaultPreferences = defaultPreferences; |
| this.refName = refName; |
| this.created = created; |
| this.deleted = deleted; |
| } |
| |
| Optional<AccountState> getAccountState() throws IOException { |
| return AccountsNoteDbImpl.getFromAccountConfig( |
| externalIds, accountConfig, externalIdNotes, defaultPreferences); |
| } |
| } |
| |
| private class DeletedAccount extends UpdatedAccount { |
| DeletedAccount(String message, String refName) { |
| super(message, null, null, refName, false, true); |
| } |
| } |
| } |