diff options
author | Alexandre Philbert <alexandre.philbert@ericsson.com> | 2016-02-19 12:12:01 -0500 |
---|---|---|
committer | David Pursehouse <david.pursehouse@sonymobile.com> | 2016-03-02 11:21:56 +0900 |
commit | ed99a12416edb6b9a825a2e1a8fba4935477a2fe (patch) | |
tree | 814a5cfe491a7ffcb71f3c79674a28a272f0920c | |
parent | 7016d7a8747374234c8d37cf4a39938725927388 (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
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; - } } |