Convert project index to use ProjectData instead of ProjectState

With this change, the project index does not depend on the Gerrit
server, so that projects under non-Gerrit enabled hosts can be
indexed without dependencies on the server.

Change-Id: I07beec95d1586432bd078d492111b0f6e57c5dcb
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index c983a44..780f023 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -30,7 +30,7 @@
 import com.google.gerrit.server.index.project.ProjectField;
 import com.google.gerrit.server.index.project.ProjectIndex;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
@@ -56,12 +56,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectState>
+public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectData>
     implements ProjectIndex {
   static class ProjectMapping {
     MappingProperties projects;
 
-    ProjectMapping(Schema<ProjectState> schema) {
+    ProjectMapping(Schema<ProjectData> schema) {
       this.projects = ElasticMapping.createMapping(schema);
     }
   }
@@ -80,14 +80,14 @@
       SitePaths sitePaths,
       Provider<ProjectCache> projectCache,
       JestClientBuilder clientBuilder,
-      @Assisted Schema<ProjectState> schema) {
+      @Assisted Schema<ProjectData> schema) {
     super(cfg, sitePaths, schema, clientBuilder, PROJECTS_PREFIX);
     this.projectCache = projectCache;
     this.mapping = new ProjectMapping(schema);
   }
 
   @Override
-  public void replace(ProjectState projectState) throws IOException {
+  public void replace(ProjectData projectState) throws IOException {
     Bulk bulk =
         new Bulk.Builder()
             .defaultIndex(indexName)
@@ -105,7 +105,7 @@
   }
 
   @Override
-  public DataSource<ProjectState> getSource(Predicate<ProjectState> p, QueryOptions opts)
+  public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
       throws QueryParseException {
     return new QuerySource(p, opts);
   }
@@ -122,15 +122,15 @@
   }
 
   @Override
-  protected String getId(ProjectState projectState) {
+  protected String getId(ProjectData projectState) {
     return projectState.getProject().getName();
   }
 
-  private class QuerySource implements DataSource<ProjectState> {
+  private class QuerySource implements DataSource<ProjectData> {
     private final Search search;
     private final Set<String> fields;
 
-    QuerySource(Predicate<ProjectState> p, QueryOptions opts) throws QueryParseException {
+    QuerySource(Predicate<ProjectData> p, QueryOptions opts) throws QueryParseException {
       QueryBuilder qb = queryBuilder.toQueryBuilder(p);
       fields = IndexUtils.projectFields(opts);
       SearchSourceBuilder searchSource =
@@ -157,9 +157,9 @@
     }
 
     @Override
-    public ResultSet<ProjectState> read() throws OrmException {
+    public ResultSet<ProjectData> read() throws OrmException {
       try {
-        List<ProjectState> results = Collections.emptyList();
+        List<ProjectData> results = Collections.emptyList();
         JestResult result = client.execute(search);
         if (result.isSucceeded()) {
           JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
@@ -167,21 +167,21 @@
             JsonArray json = obj.getAsJsonArray("hits");
             results = Lists.newArrayListWithCapacity(json.size());
             for (int i = 0; i < json.size(); i++) {
-              results.add(toProjectState(json.get(i)));
+              results.add(toProjectData(json.get(i)));
             }
           }
         } else {
           log.error(result.getErrorMessage());
         }
-        final List<ProjectState> r = Collections.unmodifiableList(results);
-        return new ResultSet<ProjectState>() {
+        final List<ProjectData> r = Collections.unmodifiableList(results);
+        return new ResultSet<ProjectData>() {
           @Override
-          public Iterator<ProjectState> iterator() {
+          public Iterator<ProjectData> iterator() {
             return r.iterator();
           }
 
           @Override
-          public List<ProjectState> toList() {
+          public List<ProjectData> toList() {
             return r;
           }
 
@@ -200,7 +200,7 @@
       return search.toString();
     }
 
-    private ProjectState toProjectState(JsonElement json) {
+    private ProjectData toProjectData(JsonElement json) {
       JsonElement source = json.getAsJsonObject().get("_source");
       if (source == null) {
         source = json.getAsJsonObject().get("fields");
@@ -209,7 +209,7 @@
       Project.NameKey nameKey =
           new Project.NameKey(
               source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
-      return projectCache.get().get(nameKey);
+      return projectCache.get().get(nameKey).toProjectData();
     }
   }
 }
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index 17438c7..c37a8ec 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -36,7 +36,7 @@
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
 import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gson.FieldNamingPolicy;
 import com.google.gson.Gson;
@@ -162,7 +162,7 @@
         .execute()
         .actionGet();
 
-    Schema<ProjectState> projectSchema = ProjectSchemaDefinitions.INSTANCE.getLatest();
+    Schema<ProjectData> projectSchema = ProjectSchemaDefinitions.INSTANCE.getLatest();
     ProjectMapping projectMapping = new ProjectMapping(projectSchema);
     nodeInfo
         .node
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java
index 544649f..6354f61 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -27,7 +27,7 @@
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.index.project.ProjectIndex;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
@@ -56,7 +56,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class LuceneProjectIndex extends AbstractLuceneIndex<Project.NameKey, ProjectState>
+public class LuceneProjectIndex extends AbstractLuceneIndex<Project.NameKey, ProjectData>
     implements ProjectIndex {
   private static final Logger log = LoggerFactory.getLogger(LuceneProjectIndex.class);
 
@@ -64,7 +64,7 @@
 
   private static final String NAME_SORT_FIELD = sortFieldName(NAME);
 
-  private static Term idTerm(ProjectState projectState) {
+  private static Term idTerm(ProjectData projectState) {
     return idTerm(projectState.getProject().getNameKey());
   }
 
@@ -73,10 +73,10 @@
   }
 
   private final GerritIndexWriterConfig indexWriterConfig;
-  private final QueryBuilder<ProjectState> queryBuilder;
+  private final QueryBuilder<ProjectData> queryBuilder;
   private final Provider<ProjectCache> projectCache;
 
-  private static Directory dir(Schema<ProjectState> schema, Config cfg, SitePaths sitePaths)
+  private static Directory dir(Schema<ProjectData> schema, Config cfg, SitePaths sitePaths)
       throws IOException {
     if (LuceneIndexModule.isInMemoryTest(cfg)) {
       return new RAMDirectory();
@@ -90,7 +90,7 @@
       @GerritServerConfig Config cfg,
       SitePaths sitePaths,
       Provider<ProjectCache> projectCache,
-      @Assisted Schema<ProjectState> schema)
+      @Assisted Schema<ProjectData> schema)
       throws IOException {
     super(
         schema,
@@ -107,7 +107,7 @@
   }
 
   @Override
-  public void replace(ProjectState projectState) throws IOException {
+  public void replace(ProjectData projectState) throws IOException {
     try {
       replace(idTerm(projectState), toDocument(projectState)).get();
     } catch (ExecutionException | InterruptedException e) {
@@ -125,7 +125,7 @@
   }
 
   @Override
-  public DataSource<ProjectState> getSource(Predicate<ProjectState> p, QueryOptions opts)
+  public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
       throws QueryParseException {
     return new QuerySource(
         opts,
@@ -133,7 +133,7 @@
         new Sort(new SortField(NAME_SORT_FIELD, SortField.Type.STRING, false)));
   }
 
-  private class QuerySource implements DataSource<ProjectState> {
+  private class QuerySource implements DataSource<ProjectData> {
     private final QueryOptions opts;
     private final Query query;
     private final Sort sort;
@@ -150,27 +150,27 @@
     }
 
     @Override
-    public ResultSet<ProjectState> read() throws OrmException {
+    public ResultSet<ProjectData> read() throws OrmException {
       IndexSearcher searcher = null;
       try {
         searcher = acquire();
         int realLimit = opts.start() + opts.limit();
         TopFieldDocs docs = searcher.search(query, realLimit, sort);
-        List<ProjectState> result = new ArrayList<>(docs.scoreDocs.length);
+        List<ProjectData> result = new ArrayList<>(docs.scoreDocs.length);
         for (int i = opts.start(); i < docs.scoreDocs.length; i++) {
           ScoreDoc sd = docs.scoreDocs[i];
           Document doc = searcher.doc(sd.doc, IndexUtils.projectFields(opts));
-          result.add(toProjectState(doc));
+          result.add(toProjectData(doc));
         }
-        final List<ProjectState> r = Collections.unmodifiableList(result);
-        return new ResultSet<ProjectState>() {
+        final List<ProjectData> r = Collections.unmodifiableList(result);
+        return new ResultSet<ProjectData>() {
           @Override
-          public Iterator<ProjectState> iterator() {
+          public Iterator<ProjectData> iterator() {
             return r.iterator();
           }
 
           @Override
-          public List<ProjectState> toList() {
+          public List<ProjectData> toList() {
             return r;
           }
 
@@ -193,8 +193,8 @@
     }
   }
 
-  private ProjectState toProjectState(Document doc) {
+  private ProjectData toProjectData(Document doc) {
     Project.NameKey nameKey = new Project.NameKey(doc.getField(NAME.getName()).stringValue());
-    return projectCache.get().get(nameKey);
+    return projectCache.get().get(nameKey).toProjectData();
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
index 19d2bb6..85d6a7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
@@ -24,7 +24,7 @@
 import com.google.gerrit.server.index.change.DummyChangeIndex;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.project.ProjectIndex;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.AbstractModule;
 
@@ -52,7 +52,7 @@
 
   private static class DummyProjectIndexFactory implements ProjectIndex.Factory {
     @Override
-    public ProjectIndex create(Schema<ProjectState> schema) {
+    public ProjectIndex create(Schema<ProjectData> schema) {
       throw new UnsupportedOperationException();
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
index 9323965..a416b0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
@@ -24,7 +24,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -40,7 +40,7 @@
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class AllProjectsIndexer extends SiteIndexer<Project.NameKey, ProjectState, ProjectIndex> {
+public class AllProjectsIndexer extends SiteIndexer<Project.NameKey, ProjectData, ProjectIndex> {
 
   private static final Logger log = LoggerFactory.getLogger(AllProjectsIndexer.class);
 
@@ -84,7 +84,7 @@
               () -> {
                 try {
                   projectCache.evict(name);
-                  index.replace(projectCache.get(name));
+                  index.replace(projectCache.get(name).toProjectData());
                   verboseWriter.println("Reindexed " + desc);
                   done.incrementAndGet();
                 } catch (Exception e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java
index ede7461..41bff05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java
@@ -21,13 +21,13 @@
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 
-public class IndexedProjectQuery extends IndexedQuery<Project.NameKey, ProjectState>
-    implements DataSource<ProjectState> {
+public class IndexedProjectQuery extends IndexedQuery<Project.NameKey, ProjectData>
+    implements DataSource<ProjectData> {
 
   public IndexedProjectQuery(
-      Index<Project.NameKey, ProjectState> index, Predicate<ProjectState> pred, QueryOptions opts)
+      Index<Project.NameKey, ProjectData> index, Predicate<ProjectData> pred, QueryOptions opts)
       throws QueryParseException {
     super(index, pred, opts.convertForBackend());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java
index 7b6bd96..c4f8e9e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java
@@ -21,25 +21,24 @@
 import com.google.common.collect.Iterables;
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.SchemaUtil;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 
 /** Index schema for projects. */
 public class ProjectField {
 
-  public static final FieldDef<ProjectState, String> NAME =
+  public static final FieldDef<ProjectData, String> NAME =
       exact("name").stored().build(p -> p.getProject().getName());
 
-  public static final FieldDef<ProjectState, String> DESCRIPTION =
+  public static final FieldDef<ProjectData, String> DESCRIPTION =
       fullText("description").build(p -> p.getProject().getDescription());
 
-  public static final FieldDef<ProjectState, String> PARENT_NAME =
+  public static final FieldDef<ProjectData, String> PARENT_NAME =
       exact("parent_name").build(p -> p.getProject().getParentName());
 
-  public static final FieldDef<ProjectState, Iterable<String>> NAME_PART =
+  public static final FieldDef<ProjectData, Iterable<String>> NAME_PART =
       prefix("name_part").buildRepeatable(p -> SchemaUtil.getNameParts(p.getProject().getName()));
 
-  public static final FieldDef<ProjectState, Iterable<String>> ANCESTOR_NAME =
+  public static final FieldDef<ProjectData, Iterable<String>> ANCESTOR_NAME =
       exact("ancestor_name")
-          .buildRepeatable(
-              p -> Iterables.transform(p.parents(), parent -> parent.getProject().getName()));
+          .buildRepeatable(p -> Iterables.transform(p.getAncestors(), n -> n.get()));
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java
index 80f4fa6..5fbdf04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java
@@ -18,16 +18,16 @@
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gerrit.server.query.project.ProjectPredicates;
 
-public interface ProjectIndex extends Index<Project.NameKey, ProjectState> {
+public interface ProjectIndex extends Index<Project.NameKey, ProjectData> {
 
   public interface Factory
-      extends IndexDefinition.IndexFactory<Project.NameKey, ProjectState, ProjectIndex> {}
+      extends IndexDefinition.IndexFactory<Project.NameKey, ProjectData, ProjectIndex> {}
 
   @Override
-  default Predicate<ProjectState> keyPredicate(Project.NameKey nameKey) {
+  default Predicate<ProjectData> keyPredicate(Project.NameKey nameKey) {
     return ProjectPredicates.name(nameKey);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java
index 89f2619..ee65a08 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java
@@ -17,13 +17,13 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 @Singleton
 public class ProjectIndexCollection
-    extends IndexCollection<Project.NameKey, ProjectState, ProjectIndex> {
+    extends IndexCollection<Project.NameKey, ProjectData, ProjectIndex> {
 
   @Inject
   @VisibleForTesting
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
index 8cdd28a..301f209 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
@@ -17,11 +17,11 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.Inject;
 
 public class ProjectIndexDefinition
-    extends IndexDefinition<Project.NameKey, ProjectState, ProjectIndex> {
+    extends IndexDefinition<Project.NameKey, ProjectData, ProjectIndex> {
 
   @Inject
   ProjectIndexDefinition(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java
index e50d08e..41d8820 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java
@@ -20,12 +20,12 @@
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 @Singleton
-public class ProjectIndexRewriter implements IndexRewriter<ProjectState> {
+public class ProjectIndexRewriter implements IndexRewriter<ProjectData> {
   private final ProjectIndexCollection indexes;
 
   @Inject
@@ -34,7 +34,7 @@
   }
 
   @Override
-  public Predicate<ProjectState> rewrite(Predicate<ProjectState> in, QueryOptions opts)
+  public Predicate<ProjectData> rewrite(Predicate<ProjectData> in, QueryOptions opts)
       throws QueryParseException {
     ProjectIndex index = indexes.getSearchIndex();
     checkNotNull(index, "no active search index configured for projects");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
index 368a056..2a51f32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
@@ -21,7 +21,7 @@
 import com.google.gerrit.index.Index;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
@@ -64,8 +64,8 @@
 
   @Override
   public void index(Project.NameKey nameKey) throws IOException {
-    for (Index<?, ProjectState> i : getWriteIndexes()) {
-      i.replace(projectCache.get(nameKey));
+    for (Index<?, ProjectData> i : getWriteIndexes()) {
+      i.replace(projectCache.get(nameKey).toProjectData());
     }
     fireProjectIndexedEvent(nameKey.get());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java
index 4ff6db1..ccece02 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java
@@ -18,11 +18,11 @@
 
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.SchemaDefinitions;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 
-public class ProjectSchemaDefinitions extends SchemaDefinitions<ProjectState> {
+public class ProjectSchemaDefinitions extends SchemaDefinitions<ProjectData> {
 
-  static final Schema<ProjectState> V1 =
+  static final Schema<ProjectData> V1 =
       schema(
           ProjectField.NAME,
           ProjectField.DESCRIPTION,
@@ -33,6 +33,6 @@
   public static final ProjectSchemaDefinitions INSTANCE = new ProjectSchemaDefinitions();
 
   private ProjectSchemaDefinitions() {
-    super("projects", ProjectState.class);
+    super("projects", ProjectData.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectData.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectData.java
new file mode 100644
index 0000000..407529d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectData.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 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.project;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.Project;
+
+public class ProjectData {
+  private final Project project;
+  private final ImmutableList<Project.NameKey> ancestors;
+
+  public ProjectData(Project project, Iterable<Project.NameKey> ancestors) {
+    this.project = project;
+    this.ancestors = ImmutableList.copyOf(ancestors);
+  }
+
+  public Project getProject() {
+    return project;
+  }
+
+  public ImmutableList<Project.NameKey> getAncestors() {
+    return ancestors;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 3015164..bd6386c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -553,6 +553,10 @@
     }
   }
 
+  public ProjectData toProjectData() {
+    return new ProjectData(getProject(), parents().transform(s -> s.getProject().getNameKey()));
+  }
+
   private String readFile(Path p) throws IOException {
     return Files.exists(p) ? new String(Files.readAllBytes(p), UTF_8) : null;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java
index ec33030..998bdb2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java
@@ -105,12 +105,12 @@
     }
 
     try {
-      QueryResult<ProjectState> result = queryProcessor.query(queryBuilder.parse(query));
-      List<ProjectState> projects = result.entities();
+      QueryResult<ProjectData> result = queryProcessor.query(queryBuilder.parse(query));
+      List<ProjectData> pds = result.entities();
 
-      ArrayList<ProjectInfo> projectInfos = Lists.newArrayListWithCapacity(projects.size());
-      for (ProjectState projectState : projects) {
-        projectInfos.add(json.format(projectState.getProject()));
+      ArrayList<ProjectInfo> projectInfos = Lists.newArrayListWithCapacity(pds.size());
+      for (ProjectData pd : pds) {
+        projectInfos.add(json.format(pd.getProject()));
       }
       return projectInfos;
     } catch (QueryParseException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
index 07b1722..20032ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
@@ -19,11 +19,11 @@
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.ProjectPermission;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gwtorm.server.OrmException;
 
-public class ProjectIsVisibleToPredicate extends IsVisibleToPredicate<ProjectState> {
+public class ProjectIsVisibleToPredicate extends IsVisibleToPredicate<ProjectData> {
   protected final PermissionBackend permissionBackend;
   protected final CurrentUser user;
 
@@ -34,10 +34,10 @@
   }
 
   @Override
-  public boolean match(ProjectState projectState) throws OrmException {
+  public boolean match(ProjectData pd) throws OrmException {
     return permissionBackend
         .user(user)
-        .project(projectState.getProject().getNameKey())
+        .project(pd.getProject().getNameKey())
         .testOrFalse(ProjectPermission.READ);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index 45e65d0..dbaf4a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -19,15 +19,15 @@
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.index.project.ProjectField;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 
 public class ProjectPredicates {
-  public static Predicate<ProjectState> name(Project.NameKey nameKey) {
+  public static Predicate<ProjectData> name(Project.NameKey nameKey) {
     return new ProjectPredicate(ProjectField.NAME, nameKey.get());
   }
 
-  static class ProjectPredicate extends IndexPredicate<ProjectState> {
-    ProjectPredicate(FieldDef<ProjectState, ?> def, String value) {
+  static class ProjectPredicate extends IndexPredicate<ProjectData> {
+    ProjectPredicate(FieldDef<ProjectData, ?> def, String value) {
       super(def, value);
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index 2457f33..608ec9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -20,14 +20,14 @@
 import com.google.gerrit.index.query.QueryBuilder;
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.Inject;
 
 /** Parses a query string meant to be applied to project objects. */
-public class ProjectQueryBuilder extends QueryBuilder<ProjectState> {
+public class ProjectQueryBuilder extends QueryBuilder<ProjectData> {
   public static final String FIELD_LIMIT = "limit";
 
-  private static final QueryBuilder.Definition<ProjectState, ProjectQueryBuilder> mydef =
+  private static final QueryBuilder.Definition<ProjectData, ProjectQueryBuilder> mydef =
       new QueryBuilder.Definition<>(ProjectQueryBuilder.class);
 
   @Inject
@@ -36,12 +36,12 @@
   }
 
   @Operator
-  public Predicate<ProjectState> name(String name) {
+  public Predicate<ProjectData> name(String name) {
     return ProjectPredicates.name(new Project.NameKey(name));
   }
 
   @Operator
-  public Predicate<ProjectState> limit(String query) throws QueryParseException {
+  public Predicate<ProjectData> limit(String query) throws QueryParseException {
     Integer limit = Ints.tryParse(query);
     if (limit == null) {
       throw error("Invalid limit: " + query);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
index 234a67b..1e181e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
@@ -29,7 +29,7 @@
 import com.google.gerrit.server.index.project.ProjectIndexRewriter;
 import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
 import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectData;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -39,7 +39,7 @@
  * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
  * holding on to a single instance.
  */
-public class ProjectQueryProcessor extends QueryProcessor<ProjectState> {
+public class ProjectQueryProcessor extends QueryProcessor<ProjectData> {
   private final PermissionBackend permissionBackend;
   private final Provider<CurrentUser> userProvider;
 
@@ -72,7 +72,7 @@
   }
 
   @Override
-  protected Predicate<ProjectState> enforceVisibility(Predicate<ProjectState> pred) {
+  protected Predicate<ProjectData> enforceVisibility(Predicate<ProjectData> pred) {
     return new AndSource<>(
         pred, new ProjectIsVisibleToPredicate(permissionBackend, userProvider.get()), start);
   }