LCOV - code coverage report
Current view: top level - server/group/db - GroupsUpdate.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 193 207 93.2 %
Date: 2022-11-19 15:00:39 Functions: 28 30 93.3 %

          Line data    Source code
       1             : // Copyright (C) 2017 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.group.db;
      16             : 
      17             : import com.google.auto.value.AutoValue;
      18             : import com.google.common.annotations.VisibleForTesting;
      19             : import com.google.common.base.Throwables;
      20             : import com.google.common.collect.ImmutableSet;
      21             : import com.google.common.collect.Sets;
      22             : import com.google.common.flogger.FluentLogger;
      23             : import com.google.gerrit.common.Nullable;
      24             : import com.google.gerrit.entities.Account;
      25             : import com.google.gerrit.entities.AccountGroup;
      26             : import com.google.gerrit.entities.InternalGroup;
      27             : import com.google.gerrit.entities.Project;
      28             : import com.google.gerrit.exceptions.DuplicateKeyException;
      29             : import com.google.gerrit.exceptions.NoSuchGroupException;
      30             : import com.google.gerrit.git.RefUpdateUtil;
      31             : import com.google.gerrit.server.GerritPersonIdent;
      32             : import com.google.gerrit.server.IdentifiedUser;
      33             : import com.google.gerrit.server.account.AccountCache;
      34             : import com.google.gerrit.server.account.GroupBackend;
      35             : import com.google.gerrit.server.account.GroupCache;
      36             : import com.google.gerrit.server.account.GroupIncludeCache;
      37             : import com.google.gerrit.server.config.AllUsersName;
      38             : import com.google.gerrit.server.config.GerritServerId;
      39             : import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
      40             : import com.google.gerrit.server.git.GitRepositoryManager;
      41             : import com.google.gerrit.server.git.meta.MetaDataUpdate;
      42             : import com.google.gerrit.server.group.GroupAuditService;
      43             : import com.google.gerrit.server.index.group.GroupIndexer;
      44             : import com.google.gerrit.server.logging.Metadata;
      45             : import com.google.gerrit.server.logging.TraceContext;
      46             : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
      47             : import com.google.gerrit.server.update.RetryHelper;
      48             : import com.google.gerrit.server.util.time.TimeUtil;
      49             : import com.google.inject.Provider;
      50             : import com.google.inject.assistedinject.Assisted;
      51             : import com.google.inject.assistedinject.AssistedInject;
      52             : import java.io.IOException;
      53             : import java.time.Instant;
      54             : import java.util.Objects;
      55             : import java.util.Optional;
      56             : import java.util.Set;
      57             : import java.util.concurrent.Future;
      58             : import java.util.concurrent.TimeUnit;
      59             : import org.eclipse.jgit.errors.ConfigInvalidException;
      60             : import org.eclipse.jgit.lib.BatchRefUpdate;
      61             : import org.eclipse.jgit.lib.PersonIdent;
      62             : import org.eclipse.jgit.lib.Repository;
      63             : 
      64             : /**
      65             :  * A database accessor for write calls related to groups.
      66             :  *
      67             :  * <p>All calls which write group related details to the database are gathered here. Other classes
      68             :  * should always use this class instead of accessing the database directly. There are a few
      69             :  * exceptions though: schema classes, wrapper classes, and classes executed during init. The latter
      70             :  * ones should use {@link com.google.gerrit.pgm.init.GroupsOnInit} instead.
      71             :  *
      72             :  * <p>If not explicitly stated, all methods of this class refer to <em>internal</em> groups.
      73             :  */
      74             : public class GroupsUpdate {
      75             :   public interface Factory {
      76             :     /**
      77             :      * Creates a {@link GroupsUpdate} which uses the identity of the specified user to mark database
      78             :      * modifications executed by it. For NoteDb, this identity is used as author and committer for
      79             :      * all related commits.
      80             :      *
      81             :      * <p><strong>Note</strong>: Please use this method with care and consider using the {@link
      82             :      * com.google.gerrit.server.UserInitiated} annotation on the provider of a {@link GroupsUpdate}
      83             :      * instead.
      84             :      *
      85             :      * @param currentUser the user to which modifications should be attributed
      86             :      */
      87             :     GroupsUpdate create(IdentifiedUser currentUser);
      88             : 
      89             :     /**
      90             :      * Creates a {@link GroupsUpdate} which uses the server identity to mark database modifications
      91             :      * executed by it. For NoteDb, this identity is used as author and committer for all related
      92             :      * commits.
      93             :      *
      94             :      * <p><strong>Note</strong>: Please use this method with care and consider using the {@link
      95             :      * com.google.gerrit.server.ServerInitiated} annotation on the provider of a {@link
      96             :      * GroupsUpdate} instead.
      97             :      */
      98             :     GroupsUpdate createWithServerIdent();
      99             :   }
     100             : 
     101         151 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     102             : 
     103             :   private final GitRepositoryManager repoManager;
     104             :   private final AllUsersName allUsersName;
     105             :   private final GroupCache groupCache;
     106             :   private final GroupIncludeCache groupIncludeCache;
     107             :   private final Provider<GroupIndexer> indexer;
     108             :   private final GroupAuditService groupAuditService;
     109             :   private final RenameGroupOp.Factory renameGroupOpFactory;
     110             :   private final Optional<IdentifiedUser> currentUser;
     111             :   private final AuditLogFormatter auditLogFormatter;
     112             :   private final PersonIdent authorIdent;
     113             :   private final MetaDataUpdateFactory metaDataUpdateFactory;
     114             :   private final GitReferenceUpdated gitRefUpdated;
     115             :   private final RetryHelper retryHelper;
     116             : 
     117             :   @AssistedInject
     118             :   @SuppressWarnings("BindingAnnotationWithoutInject")
     119             :   GroupsUpdate(
     120             :       GitRepositoryManager repoManager,
     121             :       AllUsersName allUsersName,
     122             :       GroupBackend groupBackend,
     123             :       GroupCache groupCache,
     124             :       GroupIncludeCache groupIncludeCache,
     125             :       Provider<GroupIndexer> indexer,
     126             :       GroupAuditService auditService,
     127             :       AccountCache accountCache,
     128             :       RenameGroupOp.Factory renameGroupOpFactory,
     129             :       @GerritServerId String serverId,
     130             :       @GerritPersonIdent PersonIdent serverIdent,
     131             :       MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
     132             :       GitReferenceUpdated gitRefUpdated,
     133             :       RetryHelper retryHelper) {
     134         140 :     this(
     135             :         repoManager,
     136             :         allUsersName,
     137             :         groupBackend,
     138             :         groupCache,
     139             :         groupIncludeCache,
     140             :         indexer,
     141             :         auditService,
     142             :         accountCache,
     143             :         renameGroupOpFactory,
     144             :         serverId,
     145             :         serverIdent,
     146             :         metaDataUpdateInternalFactory,
     147             :         gitRefUpdated,
     148             :         retryHelper,
     149         140 :         Optional.empty());
     150         140 :   }
     151             : 
     152             :   @AssistedInject
     153             :   @SuppressWarnings("BindingAnnotationWithoutInject")
     154             :   GroupsUpdate(
     155             :       GitRepositoryManager repoManager,
     156             :       AllUsersName allUsersName,
     157             :       GroupBackend groupBackend,
     158             :       GroupCache groupCache,
     159             :       GroupIncludeCache groupIncludeCache,
     160             :       Provider<GroupIndexer> indexer,
     161             :       GroupAuditService auditService,
     162             :       AccountCache accountCache,
     163             :       RenameGroupOp.Factory renameGroupOpFactory,
     164             :       @GerritServerId String serverId,
     165             :       @GerritPersonIdent PersonIdent serverIdent,
     166             :       MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
     167             :       GitReferenceUpdated gitRefUpdated,
     168             :       RetryHelper retryHelper,
     169             :       @Assisted IdentifiedUser currentUser) {
     170          35 :     this(
     171             :         repoManager,
     172             :         allUsersName,
     173             :         groupBackend,
     174             :         groupCache,
     175             :         groupIncludeCache,
     176             :         indexer,
     177             :         auditService,
     178             :         accountCache,
     179             :         renameGroupOpFactory,
     180             :         serverId,
     181             :         serverIdent,
     182             :         metaDataUpdateInternalFactory,
     183             :         gitRefUpdated,
     184             :         retryHelper,
     185          35 :         Optional.of(currentUser));
     186          35 :   }
     187             : 
     188             :   @SuppressWarnings("BindingAnnotationWithoutInject")
     189             :   private GroupsUpdate(
     190             :       GitRepositoryManager repoManager,
     191             :       AllUsersName allUsersName,
     192             :       GroupBackend groupBackend,
     193             :       GroupCache groupCache,
     194             :       GroupIncludeCache groupIncludeCache,
     195             :       Provider<GroupIndexer> indexer,
     196             :       GroupAuditService auditService,
     197             :       AccountCache accountCache,
     198             :       RenameGroupOp.Factory renameGroupOpFactory,
     199             :       @GerritServerId String serverId,
     200             :       @GerritPersonIdent PersonIdent serverIdent,
     201             :       MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
     202             :       GitReferenceUpdated gitRefUpdated,
     203             :       RetryHelper retryHelper,
     204         151 :       Optional<IdentifiedUser> currentUser) {
     205         151 :     this.repoManager = repoManager;
     206         151 :     this.allUsersName = allUsersName;
     207         151 :     this.groupCache = groupCache;
     208         151 :     this.groupIncludeCache = groupIncludeCache;
     209         151 :     this.indexer = indexer;
     210         151 :     this.groupAuditService = auditService;
     211         151 :     this.renameGroupOpFactory = renameGroupOpFactory;
     212         151 :     this.gitRefUpdated = gitRefUpdated;
     213         151 :     this.retryHelper = retryHelper;
     214         151 :     this.currentUser = currentUser;
     215             : 
     216         151 :     auditLogFormatter = AuditLogFormatter.createBackedBy(accountCache, groupBackend, serverId);
     217         151 :     metaDataUpdateFactory =
     218         151 :         getMetaDataUpdateFactory(
     219             :             metaDataUpdateInternalFactory, currentUser, serverIdent, auditLogFormatter);
     220         151 :     authorIdent = getAuthorIdent(serverIdent, currentUser);
     221         151 :   }
     222             : 
     223             :   private static MetaDataUpdateFactory getMetaDataUpdateFactory(
     224             :       MetaDataUpdate.InternalFactory metaDataUpdateInternalFactory,
     225             :       Optional<IdentifiedUser> currentUser,
     226             :       PersonIdent serverIdent,
     227             :       AuditLogFormatter auditLogFormatter) {
     228         151 :     return (projectName, repository, batchRefUpdate) -> {
     229         151 :       MetaDataUpdate metaDataUpdate =
     230         151 :           metaDataUpdateInternalFactory.create(projectName, repository, batchRefUpdate);
     231         151 :       metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
     232             :       PersonIdent authorIdent;
     233         151 :       if (currentUser.isPresent()) {
     234          35 :         metaDataUpdate.setAuthor(currentUser.get());
     235          35 :         authorIdent =
     236          35 :             auditLogFormatter.getParsableAuthorIdent(currentUser.get().getAccount(), serverIdent);
     237             :       } else {
     238         140 :         authorIdent = serverIdent;
     239             :       }
     240         151 :       metaDataUpdate.getCommitBuilder().setAuthor(authorIdent);
     241         151 :       return metaDataUpdate;
     242             :     };
     243             :   }
     244             : 
     245             :   private static PersonIdent getAuthorIdent(
     246             :       PersonIdent serverIdent, Optional<IdentifiedUser> currentUser) {
     247         151 :     return currentUser.map(user -> createPersonIdent(serverIdent, user)).orElse(serverIdent);
     248             :   }
     249             : 
     250             :   private static PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
     251          35 :     return user.newCommitterIdent(ident);
     252             :   }
     253             : 
     254             :   /**
     255             :    * Creates the specified group for the specified members (accounts).
     256             :    *
     257             :    * @param groupCreation an {@link InternalGroupCreation} which specifies all mandatory properties
     258             :    *     of the group
     259             :    * @param groupDelta a {@link GroupDelta} which specifies optional properties of the group. If
     260             :    *     this {@link GroupDelta} updates a property which was already specified by the {@link
     261             :    *     InternalGroupCreation}, the value of this {@link GroupDelta} wins.
     262             :    * @throws DuplicateKeyException if a group with the chosen name already exists
     263             :    * @throws IOException if indexing fails, or an error occurs while reading/writing from/to NoteDb
     264             :    * @return the created {@link InternalGroup}
     265             :    */
     266             :   public InternalGroup createGroup(InternalGroupCreation groupCreation, GroupDelta groupDelta)
     267             :       throws DuplicateKeyException, IOException, ConfigInvalidException {
     268          37 :     try (TraceTimer ignored =
     269          37 :         TraceContext.newTimer(
     270             :             "Creating group",
     271          37 :             Metadata.builder()
     272          37 :                 .groupName(groupDelta.getName().orElseGet(groupCreation::getNameKey).get())
     273          37 :                 .build())) {
     274          37 :       InternalGroup createdGroup = createGroupInNoteDbWithRetry(groupCreation, groupDelta);
     275          37 :       evictCachesOnGroupCreation(createdGroup);
     276          37 :       dispatchAuditEventsOnGroupCreation(createdGroup);
     277          37 :       return createdGroup;
     278             :     }
     279             :   }
     280             : 
     281             :   /**
     282             :    * Updates the specified group.
     283             :    *
     284             :    * @param groupUuid the UUID of the group to update
     285             :    * @param groupDelta a {@link GroupDelta} which indicates the desired updates on the group
     286             :    * @throws DuplicateKeyException if the new name of the group is used by another group
     287             :    * @throws IOException if indexing fails, or an error occurs while reading/writing from/to NoteDb
     288             :    * @throws NoSuchGroupException if the specified group doesn't exist
     289             :    */
     290             :   public void updateGroup(AccountGroup.UUID groupUuid, GroupDelta groupDelta)
     291             :       throws DuplicateKeyException, IOException, NoSuchGroupException, ConfigInvalidException {
     292         151 :     try (TraceTimer ignored =
     293         151 :         TraceContext.newTimer(
     294         151 :             "Updating group", Metadata.builder().groupUuid(groupUuid.get()).build())) {
     295         151 :       Optional<Instant> updatedOn = groupDelta.getUpdatedOn();
     296         151 :       if (!updatedOn.isPresent()) {
     297         151 :         updatedOn = Optional.of(TimeUtil.now());
     298         151 :         groupDelta = groupDelta.toBuilder().setUpdatedOn(updatedOn.get()).build();
     299             :       }
     300             : 
     301         151 :       UpdateResult result = updateGroupInNoteDbWithRetry(groupUuid, groupDelta);
     302         151 :       updateNameInProjectConfigsIfNecessary(result);
     303         151 :       evictCachesOnGroupUpdate(result);
     304         151 :       dispatchAuditEventsOnGroupUpdate(result, updatedOn.get());
     305             :     }
     306         151 :   }
     307             : 
     308             :   private InternalGroup createGroupInNoteDbWithRetry(
     309             :       InternalGroupCreation groupCreation, GroupDelta groupDelta)
     310             :       throws IOException, ConfigInvalidException, DuplicateKeyException {
     311             :     try {
     312          37 :       return retryHelper
     313          37 :           .groupUpdate("createGroup", () -> createGroupInNoteDb(groupCreation, groupDelta))
     314          37 :           .call();
     315           1 :     } catch (Exception e) {
     316           0 :       Throwables.throwIfUnchecked(e);
     317           0 :       Throwables.throwIfInstanceOf(e, IOException.class);
     318           0 :       Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
     319           0 :       Throwables.throwIfInstanceOf(e, DuplicateKeyException.class);
     320           0 :       throw new IOException(e);
     321             :     }
     322             :   }
     323             : 
     324             :   @VisibleForTesting
     325             :   public InternalGroup createGroupInNoteDb(
     326             :       InternalGroupCreation groupCreation, GroupDelta groupDelta)
     327             :       throws IOException, ConfigInvalidException, DuplicateKeyException {
     328          37 :     try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
     329          37 :       AccountGroup.NameKey groupName = groupDelta.getName().orElseGet(groupCreation::getNameKey);
     330          37 :       GroupNameNotes groupNameNotes =
     331          37 :           GroupNameNotes.forNewGroup(
     332          37 :               allUsersName, allUsersRepo, groupCreation.getGroupUUID(), groupName);
     333             : 
     334          37 :       GroupConfig groupConfig =
     335          37 :           GroupConfig.createForNewGroup(allUsersName, allUsersRepo, groupCreation);
     336          37 :       groupConfig.setGroupDelta(groupDelta, auditLogFormatter);
     337             : 
     338          37 :       commit(allUsersRepo, groupConfig, groupNameNotes);
     339             : 
     340          37 :       return groupConfig
     341          37 :           .getLoadedGroup()
     342          37 :           .orElseThrow(
     343           0 :               () -> new IllegalStateException("Created group wasn't automatically loaded"));
     344             :     }
     345             :   }
     346             : 
     347             :   private UpdateResult updateGroupInNoteDbWithRetry(
     348             :       AccountGroup.UUID groupUuid, GroupDelta groupDelta)
     349             :       throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException {
     350             :     try {
     351         151 :       return retryHelper
     352         151 :           .groupUpdate("updateGroup", () -> updateGroupInNoteDb(groupUuid, groupDelta))
     353         151 :           .call();
     354           1 :     } catch (Exception e) {
     355           1 :       Throwables.throwIfUnchecked(e);
     356           1 :       Throwables.throwIfInstanceOf(e, IOException.class);
     357           1 :       Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
     358           1 :       Throwables.throwIfInstanceOf(e, DuplicateKeyException.class);
     359           0 :       Throwables.throwIfInstanceOf(e, NoSuchGroupException.class);
     360           0 :       throw new IOException(e);
     361             :     }
     362             :   }
     363             : 
     364             :   @VisibleForTesting
     365             :   public UpdateResult updateGroupInNoteDb(AccountGroup.UUID groupUuid, GroupDelta groupDelta)
     366             :       throws IOException, ConfigInvalidException, DuplicateKeyException, NoSuchGroupException {
     367         151 :     try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
     368         151 :       GroupConfig groupConfig = GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid);
     369         151 :       groupConfig.setGroupDelta(groupDelta, auditLogFormatter);
     370         151 :       if (!groupConfig.getLoadedGroup().isPresent()) {
     371           1 :         throw new NoSuchGroupException(groupUuid);
     372             :       }
     373             : 
     374         151 :       InternalGroup originalGroup = groupConfig.getLoadedGroup().get();
     375         151 :       GroupNameNotes groupNameNotes = null;
     376         151 :       if (groupDelta.getName().isPresent()) {
     377           3 :         AccountGroup.NameKey oldName = originalGroup.getNameKey();
     378           3 :         AccountGroup.NameKey newName = groupDelta.getName().get();
     379           3 :         groupNameNotes =
     380           3 :             GroupNameNotes.forRename(allUsersName, allUsersRepo, groupUuid, oldName, newName);
     381             :       }
     382             : 
     383         151 :       commit(allUsersRepo, groupConfig, groupNameNotes);
     384             : 
     385         151 :       InternalGroup updatedGroup =
     386             :           groupConfig
     387         151 :               .getLoadedGroup()
     388         151 :               .orElseThrow(
     389           0 :                   () -> new IllegalStateException("Updated group wasn't automatically loaded"));
     390         151 :       return getUpdateResult(originalGroup, updatedGroup);
     391             :     }
     392             :   }
     393             : 
     394             :   private static UpdateResult getUpdateResult(
     395             :       InternalGroup originalGroup, InternalGroup updatedGroup) {
     396         151 :     Set<Account.Id> addedMembers =
     397         151 :         Sets.difference(updatedGroup.getMembers(), originalGroup.getMembers());
     398         151 :     Set<Account.Id> deletedMembers =
     399         151 :         Sets.difference(originalGroup.getMembers(), updatedGroup.getMembers());
     400         151 :     Set<AccountGroup.UUID> addedSubgroups =
     401         151 :         Sets.difference(updatedGroup.getSubgroups(), originalGroup.getSubgroups());
     402         151 :     Set<AccountGroup.UUID> deletedSubgroups =
     403         151 :         Sets.difference(originalGroup.getSubgroups(), updatedGroup.getSubgroups());
     404             : 
     405             :     UpdateResult.Builder resultBuilder =
     406         151 :         UpdateResult.builder()
     407         151 :             .setGroupUuid(updatedGroup.getGroupUUID())
     408         151 :             .setGroupId(updatedGroup.getId())
     409         151 :             .setGroupName(updatedGroup.getNameKey())
     410         151 :             .setAddedMembers(addedMembers)
     411         151 :             .setDeletedMembers(deletedMembers)
     412         151 :             .setAddedSubgroups(addedSubgroups)
     413         151 :             .setDeletedSubgroups(deletedSubgroups);
     414         151 :     if (!Objects.equals(originalGroup.getNameKey(), updatedGroup.getNameKey())) {
     415           3 :       resultBuilder.setPreviousGroupName(originalGroup.getNameKey());
     416             :     }
     417         151 :     return resultBuilder.build();
     418             :   }
     419             : 
     420             :   private void commit(
     421             :       Repository allUsersRepo, GroupConfig groupConfig, @Nullable GroupNameNotes groupNameNotes)
     422             :       throws IOException {
     423         151 :     BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate();
     424         151 :     try (MetaDataUpdate metaDataUpdate =
     425         151 :         metaDataUpdateFactory.create(allUsersName, allUsersRepo, batchRefUpdate)) {
     426         151 :       groupConfig.commit(metaDataUpdate);
     427             :     }
     428         151 :     if (groupNameNotes != null) {
     429             :       // MetaDataUpdates unfortunately can't be reused. -> Create a new one.
     430          37 :       try (MetaDataUpdate metaDataUpdate =
     431          37 :           metaDataUpdateFactory.create(allUsersName, allUsersRepo, batchRefUpdate)) {
     432          37 :         groupNameNotes.commit(metaDataUpdate);
     433             :       }
     434             :     }
     435             : 
     436         151 :     RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo);
     437         151 :     gitRefUpdated.fire(
     438         151 :         allUsersName, batchRefUpdate, currentUser.map(user -> user.state()).orElse(null));
     439         151 :   }
     440             : 
     441             :   private void evictCachesOnGroupCreation(InternalGroup createdGroup) {
     442          37 :     logger.atFine().log("evict caches on creation of group %s", createdGroup.getGroupUUID());
     443             :     // By UUID is used for the index and hence should be evicted before refreshing the index.
     444          37 :     groupCache.evict(createdGroup.getGroupUUID());
     445          37 :     indexer.get().index(createdGroup.getGroupUUID());
     446             :     // These caches use the result from the index and hence must be evicted after refreshing the
     447             :     // index.
     448          37 :     groupCache.evict(createdGroup.getId());
     449          37 :     groupCache.evict(createdGroup.getNameKey());
     450          37 :     createdGroup.getMembers().forEach(groupIncludeCache::evictGroupsWithMember);
     451          37 :     createdGroup.getSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
     452          37 :   }
     453             : 
     454             :   private void evictCachesOnGroupUpdate(UpdateResult result) {
     455         151 :     logger.atFine().log("evict caches on update of group %s", result.getGroupUuid());
     456             :     // By UUID is used for the index and hence should be evicted before refreshing the index.
     457         151 :     groupCache.evict(result.getGroupUuid());
     458         151 :     indexer.get().index(result.getGroupUuid());
     459             :     // These caches use the result from the index and hence must be evicted after refreshing the
     460             :     // index.
     461         151 :     groupCache.evict(result.getGroupId());
     462         151 :     groupCache.evict(result.getGroupName());
     463         151 :     result.getPreviousGroupName().ifPresent(groupCache::evict);
     464             : 
     465         151 :     result.getAddedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
     466         151 :     result.getDeletedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
     467         151 :     result.getAddedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
     468         151 :     result.getDeletedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
     469         151 :   }
     470             : 
     471             :   private void updateNameInProjectConfigsIfNecessary(UpdateResult result) {
     472         151 :     if (result.getPreviousGroupName().isPresent()) {
     473           3 :       AccountGroup.NameKey previousName = result.getPreviousGroupName().get();
     474             : 
     475             :       @SuppressWarnings("unused")
     476           3 :       Future<?> possiblyIgnoredError =
     477             :           renameGroupOpFactory
     478           3 :               .create(
     479             :                   authorIdent,
     480           3 :                   result.getGroupUuid(),
     481           3 :                   previousName.get(),
     482           3 :                   result.getGroupName().get())
     483           3 :               .start(0, TimeUnit.MILLISECONDS);
     484             :     }
     485         151 :   }
     486             : 
     487             :   private void dispatchAuditEventsOnGroupCreation(InternalGroup createdGroup) {
     488          37 :     if (!currentUser.isPresent()) {
     489          14 :       return;
     490             :     }
     491             : 
     492          29 :     if (!createdGroup.getMembers().isEmpty()) {
     493          28 :       groupAuditService.dispatchAddMembers(
     494          28 :           currentUser.get().getAccountId(),
     495          28 :           createdGroup.getGroupUUID(),
     496          28 :           createdGroup.getMembers(),
     497          28 :           createdGroup.getCreatedOn());
     498             :     }
     499          29 :     if (!createdGroup.getSubgroups().isEmpty()) {
     500           0 :       groupAuditService.dispatchAddSubgroups(
     501           0 :           currentUser.get().getAccountId(),
     502           0 :           createdGroup.getGroupUUID(),
     503           0 :           createdGroup.getSubgroups(),
     504           0 :           createdGroup.getCreatedOn());
     505             :     }
     506          29 :   }
     507             : 
     508             :   private void dispatchAuditEventsOnGroupUpdate(UpdateResult result, Instant updatedOn) {
     509         151 :     if (!currentUser.isPresent()) {
     510         138 :       return;
     511             :     }
     512             : 
     513          19 :     if (!result.getAddedMembers().isEmpty()) {
     514          18 :       groupAuditService.dispatchAddMembers(
     515          18 :           currentUser.get().getAccountId(),
     516          18 :           result.getGroupUuid(),
     517          18 :           result.getAddedMembers(),
     518             :           updatedOn);
     519             :     }
     520          19 :     if (!result.getDeletedMembers().isEmpty()) {
     521           5 :       groupAuditService.dispatchDeleteMembers(
     522           5 :           currentUser.get().getAccountId(),
     523           5 :           result.getGroupUuid(),
     524           5 :           result.getDeletedMembers(),
     525             :           updatedOn);
     526             :     }
     527          19 :     if (!result.getAddedSubgroups().isEmpty()) {
     528           4 :       groupAuditService.dispatchAddSubgroups(
     529           4 :           currentUser.get().getAccountId(),
     530           4 :           result.getGroupUuid(),
     531           4 :           result.getAddedSubgroups(),
     532             :           updatedOn);
     533             :     }
     534          19 :     if (!result.getDeletedSubgroups().isEmpty()) {
     535           4 :       groupAuditService.dispatchDeleteSubgroups(
     536           4 :           currentUser.get().getAccountId(),
     537           4 :           result.getGroupUuid(),
     538           4 :           result.getDeletedSubgroups(),
     539             :           updatedOn);
     540             :     }
     541          19 :   }
     542             : 
     543             :   @FunctionalInterface
     544             :   private interface MetaDataUpdateFactory {
     545             :     MetaDataUpdate create(
     546             :         Project.NameKey projectName, Repository repository, BatchRefUpdate batchRefUpdate)
     547             :         throws IOException;
     548             :   }
     549             : 
     550             :   @AutoValue
     551         151 :   abstract static class UpdateResult {
     552             :     abstract AccountGroup.UUID getGroupUuid();
     553             : 
     554             :     abstract AccountGroup.Id getGroupId();
     555             : 
     556             :     abstract AccountGroup.NameKey getGroupName();
     557             : 
     558             :     abstract Optional<AccountGroup.NameKey> getPreviousGroupName();
     559             : 
     560             :     abstract ImmutableSet<Account.Id> getAddedMembers();
     561             : 
     562             :     abstract ImmutableSet<Account.Id> getDeletedMembers();
     563             : 
     564             :     abstract ImmutableSet<AccountGroup.UUID> getAddedSubgroups();
     565             : 
     566             :     abstract ImmutableSet<AccountGroup.UUID> getDeletedSubgroups();
     567             : 
     568             :     static Builder builder() {
     569         151 :       return new AutoValue_GroupsUpdate_UpdateResult.Builder();
     570             :     }
     571             : 
     572             :     @AutoValue.Builder
     573         151 :     abstract static class Builder {
     574             :       abstract Builder setGroupUuid(AccountGroup.UUID groupUuid);
     575             : 
     576             :       abstract Builder setGroupId(AccountGroup.Id groupId);
     577             : 
     578             :       abstract Builder setGroupName(AccountGroup.NameKey name);
     579             : 
     580             :       abstract Builder setPreviousGroupName(AccountGroup.NameKey previousName);
     581             : 
     582             :       abstract Builder setAddedMembers(Set<Account.Id> addedMembers);
     583             : 
     584             :       abstract Builder setDeletedMembers(Set<Account.Id> deletedMembers);
     585             : 
     586             :       abstract Builder setAddedSubgroups(Set<AccountGroup.UUID> addedSubgroups);
     587             : 
     588             :       abstract Builder setDeletedSubgroups(Set<AccountGroup.UUID> deletedSubgroups);
     589             : 
     590             :       abstract UpdateResult build();
     591             :     }
     592             :   }
     593             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750