blob: 2080e93f4b1eb4ae5145cb991411392dd8b3ec5b [file] [log] [blame]
// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Account;
import com.google.gerrit.exceptions.DuplicateKeyException;
import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.Sequences;
import com.google.inject.BindingAnnotation;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import org.eclipse.jgit.errors.ConfigInvalidException;
/**
* Creates and updates accounts.
*
* <p>This interface should be used for all account updates. See {@link AccountDelta} for what can
* be updated.
*
* <p>For creating a new account a new account ID can be retrieved from {@link
* Sequences#nextAccountId()}.
*
* <p>See the implementing classes for more information.
*/
public interface AccountsUpdate {
abstract class AccountsUpdateLoader {
/**
* Creates an {@code AccountsUpdate} which uses the identity of the specified user as author for
* all commits related to accounts. The server identity will be used as committer.
*
* <p><strong>Note</strong>: Please use this method with care and consider using the {@link
* com.google.gerrit.server.UserInitiated} annotation on the provider of an {@code
* AccountsUpdate} instead.
*
* @param currentUser the user to which modifications should be attributed
*/
public abstract AccountsUpdate create(IdentifiedUser currentUser);
/**
* Creates an {@code AccountsUpdate} which uses the server identity as author and committer for
* all commits related to accounts.
*
* <p><strong>Note</strong>: Please use this method with care and consider using the {@link
* com.google.gerrit.server.ServerInitiated} annotation on the provider of an {@code
* AccountsUpdate} instead.
*/
public abstract AccountsUpdate createWithServerIdent();
@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface WithReindex {}
@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface NoReindex {}
}
/** Data holder for the set of arguments required to update an account. Used for batch updates. */
class UpdateArguments {
public final String message;
public final Account.Id accountId;
public final AccountsUpdate.ConfigureDeltaFromState configureDeltaFromState;
public UpdateArguments(
String message,
Account.Id accountId,
AccountsUpdate.ConfigureDeltaFromState configureDeltaFromState) {
this.message = message;
this.accountId = accountId;
this.configureDeltaFromState = configureDeltaFromState;
}
}
/**
* Account updates are commonly performed by evaluating the current account state and creating a
* delta to be applied to it in a later step. This is done by implementing this interface.
*
* <p>If the current account state is not needed, use a {@link Consumer} of {@link
* com.google.gerrit.server.account.AccountDelta.Builder} instead.
*/
@FunctionalInterface
interface ConfigureDeltaFromState {
/**
* Receives the current {@link AccountState} (which is immutable) and configures an {@link
* com.google.gerrit.server.account.AccountDelta.Builder} with changes to the account.
*
* @param accountState the state of the account that is being updated
* @param delta the changes to be applied
*/
void configure(AccountState accountState, AccountDelta.Builder delta) throws IOException;
}
/** Returns an instance that runs all specified consumers. */
static ConfigureDeltaFromState joinConsumers(List<Consumer<AccountDelta.Builder>> consumers) {
return (accountStateIgnored, update) -> consumers.forEach(c -> c.accept(update));
}
/**
* Like {@link #insert(String, Account.Id, ConfigureDeltaFromState)}, but using a {@link Consumer}
* instead, i.e. the update does not depend on the current account state (which, for insertion,
* would only contain the account ID).
*/
AccountState insert(String message, Account.Id accountId, Consumer<AccountDelta.Builder> init)
throws IOException, ConfigInvalidException;
/**
* Inserts a new account.
*
* @param message commit message for the account creation, must not be {@code null or empty}
* @param accountId ID of the new account
* @param init to populate the new account
* @return the newly created account
* @throws DuplicateKeyException if the account already exists
* @throws IOException if creating the user branch fails due to an IO error
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
AccountState insert(String message, Account.Id accountId, ConfigureDeltaFromState init)
throws IOException, ConfigInvalidException;
/**
* Like {@link #update(String, Account.Id, ConfigureDeltaFromState)}, but using a {@link Consumer}
* instead, i.e. the update does not depend on the current account state.
*/
Optional<AccountState> update(
String message, Account.Id accountId, Consumer<AccountDelta.Builder> update)
throws IOException, ConfigInvalidException;
/**
* Gets the account and updates it atomically.
*
* <p>Changing the registration date of an account is not supported.
*
* @param message commit message for the account update, must not be {@code null or empty}
* @param accountId ID of the account
* @param configureDeltaFromState deltaBuilder to update the account, only invoked if the account
* exists
* @return the updated account, {@link Optional#empty} if the account doesn't exist
* @throws IOException if updating the user branch fails due to an IO error
* @throws LockFailureException if updating the user branch still fails due to concurrent updates
* after the retry timeout exceeded
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
Optional<AccountState> update(
String message, Account.Id accountId, ConfigureDeltaFromState configureDeltaFromState)
throws LockFailureException, IOException, ConfigInvalidException;
/**
* Updates multiple different accounts atomically. This will only store a single new value (aka
* set of all external IDs of the host) in the external ID cache, which is important for storage
* economy. All {@code updates} must be for different accounts.
*
* <p>NOTE on error handling: Since updates are executed in multiple stages, with some stages
* resulting from the union of all individual updates, we cannot point to the update that caused
* the error. Callers should be aware that a single "update of death" (or a set of updates that
* together have this property) will always prevent the entire batch from being executed.
*/
ImmutableList<Optional<AccountState>> updateBatch(List<UpdateArguments> updates)
throws IOException, ConfigInvalidException;
/**
* Deletes all the account state data.
*
* @param message commit message for the account update, must not be {@code null or empty}
* @param accountId ID of the account
* @throws IOException if updating the user branch fails due to an IO error
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
void delete(String message, Account.Id accountId) throws IOException, ConfigInvalidException;
}