Merge "Merge branch 'stable-2.12'"

This commit is contained in:
Edwin Kempin
2016-03-10 15:57:24 +00:00
committed by Gerrit Code Review
20 changed files with 815 additions and 343 deletions

View File

@@ -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;
}
}

View File

@@ -52,7 +52,7 @@ import com.google.gerrit.server.change.Revert;
import com.google.gerrit.server.change.Reviewers;
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.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
@@ -77,7 +77,7 @@ class ChangeApiImpl implements ChangeApi {
private final Revisions revisions;
private final ReviewerApiImpl.Factory reviewerApi;
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;
@@ -104,7 +104,7 @@ class ChangeApiImpl implements ChangeApi {
Revisions revisions,
ReviewerApiImpl.Factory reviewerApi,
RevisionApiImpl.Factory revisionApi,
Provider<SuggestReviewers> suggestReviewers,
Provider<SuggestChangeReviewers> suggestReviewers,
Abandon abandon,
Revert revert,
Restore restore,
@@ -304,7 +304,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);

View File

@@ -26,6 +26,7 @@ import static com.google.gerrit.server.change.VoteResource.VOTE_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;
@@ -72,7 +73,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);

View File

@@ -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();

View File

@@ -15,11 +15,14 @@
package com.google.gerrit.server.change;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.ParameterizedString;
@@ -71,6 +74,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -96,7 +100,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private static final String CLICK_FAILURE_TOOLTIP =
"Clicking the button would fail";
private static final String CHANGES_NOT_MERGEABLE =
"See the \"Submitted Together\" tab for problems";
"See the \"Submitted Together\" tab for problems, specially see: ";
public static class Output {
transient Change change;
@@ -263,11 +267,18 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
MergeOp.checkSubmitRule(c);
}
Boolean csIsMergeable = isPatchSetMergeable(cs);
if (csIsMergeable == null) {
Collection<ChangeData> unmergeable = unmergeableChanges(cs);
if (unmergeable == null) {
return CLICK_FAILURE_TOOLTIP;
} else if (!csIsMergeable) {
return CHANGES_NOT_MERGEABLE;
} else if (!unmergeable.isEmpty()) {
return CHANGES_NOT_MERGEABLE + Joiner.on(", ").join(
Iterables.transform(unmergeable,
new Function<ChangeData, String>() {
@Override
public String apply(ChangeData cd) {
return String.valueOf(cd.getId().get());
}
}));
}
} catch (ResourceConflictException e) {
return BLOCKED_SUBMIT_TOOLTIP;
@@ -407,11 +418,11 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
}
public Boolean isPatchSetMergeable(ChangeSet cs)
public Collection<ChangeData> unmergeableChanges(ChangeSet cs)
throws OrmException, IOException {
Map<ChangeData, Boolean> mergeabilityMap = new HashMap<>();
Set<ChangeData> mergeabilityMap = new HashSet<>();
for (ChangeData change : cs.changes()) {
mergeabilityMap.put(change, false);
mergeabilityMap.add(change);
}
Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
@@ -442,17 +453,19 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
// Skip whole check, cannot determine if mergeable
return null;
}
mergeabilityMap.put(change, mergeable);
if (mergeable) {
mergeabilityMap.remove(change);
}
if (isLastInChain && isMergeCommit && mergeable) {
for (ChangeData c : targetBranch) {
mergeabilityMap.put(c, true);
mergeabilityMap.remove(c);
}
break;
}
}
}
return !mergeabilityMap.values().contains(Boolean.FALSE);
return mergeabilityMap;
}
private HashMap<Change.Id, RevCommit> findCommits(

View File

@@ -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();
}
};
}
}
}

View File

@@ -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;
}
}