ListProjects: re-implement using secondary index

The GWT UI and other parts of Gerrit still rely on the in-memory
cache for rendering the project list.
This is the first step that moves some use-cases to the QueryProjects
engine: full list without filters and showing only the active and readonly
projects.

All other existing use-cases are still based on the in-memory
cache and are going to be addressed in the follow-up of this change.

With regards to filtering by project name substring, it is not
implemented on top of the secondary index because of Issue 10446.

Bug: Issue 10380
Change-Id: I8effed5f75bdf353d9b23a3d349009e5f0535186
This commit is contained in:
Luca Milanesio 2019-02-01 11:56:42 +01:00
parent a65e60acf8
commit 5017ba50ce
2 changed files with 67 additions and 4 deletions

View File

@ -1354,7 +1354,7 @@ any of their groups is used.
This limit applies not only to the link:cmd-query.html[`gerrit query`] This limit applies not only to the link:cmd-query.html[`gerrit query`]
command, but also to the web UI results pagination size in the new command, but also to the web UI results pagination size in the new
PolyGerrit UI. PolyGerrit UI and, limited to the full project list, in the old GWT UI.
[[capability_readAs]] [[capability_readAs]]

View File

@ -14,11 +14,15 @@
package com.google.gerrit.server.restapi.project; package com.google.gerrit.server.restapi.project;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Ordering.natural;
import static com.google.gerrit.extensions.client.ProjectState.HIDDEN; import static com.google.gerrit.extensions.client.ProjectState.HIDDEN;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings; import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
@ -29,6 +33,7 @@ import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException; import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult; import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.extensions.restapi.Url;
@ -51,7 +56,9 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.TreeFormatter; import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -67,6 +74,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeMap; import java.util.TreeMap;
@ -255,6 +263,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private String matchSubstring; private String matchSubstring;
private String matchRegex; private String matchRegex;
private AccountGroup.UUID groupUuid; private AccountGroup.UUID groupUuid;
private final Provider<QueryProjects> queryProjectsProvider;
@Inject @Inject
protected ListProjects( protected ListProjects(
@ -265,7 +274,8 @@ public class ListProjects implements RestReadView<TopLevelResource> {
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
ProjectNode.Factory projectNodeFactory, ProjectNode.Factory projectNodeFactory,
WebLinks webLinks) { WebLinks webLinks,
Provider<QueryProjects> queryProjectsProvider) {
this.currentUser = currentUser; this.currentUser = currentUser;
this.projectCache = projectCache; this.projectCache = projectCache;
this.groupResolver = groupResolver; this.groupResolver = groupResolver;
@ -274,6 +284,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectNodeFactory = projectNodeFactory; this.projectNodeFactory = projectNodeFactory;
this.webLinks = webLinks; this.webLinks = webLinks;
this.queryProjectsProvider = queryProjectsProvider;
} }
public List<String> getShowBranch() { public List<String> getShowBranch() {
@ -312,10 +323,62 @@ public class ListProjects implements RestReadView<TopLevelResource> {
public SortedMap<String, ProjectInfo> apply() public SortedMap<String, ProjectInfo> apply()
throws BadRequestException, PermissionBackendException { throws BadRequestException, PermissionBackendException {
Optional<String> projectQuery = expressAsProjectsQuery();
if (projectQuery.isPresent()) {
return applyAsQuery(projectQuery.get());
}
format = OutputFormat.JSON; format = OutputFormat.JSON;
return display(null); return display(null);
} }
private Optional<String> expressAsProjectsQuery() {
return !all
&& state != HIDDEN
&& isNullOrEmpty(matchPrefix)
&& isNullOrEmpty(matchRegex)
&& isNullOrEmpty(matchSubstring) // TODO: see Issue 10446
&& type == FilterType.ALL
&& showBranch.isEmpty()
? Optional.of(stateToQuery())
: Optional.empty();
}
private String stateToQuery() {
List<String> queries = new ArrayList<>();
if (state == null) {
queries.add("(state:active OR state:read-only)");
} else {
queries.add(String.format("(state:%s)", state.name()));
}
return Joiner.on(" AND ").join(queries).toString();
}
private SortedMap<String, ProjectInfo> applyAsQuery(String query) throws BadRequestException {
try {
return queryProjectsProvider
.get()
.withQuery(query)
.withStart(start)
.withLimit(limit)
.apply()
.stream()
.collect(
ImmutableSortedMap.toImmutableSortedMap(
natural(), p -> p.name, p -> showDescription ? p : nullifyDescription(p)));
} catch (OrmException | MethodNotAllowedException e) {
logger.atWarning().withCause(e).log(
"Internal error while processing the query '{}' request", query);
throw new BadRequestException("Internal error while processing the query request");
}
}
private ProjectInfo nullifyDescription(ProjectInfo p) {
p.description = null;
return p;
}
public SortedMap<String, ProjectInfo> display(@Nullable OutputStream displayOutputStream) public SortedMap<String, ProjectInfo> display(@Nullable OutputStream displayOutputStream)
throws BadRequestException, PermissionBackendException { throws BadRequestException, PermissionBackendException {
if (all && state != null) { if (all && state != null) {
@ -393,7 +456,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
} }
if (showDescription) { if (showDescription) {
info.description = Strings.emptyToNull(e.getProject().getDescription()); info.description = emptyToNull(e.getProject().getDescription());
} }
info.state = e.getProject().getState(); info.state = e.getProject().getState();