| // 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.group.db; | |
| import static com.google.gerrit.server.update.context.RefUpdateContext.RefUpdateType.GROUPS_UPDATE; | |
| import com.google.auto.value.AutoValue; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Throwables; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Sets; | |
| import com.google.common.flogger.FluentLogger; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.entities.AccountGroup; | |
| import com.google.gerrit.entities.InternalGroup; | |
| import com.google.gerrit.entities.Project; | |
| import com.google.gerrit.exceptions.DuplicateKeyException; | |
| import com.google.gerrit.exceptions.NoSuchGroupException; | |
| import com.google.gerrit.git.RefUpdateUtil; | |
| import com.google.gerrit.server.GerritPersonIdent; | |
| import com.google.gerrit.server.IdentifiedUser; | |
| import com.google.gerrit.server.account.AccountCache; | |
| import com.google.gerrit.server.account.GroupBackend; | |
| import com.google.gerrit.server.account.GroupCache; | |
| import com.google.gerrit.server.account.GroupIncludeCache; | |
| import com.google.gerrit.server.config.AllUsersName; | |
| import com.google.gerrit.server.config.GerritServerId; | |
| 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.group.GroupAuditService; | |
| import com.google.gerrit.server.index.group.GroupIndexer; | |
| import com.google.gerrit.server.logging.Metadata; | |
| import com.google.gerrit.server.logging.TraceContext; | |
| import com.google.gerrit.server.logging.TraceContext.TraceTimer; | |
| import com.google.gerrit.server.update.RetryHelper; | |
| import com.google.gerrit.server.update.context.RefUpdateContext; | |
| import com.google.gerrit.server.util.time.TimeUtil; | |
| import com.google.inject.Provider; | |
| import com.google.inject.assistedinject.Assisted; | |
| import com.google.inject.assistedinject.AssistedInject; | |
| import java.io.IOException; | |
| import java.time.Instant; | |
| import java.util.Objects; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| import java.util.concurrent.Future; | |
| import java.util.concurrent.TimeUnit; | |
| import org.eclipse.jgit.errors.ConfigInvalidException; | |
| import org.eclipse.jgit.lib.BatchRefUpdate; | |
| import org.eclipse.jgit.lib.PersonIdent; | |
| import org.eclipse.jgit.lib.Repository; | |
| /** | |
| * A database accessor for write calls related to groups. | |
| * | |
| * <p>All calls which write group related details to the database are gathered here. Other classes | |
| * should always use this class instead of accessing the database directly. There are a few | |
| * exceptions though: schema classes, wrapper classes, and classes executed during init. The latter | |
| * ones should use {@link com.google.gerrit.pgm.init.GroupsOnInit} instead. | |
| * | |
| * <p>If not explicitly stated, all methods of this class refer to <em>internal</em> groups. | |
| */ | |
| public class GroupsUpdate { | |
| public interface Factory { | |
| /** | |
| * Creates a {@link GroupsUpdate} which uses the identity of the specified user to mark database | |
| * modifications executed by it. For NoteDb, this identity is used as author and committer for | |
| * all related commits. | |
| * | |
| * <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 a {@link GroupsUpdate} | |
| * instead. | |
| * | |
| * @param currentUser the user to which modifications should be attributed | |
| */ | |
| GroupsUpdate create(IdentifiedUser currentUser); | |
| /** | |
| * Creates a {@link GroupsUpdate} which uses the server identity to mark database modifications | |
| * executed by it. For NoteDb, this identity is used as author and committer for all related | |
| * commits. | |
| * | |
| * <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 a {@link | |
| * GroupsUpdate} instead. | |
| */ | |
| GroupsUpdate createWithServerIdent(); | |
| } | |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); | |
| private final GitRepositoryManager repoManager; | |
| private final AllUsersName allUsersName; | |
| private final GroupCache groupCache; | |
| private final GroupIncludeCache groupIncludeCache; | |
| private final Provider<GroupIndexer> indexer; | |
| private final GroupAuditService groupAuditService; | |
| private final RenameGroupOp.Factory renameGroupOpFactory; | |
| private final Optional<IdentifiedUser> currentUser; | |
| private final AuditLogFormatter auditLogFormatter; | |
| private final PersonIdent authorIdent; | |
| private final MetaDataUpdateFactory metaDataUpdateFactory; | |
| private final GitReferenceUpdated gitRefUpdated; | |
| private final RetryHelper retryHelper; | |
| @AssistedInject | |
| GroupsUpdate( | |
| GitRepositoryManager repoManager, | |
| AllUsersName allUsersName, | |
| GroupBackend groupBackend, | |
| GroupCache groupCache, | |
| GroupIncludeCache groupIncludeCache, | |
| Provider<GroupIndexer> indexer, | |
| GroupAuditService auditService, | |
| AccountCache accountCache, | |
| RenameGroupOp.Factory renameGroupOpFactory, | |
| @GerritServerId String serverId, | |
| @GerritPersonIdent PersonIdent serverIdent, | |
| MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory, | |
| GitReferenceUpdated gitRefUpdated, | |
| RetryHelper retryHelper) { | |
| this( | |
| repoManager, | |
| allUsersName, | |
| groupBackend, | |
| groupCache, | |
| groupIncludeCache, | |
| indexer, | |
| auditService, | |
| accountCache, | |
| renameGroupOpFactory, | |
| serverId, | |
| serverIdent, | |
| metaDataUpdateInternalFactory, | |
| gitRefUpdated, | |
| retryHelper, | |
| Optional.empty()); | |
| } | |
| @AssistedInject | |
| GroupsUpdate( | |
| GitRepositoryManager repoManager, | |
| AllUsersName allUsersName, | |
| GroupBackend groupBackend, | |
| GroupCache groupCache, | |
| GroupIncludeCache groupIncludeCache, | |
| Provider<GroupIndexer> indexer, | |
| GroupAuditService auditService, | |
| AccountCache accountCache, | |
| RenameGroupOp.Factory renameGroupOpFactory, | |
| @GerritServerId String serverId, | |
| @GerritPersonIdent PersonIdent serverIdent, | |
| MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory, | |
| GitReferenceUpdated gitRefUpdated, | |
| RetryHelper retryHelper, | |
| @Assisted IdentifiedUser currentUser) { | |
| this( | |
| repoManager, | |
| allUsersName, | |
| groupBackend, | |
| groupCache, | |
| groupIncludeCache, | |
| indexer, | |
| auditService, | |
| accountCache, | |
| renameGroupOpFactory, | |
| serverId, | |
| serverIdent, | |
| metaDataUpdateInternalFactory, | |
| gitRefUpdated, | |
| retryHelper, | |
| Optional.of(currentUser)); | |
| } | |
| private GroupsUpdate( | |
| GitRepositoryManager repoManager, | |
| AllUsersName allUsersName, | |
| GroupBackend groupBackend, | |
| GroupCache groupCache, | |
| GroupIncludeCache groupIncludeCache, | |
| Provider<GroupIndexer> indexer, | |
| GroupAuditService auditService, | |
| AccountCache accountCache, | |
| RenameGroupOp.Factory renameGroupOpFactory, | |
| @GerritServerId String serverId, | |
| @GerritPersonIdent PersonIdent serverIdent, | |
| MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory, | |
| GitReferenceUpdated gitRefUpdated, | |
| RetryHelper retryHelper, | |
| Optional<IdentifiedUser> currentUser) { | |
| this.repoManager = repoManager; | |
| this.allUsersName = allUsersName; | |
| this.groupCache = groupCache; | |
| this.groupIncludeCache = groupIncludeCache; | |
| this.indexer = indexer; | |
| this.groupAuditService = auditService; | |
| this.renameGroupOpFactory = renameGroupOpFactory; | |
| this.gitRefUpdated = gitRefUpdated; | |
| this.retryHelper = retryHelper; | |
| this.currentUser = currentUser; | |
| auditLogFormatter = AuditLogFormatter.createBackedBy(accountCache, groupBackend, serverId); | |
| metaDataUpdateFactory = | |
| getMetaDataUpdateFactory( | |
| metaDataUpdateInternalFactory, currentUser, serverIdent, auditLogFormatter); | |
| authorIdent = getAuthorIdent(serverIdent, currentUser); | |
| } | |
| private static MetaDataUpdateFactory getMetaDataUpdateFactory( | |
| MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory, | |
| Optional<IdentifiedUser> currentUser, | |
| PersonIdent serverIdent, | |
| AuditLogFormatter auditLogFormatter) { | |
| return (projectName, repository, batchRefUpdate) -> { | |
| MetaDataUpdate metaDataUpdate = | |
| metaDataUpdateInternalFactory.create(projectName, repository, batchRefUpdate); | |
| metaDataUpdate.getCommitBuilder().setCommitter(serverIdent); | |
| PersonIdent authorIdent; | |
| if (currentUser.isPresent()) { | |
| metaDataUpdate.setAuthor(currentUser.get()); | |
| authorIdent = | |
| auditLogFormatter.getParsableAuthorIdent(currentUser.get().getAccount(), serverIdent); | |
| } else { | |
| authorIdent = serverIdent; | |
| } | |
| metaDataUpdate.getCommitBuilder().setAuthor(authorIdent); | |
| return metaDataUpdate; | |
| }; | |
| } | |
| private static PersonIdent getAuthorIdent( | |
| PersonIdent serverIdent, Optional<IdentifiedUser> currentUser) { | |
| return currentUser.map(user -> createPersonIdent(serverIdent, user)).orElse(serverIdent); | |
| } | |
| private static PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) { | |
| return user.newCommitterIdent(ident); | |
| } | |
| /** | |
| * Creates the specified group for the specified members (accounts). | |
| * | |
| * @param groupCreation an {@link InternalGroupCreation} which specifies all mandatory properties | |
| * of the group | |
| * @param groupDelta a {@link GroupDelta} which specifies optional properties of the group. If | |
| * this {@link GroupDelta} updates a property which was already specified by the {@link | |
| * InternalGroupCreation}, the value of this {@link GroupDelta} wins. | |
| * @throws DuplicateKeyException if a group with the chosen name already exists | |
| * @throws IOException if indexing fails, or an error occurs while reading/writing from/to NoteDb | |
| * @return the created {@link InternalGroup} | |
| */ | |
| public InternalGroup createGroup(InternalGroupCreation groupCreation, GroupDelta groupDelta) | |
| throws DuplicateKeyException, IOException, ConfigInvalidException { | |
| try (TraceTimer ignored = | |
| TraceContext.newTimer( | |
| "Creating group", | |
| Metadata.builder() | |
| .groupName(groupDelta.getName().orElseGet(groupCreation::getNameKey).get()) | |
| .build())) { | |
| InternalGroup createdGroup = createGroupInNoteDbWithRetry(groupCreation, groupDelta); | |
| evictCachesOnGroupCreation(createdGroup); | |
| dispatchAuditEventsOnGroupCreation(createdGroup); | |
| return createdGroup; | |
| } | |
| } | |
| /** | |
| * Updates the specified group. | |
| * | |
| * @param groupUuid the UUID of the group to update | |
| * @param groupDelta a {@link GroupDelta} which indicates the desired updates on the group | |
| * @throws DuplicateKeyException if the new name of the group is used by another group | |
| * @throws IOException if indexing fails, or an error occurs while reading/writing from/to NoteDb | |
| * @throws NoSuchGroupException if the specified group doesn't exist | |
| */ | |
| public void updateGroup(AccountGroup.UUID groupUuid, GroupDelta groupDelta) | |
| throws DuplicateKeyException, IOException, NoSuchGroupException, ConfigInvalidException { | |
| try (TraceTimer ignored = | |
| TraceContext.newTimer( | |
| "Updating group", Metadata.builder().groupUuid(groupUuid.get()).build())) { | |
| Optional<Instant> updatedOn = groupDelta.getUpdatedOn(); | |
| if (!updatedOn.isPresent()) { | |
| updatedOn = Optional.of(TimeUtil.now()); | |
| groupDelta = groupDelta.toBuilder().setUpdatedOn(updatedOn.get()).build(); | |
| } | |
| UpdateResult result = updateGroupInNoteDbWithRetry(groupUuid, groupDelta); | |
| updateNameInProjectConfigsIfNecessary(result); | |
| evictCachesOnGroupUpdate(result); | |
| dispatchAuditEventsOnGroupUpdate(result, updatedOn.get()); | |
| } | |
| } | |
| private InternalGroup createGroupInNoteDbWithRetry( | |
| InternalGroupCreation groupCreation, GroupDelta groupDelta) | |
| throws IOException, ConfigInvalidException, DuplicateKeyException { | |
| try (RefUpdateContext ctx = RefUpdateContext.open(GROUPS_UPDATE)) { | |
| try { | |
| return retryHelper | |
| .groupUpdate("createGroup", () -> createGroupInNoteDb(groupCreation, groupDelta)) | |
| .call(); | |
| } catch (Exception e) { | |
| Throwables.throwIfUnchecked(e); | |
| Throwables.throwIfInstanceOf(e, IOException.class); | |
| Throwables.throwIfInstanceOf(e, ConfigInvalidException.class); | |
| Throwables.throwIfInstanceOf(e, DuplicateKeyException.class); | |
| throw new IOException(e); | |
| } | |
| } | |
| } | |
| @VisibleForTesting | |
| public InternalGroup createGroupInNoteDb( | |
| InternalGroupCreation groupCreation, GroupDelta groupDelta) | |
| throws IOException, ConfigInvalidException, DuplicateKeyException { | |
| try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) { | |
| AccountGroup.NameKey groupName = groupDelta.getName().orElseGet(groupCreation::getNameKey); | |
| GroupNameNotes groupNameNotes = | |
| GroupNameNotes.forNewGroup( | |
| allUsersName, allUsersRepo, groupCreation.getGroupUUID(), groupName); | |
| GroupConfig groupConfig = | |
| GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation); | |
| groupConfig.setGroupDelta(groupDelta, auditLogFormatter); | |
| commit(allUsersRepo, groupConfig, groupNameNotes); | |
| return groupConfig | |
| .getLoadedGroup() | |
| .orElseThrow( | |
| () -> new IllegalStateException("Created group wasn't automatically loaded")); | |
| } | |
| } | |
| private UpdateResult updateGroupInNoteDbWithRetry( | |
| AccountGroup.UUID groupUuid, GroupDelta groupDelta) | |
| throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException { | |
| try { | |
| return retryHelper | |
| .groupUpdate("updateGroup", () -> updateGroupInNoteDb(groupUuid, groupDelta)) | |
| .call(); | |
| } catch (Exception e) { | |
| Throwables.throwIfUnchecked(e); | |
| Throwables.throwIfInstanceOf(e, IOException.class); | |
| Throwables.throwIfInstanceOf(e, ConfigInvalidException.class); | |
| Throwables.throwIfInstanceOf(e, DuplicateKeyException.class); | |
| Throwables.throwIfInstanceOf(e, NoSuchGroupException.class); | |
| throw new IOException(e); | |
| } | |
| } | |
| @VisibleForTesting | |
| public UpdateResult updateGroupInNoteDb(AccountGroup.UUID groupUuid, GroupDelta groupDelta) | |
| throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException { | |
| try (RefUpdateContext ctx = RefUpdateContext.open(GROUPS_UPDATE)) { | |
| try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) { | |
| GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid); | |
| groupConfig.setGroupDelta(groupDelta, auditLogFormatter); | |
| if (!groupConfig.getLoadedGroup().isPresent()) { | |
| throw new NoSuchGroupException(groupUuid); | |
| } | |
| InternalGroup originalGroup = groupConfig.getLoadedGroup().get(); | |
| GroupNameNotes groupNameNotes = null; | |
| if (groupDelta.getName().isPresent()) { | |
| AccountGroup.NameKey oldName = originalGroup.getNameKey(); | |
| AccountGroup.NameKey newName = groupDelta.getName().get(); | |
| groupNameNotes = | |
| GroupNameNotes.forRename(allUsersName, allUsersRepo, groupUuid, oldName, newName); | |
| } | |
| commit(allUsersRepo, groupConfig, groupNameNotes); | |
| InternalGroup updatedGroup = | |
| groupConfig | |
| .getLoadedGroup() | |
| .orElseThrow( | |
| () -> new IllegalStateException("Updated group wasn't automatically loaded")); | |
| return getUpdateResult(originalGroup, updatedGroup); | |
| } | |
| } | |
| } | |
| private static UpdateResult getUpdateResult( | |
| InternalGroup originalGroup, InternalGroup updatedGroup) { | |
| Set<Account.Id> addedMembers = | |
| Sets.difference(updatedGroup.getMembers(), originalGroup.getMembers()); | |
| Set<Account.Id> deletedMembers = | |
| Sets.difference(originalGroup.getMembers(), updatedGroup.getMembers()); | |
| Set<AccountGroup.UUID> addedSubgroups = | |
| Sets.difference(updatedGroup.getSubgroups(), originalGroup.getSubgroups()); | |
| Set<AccountGroup.UUID> deletedSubgroups = | |
| Sets.difference(originalGroup.getSubgroups(), updatedGroup.getSubgroups()); | |
| UpdateResult.Builder resultBuilder = | |
| UpdateResult.builder() | |
| .setGroupUuid(updatedGroup.getGroupUUID()) | |
| .setGroupId(updatedGroup.getId()) | |
| .setGroupName(updatedGroup.getNameKey()) | |
| .setAddedMembers(addedMembers) | |
| .setDeletedMembers(deletedMembers) | |
| .setAddedSubgroups(addedSubgroups) | |
| .setDeletedSubgroups(deletedSubgroups); | |
| if (!Objects.equals(originalGroup.getNameKey(), updatedGroup.getNameKey())) { | |
| resultBuilder.setPreviousGroupName(originalGroup.getNameKey()); | |
| } | |
| return resultBuilder.build(); | |
| } | |
| private void commit( | |
| Repository allUsersRepo, GroupConfig groupConfig, @Nullable GroupNameNotes groupNameNotes) | |
| throws IOException { | |
| BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate(); | |
| try (MetaDataUpdate metaDataUpdate = | |
| metaDataUpdateFactory.create(allUsersName, allUsersRepo, batchRefUpdate)) { | |
| groupConfig.commit(metaDataUpdate); | |
| } | |
| if (groupNameNotes != null) { | |
| // MetaDataUpdates unfortunately can't be reused. -> Create a new one. | |
| try (MetaDataUpdate metaDataUpdate = | |
| metaDataUpdateFactory.create(allUsersName, allUsersRepo, batchRefUpdate)) { | |
| groupNameNotes.commit(metaDataUpdate); | |
| } | |
| } | |
| RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo); | |
| gitRefUpdated.fire( | |
| allUsersName, batchRefUpdate, currentUser.map(user -> user.state()).orElse(null)); | |
| } | |
| private void evictCachesOnGroupCreation(InternalGroup createdGroup) { | |
| logger.atFine().log("evict caches on creation of group %s", createdGroup.getGroupUUID()); | |
| // By UUID is used for the index and hence should be evicted before refreshing the index. | |
| groupCache.evict(createdGroup.getGroupUUID()); | |
| indexer.get().index(createdGroup.getGroupUUID()); | |
| // These caches use the result from the index and hence must be evicted after refreshing the | |
| // index. | |
| groupCache.evict(createdGroup.getId()); | |
| groupCache.evict(createdGroup.getNameKey()); | |
| createdGroup.getMembers().forEach(groupIncludeCache::evictGroupsWithMember); | |
| createdGroup.getSubgroups().forEach(groupIncludeCache::evictParentGroupsOf); | |
| } | |
| private void evictCachesOnGroupUpdate(UpdateResult result) { | |
| logger.atFine().log("evict caches on update of group %s", result.getGroupUuid()); | |
| // By UUID is used for the index and hence should be evicted before refreshing the index. | |
| groupCache.evict(result.getGroupUuid()); | |
| indexer.get().index(result.getGroupUuid()); | |
| // These caches use the result from the index and hence must be evicted after refreshing the | |
| // index. | |
| groupCache.evict(result.getGroupId()); | |
| groupCache.evict(result.getGroupName()); | |
| result.getPreviousGroupName().ifPresent(groupCache::evict); | |
| result.getAddedMembers().forEach(groupIncludeCache::evictGroupsWithMember); | |
| result.getDeletedMembers().forEach(groupIncludeCache::evictGroupsWithMember); | |
| result.getAddedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf); | |
| result.getDeletedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf); | |
| } | |
| private void updateNameInProjectConfigsIfNecessary(UpdateResult result) { | |
| if (result.getPreviousGroupName().isPresent()) { | |
| AccountGroup.NameKey previousName = result.getPreviousGroupName().get(); | |
| @SuppressWarnings("unused") | |
| Future<?> possiblyIgnoredError = | |
| renameGroupOpFactory | |
| .create( | |
| authorIdent, | |
| result.getGroupUuid(), | |
| previousName.get(), | |
| result.getGroupName().get()) | |
| .start(0, TimeUnit.MILLISECONDS); | |
| } | |
| } | |
| private void dispatchAuditEventsOnGroupCreation(InternalGroup createdGroup) { | |
| if (!currentUser.isPresent()) { | |
| return; | |
| } | |
| if (!createdGroup.getMembers().isEmpty()) { | |
| groupAuditService.dispatchAddMembers( | |
| currentUser.get().getAccountId(), | |
| createdGroup.getGroupUUID(), | |
| createdGroup.getMembers(), | |
| createdGroup.getCreatedOn()); | |
| } | |
| if (!createdGroup.getSubgroups().isEmpty()) { | |
| groupAuditService.dispatchAddSubgroups( | |
| currentUser.get().getAccountId(), | |
| createdGroup.getGroupUUID(), | |
| createdGroup.getSubgroups(), | |
| createdGroup.getCreatedOn()); | |
| } | |
| } | |
| private void dispatchAuditEventsOnGroupUpdate(UpdateResult result, Instant updatedOn) { | |
| if (!currentUser.isPresent()) { | |
| return; | |
| } | |
| if (!result.getAddedMembers().isEmpty()) { | |
| groupAuditService.dispatchAddMembers( | |
| currentUser.get().getAccountId(), | |
| result.getGroupUuid(), | |
| result.getAddedMembers(), | |
| updatedOn); | |
| } | |
| if (!result.getDeletedMembers().isEmpty()) { | |
| groupAuditService.dispatchDeleteMembers( | |
| currentUser.get().getAccountId(), | |
| result.getGroupUuid(), | |
| result.getDeletedMembers(), | |
| updatedOn); | |
| } | |
| if (!result.getAddedSubgroups().isEmpty()) { | |
| groupAuditService.dispatchAddSubgroups( | |
| currentUser.get().getAccountId(), | |
| result.getGroupUuid(), | |
| result.getAddedSubgroups(), | |
| updatedOn); | |
| } | |
| if (!result.getDeletedSubgroups().isEmpty()) { | |
| groupAuditService.dispatchDeleteSubgroups( | |
| currentUser.get().getAccountId(), | |
| result.getGroupUuid(), | |
| result.getDeletedSubgroups(), | |
| updatedOn); | |
| } | |
| } | |
| @FunctionalInterface | |
| private interface MetaDataUpdateFactory { | |
| MetaDataUpdate create( | |
| Project.NameKey projectName, Repository repository, BatchRefUpdate batchRefUpdate) | |
| throws IOException; | |
| } | |
| @AutoValue | |
| abstract static class UpdateResult { | |
| abstract AccountGroup.UUID getGroupUuid(); | |
| abstract AccountGroup.Id getGroupId(); | |
| abstract AccountGroup.NameKey getGroupName(); | |
| abstract Optional<AccountGroup.NameKey> getPreviousGroupName(); | |
| abstract ImmutableSet<Account.Id> getAddedMembers(); | |
| abstract ImmutableSet<Account.Id> getDeletedMembers(); | |
| abstract ImmutableSet<AccountGroup.UUID> getAddedSubgroups(); | |
| abstract ImmutableSet<AccountGroup.UUID> getDeletedSubgroups(); | |
| static Builder builder() { | |
| return new AutoValue_GroupsUpdate_UpdateResult.Builder(); | |
| } | |
| @AutoValue.Builder | |
| abstract static class Builder { | |
| abstract Builder setGroupUuid(AccountGroup.UUID groupUuid); | |
| abstract Builder setGroupId(AccountGroup.Id groupId); | |
| abstract Builder setGroupName(AccountGroup.NameKey name); | |
| abstract Builder setPreviousGroupName(AccountGroup.NameKey previousName); | |
| abstract Builder setAddedMembers(Set<Account.Id> addedMembers); | |
| abstract Builder setDeletedMembers(Set<Account.Id> deletedMembers); | |
| abstract Builder setAddedSubgroups(Set<AccountGroup.UUID> addedSubgroups); | |
| abstract Builder setDeletedSubgroups(Set<AccountGroup.UUID> deletedSubgroups); | |
| abstract UpdateResult build(); | |
| } | |
| } | |
| } |