Materialize stream of all groups as late as possible

In most situations, we actually work with a filtered list of all groups.
By staying with streams as long as possible, we can avoid to
materialize an unnecessarily large list. When we switch to NoteDb for
groups, this has the additional benefit that we don't need to read in
all groups from NoteDb at once.

Change-Id: Ieaab464e5a2fb727c2defffa6bd7b52d4738721b
This commit is contained in:
Alice Kober-Sotzek
2017-09-22 15:52:35 +02:00
committed by David Pursehouse
parent a1c4658a97
commit 1d8e95f132
4 changed files with 119 additions and 111 deletions

View File

@@ -357,7 +357,8 @@ public abstract class AbstractDaemonTest {
// later on. As test indexes are non-permanent, closing an instance and opening another one // later on. As test indexes are non-permanent, closing an instance and opening another one
// removes all indexed data. // removes all indexed data.
// As a workaround, we simply reindex all available groups here. // As a workaround, we simply reindex all available groups here.
for (AccountGroup group : groups.getAll(db).collect(toList())) { Iterable<AccountGroup> allGroups = groups.getAll(db)::iterator;
for (AccountGroup group : allGroups) {
groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey()); groupCache.evict(group.getGroupUUID(), group.getId(), group.getNameKey());
} }

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.account;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -29,7 +30,6 @@ import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
/** Implementation of GroupBackend for the internal group system. */ /** Implementation of GroupBackend for the internal group system. */
@@ -75,18 +75,23 @@ public class InternalGroupBackend implements GroupBackend {
try { try {
return groups return groups
.getAll(db.get()) .getAll(db.get())
.filter( .filter(group -> startsWithIgnoreCase(group, name))
group -> .filter(this::isVisible)
// startsWithIgnoreCase && isVisible
group.getName().regionMatches(true, 0, name, 0, name.length())
&& groupControlFactory.controlFor(group).isVisible())
.map(GroupReference::forGroup) .map(GroupReference::forGroup)
.collect(toList()); .collect(toList());
} catch (OrmException e) { } catch (OrmException e) {
return Collections.emptyList(); return ImmutableList.of();
} }
} }
private static boolean startsWithIgnoreCase(AccountGroup group, String name) {
return group.getName().regionMatches(true, 0, name, 0, name.length());
}
private boolean isVisible(AccountGroup group) {
return groupControlFactory.controlFor(group).isVisible();
}
@Override @Override
public GroupMembership membershipsOf(IdentifiedUser user) { public GroupMembership membershipsOf(IdentifiedUser user) {
return groupMembershipFactory.create(user); return groupMembershipFactory.create(user);

View File

@@ -18,9 +18,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.gerrit.common.data.GroupDescription; import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions; import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.data.GroupReference;
@@ -34,6 +34,7 @@ import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.GetGroups; import com.google.gerrit.server.account.GetGroups;
@@ -41,6 +42,7 @@ import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.project.ProjectControl; import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -48,16 +50,14 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
/** List groups visible to the calling user. */ /** List groups visible to the calling user. */
@@ -277,37 +277,39 @@ public class ListGroups implements RestReadView<TopLevelResource> {
} }
private List<GroupInfo> getAllGroups() throws OrmException { private List<GroupInfo> getAllGroups() throws OrmException {
List<GroupInfo> groupInfos; Pattern pattern = getRegexPattern();
List<GroupDescription.Internal> groupList; Stream<GroupDescription.Internal> existingGroups =
if (!projects.isEmpty()) { getAllExistingGroups()
Map<AccountGroup.UUID, GroupDescription.Internal> groups = new HashMap<>(); .filter(group -> !isNotRelevant(pattern, group))
for (ProjectControl projectControl : projects) { .sorted(GROUP_COMPARATOR)
final Set<GroupReference> groupsRefs = projectControl.getProjectState().getAllGroups(); .skip(start);
for (GroupReference groupRef : groupsRefs) { if (limit > 0) {
Optional<InternalGroup> internalGroup = groupCache.get(groupRef.getUUID()); existingGroups = existingGroups.limit(limit);
internalGroup.ifPresent(
group -> groups.put(group.getGroupUUID(), new InternalGroupDescription(group)));
}
}
groupList = filterGroups(groups.values());
} else {
groupList = filterGroups(getAllExistingInternalGroups());
}
groupInfos = Lists.newArrayListWithCapacity(groupList.size());
int found = 0;
int foundIndex = 0;
for (GroupDescription.Internal group : groupList) {
if (foundIndex++ < start) {
continue;
}
if (limit > 0 && ++found > limit) {
break;
} }
List<GroupDescription.Internal> relevantGroups = existingGroups.collect(toImmutableList());
List<GroupInfo> groupInfos = Lists.newArrayListWithCapacity(relevantGroups.size());
for (GroupDescription.Internal group : relevantGroups) {
groupInfos.add(json.addOptions(options).format(group)); groupInfos.add(json.addOptions(options).format(group));
} }
return groupInfos; return groupInfos;
} }
private Stream<GroupDescription.Internal> getAllExistingGroups() throws OrmException {
if (!projects.isEmpty()) {
return projects
.stream()
.map(ProjectControl::getProjectState)
.map(ProjectState::getAllGroups)
.flatMap(Collection::stream)
.map(GroupReference::getUUID)
.distinct()
.map(groupCache::get)
.flatMap(Streams::stream)
.map(InternalGroupDescription::new);
}
return groups.getAll(db.get()).map(GroupDescriptions::forAccountGroup);
}
private List<GroupInfo> suggestGroups() throws OrmException, BadRequestException { private List<GroupInfo> suggestGroups() throws OrmException, BadRequestException {
if (conflictingSuggestParameters()) { if (conflictingSuggestParameters()) {
throw new BadRequestException( throw new BadRequestException(
@@ -363,69 +365,56 @@ public class ListGroups implements RestReadView<TopLevelResource> {
} }
private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user) throws OrmException { private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user) throws OrmException {
List<GroupInfo> groups = new ArrayList<>(); Pattern pattern = getRegexPattern();
int found = 0; Stream<GroupDescription.Internal> foundGroups =
int foundIndex = 0; groups
for (GroupDescription.Internal g : filterGroups(getAllExistingInternalGroups())) {
GroupControl ctl = groupControlFactory.controlFor(g);
try {
if (genericGroupControlFactory.controlFor(user, g.getGroupUUID()).isOwner()) {
if (foundIndex++ < start) {
continue;
}
if (limit > 0 && ++found > limit) {
break;
}
groups.add(json.addOptions(options).format(ctl.getGroup()));
}
} catch (NoSuchGroupException e) {
continue;
}
}
return groups;
}
private ImmutableList<GroupDescription.Internal> getAllExistingInternalGroups() {
try {
return groups
.getAll(db.get()) .getAll(db.get())
.map(GroupDescriptions::forAccountGroup) .map(GroupDescriptions::forAccountGroup)
.collect(toImmutableList()); .filter(group -> !isNotRelevant(pattern, group))
} catch (OrmException e) { .filter(group -> isOwner(user, group))
return ImmutableList.of(); .sorted(GROUP_COMPARATOR)
.skip(start);
if (limit > 0) {
foundGroups = foundGroups.limit(limit);
}
List<GroupDescription.Internal> ownedGroups = foundGroups.collect(toImmutableList());
List<GroupInfo> groupInfos = new ArrayList<>(ownedGroups.size());
for (GroupDescription.Internal group : ownedGroups) {
groupInfos.add(json.addOptions(options).format(group));
}
return groupInfos;
}
private boolean isOwner(CurrentUser user, GroupDescription.Internal group) {
try {
return genericGroupControlFactory.controlFor(user, group.getGroupUUID()).isOwner();
} catch (NoSuchGroupException e) {
return false;
} }
} }
private List<GroupDescription.Internal> filterGroups( private Pattern getRegexPattern() {
Collection<GroupDescription.Internal> groups) { return Strings.isNullOrEmpty(matchRegex) ? null : Pattern.compile(matchRegex);
List<GroupDescription.Internal> filteredGroups = new ArrayList<>(groups.size()); }
Pattern pattern = Strings.isNullOrEmpty(matchRegex) ? null : Pattern.compile(matchRegex);
for (GroupDescription.Internal group : groups) { private boolean isNotRelevant(Pattern pattern, GroupDescription.Internal group) {
if (!Strings.isNullOrEmpty(matchSubstring)) { if (!Strings.isNullOrEmpty(matchSubstring)) {
if (!group if (!group.getName().toLowerCase(Locale.US).contains(matchSubstring.toLowerCase(Locale.US))) {
.getName() return true;
.toLowerCase(Locale.US)
.contains(matchSubstring.toLowerCase(Locale.US))) {
continue;
} }
} else if (pattern != null) { } else if (pattern != null) {
if (!pattern.matcher(group.getName()).matches()) { if (!pattern.matcher(group.getName()).matches()) {
continue; return true;
} }
} }
if (visibleToAll && !group.isVisibleToAll()) { if (visibleToAll && !group.isVisibleToAll()) {
continue; return true;
} }
if (!groupsToInspect.isEmpty() && !groupsToInspect.contains(group.getGroupUUID())) { if (!groupsToInspect.isEmpty() && !groupsToInspect.contains(group.getGroupUUID())) {
continue; return true;
} }
GroupControl c = groupControlFactory.controlFor(group); GroupControl c = groupControlFactory.controlFor(group);
if (c.isVisible()) { return !c.isVisible();
filteredGroups.add(group);
}
}
filteredGroups.sort(GROUP_COMPARATOR);
return filteredGroups;
} }
} }

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.server.group; package com.google.gerrit.server.group;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet; import static java.util.stream.Collectors.toSet;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@@ -45,6 +44,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
@@ -211,29 +211,42 @@ public class SystemGroupBackend extends AbstractGroupBackend {
if (configuredNames.isEmpty()) { if (configuredNames.isEmpty()) {
return; return;
} }
List<AccountGroup> allGroups;
Optional<AccountGroup> conflictingGroup;
try { try {
allGroups = groups.getAll(schema.open()).collect(toList()); conflictingGroup =
} catch (OrmException e) { groups
.getAll(schema.open())
.filter(group -> hasConfiguredName(byLowerCaseConfiguredName, group))
.findAny();
} catch (OrmException ignored) {
return; return;
} }
for (AccountGroup g : allGroups) {
String name = g.getName().toLowerCase(Locale.US); if (conflictingGroup.isPresent()) {
if (byLowerCaseConfiguredName.keySet().contains(name)) { AccountGroup group = conflictingGroup.get();
AccountGroup.UUID uuidSystemGroup = byLowerCaseConfiguredName.get(name); String groupName = group.getName();
AccountGroup.UUID systemGroupUuid = byLowerCaseConfiguredName.get(groupName);
throw new StartupException( throw new StartupException(
String.format( getAmbiguousNameMessage(groupName, group.getGroupUUID(), systemGroupUuid));
}
}
private static boolean hasConfiguredName(
Map<String, AccountGroup.UUID> byLowerCaseConfiguredName, AccountGroup group) {
String name = group.getName().toLowerCase(Locale.US);
return byLowerCaseConfiguredName.keySet().contains(name);
}
private static String getAmbiguousNameMessage(
String groupName, AccountGroup.UUID groupUuid, AccountGroup.UUID systemGroupUuid) {
return String.format(
"The configured name '%s' for system group '%s' is ambiguous" "The configured name '%s' for system group '%s' is ambiguous"
+ " with the name '%s' of existing group '%s'." + " with the name '%s' of existing group '%s'."
+ " Please remove/change the value for groups.%s.name in" + " Please remove/change the value for groups.%s.name in"
+ " gerrit.config.", + " gerrit.config.",
configuredNames.get(uuidSystemGroup), groupName, systemGroupUuid.get(), groupName, groupUuid.get(), systemGroupUuid.get());
uuidSystemGroup.get(),
g.getName(),
g.getGroupUUID().get(),
uuidSystemGroup.get()));
}
}
} }
} }
} }