| // 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 Licens | |
| package com.google.gerrit.server.account; | |
| import com.google.auto.value.AutoValue; | |
| import com.google.common.base.Strings; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.entities.NotifyConfig.NotifyType; | |
| 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.ProjectWatchKey; | |
| import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException; | |
| import com.google.gerrit.server.account.externalids.ExternalId; | |
| import java.util.Collection; | |
| import java.util.Map; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| /** | |
| * Data holder for updates to be applied to an account. | |
| * | |
| * <p>Instances of this type are passed to {@link AccountsUpdate}, which modifies the account | |
| * accordingly. | |
| * | |
| * <p>Updates can be applied to account properties (name, email etc.), external IDs, preferences | |
| * (general, diff and edit preferences) and project watches. The account ID and the registration | |
| * date cannot be updated. | |
| * | |
| * <p>For the account properties there are getters in this class and setters in the {@link Builder} | |
| * that correspond to the fields in {@link Account}. | |
| */ | |
| @AutoValue | |
| public abstract class AccountDelta { | |
| public static Builder builder() { | |
| return new Builder.WrapperThatConvertsNullStringArgsToEmptyStrings( | |
| new AutoValue_AccountDelta.Builder()); | |
| } | |
| /** | |
| * Returns the new value for the full name. | |
| * | |
| * @return the new value for the full name, {@code Optional#empty()} if the full name is not being | |
| * updated, {@code Optional#of("")} if the full name is unset, the wrapped value is never | |
| * {@code null} | |
| */ | |
| public abstract Optional<String> getFullName(); | |
| /** | |
| * Returns the new value for the display name. | |
| * | |
| * @return the new value for the display name, {@code Optional#empty()} if the display name is not | |
| * being updated, {@code Optional#of("")} if the display name is unset, the wrapped value is | |
| * never {@code null} | |
| */ | |
| public abstract Optional<String> getDisplayName(); | |
| /** | |
| * Returns the new value for the preferred email. | |
| * | |
| * @return the new value for the preferred email, {@code Optional#empty()} if the preferred email | |
| * is not being updated, {@code Optional#of("")} if the preferred email is unset, the wrapped | |
| * value is never {@code null} | |
| */ | |
| public abstract Optional<String> getPreferredEmail(); | |
| /** | |
| * Returns the new value for the active flag. | |
| * | |
| * @return the new value for the active flag, {@code Optional#empty()} if the active flag is not | |
| * being updated, the wrapped value is never {@code null} | |
| */ | |
| public abstract Optional<Boolean> getActive(); | |
| /** | |
| * Returns the new value for the status. | |
| * | |
| * @return the new value for the status, {@code Optional#empty()} if the status is not being | |
| * updated, {@code Optional#of("")} if the status is unset, the wrapped value is never {@code | |
| * null} | |
| */ | |
| public abstract Optional<String> getStatus(); | |
| /** | |
| * Returns external IDs that should be newly created for the account. | |
| * | |
| * @return external IDs that should be newly created for the account | |
| */ | |
| public abstract ImmutableSet<ExternalId> getCreatedExternalIds(); | |
| /** | |
| * Returns external IDs that should be updated for the account. | |
| * | |
| * @return external IDs that should be updated for the account | |
| */ | |
| public abstract ImmutableSet<ExternalId> getUpdatedExternalIds(); | |
| /** | |
| * Returns external IDs that should be deleted for the account. | |
| * | |
| * @return external IDs that should be deleted for the account | |
| */ | |
| public abstract ImmutableSet<ExternalId> getDeletedExternalIds(); | |
| /** | |
| * Returns external IDs that should be updated for the account. | |
| * | |
| * @return external IDs that should be updated for the account | |
| */ | |
| public abstract ImmutableMap<ProjectWatchKey, Set<NotifyType>> getUpdatedProjectWatches(); | |
| /** | |
| * Returns project watches that should be deleted for the account. | |
| * | |
| * @return project watches that should be deleted for the account | |
| */ | |
| public abstract ImmutableSet<ProjectWatchKey> getDeletedProjectWatches(); | |
| /** | |
| * Returns the new value for the general preferences. | |
| * | |
| * <p>Only preferences that are non-null in the returned GeneralPreferencesInfo should be updated. | |
| * | |
| * @return the new value for the general preferences, {@code Optional#empty()} if the general | |
| * preferences are not being updated, the wrapped value is never {@code null} | |
| */ | |
| public abstract Optional<GeneralPreferencesInfo> getGeneralPreferences(); | |
| /** | |
| * Returns the new value for the diff preferences. | |
| * | |
| * <p>Only preferences that are non-null in the returned DiffPreferencesInfo should be updated. | |
| * | |
| * @return the new value for the diff preferences, {@code Optional#empty()} if the diff | |
| * preferences are not being updated, the wrapped value is never {@code null} | |
| */ | |
| public abstract Optional<DiffPreferencesInfo> getDiffPreferences(); | |
| /** | |
| * Returns the new value for the edit preferences. | |
| * | |
| * <p>Only preferences that are non-null in the returned DiffPreferencesInfo should be updated. | |
| * | |
| * @return the new value for the edit preferences, {@code Optional#empty()} if the edit | |
| * preferences are not being updated, the wrapped value is never {@code null} | |
| */ | |
| public abstract Optional<EditPreferencesInfo> getEditPreferences(); | |
| /** | |
| * Returns whether the delta for this account is deleting the account. | |
| * | |
| * <p>If set to true, deletion takes precedence on any other change in this delta. | |
| * | |
| * @return whether the account should be deleted. | |
| */ | |
| public abstract Optional<Boolean> getShouldDeleteAccount(); | |
| public boolean hasExternalIdUpdates() { | |
| return !this.getCreatedExternalIds().isEmpty() | |
| || !this.getDeletedExternalIds().isEmpty() | |
| || !this.getUpdatedExternalIds().isEmpty(); | |
| } | |
| /** | |
| * Class to build an {@link AccountDelta}. | |
| * | |
| * <p>Account data is only updated if the corresponding setter is invoked. If a setter is not | |
| * invoked the corresponding data stays unchanged. To unset string values the setter can be | |
| * invoked with either {@code null} or an empty string ({@code null} is converted to an empty | |
| * string by using the {@link WrapperThatConvertsNullStringArgsToEmptyStrings} wrapper, see {@link | |
| * AccountDelta#builder()}). | |
| */ | |
| @AutoValue.Builder | |
| public abstract static class Builder { | |
| /** | |
| * Sets a new full name for the account. | |
| * | |
| * @param fullName the new full name, if {@code null} or empty string the full name is unset | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setFullName(@Nullable String fullName); | |
| /** | |
| * Sets a new display name for the account. | |
| * | |
| * @param displayName the new display name, if {@code null} or empty string the display name is | |
| * unset | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setDisplayName(@Nullable String displayName); | |
| /** | |
| * Sets a new preferred email for the account. | |
| * | |
| * @param preferredEmail the new preferred email, if {@code null} or empty string the preferred | |
| * email is unset | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setPreferredEmail(@Nullable String preferredEmail); | |
| /** | |
| * Sets the active flag for the account. | |
| * | |
| * @param active {@code true} if the account should be set to active, {@code false} if the | |
| * account should be set to inactive | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setActive(boolean active); | |
| /** | |
| * Sets a new status for the account. | |
| * | |
| * @param status the new status, if {@code null} or empty string the status is unset | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setStatus(@Nullable String status); | |
| /** | |
| * Returns a builder for the set of created external IDs. | |
| * | |
| * @return builder for the set of created external IDs. | |
| */ | |
| abstract ImmutableSet.Builder<ExternalId> createdExternalIdsBuilder(); | |
| /** | |
| * Adds a new external ID for the account. | |
| * | |
| * <p>The account ID of the external ID must match the account ID of the account that is | |
| * updated. | |
| * | |
| * <p>If an external ID with the same ID already exists the account update will fail with {@link | |
| * DuplicateExternalIdKeyException}. | |
| * | |
| * @param extId external ID that should be added | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder addExternalId(ExternalId extId) { | |
| return addExternalIds(ImmutableSet.of(extId)); | |
| } | |
| /** | |
| * Adds new external IDs for the account. | |
| * | |
| * <p>The account IDs of the external IDs must match the account ID of the account that is | |
| * updated. | |
| * | |
| * <p>If any of the external ID keys already exists, the insert fails with {@link | |
| * DuplicateExternalIdKeyException}. | |
| * | |
| * @param extIds external IDs that should be added | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder addExternalIds(Collection<ExternalId> extIds) { | |
| createdExternalIdsBuilder().addAll(extIds); | |
| return this; | |
| } | |
| /** | |
| * Returns a builder for the set of updated external IDs. | |
| * | |
| * @return builder for the set of updated external IDs. | |
| */ | |
| abstract ImmutableSet.Builder<ExternalId> updatedExternalIdsBuilder(); | |
| /** | |
| * Updates an external ID for the account. | |
| * | |
| * <p>The account ID of the external ID must match the account ID of the account that is | |
| * updated. | |
| * | |
| * <p>If no external ID with the ID exists the external ID is created. | |
| * | |
| * @param extId external ID that should be updated | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder updateExternalId(ExternalId extId) { | |
| return updateExternalIds(ImmutableSet.of(extId)); | |
| } | |
| /** | |
| * Updates external IDs for the account. | |
| * | |
| * <p>The account IDs of the external IDs must match the account ID of the account that is | |
| * updated. | |
| * | |
| * <p>If any of the external IDs already exists, it is overwritten. New external IDs are | |
| * inserted. | |
| * | |
| * @param extIds external IDs that should be updated | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder updateExternalIds(Collection<ExternalId> extIds) { | |
| updatedExternalIdsBuilder().addAll(extIds); | |
| return this; | |
| } | |
| /** | |
| * Returns a builder for the set of deleted external IDs. | |
| * | |
| * @return builder for the set of deleted external IDs. | |
| */ | |
| abstract ImmutableSet.Builder<ExternalId> deletedExternalIdsBuilder(); | |
| /** | |
| * Deletes an external ID for the account. | |
| * | |
| * <p>The account ID of the external ID must match the account ID of the account that is | |
| * updated. | |
| * | |
| * <p>If no external ID with the ID exists this is a no-op. | |
| * | |
| * @param extId external ID that should be deleted | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder deleteExternalId(ExternalId extId) { | |
| return deleteExternalIds(ImmutableSet.of(extId)); | |
| } | |
| /** | |
| * Deletes external IDs for the account. | |
| * | |
| * <p>The account IDs of the external IDs must match the account ID of the account that is | |
| * updated. | |
| * | |
| * <p>For non-existing external IDs this is a no-op. | |
| * | |
| * @param extIds external IDs that should be deleted | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder deleteExternalIds(Collection<ExternalId> extIds) { | |
| deletedExternalIdsBuilder().addAll(extIds); | |
| return this; | |
| } | |
| /** | |
| * Replaces an external ID. | |
| * | |
| * @param extIdToDelete external ID that should be deleted | |
| * @param extIdToAdd external ID that should be added | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder replaceExternalId(ExternalId extIdToDelete, ExternalId extIdToAdd) { | |
| return replaceExternalIds(ImmutableSet.of(extIdToDelete), ImmutableSet.of(extIdToAdd)); | |
| } | |
| /** | |
| * Replaces an external IDs. | |
| * | |
| * @param extIdsToDelete external IDs that should be deleted | |
| * @param extIdsToAdd external IDs that should be added | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder replaceExternalIds( | |
| Collection<ExternalId> extIdsToDelete, Collection<ExternalId> extIdsToAdd) { | |
| return deleteExternalIds(extIdsToDelete).addExternalIds(extIdsToAdd); | |
| } | |
| /** | |
| * Returns a builder for the map of updated project watches. | |
| * | |
| * @return builder for the map of updated project watches. | |
| */ | |
| abstract ImmutableMap.Builder<ProjectWatchKey, Set<NotifyType>> updatedProjectWatchesBuilder(); | |
| /** | |
| * Updates a project watch for the account. | |
| * | |
| * <p>If no project watch with the key exists the project watch is created. | |
| * | |
| * @param projectWatchKey key of the project watch that should be updated | |
| * @param notifyTypes the notify types that should be set for the project watch | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder updateProjectWatch( | |
| ProjectWatchKey projectWatchKey, Set<NotifyType> notifyTypes) { | |
| return updateProjectWatches(ImmutableMap.of(projectWatchKey, notifyTypes)); | |
| } | |
| /** | |
| * Updates project watches for the account. | |
| * | |
| * <p>If any of the project watches already exists, it is overwritten. New project watches are | |
| * inserted. | |
| * | |
| * @param projectWatches project watches that should be updated | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder updateProjectWatches(Map<ProjectWatchKey, Set<NotifyType>> projectWatches) { | |
| updatedProjectWatchesBuilder().putAll(projectWatches); | |
| return this; | |
| } | |
| /** | |
| * Returns a builder for the set of deleted project watches. | |
| * | |
| * @return builder for the set of deleted project watches. | |
| */ | |
| abstract ImmutableSet.Builder<ProjectWatchKey> deletedProjectWatchesBuilder(); | |
| /** | |
| * Deletes a project watch for the account. | |
| * | |
| * <p>If no project watch with the ID exists this is a no-op. | |
| * | |
| * @param projectWatch project watch that should be deleted | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder deleteProjectWatch(ProjectWatchKey projectWatch) { | |
| return deleteProjectWatches(ImmutableSet.of(projectWatch)); | |
| } | |
| /** | |
| * Deletes project watches for the account. | |
| * | |
| * <p>For non-existing project watches this is a no-op. | |
| * | |
| * @param projectWatches project watches that should be deleted | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder deleteProjectWatches(Collection<ProjectWatchKey> projectWatches) { | |
| deletedProjectWatchesBuilder().addAll(projectWatches); | |
| return this; | |
| } | |
| /** | |
| * Sets the general preferences for the account. | |
| * | |
| * <p>Updates any preference that is non-null in the provided GeneralPreferencesInfo. | |
| * | |
| * @param generalPreferences the general preferences that should be set | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setGeneralPreferences(GeneralPreferencesInfo generalPreferences); | |
| /** | |
| * Sets the diff preferences for the account. | |
| * | |
| * <p>Updates any preference that is non-null in the provided DiffPreferencesInfo. | |
| * | |
| * @param diffPreferences the diff preferences that should be set | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setDiffPreferences(DiffPreferencesInfo diffPreferences); | |
| /** | |
| * Sets the edit preferences for the account. | |
| * | |
| * <p>Updates any preference that is non-null in the provided EditPreferencesInfo. | |
| * | |
| * @param editPreferences the edit preferences that should be set | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public abstract Builder setEditPreferences(EditPreferencesInfo editPreferences); | |
| @CanIgnoreReturnValue | |
| public abstract Builder setShouldDeleteAccount(boolean shouldDelete); | |
| /** | |
| * Builds an AccountDelta that deletes all data. | |
| * | |
| * @param extIdsToDelete external IDs that should be deleted | |
| * @return the builder | |
| */ | |
| @CanIgnoreReturnValue | |
| public Builder deleteAccount(Collection<ExternalId> extIdsToDelete) { | |
| deleteExternalIds(extIdsToDelete); | |
| setShouldDeleteAccount(true); | |
| return this; | |
| } | |
| /** Builds the instance. */ | |
| public abstract AccountDelta build(); | |
| /** | |
| * Wrapper for {@link Builder} that converts {@code null} string arguments to empty strings for | |
| * all setter methods. This allows us to treat setter invocations with a {@code null} string | |
| * argument as signal to unset the corresponding field. E.g. for a builder method {@code | |
| * setX(String)} the following semantics apply: | |
| * | |
| * <ul> | |
| * <li>Method is not invoked: X stays unchanged, X is stored as {@code Optional.empty()}. | |
| * <li>Argument is a non-empty string Y: X is updated to the Y, X is stored as {@code | |
| * Optional.of(Y)}. | |
| * <li>Argument is an empty string: X is unset, X is stored as {@code Optional.of("")} | |
| * <li>Argument is {@code null}: X is unset, X is stored as {@code Optional.of("")} (since the | |
| * wrapper converts {@code null} to an empty string) | |
| * </ul> | |
| * | |
| * Without the wrapper calling {@code setX(null)} would fail with a {@link | |
| * NullPointerException}. Hence all callers would need to take care to call {@link | |
| * Strings#nullToEmpty(String)} for all string arguments and likely it would be forgotten in | |
| * some places. | |
| * | |
| * <p>This means the stored values are interpreted like this: | |
| * | |
| * <ul> | |
| * <li>{@code Optional.empty()}: property stays unchanged | |
| * <li>{@code Optional.of(<non-empty-string>)}: property is updated | |
| * <li>{@code Optional.of("")}: property is unset | |
| * </ul> | |
| * | |
| * This wrapper forwards all method invocations to the wrapped {@link Builder} instance that was | |
| * created by AutoValue. For methods that return the AutoValue {@link Builder} instance the | |
| * return value is replaced with the wrapper instance so that all chained calls go through the | |
| * wrapper. | |
| */ | |
| private static class WrapperThatConvertsNullStringArgsToEmptyStrings extends Builder { | |
| private final Builder delegate; | |
| private WrapperThatConvertsNullStringArgsToEmptyStrings(Builder delegate) { | |
| this.delegate = delegate; | |
| } | |
| @Override | |
| public Builder setFullName(String fullName) { | |
| delegate.setFullName(Strings.nullToEmpty(fullName)); | |
| return this; | |
| } | |
| @Override | |
| public Builder setDisplayName(String displayName) { | |
| delegate.setDisplayName(Strings.nullToEmpty(displayName)); | |
| return this; | |
| } | |
| @Override | |
| public Builder setPreferredEmail(String preferredEmail) { | |
| delegate.setPreferredEmail(Strings.nullToEmpty(preferredEmail)); | |
| return this; | |
| } | |
| @Override | |
| public Builder setActive(boolean active) { | |
| delegate.setActive(active); | |
| return this; | |
| } | |
| @Override | |
| public Builder setStatus(String status) { | |
| delegate.setStatus(Strings.nullToEmpty(status)); | |
| return this; | |
| } | |
| @Override | |
| public AccountDelta build() { | |
| return delegate.build(); | |
| } | |
| @Override | |
| ImmutableSet.Builder<ExternalId> createdExternalIdsBuilder() { | |
| return delegate.createdExternalIdsBuilder(); | |
| } | |
| @Override | |
| public Builder addExternalIds(Collection<ExternalId> extIds) { | |
| delegate.addExternalIds(extIds); | |
| return this; | |
| } | |
| @Override | |
| ImmutableSet.Builder<ExternalId> updatedExternalIdsBuilder() { | |
| return delegate.updatedExternalIdsBuilder(); | |
| } | |
| @Override | |
| public Builder updateExternalIds(Collection<ExternalId> extIds) { | |
| delegate.updateExternalIds(extIds); | |
| return this; | |
| } | |
| @Override | |
| ImmutableSet.Builder<ExternalId> deletedExternalIdsBuilder() { | |
| return delegate.deletedExternalIdsBuilder(); | |
| } | |
| @Override | |
| public Builder deleteExternalIds(Collection<ExternalId> extIds) { | |
| delegate.deleteExternalIds(extIds); | |
| return this; | |
| } | |
| @Override | |
| ImmutableMap.Builder<ProjectWatchKey, Set<NotifyType>> updatedProjectWatchesBuilder() { | |
| return delegate.updatedProjectWatchesBuilder(); | |
| } | |
| @Override | |
| public Builder updateProjectWatches(Map<ProjectWatchKey, Set<NotifyType>> projectWatches) { | |
| delegate.updateProjectWatches(projectWatches); | |
| return this; | |
| } | |
| @Override | |
| ImmutableSet.Builder<ProjectWatchKey> deletedProjectWatchesBuilder() { | |
| return delegate.deletedProjectWatchesBuilder(); | |
| } | |
| @Override | |
| public Builder deleteProjectWatches(Collection<ProjectWatchKey> projectWatches) { | |
| delegate.deleteProjectWatches(projectWatches); | |
| return this; | |
| } | |
| @Override | |
| public Builder setGeneralPreferences(GeneralPreferencesInfo generalPreferences) { | |
| delegate.setGeneralPreferences(generalPreferences); | |
| return this; | |
| } | |
| @Override | |
| public Builder setDiffPreferences(DiffPreferencesInfo diffPreferences) { | |
| delegate.setDiffPreferences(diffPreferences); | |
| return this; | |
| } | |
| @Override | |
| public Builder setEditPreferences(EditPreferencesInfo editPreferences) { | |
| delegate.setEditPreferences(editPreferences); | |
| return this; | |
| } | |
| @Override | |
| public Builder setShouldDeleteAccount(boolean shouldDelete) { | |
| delegate.setShouldDeleteAccount(shouldDelete); | |
| return this; | |
| } | |
| } | |
| } | |
| } |