diff options
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java')
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java new file mode 100644 index 0000000000..1b9f051e6a --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java @@ -0,0 +1,453 @@ +// Copyright (C) 2009 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.query.change; + +import com.google.gerrit.common.data.ApprovalTypes; +import com.google.gerrit.reviewdb.Account; +import com.google.gerrit.reviewdb.AccountGroup; +import com.google.gerrit.reviewdb.Change; +import com.google.gerrit.reviewdb.Project; +import com.google.gerrit.reviewdb.RevId; +import com.google.gerrit.reviewdb.ReviewDb; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.AccountResolver; +import com.google.gerrit.server.account.GroupCache; +import com.google.gerrit.server.config.AuthConfig; +import com.google.gerrit.server.config.WildProjectName; +import com.google.gerrit.server.patch.PatchListCache; +import com.google.gerrit.server.project.ChangeControl; +import com.google.gerrit.server.query.IntPredicate; +import com.google.gerrit.server.query.Predicate; +import com.google.gerrit.server.query.QueryBuilder; +import com.google.gerrit.server.query.QueryParseException; +import com.google.gwtorm.client.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.assistedinject.Assisted; + +import org.eclipse.jgit.lib.AbbreviatedObjectId; + +import java.util.Collection; +import java.util.HashSet; +import java.util.regex.Pattern; + +/** + * Parses a query string meant to be applied to change objects. + */ +public class ChangeQueryBuilder extends QueryBuilder<ChangeData> { + private static final Pattern PAT_LEGACY_ID = Pattern.compile("^[1-9][0-9]*$"); + private static final Pattern PAT_CHANGE_ID = + Pattern.compile("^[iI][0-9a-f]{4,}.*$"); + private static final Pattern DEF_CHANGE = + Pattern.compile("^([1-9][0-9]*|[iI][0-9a-f]{4,}.*)$"); + + private static final Pattern PAT_COMMIT = + Pattern.compile("^([0-9a-fA-F]{4," + RevId.LEN + "})$"); + private static final Pattern PAT_EMAIL = + Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+"); + + private static final Pattern PAT_LABEL = + Pattern.compile("^[a-zA-Z][a-zA-Z0-9]*((=|>=|<=)[+-]?|[+-])\\d+$"); + + public static final String FIELD_AGE = "age"; + public static final String FIELD_BRANCH = "branch"; + public static final String FIELD_CHANGE = "change"; + public static final String FIELD_COMMIT = "commit"; + public static final String FIELD_DRAFTBY = "draftby"; + public static final String FIELD_FILE = "file"; + public static final String FIELD_IS = "is"; + public static final String FIELD_HAS = "has"; + public static final String FIELD_LABEL = "label"; + public static final String FIELD_LIMIT = "limit"; + public static final String FIELD_OWNER = "owner"; + public static final String FIELD_PROJECT = "project"; + public static final String FIELD_REF = "ref"; + public static final String FIELD_REVIEWER = "reviewer"; + public static final String FIELD_STARREDBY = "starredby"; + public static final String FIELD_STATUS = "status"; + public static final String FIELD_TOPIC = "topic"; + public static final String FIELD_TR = "tr"; + public static final String FIELD_VISIBLETO = "visibleto"; + public static final String FIELD_WATCHEDBY = "watchedby"; + + private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef = + new QueryBuilder.Definition<ChangeData, ChangeQueryBuilder>( + ChangeQueryBuilder.class); + + static class Arguments { + final Provider<ReviewDb> dbProvider; + final Provider<ChangeQueryRewriter> rewriter; + final IdentifiedUser.GenericFactory userFactory; + final ChangeControl.Factory changeControlFactory; + final ChangeControl.GenericFactory changeControlGenericFactory; + final AccountResolver accountResolver; + final GroupCache groupCache; + final AuthConfig authConfig; + final ApprovalTypes approvalTypes; + final Project.NameKey wildProjectName; + final PatchListCache patchListCache; + + @Inject + Arguments(Provider<ReviewDb> dbProvider, + Provider<ChangeQueryRewriter> rewriter, + IdentifiedUser.GenericFactory userFactory, + ChangeControl.Factory changeControlFactory, + ChangeControl.GenericFactory changeControlGenericFactory, + AccountResolver accountResolver, GroupCache groupCache, + AuthConfig authConfig, ApprovalTypes approvalTypes, + @WildProjectName Project.NameKey wildProjectName, + PatchListCache patchListCache) { + this.dbProvider = dbProvider; + this.rewriter = rewriter; + this.userFactory = userFactory; + this.changeControlFactory = changeControlFactory; + this.changeControlGenericFactory = changeControlGenericFactory; + this.accountResolver = accountResolver; + this.groupCache = groupCache; + this.authConfig = authConfig; + this.approvalTypes = approvalTypes; + this.wildProjectName = wildProjectName; + this.patchListCache = patchListCache; + } + } + + public interface Factory { + ChangeQueryBuilder create(CurrentUser user); + } + + private final Arguments args; + private final CurrentUser currentUser; + private boolean allowsFile; + + @Inject + ChangeQueryBuilder(Arguments args, @Assisted CurrentUser currentUser) { + super(mydef); + this.args = args; + this.currentUser = currentUser; + } + + public void setAllowFile(boolean on) { + allowsFile = on; + } + + @Operator + public Predicate<ChangeData> age(String value) { + return new AgePredicate(args.dbProvider, value); + } + + @Operator + public Predicate<ChangeData> change(String query) { + if (PAT_LEGACY_ID.matcher(query).matches()) { + return new LegacyChangeIdPredicate(args.dbProvider, Change.Id + .parse(query)); + + } else if (PAT_CHANGE_ID.matcher(query).matches()) { + if (query.charAt(0) == 'i') { + query = "I" + query.substring(1); + } + return new ChangeIdPredicate(args.dbProvider, query); + } + + throw new IllegalArgumentException(); + } + + @Operator + public Predicate<ChangeData> status(String statusName) { + if ("open".equals(statusName)) { + return status_open(); + + } else if ("closed".equals(statusName)) { + return ChangeStatusPredicate.closed(args.dbProvider); + + } else if ("reviewed".equalsIgnoreCase(statusName)) { + return new IsReviewedPredicate(args.dbProvider); + + } else { + return new ChangeStatusPredicate(args.dbProvider, statusName); + } + } + + public Predicate<ChangeData> status_open() { + return ChangeStatusPredicate.open(args.dbProvider); + } + + @Operator + public Predicate<ChangeData> has(String value) { + if ("star".equalsIgnoreCase(value)) { + return new IsStarredByPredicate(args.dbProvider, currentUser); + } + + if ("draft".equalsIgnoreCase(value)) { + if (currentUser instanceof IdentifiedUser) { + return new HasDraftByPredicate(args.dbProvider, + ((IdentifiedUser) currentUser).getAccountId()); + } + } + + throw new IllegalArgumentException(); + } + + @Operator + public Predicate<ChangeData> is(String value) { + if ("starred".equalsIgnoreCase(value)) { + return new IsStarredByPredicate(args.dbProvider, currentUser); + } + + if ("watched".equalsIgnoreCase(value)) { + return new IsWatchedByPredicate(args, currentUser); + } + + if ("visible".equalsIgnoreCase(value)) { + return is_visible(); + } + + if ("reviewed".equalsIgnoreCase(value)) { + return new IsReviewedPredicate(args.dbProvider); + } + + try { + return status(value); + } catch (IllegalArgumentException e) { + // not status: alias? + } + + throw new IllegalArgumentException(); + } + + @Operator + public Predicate<ChangeData> commit(String id) { + return new CommitPredicate(args.dbProvider, AbbreviatedObjectId + .fromString(id)); + } + + @Operator + public Predicate<ChangeData> project(String name) { + return new ProjectPredicate(args.dbProvider, name); + } + + @Operator + public Predicate<ChangeData> branch(String name) { + return new BranchPredicate(args.dbProvider, name); + } + + @Operator + public Predicate<ChangeData> topic(String name) { + return new TopicPredicate(args.dbProvider, name); + } + + @Operator + public Predicate<ChangeData> ref(String ref) { + return new RefPredicate(args.dbProvider, ref); + } + + @Operator + public Predicate<ChangeData> file(String file) throws QueryParseException { + if (!allowsFile) { + throw error("operator not permitted here: file:" + file); + } + + if (file.startsWith("^")) { + return new RegexFilePredicate(args.dbProvider, args.patchListCache, file); + } + + throw new IllegalArgumentException(); + } + + @Operator + public Predicate<ChangeData> label(String name) { + return new LabelPredicate(args.changeControlGenericFactory, + args.userFactory, args.dbProvider, args.approvalTypes, name); + } + + @Operator + public Predicate<ChangeData> starredby(String who) + throws QueryParseException, OrmException { + Account account = args.accountResolver.find(who); + if (account == null) { + throw error("User " + who + " not found"); + } + return new IsStarredByPredicate(args.dbProvider, // + args.userFactory.create(args.dbProvider, account.getId())); + } + + @Operator + public Predicate<ChangeData> watchedby(String who) + throws QueryParseException, OrmException { + Account account = args.accountResolver.find(who); + if (account == null) { + throw error("User " + who + " not found"); + } + return new IsWatchedByPredicate(args, args.userFactory.create( + args.dbProvider, account.getId())); + } + + @Operator + public Predicate<ChangeData> draftby(String who) throws QueryParseException, + OrmException { + Account account = args.accountResolver.find(who); + if (account == null) { + throw error("User " + who + " not found"); + } + return new HasDraftByPredicate(args.dbProvider, account.getId()); + } + + @Operator + public Predicate<ChangeData> visibleto(String who) + throws QueryParseException, OrmException { + Account account = args.accountResolver.find(who); + if (account != null) { + return visibleto(args.userFactory + .create(args.dbProvider, account.getId())); + } + + // If its not an account, maybe its a group? + // + AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who)); + if (g != null) { + return visibleto(new SingleGroupUser(args.authConfig, g.getId())); + } + + Collection<AccountGroup> matches = + args.groupCache.get(new AccountGroup.ExternalNameKey(who)); + if (matches != null && !matches.isEmpty()) { + HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>(); + for (AccountGroup group : matches) { + ids.add(group.getId()); + } + return visibleto(new SingleGroupUser(args.authConfig, ids)); + } + + throw error("No user or group matches \"" + who + "\"."); + } + + public Predicate<ChangeData> visibleto(CurrentUser user) { + return new IsVisibleToPredicate(args.dbProvider, // + args.changeControlFactory, // + user); + } + + public Predicate<ChangeData> is_visible() { + return visibleto(currentUser); + } + + @Operator + public Predicate<ChangeData> owner(String who) throws QueryParseException, + OrmException { + Account account = args.accountResolver.find(who); + if (account == null) { + throw error("User " + who + " not found"); + } + return new OwnerPredicate(args.dbProvider, account.getId()); + } + + @Operator + public Predicate<ChangeData> reviewer(String nameOrEmail) + throws QueryParseException, OrmException { + Account account = args.accountResolver.find(nameOrEmail); + if (account == null) { + throw error("Reviewer " + nameOrEmail + " not found"); + } + return new ReviewerPredicate(args.dbProvider, account.getId()); + } + + @Operator + public Predicate<ChangeData> tr(String trackingId) { + return new TrackingIdPredicate(args.dbProvider, trackingId); + } + + @Operator + public Predicate<ChangeData> bug(String trackingId) { + return tr(trackingId); + } + + @Operator + public Predicate<ChangeData> limit(String limit) { + return limit(Integer.parseInt(limit)); + } + + public Predicate<ChangeData> limit(int limit) { + return new IntPredicate<ChangeData>(FIELD_LIMIT, limit) { + @Override + public boolean match(ChangeData object) { + return true; + } + + @Override + public int getCost() { + return 0; + } + }; + } + + @Operator + public Predicate<ChangeData> sortkey_after(String sortKey) { + return new SortKeyPredicate.After(args.dbProvider, sortKey); + } + + @Operator + public Predicate<ChangeData> sortkey_before(String sortKey) { + return new SortKeyPredicate.Before(args.dbProvider, sortKey); + } + + @Operator + public Predicate<ChangeData> resume_sortkey(String sortKey) { + return sortkey_before(sortKey); + } + + @SuppressWarnings("unchecked") + public boolean hasLimit(Predicate<ChangeData> p) { + return find(p, IntPredicate.class, FIELD_LIMIT) != null; + } + + @SuppressWarnings("unchecked") + public int getLimit(Predicate<ChangeData> p) { + return ((IntPredicate) find(p, IntPredicate.class, FIELD_LIMIT)).intValue(); + } + + @SuppressWarnings("unchecked") + public boolean hasSortKey(Predicate<ChangeData> p) { + return find(p, SortKeyPredicate.class, "sortkey_after") != null + || find(p, SortKeyPredicate.class, "sortkey_before") != null; + } + + @SuppressWarnings("unchecked") + @Override + protected Predicate<ChangeData> defaultField(String query) + throws QueryParseException { + if (query.startsWith("refs/")) { + return ref(query); + + } else if (DEF_CHANGE.matcher(query).matches()) { + return change(query); + + } else if (PAT_COMMIT.matcher(query).matches()) { + return commit(query); + + } else if (PAT_EMAIL.matcher(query).find()) { + try { + return Predicate.or(owner(query), reviewer(query)); + } catch (OrmException err) { + throw error("Cannot lookup user", err); + } + + } else if (PAT_LABEL.matcher(query).find()) { + return label(query); + + } else { + throw error("Unsupported query:" + query); + } + } +} |