368 lines
13 KiB
Java
368 lines
13 KiB
Java
// Copyright (C) 2008 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.httpd.rpc;
|
|
|
|
import com.google.gerrit.common.data.AccountDashboardInfo;
|
|
import com.google.gerrit.common.data.ChangeInfo;
|
|
import com.google.gerrit.common.data.ChangeListService;
|
|
import com.google.gerrit.common.data.SingleListChangeInfo;
|
|
import com.google.gerrit.common.data.ToggleStarRequest;
|
|
import com.google.gerrit.common.errors.InvalidQueryException;
|
|
import com.google.gerrit.common.errors.NoSuchEntityException;
|
|
import com.google.gerrit.reviewdb.Account;
|
|
import com.google.gerrit.reviewdb.Change;
|
|
import com.google.gerrit.reviewdb.ChangeAccess;
|
|
import com.google.gerrit.reviewdb.PatchSetApproval;
|
|
import com.google.gerrit.reviewdb.ReviewDb;
|
|
import com.google.gerrit.reviewdb.StarredChange;
|
|
import com.google.gerrit.server.CurrentUser;
|
|
import com.google.gerrit.server.account.AccountInfoCacheFactory;
|
|
import com.google.gerrit.server.project.ChangeControl;
|
|
import com.google.gerrit.server.project.NoSuchChangeException;
|
|
import com.google.gerrit.server.query.Predicate;
|
|
import com.google.gerrit.server.query.QueryParseException;
|
|
import com.google.gerrit.server.query.change.ChangeData;
|
|
import com.google.gerrit.server.query.change.ChangeDataSource;
|
|
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
|
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
|
|
import com.google.gwt.user.client.rpc.AsyncCallback;
|
|
import com.google.gwtjsonrpc.client.VoidResult;
|
|
import com.google.gwtorm.client.OrmException;
|
|
import com.google.gwtorm.client.ResultSet;
|
|
import com.google.gwtorm.client.impl.ListResultSet;
|
|
import com.google.inject.Inject;
|
|
import com.google.inject.Provider;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
public class ChangeListServiceImpl extends BaseServiceImplementation implements
|
|
ChangeListService {
|
|
private static final Comparator<ChangeInfo> ID_COMP =
|
|
new Comparator<ChangeInfo>() {
|
|
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
|
|
return o1.getId().get() - o2.getId().get();
|
|
}
|
|
};
|
|
private static final Comparator<ChangeInfo> SORT_KEY_COMP =
|
|
new Comparator<ChangeInfo>() {
|
|
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
|
|
return o2.getSortKey().compareTo(o1.getSortKey());
|
|
}
|
|
};
|
|
private static final Comparator<Change> QUERY_PREV =
|
|
new Comparator<Change>() {
|
|
public int compare(final Change a, final Change b) {
|
|
return a.getSortKey().compareTo(b.getSortKey());
|
|
}
|
|
};
|
|
private static final Comparator<Change> QUERY_NEXT =
|
|
new Comparator<Change>() {
|
|
public int compare(final Change a, final Change b) {
|
|
return b.getSortKey().compareTo(a.getSortKey());
|
|
}
|
|
};
|
|
|
|
private static final int MAX_PER_PAGE = 100;
|
|
|
|
private static int safePageSize(final int pageSize) {
|
|
return 0 < pageSize && pageSize <= MAX_PER_PAGE ? pageSize : MAX_PER_PAGE;
|
|
}
|
|
|
|
private final Provider<CurrentUser> currentUser;
|
|
private final ChangeControl.Factory changeControlFactory;
|
|
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
|
|
|
|
private final ChangeQueryBuilder.Factory queryBuilder;
|
|
private final Provider<ChangeQueryRewriter> queryRewriter;
|
|
|
|
@Inject
|
|
ChangeListServiceImpl(final Provider<ReviewDb> schema,
|
|
final Provider<CurrentUser> currentUser,
|
|
final ChangeControl.Factory changeControlFactory,
|
|
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
|
|
final ChangeQueryBuilder.Factory queryBuilder,
|
|
final Provider<ChangeQueryRewriter> queryRewriter) {
|
|
super(schema, currentUser);
|
|
this.currentUser = currentUser;
|
|
this.changeControlFactory = changeControlFactory;
|
|
this.accountInfoCacheFactory = accountInfoCacheFactory;
|
|
this.queryBuilder = queryBuilder;
|
|
this.queryRewriter = queryRewriter;
|
|
}
|
|
|
|
private boolean canRead(final Change c) {
|
|
try {
|
|
return changeControlFactory.controlFor(c).isVisible();
|
|
} catch (NoSuchChangeException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void allQueryPrev(final String query, final String pos,
|
|
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
|
|
run(callback, new QueryPrev(pageSize, pos) {
|
|
@Override
|
|
ResultSet<Change> query(ReviewDb db, int lim, String key)
|
|
throws OrmException, InvalidQueryException {
|
|
return searchQuery(db, query, lim, key, QUERY_PREV);
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void allQueryNext(final String query, final String pos,
|
|
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
|
|
run(callback, new QueryNext(pageSize, pos) {
|
|
@Override
|
|
ResultSet<Change> query(ReviewDb db, int lim, String key)
|
|
throws OrmException, InvalidQueryException {
|
|
return searchQuery(db, query, lim, key, QUERY_NEXT);
|
|
}
|
|
});
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private ResultSet<Change> searchQuery(final ReviewDb db, String query,
|
|
final int limit, final String key, final Comparator<Change> cmp)
|
|
throws OrmException, InvalidQueryException {
|
|
try {
|
|
final ChangeQueryBuilder builder = queryBuilder.create(currentUser.get());
|
|
final Predicate<ChangeData> visibleToMe = builder.is_visible();
|
|
Predicate<ChangeData> q = builder.parse(query);
|
|
q = Predicate.and(q, //
|
|
cmp == QUERY_PREV //
|
|
? builder.sortkey_after(key) //
|
|
: builder.sortkey_before(key), //
|
|
builder.limit(limit), //
|
|
visibleToMe //
|
|
);
|
|
|
|
ChangeQueryRewriter rewriter = queryRewriter.get();
|
|
Predicate<ChangeData> s = rewriter.rewrite(q);
|
|
if (!(s instanceof ChangeDataSource)) {
|
|
s = rewriter.rewrite(Predicate.and(builder.status_open(), q));
|
|
}
|
|
|
|
if (s instanceof ChangeDataSource) {
|
|
ArrayList<Change> r = new ArrayList<Change>();
|
|
HashSet<Change.Id> want = new HashSet<Change.Id>();
|
|
for (ChangeData d : ((ChangeDataSource) s).read()) {
|
|
if (d.hasChange()) {
|
|
// Checking visibleToMe here should be unnecessary, the
|
|
// query should have already performed it. But we don't
|
|
// want to trust the query rewriter that much yet.
|
|
//
|
|
if (visibleToMe.match(d)) {
|
|
r.add(d.getChange());
|
|
}
|
|
} else {
|
|
want.add(d.getId());
|
|
}
|
|
}
|
|
|
|
// Here we have to check canRead. Its impossible to
|
|
// do that test without the change object, and it being
|
|
// missing above means we have to compute it ourselves.
|
|
//
|
|
if (!want.isEmpty()) {
|
|
for (Change c : db.changes().get(want)) {
|
|
if (canRead(c)) {
|
|
r.add(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
Collections.sort(r, cmp);
|
|
return new ListResultSet<Change>(r);
|
|
} else {
|
|
throw new InvalidQueryException("Not Supported", s.toString());
|
|
}
|
|
} catch (QueryParseException e) {
|
|
throw new InvalidQueryException(e.getMessage(), query);
|
|
}
|
|
}
|
|
|
|
public void forAccount(final Account.Id id,
|
|
final AsyncCallback<AccountDashboardInfo> callback) {
|
|
final Account.Id me = getAccountId();
|
|
final Account.Id target = id != null ? id : me;
|
|
if (target == null) {
|
|
callback.onFailure(new NoSuchEntityException());
|
|
return;
|
|
}
|
|
|
|
run(callback, new Action<AccountDashboardInfo>() {
|
|
public AccountDashboardInfo run(final ReviewDb db) throws OrmException,
|
|
Failure {
|
|
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
|
|
final Account user = ac.get(target);
|
|
if (user == null) {
|
|
throw new Failure(new NoSuchEntityException());
|
|
}
|
|
|
|
final Set<Change.Id> stars = currentUser.get().getStarredChanges();
|
|
final ChangeAccess changes = db.changes();
|
|
final AccountDashboardInfo d;
|
|
|
|
final Set<Change.Id> openReviews = new HashSet<Change.Id>();
|
|
final Set<Change.Id> closedReviews = new HashSet<Change.Id>();
|
|
for (final PatchSetApproval ca : db.patchSetApprovals().openByUser(id)) {
|
|
openReviews.add(ca.getPatchSetId().getParentKey());
|
|
}
|
|
for (final PatchSetApproval ca : db.patchSetApprovals()
|
|
.closedByUser(id)) {
|
|
closedReviews.add(ca.getPatchSetId().getParentKey());
|
|
}
|
|
|
|
d = new AccountDashboardInfo(target);
|
|
d.setByOwner(filter(changes.byOwnerOpen(target), stars, ac));
|
|
d.setClosed(filter(changes.byOwnerClosed(target), stars, ac));
|
|
|
|
for (final ChangeInfo c : d.getByOwner()) {
|
|
openReviews.remove(c.getId());
|
|
}
|
|
d.setForReview(filter(changes.get(openReviews), stars, ac));
|
|
Collections.sort(d.getForReview(), ID_COMP);
|
|
|
|
for (final ChangeInfo c : d.getClosed()) {
|
|
closedReviews.remove(c.getId());
|
|
}
|
|
if (!closedReviews.isEmpty()) {
|
|
d.getClosed().addAll(filter(changes.get(closedReviews), stars, ac));
|
|
Collections.sort(d.getClosed(), SORT_KEY_COMP);
|
|
}
|
|
|
|
d.setAccounts(ac.create());
|
|
return d;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void toggleStars(final ToggleStarRequest req,
|
|
final AsyncCallback<VoidResult> callback) {
|
|
run(callback, new Action<VoidResult>() {
|
|
public VoidResult run(final ReviewDb db) throws OrmException {
|
|
final Account.Id me = getAccountId();
|
|
final Set<Change.Id> existing = currentUser.get().getStarredChanges();
|
|
List<StarredChange> add = new ArrayList<StarredChange>();
|
|
List<StarredChange.Key> remove = new ArrayList<StarredChange.Key>();
|
|
|
|
if (req.getAddSet() != null) {
|
|
for (final Change.Id id : req.getAddSet()) {
|
|
if (!existing.contains(id)) {
|
|
add.add(new StarredChange(new StarredChange.Key(me, id)));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (req.getRemoveSet() != null) {
|
|
for (final Change.Id id : req.getRemoveSet()) {
|
|
remove.add(new StarredChange.Key(me, id));
|
|
}
|
|
}
|
|
|
|
db.starredChanges().insert(add);
|
|
db.starredChanges().deleteKeys(remove);
|
|
return VoidResult.INSTANCE;
|
|
}
|
|
});
|
|
}
|
|
|
|
public void myStarredChangeIds(final AsyncCallback<Set<Change.Id>> callback) {
|
|
callback.onSuccess(currentUser.get().getStarredChanges());
|
|
}
|
|
|
|
private List<ChangeInfo> filter(final ResultSet<Change> rs,
|
|
final Set<Change.Id> starred, final AccountInfoCacheFactory accts) {
|
|
final ArrayList<ChangeInfo> r = new ArrayList<ChangeInfo>();
|
|
for (final Change c : rs) {
|
|
if (canRead(c)) {
|
|
final ChangeInfo ci = new ChangeInfo(c);
|
|
accts.want(ci.getOwner());
|
|
ci.setStarred(starred.contains(ci.getId()));
|
|
r.add(ci);
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
private abstract class QueryNext implements Action<SingleListChangeInfo> {
|
|
protected final String pos;
|
|
protected final int limit;
|
|
protected final int slim;
|
|
|
|
QueryNext(final int pageSize, final String pos) {
|
|
this.pos = pos;
|
|
this.limit = safePageSize(pageSize);
|
|
this.slim = limit + 1;
|
|
}
|
|
|
|
public SingleListChangeInfo run(final ReviewDb db) throws OrmException,
|
|
InvalidQueryException {
|
|
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
|
|
final SingleListChangeInfo d = new SingleListChangeInfo();
|
|
final Set<Change.Id> starred = currentUser.get().getStarredChanges();
|
|
|
|
final ArrayList<ChangeInfo> list = new ArrayList<ChangeInfo>();
|
|
final ResultSet<Change> rs = query(db, slim, pos);
|
|
for (final Change c : rs) {
|
|
final ChangeInfo ci = new ChangeInfo(c);
|
|
ac.want(ci.getOwner());
|
|
ci.setStarred(starred.contains(ci.getId()));
|
|
list.add(ci);
|
|
if (list.size() == slim) {
|
|
rs.close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
final boolean atEnd = finish(list);
|
|
d.setChanges(list, atEnd);
|
|
d.setAccounts(ac.create());
|
|
return d;
|
|
}
|
|
|
|
boolean finish(final ArrayList<ChangeInfo> list) {
|
|
final boolean atEnd = list.size() <= limit;
|
|
if (list.size() == slim) {
|
|
list.remove(limit);
|
|
}
|
|
return atEnd;
|
|
}
|
|
|
|
abstract ResultSet<Change> query(final ReviewDb db, final int slim,
|
|
String sortKey) throws OrmException, InvalidQueryException;
|
|
}
|
|
|
|
private abstract class QueryPrev extends QueryNext {
|
|
QueryPrev(int pageSize, String pos) {
|
|
super(pageSize, pos);
|
|
}
|
|
|
|
@Override
|
|
boolean finish(final ArrayList<ChangeInfo> list) {
|
|
final boolean atEnd = super.finish(list);
|
|
Collections.reverse(list);
|
|
return atEnd;
|
|
}
|
|
}
|
|
}
|