Add query methods to project API

Change-Id: I53f3b8611ea9c51c5325e80c17c123eea26b985e
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
index e4a659c..02cce3a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -58,6 +58,24 @@
 
   ListRequest list();
 
+  /**
+   * Query projects.
+   *
+   * <p>Example code: {@code query().withQuery("name:project").get()}
+   *
+   * @return API for setting parameters and getting result.
+   */
+  QueryRequest query();
+
+  /**
+   * Query projects.
+   *
+   * <p>Shortcut API for {@code query().withQuery(String)}.
+   *
+   * @see #query()
+   */
+  QueryRequest query(String query);
+
   abstract class ListRequest {
     public enum FilterType {
       CODE,
@@ -172,6 +190,56 @@
   }
 
   /**
+   * API for setting parameters and getting result. Used for {@code query()}.
+   *
+   * @see #query()
+   */
+  abstract class QueryRequest {
+    private String query;
+    private int limit;
+    private int start;
+
+    /** Execute query and returns the matched projects as list. */
+    public abstract List<ProjectInfo> get() throws RestApiException;
+
+    /**
+     * Set query.
+     *
+     * @param query needs to be in human-readable form.
+     */
+    public QueryRequest withQuery(String query) {
+      this.query = query;
+      return this;
+    }
+
+    /**
+     * Set limit for returned list of projects. Optional; server-default is used when not provided.
+     */
+    public QueryRequest withLimit(int limit) {
+      this.limit = limit;
+      return this;
+    }
+
+    /** Set number of projects to skip. Optional; no projects are skipped when not provided. */
+    public QueryRequest withStart(int start) {
+      this.start = start;
+      return this;
+    }
+
+    public String getQuery() {
+      return query;
+    }
+
+    public int getLimit() {
+      return limit;
+    }
+
+    public int getStart() {
+      return start;
+    }
+  }
+
+  /**
    * A default implementation which allows source compatibility when adding new methods to the
    * interface.
    */
@@ -195,5 +263,15 @@
     public ListRequest list() {
       throw new NotImplementedException();
     }
+
+    @Override
+    public QueryRequest query() {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public QueryRequest query(String query) {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
index 702a7e9..9490075 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
@@ -22,14 +22,18 @@
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.ListProjects;
 import com.google.gerrit.server.project.ListProjects.FilterType;
 import com.google.gerrit.server.project.ProjectsCollection;
+import com.google.gerrit.server.project.QueryProjects;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+import java.util.List;
 import java.util.SortedMap;
 
 @Singleton
@@ -37,15 +41,18 @@
   private final ProjectsCollection projects;
   private final ProjectApiImpl.Factory api;
   private final Provider<ListProjects> listProvider;
+  private final Provider<QueryProjects> queryProvider;
 
   @Inject
   ProjectsImpl(
       ProjectsCollection projects,
       ProjectApiImpl.Factory api,
-      Provider<ListProjects> listProvider) {
+      Provider<ListProjects> listProvider,
+      Provider<QueryProjects> queryProvider) {
     this.projects = projects;
     this.api = api;
     this.listProvider = listProvider;
+    this.queryProvider = queryProvider;
   }
 
   @Override
@@ -124,4 +131,32 @@
 
     return lp.apply();
   }
+
+  @Override
+  public QueryRequest query() {
+    return new QueryRequest() {
+      @Override
+      public List<ProjectInfo> get() throws RestApiException {
+        return ProjectsImpl.this.query(this);
+      }
+    };
+  }
+
+  @Override
+  public QueryRequest query(String query) {
+    return query().withQuery(query);
+  }
+
+  private List<ProjectInfo> query(QueryRequest r) throws RestApiException {
+    try {
+      QueryProjects myQueryProjects = queryProvider.get();
+      myQueryProjects.setQuery(r.getQuery());
+      myQueryProjects.setLimit(r.getLimit());
+      myQueryProjects.setStart(r.getStart());
+
+      return myQueryProjects.apply(TopLevelResource.INSTANCE);
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot query projects", e);
+    }
+  }
 }