summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
diff options
context:
space:
mode:
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.java453
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);
+ }
+ }
+}