summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandre Philbert <alexandre.philbert@ericsson.com>2016-02-19 12:12:01 -0500
committerDavid Pursehouse <david.pursehouse@sonymobile.com>2016-03-02 11:21:56 +0900
commited99a12416edb6b9a825a2e1a8fba4935477a2fe (patch)
tree814a5cfe491a7ffcb71f3c79674a28a272f0920c
parent7016d7a8747374234c8d37cf4a39938725927388 (diff)
Move the logic out of SuggestReviewers and make super class
The logic was moved into a class called ReviewersUtil so that it can be reused by multiple implementations using different resources (i.e: ChangeResource and ProjectResource). This refactor is made to enable a bug fix[1] in the reviewers plugin. [1] https://gerrit-review.googlesource.com/#/c/75307 Bug: Issue 3892 Change-Id: I7c48463f44c64e35a5be7b83f524bd4c616c32da
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java286
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java8
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java3
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java2
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java76
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java312
6 files changed, 409 insertions, 278 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
new file mode 100644
index 0000000000..e38f88c731
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -0,0 +1,286 @@
+// Copyright (C) 2016 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;
+
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.GroupBaseInfo;
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountControl;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupMembers;
+import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.change.ReviewerSuggestionCache;
+import com.google.gerrit.server.change.SuggestReviewers;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ReviewersUtil {
+ private static final String MAX_SUFFIX = "\u9fa5";
+ private static final Ordering<SuggestedReviewerInfo> ORDERING =
+ Ordering.natural().onResultOf(new Function<SuggestedReviewerInfo, String>() {
+ @Nullable
+ @Override
+ public String apply(@Nullable SuggestedReviewerInfo suggestedReviewerInfo) {
+ if (suggestedReviewerInfo == null) {
+ return null;
+ }
+ return suggestedReviewerInfo.account != null
+ ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email,
+ Strings.nullToEmpty(suggestedReviewerInfo.account.name))
+ : Strings.nullToEmpty(suggestedReviewerInfo.group.name);
+ }
+ });
+ private final AccountLoader accountLoader;
+ private final AccountCache accountCache;
+ private final ReviewerSuggestionCache reviewerSuggestionCache;
+ private final AccountControl accountControl;
+ private final Provider<ReviewDb> dbProvider;
+ private final GroupBackend groupBackend;
+ private final GroupMembers.Factory groupMembersFactory;
+ private final Provider<CurrentUser> currentUser;
+
+ @Inject
+ ReviewersUtil(AccountLoader.Factory accountLoaderFactory,
+ AccountCache accountCache,
+ ReviewerSuggestionCache reviewerSuggestionCache,
+ AccountControl.Factory accountControlFactory,
+ Provider<ReviewDb> dbProvider,
+ GroupBackend groupBackend,
+ GroupMembers.Factory groupMembersFactory,
+ Provider<CurrentUser> currentUser) {
+ this.accountLoader = accountLoaderFactory.create(true);
+ this.accountCache = accountCache;
+ this.reviewerSuggestionCache = reviewerSuggestionCache;
+ this.accountControl = accountControlFactory.get();
+ this.dbProvider = dbProvider;
+ this.groupBackend = groupBackend;
+ this.groupMembersFactory = groupMembersFactory;
+ this.currentUser = currentUser;
+ }
+
+ public interface VisibilityControl {
+ boolean isVisibleTo(Account.Id account) throws OrmException;
+ }
+
+ public List<SuggestedReviewerInfo> suggestReviewers(
+ SuggestReviewers suggestReviewers, ProjectControl projectControl,
+ VisibilityControl visibilityControl)
+ throws IOException, OrmException, BadRequestException {
+ String query = suggestReviewers.getQuery();
+ boolean suggestAccounts = suggestReviewers.getSuggestAccounts();
+ int suggestFrom = suggestReviewers.getSuggestFrom();
+ boolean useFullTextSearch = suggestReviewers.getUseFullTextSearch();
+ int limit = suggestReviewers.getLimit();
+
+ if (Strings.isNullOrEmpty(query)) {
+ throw new BadRequestException("missing query field");
+ }
+
+ if (!suggestAccounts || query.length() < suggestFrom) {
+ return Collections.emptyList();
+ }
+
+ List<AccountInfo> suggestedAccounts;
+ if (useFullTextSearch) {
+ suggestedAccounts = suggestAccountFullTextSearch(suggestReviewers, visibilityControl);
+ } else {
+ suggestedAccounts = suggestAccount(suggestReviewers, visibilityControl);
+ }
+
+ List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
+ for (AccountInfo a : suggestedAccounts) {
+ SuggestedReviewerInfo info = new SuggestedReviewerInfo();
+ info.account = a;
+ reviewer.add(info);
+ }
+
+ for (GroupReference g : suggestAccountGroup(suggestReviewers, projectControl)) {
+ if (suggestGroupAsReviewer(suggestReviewers, projectControl.getProject(),
+ g, visibilityControl)) {
+ GroupBaseInfo info = new GroupBaseInfo();
+ info.id = Url.encode(g.getUUID().get());
+ info.name = g.getName();
+ SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
+ suggestedReviewerInfo.group = info;
+ reviewer.add(suggestedReviewerInfo);
+ }
+ }
+
+ reviewer = ORDERING.immutableSortedCopy(reviewer);
+ if (reviewer.size() <= limit) {
+ return reviewer;
+ } else {
+ return reviewer.subList(0, limit);
+ }
+ }
+
+ private List<AccountInfo> suggestAccountFullTextSearch(
+ SuggestReviewers suggestReviewers, VisibilityControl visibilityControl)
+ throws IOException, OrmException {
+ List<AccountInfo> results = reviewerSuggestionCache.search(
+ suggestReviewers.getQuery(), suggestReviewers.getFullTextMaxMatches());
+
+ Iterator<AccountInfo> it = results.iterator();
+ while (it.hasNext()) {
+ Account.Id accountId = new Account.Id(it.next()._accountId);
+ if (!(visibilityControl.isVisibleTo(accountId)
+ && accountControl.canSee(accountId))) {
+ it.remove();
+ }
+ }
+
+ return results;
+ }
+
+ private List<AccountInfo> suggestAccount(SuggestReviewers suggestReviewers,
+ VisibilityControl visibilityControl)
+ throws OrmException {
+ String query = suggestReviewers.getQuery();
+ int limit = suggestReviewers.getLimit();
+
+ String a = query;
+ String b = a + MAX_SUFFIX;
+
+ Map<Account.Id, AccountInfo> r = new LinkedHashMap<>();
+ Map<Account.Id, String> queryEmail = new HashMap<>();
+
+ for (Account p : dbProvider.get().accounts()
+ .suggestByFullName(a, b, limit)) {
+ if (p.isActive()) {
+ addSuggestion(r, p.getId(), visibilityControl);
+ }
+ }
+
+ if (r.size() < limit) {
+ for (Account p : dbProvider.get().accounts()
+ .suggestByPreferredEmail(a, b, limit - r.size())) {
+ if (p.isActive()) {
+ addSuggestion(r, p.getId(), visibilityControl);
+ }
+ }
+ }
+
+ if (r.size() < limit) {
+ for (AccountExternalId e : dbProvider.get().accountExternalIds()
+ .suggestByEmailAddress(a, b, limit - r.size())) {
+ if (!r.containsKey(e.getAccountId())) {
+ Account p = accountCache.get(e.getAccountId()).getAccount();
+ if (p.isActive()) {
+ if (addSuggestion(r, p.getId(), visibilityControl)) {
+ queryEmail.put(e.getAccountId(), e.getEmailAddress());
+ }
+ }
+ }
+ }
+ }
+
+ accountLoader.fill();
+ for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
+ AccountInfo info = r.get(p.getKey());
+ if (info != null) {
+ info.email = p.getValue();
+ }
+ }
+ return new ArrayList<>(r.values());
+ }
+
+ private boolean addSuggestion(Map<Account.Id, AccountInfo> map,
+ Account.Id account, VisibilityControl visibilityControl)
+ throws OrmException {
+ if (!map.containsKey(account)
+ // Can the suggestion see the change?
+ && visibilityControl.isVisibleTo(account)
+ // Can the account see the current user?
+ && accountControl.canSee(account)) {
+ map.put(account, accountLoader.get(account));
+ return true;
+ }
+ return false;
+ }
+
+ private List<GroupReference> suggestAccountGroup(
+ SuggestReviewers suggestReviewers, ProjectControl ctl) {
+ return Lists.newArrayList(
+ Iterables.limit(groupBackend.suggest(suggestReviewers.getQuery(), ctl),
+ suggestReviewers.getLimit()));
+ }
+
+ private boolean suggestGroupAsReviewer(SuggestReviewers suggestReviewers,
+ Project project, GroupReference group,
+ VisibilityControl visibilityControl) throws OrmException, IOException {
+ int maxAllowed = suggestReviewers.getMaxAllowed();
+
+ if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
+ return false;
+ }
+
+ try {
+ Set<Account> members = groupMembersFactory
+ .create(currentUser.get())
+ .listAccounts(group.getUUID(), project.getNameKey());
+
+ if (members.isEmpty()) {
+ return false;
+ }
+
+ if (maxAllowed > 0 && members.size() > maxAllowed) {
+ return false;
+ }
+
+ // require that at least one member in the group can see the change
+ for (Account account : members) {
+ if (visibilityControl.isVisibleTo(account.getId())) {
+ return true;
+ }
+ }
+ } catch (NoSuchGroupException e) {
+ return false;
+ } catch (NoSuchProjectException e) {
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index b55642cccc..d4f685108b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -48,7 +48,7 @@ import com.google.gerrit.server.change.Restore;
import com.google.gerrit.server.change.Revert;
import com.google.gerrit.server.change.Revisions;
import com.google.gerrit.server.change.SubmittedTogether;
-import com.google.gerrit.server.change.SuggestReviewers;
+import com.google.gerrit.server.change.SuggestChangeReviewers;
import com.google.gerrit.server.git.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -70,7 +70,7 @@ class ChangeApiImpl implements ChangeApi {
private final Changes changeApi;
private final Revisions revisions;
private final RevisionApiImpl.Factory revisionApi;
- private final Provider<SuggestReviewers> suggestReviewers;
+ private final Provider<SuggestChangeReviewers> suggestReviewers;
private final ChangeResource change;
private final Abandon abandon;
private final Revert revert;
@@ -92,7 +92,7 @@ class ChangeApiImpl implements ChangeApi {
Changes changeApi,
Revisions revisions,
RevisionApiImpl.Factory revisionApi,
- Provider<SuggestReviewers> suggestReviewers,
+ Provider<SuggestChangeReviewers> suggestReviewers,
Abandon abandon,
Revert revert,
Restore restore,
@@ -257,7 +257,7 @@ class ChangeApiImpl implements ChangeApi {
private List<SuggestedReviewerInfo> suggestReviewers(SuggestedReviewersRequest r)
throws RestApiException {
try {
- SuggestReviewers mySuggestReviewers = suggestReviewers.get();
+ SuggestChangeReviewers mySuggestReviewers = suggestReviewers.get();
mySuggestReviewers.setQuery(r.getQuery());
mySuggestReviewers.setLimit(r.getLimit());
return mySuggestReviewers.apply(change);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 9c588ed769..55c65e7744 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -25,6 +25,7 @@ import static com.google.gerrit.server.change.RevisionResource.REVISION_KIND;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.change.SuggestChangeReviewers;
import com.google.gerrit.server.change.Reviewed.DeleteReviewed;
import com.google.gerrit.server.change.Reviewed.PutReviewed;
@@ -69,7 +70,7 @@ public class Module extends RestApiModule {
post(CHANGE_KIND, "index").to(Index.class);
post(CHANGE_KIND, "reviewers").to(PostReviewers.class);
- get(CHANGE_KIND, "suggest_reviewers").to(SuggestReviewers.class);
+ get(CHANGE_KIND, "suggest_reviewers").to(SuggestChangeReviewers.class);
child(CHANGE_KIND, "reviewers").to(Reviewers.class);
get(REVIEWER_KIND).to(GetReviewer.class);
delete(REVIEWER_KIND).to(DeleteReviewer.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
index ffb63f626c..20078cca1b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
@@ -102,7 +102,7 @@ public class ReviewerSuggestionCache {
});
}
- List<AccountInfo> search(String query, int n) throws IOException {
+ public List<AccountInfo> search(String query, int n) throws IOException {
IndexSearcher searcher = get();
if (searcher == null) {
return Collections.emptyList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
new file mode 100644
index 0000000000..1596e316fc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2016 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.change;
+
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.ReviewersUtil;
+import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.ReviewersUtil.VisibilityControl;
+import com.google.gerrit.server.account.AccountVisibility;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+import java.util.List;
+
+public class SuggestChangeReviewers extends SuggestReviewers
+ implements RestReadView<ChangeResource> {
+ @Inject
+ SuggestChangeReviewers(AccountVisibility av,
+ GenericFactory identifiedUserFactory,
+ Provider<ReviewDb> dbProvider,
+ @GerritServerConfig Config cfg,
+ ReviewersUtil reviewersUtil) {
+ super(av, identifiedUserFactory, dbProvider, cfg, reviewersUtil);
+ }
+
+ @Override
+ public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
+ throws BadRequestException, OrmException, IOException {
+ return reviewersUtil.suggestReviewers(this,
+ rsrc.getControl().getProjectControl(), getVisibility(rsrc));
+ }
+
+ private VisibilityControl getVisibility(final ChangeResource rsrc) {
+ if (rsrc.getControl().getRefControl().isVisibleByRegisteredUsers()) {
+ return new VisibilityControl() {
+ @Override
+ public boolean isVisibleTo(Account.Id account) throws OrmException {
+ return true;
+ }
+ };
+ } else {
+ return new VisibilityControl() {
+ @Override
+ public boolean isVisibleTo(Account.Id account) throws OrmException {
+ IdentifiedUser who =
+ identifiedUserFactory.create(dbProvider, account);
+ // we can't use changeControl directly as it won't suggest reviewers
+ // to drafts
+ return rsrc.getControl().forUser(who).isRefVisible();
+ }
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
index 4561ae45bc..3b610336e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
@@ -14,89 +14,33 @@
package com.google.gerrit.server.change;
-import com.google.common.base.Function;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.extensions.common.GroupBaseInfo;
-import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountControl;
-import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.ReviewersUtil;
import com.google.gerrit.server.account.AccountVisibility;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class SuggestReviewers implements RestReadView<ChangeResource> {
- private static final String MAX_SUFFIX = "\u9fa5";
+public class SuggestReviewers {
private static final int DEFAULT_MAX_SUGGESTED = 10;
private static final int DEFAULT_MAX_MATCHES = 100;
- private static final Ordering<SuggestedReviewerInfo> ORDERING =
- Ordering.natural().onResultOf(new Function<SuggestedReviewerInfo, String>() {
- @Nullable
- @Override
- public String apply(@Nullable SuggestedReviewerInfo suggestedReviewerInfo) {
- if (suggestedReviewerInfo == null) {
- return null;
- }
- return suggestedReviewerInfo.account != null
- ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email,
- Strings.nullToEmpty(suggestedReviewerInfo.account.name))
- : Strings.nullToEmpty(suggestedReviewerInfo.group.name);
- }
- });
- private final AccountLoader accountLoader;
- private final AccountControl accountControl;
- private final GroupMembers.Factory groupMembersFactory;
- private final AccountCache accountCache;
- private final Provider<ReviewDb> dbProvider;
- private final Provider<CurrentUser> currentUser;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
- private final GroupBackend groupBackend;
+ protected final Provider<ReviewDb> dbProvider;
+ protected final IdentifiedUser.GenericFactory identifiedUserFactory;
+ protected final ReviewersUtil reviewersUtil;
+
private final boolean suggestAccounts;
private final int suggestFrom;
private final int maxAllowed;
- private int limit;
- private String query;
+ protected int limit;
+ protected String query;
private boolean useFullTextSearch;
private final int fullTextMaxMatches;
- private final int maxSuggestedReviewers;
- private final ReviewerSuggestionCache reviewerSuggestionCache;
+ protected final int maxSuggestedReviewers;
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT",
usage = "maximum number of reviewers to list")
@@ -112,27 +56,43 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
this.query = q;
}
+ public String getQuery() {
+ return query;
+ }
+
+ public boolean getSuggestAccounts() {
+ return suggestAccounts;
+ }
+
+ public int getSuggestFrom() {
+ return suggestFrom;
+ }
+
+ public boolean getUseFullTextSearch() {
+ return useFullTextSearch;
+ }
+
+ public int getFullTextMaxMatches() {
+ return fullTextMaxMatches;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public int getMaxAllowed() {
+ return maxAllowed;
+ }
+
@Inject
- SuggestReviewers(AccountVisibility av,
- AccountLoader.Factory accountLoaderFactory,
- AccountControl.Factory accountControlFactory,
- AccountCache accountCache,
- GroupMembers.Factory groupMembersFactory,
+ public SuggestReviewers(AccountVisibility av,
IdentifiedUser.GenericFactory identifiedUserFactory,
- Provider<CurrentUser> currentUser,
Provider<ReviewDb> dbProvider,
@GerritServerConfig Config cfg,
- GroupBackend groupBackend,
- ReviewerSuggestionCache reviewerSuggestionCache) {
- this.accountLoader = accountLoaderFactory.create(true);
- this.accountControl = accountControlFactory.get();
- this.accountCache = accountCache;
- this.groupMembersFactory = groupMembersFactory;
+ ReviewersUtil reviewersUtil) {
this.dbProvider = dbProvider;
this.identifiedUserFactory = identifiedUserFactory;
- this.currentUser = currentUser;
- this.groupBackend = groupBackend;
- this.reviewerSuggestionCache = reviewerSuggestionCache;
+ this.reviewersUtil = reviewersUtil;
this.maxSuggestedReviewers =
cfg.getInt("suggest", "maxSuggestedReviewers", DEFAULT_MAX_SUGGESTED);
this.limit = this.maxSuggestedReviewers;
@@ -152,196 +112,4 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
this.maxAllowed = cfg.getInt("addreviewer", "maxAllowed",
PostReviewers.DEFAULT_MAX_REVIEWERS);
}
-
- private interface VisibilityControl {
- boolean isVisibleTo(Account.Id account) throws OrmException;
- }
-
- @Override
- public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
- throws BadRequestException, OrmException, IOException {
- if (Strings.isNullOrEmpty(query)) {
- throw new BadRequestException("missing query field");
- }
-
- if (!suggestAccounts || query.length() < suggestFrom) {
- return Collections.emptyList();
- }
-
- VisibilityControl visibilityControl = getVisibility(rsrc);
- List<AccountInfo> suggestedAccounts;
- if (useFullTextSearch) {
- suggestedAccounts = suggestAccountFullTextSearch(visibilityControl);
- } else {
- suggestedAccounts = suggestAccount(visibilityControl);
- }
-
- List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
- for (AccountInfo a : suggestedAccounts) {
- SuggestedReviewerInfo info = new SuggestedReviewerInfo();
- info.account = a;
- reviewer.add(info);
- }
-
- Project p = rsrc.getControl().getProject();
- for (GroupReference g : suggestAccountGroup(
- rsrc.getControl().getProjectControl())) {
- if (suggestGroupAsReviewer(p, g, visibilityControl)) {
- GroupBaseInfo info = new GroupBaseInfo();
- info.id = Url.encode(g.getUUID().get());
- info.name = g.getName();
- SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
- suggestedReviewerInfo.group = info;
- reviewer.add(suggestedReviewerInfo);
- }
- }
-
- reviewer = ORDERING.immutableSortedCopy(reviewer);
- if (reviewer.size() <= limit) {
- return reviewer;
- } else {
- return reviewer.subList(0, limit);
- }
- }
-
- private VisibilityControl getVisibility(final ChangeResource rsrc) {
- if (rsrc.getControl().getRefControl().isVisibleByRegisteredUsers()) {
- return new VisibilityControl() {
- @Override
- public boolean isVisibleTo(Account.Id account) throws OrmException {
- return true;
- }
- };
- } else {
- return new VisibilityControl() {
- @Override
- public boolean isVisibleTo(Account.Id account) throws OrmException {
- IdentifiedUser who =
- identifiedUserFactory.create(dbProvider, account);
- // we can't use changeControl directly as it won't suggest reviewers
- // to drafts
- return rsrc.getControl().forUser(who).isRefVisible();
- }
- };
- }
- }
-
- private List<GroupReference> suggestAccountGroup(ProjectControl ctl) {
- return Lists.newArrayList(
- Iterables.limit(groupBackend.suggest(query, ctl), limit));
- }
-
- private List<AccountInfo> suggestAccount(VisibilityControl visibilityControl)
- throws OrmException {
- String a = query;
- String b = a + MAX_SUFFIX;
-
- Map<Account.Id, AccountInfo> r = new LinkedHashMap<>();
- Map<Account.Id, String> queryEmail = new HashMap<>();
-
- for (Account p : dbProvider.get().accounts()
- .suggestByFullName(a, b, limit)) {
- if (p.isActive()) {
- addSuggestion(r, p.getId(), visibilityControl);
- }
- }
-
- if (r.size() < limit) {
- for (Account p : dbProvider.get().accounts()
- .suggestByPreferredEmail(a, b, limit - r.size())) {
- if (p.isActive()) {
- addSuggestion(r, p.getId(), visibilityControl);
- }
- }
- }
-
- if (r.size() < limit) {
- for (AccountExternalId e : dbProvider.get().accountExternalIds()
- .suggestByEmailAddress(a, b, limit - r.size())) {
- if (!r.containsKey(e.getAccountId())) {
- Account p = accountCache.get(e.getAccountId()).getAccount();
- if (p.isActive()) {
- if (addSuggestion(r, p.getId(), visibilityControl)) {
- queryEmail.put(e.getAccountId(), e.getEmailAddress());
- }
- }
- }
- }
- }
-
- accountLoader.fill();
- for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
- AccountInfo info = r.get(p.getKey());
- if (info != null) {
- info.email = p.getValue();
- }
- }
- return new ArrayList<>(r.values());
- }
-
- private List<AccountInfo> suggestAccountFullTextSearch(
- VisibilityControl visibilityControl) throws IOException, OrmException {
- List<AccountInfo> results = reviewerSuggestionCache.search(
- query, fullTextMaxMatches);
-
- Iterator<AccountInfo> it = results.iterator();
- while (it.hasNext()) {
- Account.Id accountId = new Account.Id(it.next()._accountId);
- if (!(visibilityControl.isVisibleTo(accountId)
- && accountControl.canSee(accountId))) {
- it.remove();
- }
- }
-
- return results;
- }
-
- private boolean addSuggestion(Map<Account.Id, AccountInfo> map,
- Account.Id account, VisibilityControl visibilityControl)
- throws OrmException {
- if (!map.containsKey(account)
- // Can the suggestion see the change?
- && visibilityControl.isVisibleTo(account)
- // Can the account see the current user?
- && accountControl.canSee(account)) {
- map.put(account, accountLoader.get(account));
- return true;
- }
- return false;
- }
-
- private boolean suggestGroupAsReviewer(Project project,
- GroupReference group, VisibilityControl visibilityControl)
- throws OrmException, IOException {
- if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
- return false;
- }
-
- try {
- Set<Account> members = groupMembersFactory
- .create(currentUser.get())
- .listAccounts(group.getUUID(), project.getNameKey());
-
- if (members.isEmpty()) {
- return false;
- }
-
- if (maxAllowed > 0 && members.size() > maxAllowed) {
- return false;
- }
-
- // require that at least one member in the group can see the change
- for (Account account : members) {
- if (visibilityControl.isVisibleTo(account.getId())) {
- return true;
- }
- }
- } catch (NoSuchGroupException e) {
- return false;
- } catch (NoSuchProjectException e) {
- return false;
- }
-
- return false;
- }
}