All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.group.GroupsUpdate Maven / Gradle / Ivy

There is a newer version: 3.11.0
Show newest version
// 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;

import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.gerrit.server.group.Groups.getExistingGroupFromReviewDb;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.audit.AuditService;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupById;
import com.google.gerrit.reviewdb.client.AccountGroupMember;
import com.google.gerrit.reviewdb.client.AccountGroupName;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupIncludeCache;
import com.google.gerrit.server.git.RenameGroupOp;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.eclipse.jgit.lib.PersonIdent;

/**
 * A database accessor for write calls related to groups.
 *
 * 

All calls which write group related details to the database (either ReviewDb or NoteDb) 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 {@code GroupsOnInit} instead. * *

If not explicitly stated, all methods of this class refer to internal groups. */ public class GroupsUpdate { public interface Factory { /** * Creates a {@code 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. * *

Note: Please use this method with care and rather consider to use the * correct annotation on the provider of a {@code GroupsUpdate} instead. * * @param currentUser the user to which modifications should be attributed, or {@code null} if * the Gerrit server identity should be used */ GroupsUpdate create(@Nullable IdentifiedUser currentUser); } private final Groups groups; private final GroupCache groupCache; private final GroupIncludeCache groupIncludeCache; private final AuditService auditService; private final RenameGroupOp.Factory renameGroupOpFactory; @Nullable private final IdentifiedUser currentUser; private final PersonIdent committerIdent; @Inject GroupsUpdate( Groups groups, GroupCache groupCache, GroupIncludeCache groupIncludeCache, AuditService auditService, RenameGroupOp.Factory renameGroupOpFactory, @GerritPersonIdent PersonIdent serverIdent, @Assisted @Nullable IdentifiedUser currentUser) { this.groups = groups; this.groupCache = groupCache; this.groupIncludeCache = groupIncludeCache; this.auditService = auditService; this.renameGroupOpFactory = renameGroupOpFactory; this.currentUser = currentUser; committerIdent = getCommitterIdent(serverIdent, currentUser); } private static PersonIdent getCommitterIdent( PersonIdent serverIdent, @Nullable IdentifiedUser currentUser) { return currentUser != null ? createPersonIdent(serverIdent, currentUser) : serverIdent; } private static PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) { return user.newCommitterIdent(ident.getWhen(), ident.getTimeZone()); } /** * Adds/Creates the specified group for the specified members (accounts). * * @param db the {@code ReviewDb} instance to update * @param group the group to add * @param memberIds the IDs of the accounts which should be members of the created group * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the cache entry of one of the new members couldn't be invalidated, or * the new group couldn't be indexed */ public void addGroup(ReviewDb db, AccountGroup group, Set memberIds) throws OrmException, IOException { addNewGroup(db, group); addNewGroupMembers(db, group, memberIds); groupCache.onCreateGroup(group); } /** * Adds the specified group. * *

Note: This method doesn't update the index! It just adds the group to the * database. Use this method with care. * * @param db the {@code ReviewDb} instance to update * @param group the group to add * @throws OrmException if an error occurs while reading/writing from/to ReviewDb */ public static void addNewGroup(ReviewDb db, AccountGroup group) throws OrmException { AccountGroupName gn = new AccountGroupName(group); // first insert the group name to validate that the group name hasn't // already been used to create another group db.accountGroupNames().insert(ImmutableList.of(gn)); db.accountGroups().insert(ImmutableList.of(group)); } /** * Updates the specified group. * * @param db the {@code ReviewDb} instance to update * @param groupUuid the UUID of the group to update * @param groupConsumer a {@code Consumer} which performs the desired updates on the group * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the cache entry for the group couldn't be invalidated * @throws NoSuchGroupException if the specified group doesn't exist */ public void updateGroup( ReviewDb db, AccountGroup.UUID groupUuid, Consumer groupConsumer) throws OrmException, IOException, NoSuchGroupException { AccountGroup updatedGroup = updateGroupInDb(db, groupUuid, groupConsumer); groupCache.evict(updatedGroup.getGroupUUID(), updatedGroup.getId(), updatedGroup.getNameKey()); } @VisibleForTesting public AccountGroup updateGroupInDb( ReviewDb db, AccountGroup.UUID groupUuid, Consumer groupConsumer) throws OrmException, NoSuchGroupException { AccountGroup group = getExistingGroupFromReviewDb(db, groupUuid); groupConsumer.accept(group); db.accountGroups().update(ImmutableList.of(group)); return group; } /** * Renames the specified group. * * @param db the {@code ReviewDb} instance to update * @param groupUuid the UUID of the group to rename * @param newName the new name of the group * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the cache entry for the group couldn't be invalidated * @throws NoSuchGroupException if the specified group doesn't exist * @throws NameAlreadyUsedException if another group has the name {@code newName} */ public void renameGroup(ReviewDb db, AccountGroup.UUID groupUuid, AccountGroup.NameKey newName) throws OrmException, IOException, NameAlreadyUsedException, NoSuchGroupException { AccountGroup group = getExistingGroupFromReviewDb(db, groupUuid); AccountGroup.NameKey oldName = group.getNameKey(); try { AccountGroupName id = new AccountGroupName(newName, group.getId()); db.accountGroupNames().insert(ImmutableList.of(id)); } catch (OrmException e) { AccountGroupName other = db.accountGroupNames().get(newName); if (other != null) { // If we are using this identity, don't report the exception. if (other.getId().equals(group.getId())) { return; } // Otherwise, someone else has this identity. throw new NameAlreadyUsedException("group with name " + newName + " already exists"); } throw e; } group.setNameKey(newName); db.accountGroups().update(ImmutableList.of(group)); db.accountGroupNames().deleteKeys(ImmutableList.of(oldName)); groupCache.evictAfterRename(oldName); groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey()); @SuppressWarnings("unused") Future possiblyIgnoredError = renameGroupOpFactory .create(committerIdent, groupUuid, oldName.get(), newName.get()) .start(0, TimeUnit.MILLISECONDS); } /** * Adds an account as member to a group. The account is only added as a new member if it isn't * already a member of the group. * *

Note: This method doesn't check whether the account exists! * * @param db the {@code ReviewDb} instance to update * @param groupUuid the UUID of the group * @param accountId the ID of the account to add * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the cache entry of the new member couldn't be invalidated * @throws NoSuchGroupException if the specified group doesn't exist */ public void addGroupMember(ReviewDb db, AccountGroup.UUID groupUuid, Account.Id accountId) throws OrmException, IOException, NoSuchGroupException { addGroupMembers(db, groupUuid, ImmutableSet.of(accountId)); } /** * Adds several accounts as members to a group. Only accounts which currently aren't members of * the group are added. * *

Note: This method doesn't check whether the accounts exist! * * @param db the {@code ReviewDb} instance to update * @param groupUuid the UUID of the group * @param accountIds a set of IDs of accounts to add * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the group or one of the new members couldn't be indexed * @throws NoSuchGroupException if the specified group doesn't exist */ public void addGroupMembers(ReviewDb db, AccountGroup.UUID groupUuid, Set accountIds) throws OrmException, IOException, NoSuchGroupException { AccountGroup group = getExistingGroupFromReviewDb(db, groupUuid); Set newMemberIds = new HashSet<>(); for (Account.Id accountId : accountIds) { boolean isMember = groups.isMember(db, groupUuid, accountId); if (!isMember) { newMemberIds.add(accountId); } } if (newMemberIds.isEmpty()) { return; } addNewGroupMembers(db, group, newMemberIds); } private void addNewGroupMembers(ReviewDb db, AccountGroup group, Set newMemberIds) throws OrmException, IOException { Set newMembers = newMemberIds.stream() .map(accountId -> new AccountGroupMember.Key(accountId, group.getId())) .map(AccountGroupMember::new) .collect(toImmutableSet()); if (currentUser != null) { auditService.dispatchAddAccountsToGroup(currentUser.getAccountId(), newMembers); } db.accountGroupMembers().insert(newMembers); groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey()); for (AccountGroupMember newMember : newMembers) { groupIncludeCache.evictGroupsWithMember(newMember.getAccountId()); } } /** * Removes several members (accounts) from a group. Only accounts which currently are members of * the group are removed. * * @param db the {@code ReviewDb} instance to update * @param groupUuid the UUID of the group * @param accountIds a set of IDs of accounts to remove * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the group or one of the removed members couldn't be indexed * @throws NoSuchGroupException if the specified group doesn't exist */ public void removeGroupMembers( ReviewDb db, AccountGroup.UUID groupUuid, Set accountIds) throws OrmException, IOException, NoSuchGroupException { AccountGroup group = getExistingGroupFromReviewDb(db, groupUuid); AccountGroup.Id groupId = group.getId(); Set membersToRemove = new HashSet<>(); for (Account.Id accountId : accountIds) { boolean isMember = groups.isMember(db, groupUuid, accountId); if (isMember) { AccountGroupMember.Key key = new AccountGroupMember.Key(accountId, groupId); membersToRemove.add(new AccountGroupMember(key)); } } if (membersToRemove.isEmpty()) { return; } if (currentUser != null) { auditService.dispatchDeleteAccountsFromGroup(currentUser.getAccountId(), membersToRemove); } db.accountGroupMembers().delete(membersToRemove); groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey()); for (AccountGroupMember member : membersToRemove) { groupIncludeCache.evictGroupsWithMember(member.getAccountId()); } } /** * Adds several groups as subgroups to a group. Only groups which currently aren't subgroups of * the group are added. * *

The parent group must be an internal group whereas the subgroups can either be internal or * external groups. * *

Note: This method doesn't check whether the subgroups exist! * * @param db the {@code ReviewDb} instance to update * @param parentGroupUuid the UUID of the parent group * @param subgroupUuids a set of IDs of the groups to add as subgroups * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the parent group couldn't be indexed * @throws NoSuchGroupException if the specified parent group doesn't exist */ public void addSubgroups( ReviewDb db, AccountGroup.UUID parentGroupUuid, Set subgroupUuids) throws OrmException, NoSuchGroupException, IOException { AccountGroup parentGroup = getExistingGroupFromReviewDb(db, parentGroupUuid); AccountGroup.Id parentGroupId = parentGroup.getId(); Set newSubgroups = new HashSet<>(); for (AccountGroup.UUID includedGroupUuid : subgroupUuids) { boolean isSubgroup = groups.isSubgroup(db, parentGroupUuid, includedGroupUuid); if (!isSubgroup) { AccountGroupById.Key key = new AccountGroupById.Key(parentGroupId, includedGroupUuid); newSubgroups.add(new AccountGroupById(key)); } } if (newSubgroups.isEmpty()) { return; } if (currentUser != null) { auditService.dispatchAddGroupsToGroup(currentUser.getAccountId(), newSubgroups); } db.accountGroupById().insert(newSubgroups); groupCache.evict(parentGroup.getGroupUUID(), parentGroup.getId(), parentGroup.getNameKey()); for (AccountGroupById newIncludedGroup : newSubgroups) { groupIncludeCache.evictParentGroupsOf(newIncludedGroup.getIncludeUUID()); } groupIncludeCache.evictSubgroupsOf(parentGroupUuid); } /** * Removes several subgroups from a parent group. Only groups which currently are subgroups of the * group are removed. * *

The parent group must be an internal group whereas the subgroups can either be internal or * external groups. * * @param db the {@code ReviewDb} instance to update * @param parentGroupUuid the UUID of the parent group * @param subgroupUuids a set of IDs of the subgroups to remove from the parent group * @throws OrmException if an error occurs while reading/writing from/to ReviewDb * @throws IOException if the parent group couldn't be indexed * @throws NoSuchGroupException if the specified parent group doesn't exist */ public void removeSubgroups( ReviewDb db, AccountGroup.UUID parentGroupUuid, Set subgroupUuids) throws OrmException, NoSuchGroupException, IOException { AccountGroup parentGroup = getExistingGroupFromReviewDb(db, parentGroupUuid); AccountGroup.Id parentGroupId = parentGroup.getId(); Set subgroupsToRemove = new HashSet<>(); for (AccountGroup.UUID subgroupUuid : subgroupUuids) { boolean isSubgroup = groups.isSubgroup(db, parentGroupUuid, subgroupUuid); if (isSubgroup) { AccountGroupById.Key key = new AccountGroupById.Key(parentGroupId, subgroupUuid); subgroupsToRemove.add(new AccountGroupById(key)); } } if (subgroupsToRemove.isEmpty()) { return; } if (currentUser != null) { auditService.dispatchDeleteGroupsFromGroup(currentUser.getAccountId(), subgroupsToRemove); } db.accountGroupById().delete(subgroupsToRemove); groupCache.evict(parentGroup.getGroupUUID(), parentGroup.getId(), parentGroup.getNameKey()); for (AccountGroupById groupToRemove : subgroupsToRemove) { groupIncludeCache.evictParentGroupsOf(groupToRemove.getIncludeUUID()); } groupIncludeCache.evictSubgroupsOf(parentGroupUuid); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy