Merge changes I6707a293,Ie7e7397c

* changes:
  Create ChangeResource with ChangeNotes and CurrentUser
  Remove ChangeResource#getControl() and migrate callers
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 242ff51..13dcb77 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -102,7 +102,6 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.MutableNotesMigration;
 import com.google.gerrit.server.notedb.PatchSetState;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.Util;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -253,7 +252,6 @@
   @Inject private InProcessProtocol inProcessProtocol;
   @Inject private Provider<AnonymousUser> anonymousUser;
   @Inject private SchemaFactory<ReviewDb> reviewDbProvider;
-  @Inject private ChangeControl.GenericFactory changeControlFactory;
 
   private List<Repository> toClose;
 
@@ -1113,8 +1111,7 @@
   protected ChangeResource parseChangeResource(String changeId) throws Exception {
     List<ChangeNotes> notes = changeFinder.find(changeId);
     assertThat(notes).hasSize(1);
-    return changeResourceFactory.create(
-        changeControlFactory.controlFor(notes.get(0), atrScope.get().getUser()));
+    return changeResourceFactory.create(notes.get(0), atrScope.get().getUser());
   }
 
   protected String createGroup(String name) throws Exception {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
index 9401c88..f9c4808 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ActionJson.java
@@ -165,7 +165,7 @@
     }
 
     Iterable<UiAction.Description> descs =
-        uiActions.from(changeViews, changeResourceFactory.create(ctl));
+        uiActions.from(changeViews, changeResourceFactory.create(ctl.getNotes(), ctl.getUser()));
 
     // The followup action is a client-side only operation that does not
     // have a server side handler. It must be manually registered into the
@@ -198,7 +198,7 @@
       List<ActionVisitor> visitors,
       ChangeInfo changeInfo,
       RevisionInfo revisionInfo) {
-    if (!rsrc.getControl().getUser().isIdentifiedUser()) {
+    if (!rsrc.getUser().isIdentifiedUser()) {
       return ImmutableMap.of();
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ApplyFix.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ApplyFix.java
index 2ae9a86..fa26eec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ApplyFix.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ApplyFix.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -46,17 +47,20 @@
   private final FixReplacementInterpreter fixReplacementInterpreter;
   private final ChangeEditModifier changeEditModifier;
   private final ChangeEditJson changeEditJson;
+  private final ProjectCache projectCache;
 
   @Inject
   public ApplyFix(
       GitRepositoryManager gitRepositoryManager,
       FixReplacementInterpreter fixReplacementInterpreter,
       ChangeEditModifier changeEditModifier,
-      ChangeEditJson changeEditJson) {
+      ChangeEditJson changeEditJson,
+      ProjectCache projectCache) {
     this.gitRepositoryManager = gitRepositoryManager;
     this.fixReplacementInterpreter = fixReplacementInterpreter;
     this.changeEditModifier = changeEditModifier;
     this.changeEditJson = changeEditJson;
+    this.projectCache = projectCache;
   }
 
   @Override
@@ -65,7 +69,7 @@
           ResourceNotFoundException, PermissionBackendException {
     RevisionResource revisionResource = fixResource.getRevisionResource();
     Project.NameKey project = revisionResource.getProject();
-    ProjectState projectState = revisionResource.getControl().getProjectControl().getProjectState();
+    ProjectState projectState = projectCache.checkedGet(project);
     PatchSet patchSet = revisionResource.getPatchSet();
     ObjectId patchSetCommitId = ObjectId.fromString(patchSet.getRevision().get());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
index 0064281..a4d12a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -48,6 +48,7 @@
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -378,6 +379,7 @@
 
   public static class Get implements RestReadView<ChangeEditResource> {
     private final FileContentUtil fileContentUtil;
+    private final ProjectCache projectCache;
 
     @Option(
       name = "--base",
@@ -387,8 +389,9 @@
     private boolean base;
 
     @Inject
-    Get(FileContentUtil fileContentUtil) {
+    Get(FileContentUtil fileContentUtil, ProjectCache projectCache) {
       this.fileContentUtil = fileContentUtil;
+      this.projectCache = projectCache;
     }
 
     @Override
@@ -397,7 +400,7 @@
         ChangeEdit edit = rsrc.getChangeEdit();
         return Response.ok(
             fileContentUtil.getContent(
-                rsrc.getChangeResource().getControl().getProjectControl().getProjectState(),
+                projectCache.checkedGet(rsrc.getChangeResource().getProject()),
                 base
                     ? ObjectId.fromString(edit.getBasePatchSet().getRevision().get())
                     : edit.getEditCommit(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index c2b93a6..56eb46b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -346,7 +346,7 @@
   }
 
   public ChangeInfo format(RevisionResource rsrc) throws OrmException {
-    ChangeData cd = changeDataFactory.create(db.get(), rsrc.getControl());
+    ChangeData cd = changeDataFactory.create(db.get(), rsrc.getChangeResource());
     return format(cd, Optional.of(rsrc.getPatchSet().getId()), true);
   }
 
@@ -1318,7 +1318,7 @@
       RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
       rw.parseBody(commit);
       if (setCommit) {
-        out.commit = toCommit(ctl, rw, commit, has(WEB_LINKS), fillCommit);
+        out.commit = toCommit(project, rw, commit, has(WEB_LINKS), fillCommit);
       }
       if (addFooters) {
         Ref ref = repo.exactRef(ctl.getChange().getDest().get());
@@ -1345,7 +1345,9 @@
         && userProvider.get().isIdentifiedUser()) {
 
       actionJson.addRevisionActions(
-          changeInfo, out, new RevisionResource(changeResourceFactory.create(ctl), in));
+          changeInfo,
+          out,
+          new RevisionResource(changeResourceFactory.create(ctl.getNotes(), ctl.getUser()), in));
     }
 
     if (gpgApi.isEnabled() && has(PUSH_CERTIFICATES)) {
@@ -1362,9 +1364,8 @@
   }
 
   CommitInfo toCommit(
-      ChangeControl ctl, RevWalk rw, RevCommit commit, boolean addLinks, boolean fillCommit)
+      Project.NameKey project, RevWalk rw, RevCommit commit, boolean addLinks, boolean fillCommit)
       throws IOException {
-    Project.NameKey project = ctl.getProject().getNameKey();
     CommitInfo info = new CommitInfo();
     if (fillCommit) {
       info.commit = commit.name();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 69438d5..4166bf7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -17,6 +17,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.extensions.restapi.RestResource;
@@ -35,18 +36,23 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.TypeLiteral;
 import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 import org.eclipse.jgit.lib.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ChangeResource implements RestResource, HasETag {
+  private static final Logger log = LoggerFactory.getLogger(ChangeResource.class);
+
   /**
    * JSON format version number for ETag computations.
    *
@@ -59,7 +65,7 @@
       new TypeLiteral<RestView<ChangeResource>>() {};
 
   public interface Factory {
-    ChangeResource create(ChangeControl ctl);
+    ChangeResource create(ChangeNotes notes, CurrentUser user);
   }
 
   private static final String ZERO_ID_STRING = ObjectId.zeroId().name();
@@ -70,7 +76,9 @@
   private final PatchSetUtil patchSetUtil;
   private final PermissionBackend permissionBackend;
   private final StarredChangesUtil starredChangesUtil;
-  private final ChangeControl control;
+  private final ProjectCache projectCache;
+  private final ChangeNotes notes;
+  private final CurrentUser user;
 
   @Inject
   ChangeResource(
@@ -80,41 +88,40 @@
       PatchSetUtil patchSetUtil,
       PermissionBackend permissionBackend,
       StarredChangesUtil starredChangesUtil,
-      @Assisted ChangeControl control) {
+      ProjectCache projectCache,
+      @Assisted ChangeNotes notes,
+      @Assisted CurrentUser user) {
     this.db = db;
     this.accountCache = accountCache;
     this.approvalUtil = approvalUtil;
     this.patchSetUtil = patchSetUtil;
     this.permissionBackend = permissionBackend;
     this.starredChangesUtil = starredChangesUtil;
-    this.control = control;
+    this.projectCache = projectCache;
+    this.notes = notes;
+    this.user = user;
   }
 
   public PermissionBackend.ForChange permissions() {
-    return permissionBackend.user(getControl().getUser()).change(getNotes());
-  }
-
-  public ChangeControl getControl() {
-    return control;
+    return permissionBackend.user(user).change(notes);
   }
 
   public CurrentUser getUser() {
-    return getControl().getUser();
+    return user;
   }
 
   public Change.Id getId() {
-    return getControl().getId();
+    return notes.getChangeId();
   }
 
   /** @return true if {@link #getUser()} is the change's owner. */
   public boolean isUserOwner() {
-    CurrentUser user = getControl().getUser();
     Account.Id owner = getChange().getOwner();
     return user.isIdentifiedUser() && user.asIdentifiedUser().getAccountId().equals(owner);
   }
 
   public Change getChange() {
-    return getControl().getChange();
+    return notes.getChange();
   }
 
   public Project.NameKey getProject() {
@@ -122,7 +129,7 @@
   }
 
   public ChangeNotes getNotes() {
-    return getControl().getNotes();
+    return notes;
   }
 
   // This includes all information relevant for ETag computation
@@ -147,7 +154,7 @@
     }
     try {
       patchSetUtil
-          .byChange(db.get(), getNotes())
+          .byChange(db.get(), notes)
           .stream()
           .map(ps -> ps.getUploader())
           .forEach(accounts::add);
@@ -159,7 +166,7 @@
       // set of accounts that posted a message is too expensive. However everyone who posts a
       // message is automatically added as reviewer. Hence if we include removed reviewers we can
       // be sure that we have all accounts that posted messages on the change.
-      accounts.addAll(approvalUtil.getReviewers(db.get(), getNotes()).all());
+      accounts.addAll(approvalUtil.getReviewers(db.get(), notes).all());
     } catch (OrmException e) {
       // This ETag will be invalidated if it loads next time.
     }
@@ -167,7 +174,7 @@
 
     ObjectId noteId;
     try {
-      noteId = getNotes().loadRevision();
+      noteId = notes.loadRevision();
     } catch (OrmException e) {
       noteId = null; // This ETag will be invalidated if it loads next time.
     }
@@ -175,14 +182,21 @@
     // TODO(dborowitz): Include more NoteDb and other related refs, e.g. drafts
     // and edits.
 
-    for (ProjectState p : control.getProjectControl().getProjectState().tree()) {
+    Iterable<ProjectState> projectStateTree;
+    try {
+      projectStateTree = projectCache.checkedGet(getProject()).tree();
+    } catch (IOException e) {
+      log.error(String.format("could not load project %s while computing etag", getProject()));
+      projectStateTree = ImmutableList.of();
+    }
+
+    for (ProjectState p : projectStateTree) {
       hashObjectId(h, p.getConfig().getRevision(), buf);
     }
   }
 
   @Override
   public String getETag() {
-    CurrentUser user = control.getUser();
     Hasher h = Hashing.murmur3_128().newHasher();
     if (user.isIdentifiedUser()) {
       h.putString(starredChangesUtil.getObjectId(user.getAccountId(), getId()).name(), UTF_8);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index 3556b42..b363b83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -31,7 +31,6 @@
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.QueryChanges;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -98,7 +97,7 @@
     if (!canRead(change)) {
       throw new ResourceNotFoundException(id);
     }
-    return changeResourceFactory.create(controlFor(change));
+    return changeResourceFactory.create(change, user.get());
   }
 
   public ChangeResource parse(Change.Id id)
@@ -114,15 +113,15 @@
     if (!canRead(change)) {
       throw new ResourceNotFoundException(toIdString(id));
     }
-    return changeResourceFactory.create(controlFor(change));
+    return changeResourceFactory.create(change, user.get());
   }
 
   private static IdString toIdString(Change.Id id) {
     return IdString.fromDecoded(id.toString());
   }
 
-  public ChangeResource parse(ChangeControl control) {
-    return changeResourceFactory.create(control);
+  public ChangeResource parse(ChangeNotes notes, CurrentUser user) {
+    return changeResourceFactory.create(notes, user);
   }
 
   @SuppressWarnings("unchecked")
@@ -134,13 +133,4 @@
   private boolean canRead(ChangeNotes notes) throws PermissionBackendException {
     return permissionBackend.user(user).change(notes).database(db).test(ChangePermission.READ);
   }
-
-  private ChangeControl controlFor(ChangeNotes notes) throws ResourceNotFoundException {
-    try {
-      return changeControlFactory.controlFor(notes, user.get());
-    } catch (NoSuchChangeException e) {
-      throw new ResourceNotFoundException(
-          String.format("Change %s not found", notes.getChangeId()));
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
index a36a1d3..157928b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -25,21 +25,30 @@
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import java.io.IOException;
 
 public class Check
     implements RestReadView<ChangeResource>, RestModifyView<ChangeResource, FixInput> {
   private final PermissionBackend permissionBackend;
   private final Provider<CurrentUser> user;
   private final ChangeJson.Factory jsonFactory;
+  private final ProjectControl.GenericFactory projectControlFactory;
 
   @Inject
-  Check(PermissionBackend permissionBackend, Provider<CurrentUser> user, ChangeJson.Factory json) {
+  Check(
+      PermissionBackend permissionBackend,
+      Provider<CurrentUser> user,
+      ChangeJson.Factory json,
+      ProjectControl.GenericFactory projectControlFactory) {
     this.permissionBackend = permissionBackend;
     this.user = user;
     this.jsonFactory = json;
+    this.projectControlFactory = projectControlFactory;
   }
 
   @Override
@@ -49,8 +58,10 @@
 
   @Override
   public Response<ChangeInfo> apply(ChangeResource rsrc, FixInput input)
-      throws RestApiException, OrmException, PermissionBackendException {
-    if (!rsrc.isUserOwner() && !rsrc.getControl().getProjectControl().isOwner()) {
+      throws RestApiException, OrmException, PermissionBackendException, NoSuchProjectException,
+          IOException {
+    if (!rsrc.isUserOwner()
+        && !projectControlFactory.controlFor(rsrc.getProject(), rsrc.getUser()).isOwner()) {
       permissionBackend.user(user).check(GlobalPermission.MAINTAIN_SERVER);
     }
     return Response.withMustRevalidate(newChangeJson().fix(input).format(rsrc));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
index f3c5f0a..f980ade 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPick.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -53,6 +54,7 @@
   private final Provider<CurrentUser> user;
   private final CherryPickChange cherryPickChange;
   private final ChangeJson.Factory json;
+  private final ProjectControl.GenericFactory projectControlFactory;
 
   @Inject
   CherryPick(
@@ -60,12 +62,14 @@
       Provider<CurrentUser> user,
       RetryHelper retryHelper,
       CherryPickChange cherryPickChange,
-      ChangeJson.Factory json) {
+      ChangeJson.Factory json,
+      ProjectControl.GenericFactory projectControlFactory) {
     super(retryHelper);
     this.permissionBackend = permissionBackend;
     this.user = user;
     this.cherryPickChange = cherryPickChange;
     this.json = json;
+    this.projectControlFactory = projectControlFactory;
   }
 
   @Override
@@ -81,7 +85,7 @@
     }
 
     String refName = RefNames.fullName(input.destination);
-    CreateChange.checkValidCLA(rsrc.getControl().getProjectControl());
+    CreateChange.checkValidCLA(projectControlFactory.controlFor(rsrc.getProject(), rsrc.getUser()));
     permissionBackend
         .user(user)
         .project(rsrc.getChange().getProject())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
index 347070e..68db189 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
@@ -71,10 +71,7 @@
       throws RestApiException, UpdateException {
     try (BatchUpdate bu =
         updateFactory.create(
-            db.get(),
-            rsrc.getChange().getProject(),
-            rsrc.getControl().getUser(),
-            TimeUtil.nowTs())) {
+            db.get(), rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
       Op op = new Op(rsrc.getComment().key);
       bu.addOp(rsrc.getChange().getId(), op);
       bu.execute();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
index fd425ef..b6b8088 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
@@ -67,6 +68,7 @@
   private final Provider<DeleteChangeOp> deleteChangeOpProvider;
   private final DynamicItem<AccountPatchReviewStore> accountPatchReviewStore;
   private final boolean allowDrafts;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Inject
   public DeleteDraftPatchSet(
@@ -76,7 +78,8 @@
       PatchSetUtil psUtil,
       Provider<DeleteChangeOp> deleteChangeOpProvider,
       DynamicItem<AccountPatchReviewStore> accountPatchReviewStore,
-      @GerritServerConfig Config cfg) {
+      @GerritServerConfig Config cfg,
+      ChangeControl.GenericFactory changeControlFactory) {
     super(retryHelper);
     this.db = db;
     this.patchSetInfoFactory = patchSetInfoFactory;
@@ -84,6 +87,7 @@
     this.deleteChangeOpProvider = deleteChangeOpProvider;
     this.accountPatchReviewStore = accountPatchReviewStore;
     this.allowDrafts = cfg.getBoolean("change", "allowDrafts", true);
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -220,7 +224,9 @@
               allowDrafts
                   && rsrc.getPatchSet().isDraft()
                   && psUtil.byChange(db.get(), rsrc.getNotes()).size() > 1
-                  && rsrc.getControl().canDeleteDraft(db.get()));
+                  && changeControlFactory
+                      .controlFor(rsrc.getNotes(), rsrc.getUser())
+                      .canDeleteDraft(db.get()));
     } catch (OrmException e) {
       throw new IllegalStateException(e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
index 827dfcd..311a25c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DownloadContent.java
@@ -18,7 +18,7 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
@@ -27,22 +27,24 @@
 
 public class DownloadContent implements RestReadView<FileResource> {
   private final FileContentUtil fileContentUtil;
+  private final ProjectCache projectCache;
 
   @Option(name = "--parent")
   private Integer parent;
 
   @Inject
-  DownloadContent(FileContentUtil fileContentUtil) {
+  DownloadContent(FileContentUtil fileContentUtil, ProjectCache projectCache) {
     this.fileContentUtil = fileContentUtil;
+    this.projectCache = projectCache;
   }
 
   @Override
   public BinaryResult apply(FileResource rsrc)
       throws ResourceNotFoundException, IOException, NoSuchChangeException, OrmException {
     String path = rsrc.getPatchKey().get();
-    ProjectState projectState =
-        rsrc.getRevision().getControl().getProjectControl().getProjectState();
-    ObjectId revstr = ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get());
-    return fileContentUtil.downloadContent(projectState, revstr, path, parent);
+    RevisionResource rev = rsrc.getRevision();
+    ObjectId revstr = ObjectId.fromString(rev.getPatchSet().getRevision().get());
+    return fileContentUtil.downloadContent(
+        projectCache.checkedGet(rev.getProject()), revstr, path, parent);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
index 781216c..0b1b15d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DraftCommentResource.java
@@ -16,11 +16,10 @@
 
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.CurrentUser;
 import com.google.inject.TypeLiteral;
 
 public class DraftCommentResource implements RestResource {
@@ -35,12 +34,12 @@
     this.comment = c;
   }
 
-  public ChangeControl getControl() {
-    return rev.getControl();
+  public CurrentUser getUser() {
+    return rev.getUser();
   }
 
   public Change getChange() {
-    return getControl().getChange();
+    return rev.getChange();
   }
 
   public PatchSet getPatchSet() {
@@ -54,8 +53,4 @@
   String getId() {
     return comment.key.uuid;
   }
-
-  Account.Id getAuthorId() {
-    return getControl().getUser().getAccountId();
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
index 371127b..7269a60 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetArchive.java
@@ -58,7 +58,7 @@
       throw new MethodNotAllowedException("zip format is disabled");
     }
     boolean close = true;
-    final Repository repo = repoManager.openRepository(rsrc.getControl().getProject().getNameKey());
+    final Repository repo = repoManager.openRepository(rsrc.getProject());
     try {
       final RevCommit commit;
       String name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
index e33021ea..694379e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetCommit.java
@@ -55,7 +55,7 @@
       String rev = rsrc.getPatchSet().getRevision().get();
       RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
       rw.parseBody(commit);
-      CommitInfo info = json.noOptions().toCommit(rsrc.getControl(), rw, commit, addLinks, true);
+      CommitInfo info = json.noOptions().toCommit(rsrc.getProject(), rw, commit, addLinks, true);
       Response<CommitInfo> r = Response.ok(info);
       if (rsrc.isCacheable()) {
         r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index 5433653..f6b24b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.patch.ComparisonType;
 import com.google.gerrit.server.patch.Text;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -44,6 +45,7 @@
   private final GitRepositoryManager gitManager;
   private final PatchSetUtil psUtil;
   private final FileContentUtil fileContentUtil;
+  private final ProjectCache projectCache;
 
   @Option(name = "--parent")
   private Integer parent;
@@ -53,11 +55,13 @@
       Provider<ReviewDb> db,
       GitRepositoryManager gitManager,
       PatchSetUtil psUtil,
-      FileContentUtil fileContentUtil) {
+      FileContentUtil fileContentUtil,
+      ProjectCache projectCache) {
     this.db = db;
     this.gitManager = gitManager;
     this.psUtil = psUtil;
     this.fileContentUtil = fileContentUtil;
+    this.projectCache = projectCache;
   }
 
   @Override
@@ -76,7 +80,7 @@
           .base64();
     }
     return fileContentUtil.getContent(
-        rsrc.getRevision().getControl().getProjectControl().getProjectState(),
+        projectCache.checkedGet(rsrc.getRevision().getProject()),
         ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get()),
         path,
         parent);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
index eec318b..b743a97 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -46,6 +46,7 @@
 import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.git.LargeObjectException;
 import com.google.gerrit.server.patch.PatchScriptFactory;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
@@ -83,6 +84,7 @@
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
   private final Revisions revisions;
   private final WebLinks webLinks;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Option(name = "--base", metaVar = "REVISION")
   String base;
@@ -111,11 +113,13 @@
       ProjectCache projectCache,
       PatchScriptFactory.Factory patchScriptFactoryFactory,
       Revisions revisions,
-      WebLinks webLinks) {
+      WebLinks webLinks,
+      ChangeControl.GenericFactory changeControlFactory) {
     this.projectCache = projectCache;
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
     this.revisions = revisions;
     this.webLinks = webLinks;
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -135,33 +139,20 @@
 
     PatchScriptFactory psf;
     PatchSet basePatchSet = null;
+    ChangeControl ctl =
+        changeControlFactory.controlFor(
+            resource.getRevision().getNotes(), resource.getRevision().getUser());
+    PatchSet.Id pId = resource.getPatchKey().getParentKey();
+    String fileName = resource.getPatchKey().getFileName();
     if (base != null) {
       RevisionResource baseResource =
           revisions.parse(resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
       basePatchSet = baseResource.getPatchSet();
-      psf =
-          patchScriptFactoryFactory.create(
-              resource.getRevision().getControl(),
-              resource.getPatchKey().getFileName(),
-              basePatchSet.getId(),
-              resource.getPatchKey().getParentKey(),
-              prefs);
+      psf = patchScriptFactoryFactory.create(ctl, fileName, basePatchSet.getId(), pId, prefs);
     } else if (parentNum > 0) {
-      psf =
-          patchScriptFactoryFactory.create(
-              resource.getRevision().getControl(),
-              resource.getPatchKey().getFileName(),
-              parentNum - 1,
-              resource.getPatchKey().getParentKey(),
-              prefs);
+      psf = patchScriptFactoryFactory.create(ctl, fileName, parentNum - 1, pId, prefs);
     } else {
-      psf =
-          patchScriptFactoryFactory.create(
-              resource.getRevision().getControl(),
-              resource.getPatchKey().getFileName(),
-              null,
-              resource.getPatchKey().getParentKey(),
-              prefs);
+      psf = patchScriptFactoryFactory.create(ctl, fileName, null, pId, prefs);
     }
 
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
index 9d40df4..88677d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
@@ -80,7 +80,7 @@
       List<CommitInfo> result = new ArrayList<>(commits.size());
       ChangeJson changeJson = json.noOptions();
       for (RevCommit c : commits) {
-        result.add(changeJson.toCommit(rsrc.getControl(), rw, c, addLinks, true));
+        result.add(changeJson.toCommit(rsrc.getProject(), rw, c, addLinks, true));
       }
       return createResponse(rsrc, result);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
index 2275e06..b59c17c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPatch.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
 import java.io.IOException;
@@ -62,8 +61,7 @@
   @Override
   public BinaryResult apply(RevisionResource rsrc)
       throws ResourceConflictException, IOException, ResourceNotFoundException {
-    Project.NameKey project = rsrc.getControl().getProject().getNameKey();
-    final Repository repo = repoManager.openRepository(project);
+    final Repository repo = repoManager.openRepository(rsrc.getProject());
     boolean close = true;
     try {
       final RevWalk rw = new RevWalk(repo);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java
index c849134..3573ef7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -52,6 +53,7 @@
   private final ChangeNotes.Factory notesFactory;
   private final Provider<ReviewDb> dbProvider;
   private final PatchSetUtil psUtil;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Option(
     name = "--claimed-original",
@@ -68,13 +70,15 @@
       ProjectCache projectCache,
       ChangeNotes.Factory notesFactory,
       Provider<ReviewDb> dbProvider,
-      PatchSetUtil psUtil) {
+      PatchSetUtil psUtil,
+      ChangeControl.GenericFactory changeControlFactory) {
     this.mergeUtilFactory = mergeUtilFactory;
     this.repoManager = repoManager;
     this.projectCache = projectCache;
     this.notesFactory = notesFactory;
     this.dbProvider = dbProvider;
     this.psUtil = psUtil;
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -84,7 +88,9 @@
     PatchSet currentPatchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
     if (currentPatchSet == null) {
       throw new ResourceConflictException("current revision is missing");
-    } else if (!rsrc.getControl().isPatchVisible(currentPatchSet, dbProvider.get())) {
+    } else if (!changeControlFactory
+        .controlFor(rsrc.getNotes(), rsrc.getUser())
+        .isPatchVisible(currentPatchSet, dbProvider.get())) {
       throw new AuthException("current revision not accessible");
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
index cb77fd1..2a7bd4b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRevisionActions.java
@@ -65,14 +65,14 @@
   @Override
   public String getETag(RevisionResource rsrc) {
     Hasher h = Hashing.murmur3_128().newHasher();
-    CurrentUser user = rsrc.getControl().getUser();
+    CurrentUser user = rsrc.getUser();
     try {
       rsrc.getChangeResource().prepareETag(h, user);
       h.putBoolean(Submit.wholeTopicEnabled(config));
       ReviewDb db = dbProvider.get();
       ChangeSet cs = mergeSuperSet.get().completeChangeSet(db, rsrc.getChange(), user);
       for (ChangeData cd : cs.changes()) {
-        changeResourceFactory.create(cd.changeControl()).prepareETag(h, user);
+        changeResourceFactory.create(cd.notes(), user).prepareETag(h, user);
       }
       h.putBoolean(cs.furtherHiddenChanges());
     } catch (IOException | OrmException | PermissionBackendException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index cafd73b..929529d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -117,7 +117,7 @@
       return result;
     }
 
-    ChangeData cd = changeDataFactory.create(db.get(), resource.getControl());
+    ChangeData cd = changeDataFactory.create(db.get(), resource.getChangeResource());
     result.submitType = getSubmitType(cd, ps);
 
     try (Repository git = gitManager.openRepository(change.getProject())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index ca62719..81edada 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -97,7 +97,6 @@
 import com.google.gerrit.server.permissions.LabelPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -146,7 +145,7 @@
   private static final int DEFAULT_ROBOT_COMMENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
 
   private final Provider<ReviewDb> db;
-  private final ChangesCollection changes;
+  private final ChangeResource.Factory changeResourceFactory;
   private final ChangeData.Factory changeDataFactory;
   private final ApprovalsUtil approvalsUtil;
   private final ChangeMessagesUtil cmUtil;
@@ -167,7 +166,7 @@
   PostReview(
       Provider<ReviewDb> db,
       RetryHelper retryHelper,
-      ChangesCollection changes,
+      ChangeResource.Factory changeResourceFactory,
       ChangeData.Factory changeDataFactory,
       ApprovalsUtil approvalsUtil,
       ChangeMessagesUtil cmUtil,
@@ -185,7 +184,7 @@
       ProjectCache projectCache) {
     super(retryHelper);
     this.db = db;
-    this.changes = changes;
+    this.changeResourceFactory = changeResourceFactory;
     this.changeDataFactory = changeDataFactory;
     this.commentsUtil = commentsUtil;
     this.psUtil = psUtil;
@@ -470,8 +469,8 @@
           String.format("on_behalf_of account %s cannot see change", reviewer.getAccountId()));
     }
 
-    ChangeControl ctl = rev.getControl().forUser(reviewer);
-    return new RevisionResource(changes.parse(ctl), rev.getPatchSet());
+    return new RevisionResource(
+        changeResourceFactory.create(rev.getNotes(), reviewer), rev.getPatchSet());
   }
 
   private void checkLabels(
@@ -571,7 +570,7 @@
   }
 
   private Set<String> getAffectedFilePaths(RevisionResource revision) throws OrmException {
-    ChangeData changeData = changeDataFactory.create(db.get(), revision.getControl());
+    ChangeData changeData = changeDataFactory.create(db.get(), revision.getChangeResource());
     return new HashSet<>(changeData.filePaths(revision.getPatchSet()));
   }
 
@@ -1108,7 +1107,7 @@
       if (ctx.getAccountId().equals(ctx.getChange().getOwner())) {
         return true;
       }
-      ChangeData cd = changeDataFactory.create(db.get(), ctx.getControl());
+      ChangeData cd = changeDataFactory.create(db.get(), ctx);
       ReviewerSet reviewers = cd.reviewers();
       if (reviewers.byState(REVIEWER).contains(ctx.getAccountId())) {
         return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
index 5724941..3c83f81 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PreviewSubmit.java
@@ -35,7 +35,6 @@
 import com.google.gerrit.server.git.MergeOpRepoManager;
 import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gwtorm.server.OrmException;
@@ -104,8 +103,7 @@
     if (!change.getStatus().isOpen()) {
       throw new PreconditionFailedException("change is " + ChangeUtil.status(change));
     }
-    ChangeControl control = rsrc.getControl();
-    if (!control.getUser().isIdentifiedUser()) {
+    if (!rsrc.getUser().isIdentifiedUser()) {
       throw new MethodNotAllowedException("Anonymous users cannot submit");
     }
 
@@ -116,8 +114,7 @@
       throws OrmException, RestApiException, UpdateException, IOException, ConfigInvalidException,
           PermissionBackendException {
     ReviewDb db = dbProvider.get();
-    ChangeControl control = rsrc.getControl();
-    IdentifiedUser caller = control.getUser().asIdentifiedUser();
+    IdentifiedUser caller = rsrc.getUser().asIdentifiedUser();
     Change change = rsrc.getChange();
 
     @SuppressWarnings("resource") // Returned BinaryResult takes ownership and handles closing.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
index d3f87cf..6b28ba3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishChangeEdit.java
@@ -26,6 +26,8 @@
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -75,20 +77,27 @@
 
     private final ChangeEditUtil editUtil;
     private final NotifyUtil notifyUtil;
+    private final ProjectControl.GenericFactory projectControlFactory;
 
     @Inject
-    Publish(RetryHelper retryHelper, ChangeEditUtil editUtil, NotifyUtil notifyUtil) {
+    Publish(
+        RetryHelper retryHelper,
+        ChangeEditUtil editUtil,
+        NotifyUtil notifyUtil,
+        ProjectControl.GenericFactory projectControlFactory) {
       super(retryHelper);
       this.editUtil = editUtil;
       this.notifyUtil = notifyUtil;
+      this.projectControlFactory = projectControlFactory;
     }
 
     @Override
     protected Response<?> applyImpl(
         BatchUpdate.Factory updateFactory, ChangeResource rsrc, PublishChangeEditInput in)
-        throws IOException, OrmException, RestApiException, UpdateException,
-            ConfigInvalidException {
-      CreateChange.checkValidCLA(rsrc.getControl().getProjectControl());
+        throws IOException, OrmException, RestApiException, UpdateException, ConfigInvalidException,
+            NoSuchProjectException {
+      CreateChange.checkValidCLA(
+          projectControlFactory.controlFor(rsrc.getProject(), rsrc.getUser()));
       Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getNotes(), rsrc.getUser());
       if (!edit.isPresent()) {
         throw new ResourceConflictException(
@@ -99,7 +108,8 @@
       }
       editUtil.publish(
           updateFactory,
-          rsrc.getControl(),
+          rsrc.getNotes(),
+          rsrc.getUser(),
           edit.get(),
           in.notify,
           notifyUtil.resolveAccounts(in.notifyDetails));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index f448d92..39828ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.update.BatchUpdate;
@@ -83,6 +84,7 @@
   private final ReplacePatchSetSender.Factory replacePatchSetFactory;
   private final DraftPublished draftPublished;
   private final ProjectCache projectCache;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Inject
   public PublishDraftPatchSet(
@@ -95,7 +97,8 @@
       Provider<ReviewDb> dbProvider,
       ReplacePatchSetSender.Factory replacePatchSetFactory,
       DraftPublished draftPublished,
-      ProjectCache projectCache) {
+      ProjectCache projectCache,
+      ChangeControl.GenericFactory changeControlFactory) {
     super(retryHelper);
     this.accountResolver = accountResolver;
     this.approvalsUtil = approvalsUtil;
@@ -106,6 +109,7 @@
     this.replacePatchSetFactory = replacePatchSetFactory;
     this.draftPublished = draftPublished;
     this.projectCache = projectCache;
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -138,7 +142,10 @@
           .setLabel("Publish")
           .setTitle(String.format("Publish revision %d", rsrc.getPatchSet().getPatchSetId()))
           .setVisible(
-              rsrc.getPatchSet().isDraft() && rsrc.getControl().canPublish(dbProvider.get()));
+              rsrc.getPatchSet().isDraft()
+                  && changeControlFactory
+                      .controlFor(rsrc.getNotes(), rsrc.getUser())
+                      .canPublish(dbProvider.get()));
     } catch (OrmException e) {
       throw new IllegalStateException(e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
index 62742ec..4c9cf23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
@@ -28,7 +28,6 @@
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
@@ -71,11 +70,10 @@
       throws UpdateException, RestApiException, PermissionBackendException {
     rsrc.permissions().check(ChangePermission.EDIT_DESCRIPTION);
 
-    ChangeControl ctl = rsrc.getControl();
     Op op = new Op(input != null ? input : new Input(), rsrc.getPatchSet().getId());
     try (BatchUpdate u =
         updateFactory.create(
-            dbProvider.get(), rsrc.getChange().getProject(), ctl.getUser(), TimeUtil.nowTs())) {
+            dbProvider.get(), rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
       u.addOp(rsrc.getChange().getId(), op);
       u.execute();
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
index 5412a69..c5693c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -91,10 +91,7 @@
 
     try (BatchUpdate bu =
         updateFactory.create(
-            db.get(),
-            rsrc.getChange().getProject(),
-            rsrc.getControl().getUser(),
-            TimeUtil.nowTs())) {
+            db.get(), rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
       Op op = new Op(rsrc.getComment().key, in);
       bu.addOp(rsrc.getChange().getId(), op);
       bu.execute();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java
index a1a5ab7..f994fc9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java
@@ -35,6 +35,8 @@
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -70,6 +72,8 @@
   private final PermissionBackend permissionBackend;
   private final PatchSetUtil psUtil;
   private final NotifyUtil notifyUtil;
+  private final ProjectCache projectCache;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Inject
   PutMessage(
@@ -81,7 +85,9 @@
       PermissionBackend permissionBackend,
       @GerritPersonIdent PersonIdent gerritIdent,
       PatchSetUtil psUtil,
-      NotifyUtil notifyUtil) {
+      NotifyUtil notifyUtil,
+      ProjectCache projectCache,
+      ChangeControl.GenericFactory changeControlFactory) {
     super(retryHelper);
     this.repositoryManager = repositoryManager;
     this.currentUserProvider = currentUserProvider;
@@ -91,6 +97,8 @@
     this.permissionBackend = permissionBackend;
     this.psUtil = psUtil;
     this.notifyUtil = notifyUtil;
+    this.projectCache = projectCache;
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -101,7 +109,9 @@
     PatchSet ps = psUtil.current(db.get(), resource.getNotes());
     if (ps == null) {
       throw new ResourceConflictException("current revision is missing");
-    } else if (!resource.getControl().isPatchVisible(ps, db.get())) {
+    } else if (!changeControlFactory
+        .controlFor(resource.getNotes(), resource.getUser())
+        .isPatchVisible(ps, db.get())) {
       throw new AuthException("current revision not accessible");
     }
 
@@ -112,7 +122,7 @@
 
     ensureCanEditCommitMessage(resource.getNotes());
     ensureChangeIdIsCorrect(
-        resource.getControl().getProjectControl().getProjectState().isRequireChangeID(),
+        projectCache.checkedGet(resource.getProject()).isRequireChangeID(),
         resource.getChange().getKey().get(),
         sanitizedCommitMessage);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index e25cd01..19f5055 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.RebaseUtil.Base;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -73,6 +74,8 @@
   private final RebaseUtil rebaseUtil;
   private final ChangeJson.Factory json;
   private final Provider<ReviewDb> dbProvider;
+  private final Provider<CurrentUser> userProvider;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Inject
   public Rebase(
@@ -81,13 +84,17 @@
       RebaseChangeOp.Factory rebaseFactory,
       RebaseUtil rebaseUtil,
       ChangeJson.Factory json,
-      Provider<ReviewDb> dbProvider) {
+      Provider<ReviewDb> dbProvider,
+      Provider<CurrentUser> userProvider,
+      ChangeControl.GenericFactory changeControlFactory) {
     super(retryHelper);
     this.repoManager = repoManager;
     this.rebaseFactory = rebaseFactory;
     this.rebaseUtil = rebaseUtil;
     this.json = json;
     this.dbProvider = dbProvider;
+    this.userProvider = userProvider;
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -97,7 +104,7 @@
           NoSuchChangeException, PermissionBackendException {
     rsrc.permissions().database(dbProvider).check(ChangePermission.REBASE);
 
-    ChangeControl control = rsrc.getControl();
+    ChangeControl control = changeControlFactory.controlFor(rsrc.getNotes(), rsrc.getUser());
     Change change = rsrc.getChange();
     try (Repository repo = repoManager.openRepository(change.getProject());
         ObjectInserter oi = repo.newObjectInserter();
@@ -151,13 +158,14 @@
       throw new ResourceConflictException("base revision is missing: " + str);
     }
     PatchSet.Id baseId = base.patchSet().getId();
-    if (!base.control().isPatchVisible(base.patchSet(), db)) {
+    ChangeControl baseCtl = changeControlFactory.controlFor(base.notes(), userProvider.get());
+    if (!baseCtl.isPatchVisible(base.patchSet(), db)) {
       throw new AuthException("base revision not accessible: " + str);
     } else if (change.getId().equals(baseId.getParentKey())) {
       throw new ResourceConflictException("cannot rebase change onto itself");
     }
 
-    Change baseChange = base.control().getChange();
+    Change baseChange = base.notes().getChange();
     if (!baseChange.getProject().equals(change.getProject())) {
       throw new ResourceConflictException(
           "base change is in wrong project: " + baseChange.getProject());
@@ -221,12 +229,18 @@
       extends RetryingRestModifyView<ChangeResource, RebaseInput, ChangeInfo> {
     private final PatchSetUtil psUtil;
     private final Rebase rebase;
+    private final ChangeControl.GenericFactory changeControlFactory;
 
     @Inject
-    CurrentRevision(RetryHelper retryHelper, PatchSetUtil psUtil, Rebase rebase) {
+    CurrentRevision(
+        RetryHelper retryHelper,
+        PatchSetUtil psUtil,
+        Rebase rebase,
+        ChangeControl.GenericFactory changeControlFactory) {
       super(retryHelper);
       this.psUtil = psUtil;
       this.rebase = rebase;
+      this.changeControlFactory = changeControlFactory;
     }
 
     @Override
@@ -237,7 +251,9 @@
       PatchSet ps = psUtil.current(rebase.dbProvider.get(), rsrc.getNotes());
       if (ps == null) {
         throw new ResourceConflictException("current revision is missing");
-      } else if (!rsrc.getControl().isPatchVisible(ps, rebase.dbProvider.get())) {
+      } else if (!changeControlFactory
+          .controlFor(rsrc.getNotes(), rsrc.getUser())
+          .isPatchVisible(ps, rebase.dbProvider.get())) {
         throw new AuthException("current revision not accessible");
       }
       return rebase.applyImpl(updateFactory, new RevisionResource(rsrc, ps), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index 465a1b3..db705e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -163,7 +163,8 @@
     rebasedCommit = rebaseCommit(ctx, original, baseCommit, newCommitMessage);
     Base base =
         rebaseUtil.parseBase(
-            new RevisionResource(changeResourceFactory.create(ctl), originalPatchSet),
+            new RevisionResource(
+                changeResourceFactory.create(ctl.getNotes(), ctl.getUser()), originalPatchSet),
             baseCommitId.name());
 
     rebasedPatchSetId =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
index 173f522..fdb1cfc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
@@ -80,14 +79,14 @@
 
   @AutoValue
   abstract static class Base {
-    private static Base create(ChangeControl ctl, PatchSet ps) {
-      if (ctl == null) {
+    private static Base create(ChangeNotes notes, PatchSet ps) {
+      if (notes == null) {
         return null;
       }
-      return new AutoValue_RebaseUtil_Base(ctl, ps);
+      return new AutoValue_RebaseUtil_Base(notes, ps);
     }
 
-    abstract ChangeControl control();
+    abstract ChangeNotes notes();
 
     abstract PatchSet patchSet();
   }
@@ -99,20 +98,20 @@
     PatchSet.Id basePatchSetId = PatchSet.Id.fromRef(base);
     if (basePatchSetId != null) {
       Change.Id baseChangeId = basePatchSetId.getParentKey();
-      ChangeControl baseCtl = controlFor(rsrc, baseChangeId);
-      if (baseCtl != null) {
+      ChangeNotes baseNotes = notesFor(rsrc, baseChangeId);
+      if (baseNotes != null) {
         return Base.create(
-            controlFor(rsrc, basePatchSetId.getParentKey()),
-            psUtil.get(db, baseCtl.getNotes(), basePatchSetId));
+            notesFor(rsrc, basePatchSetId.getParentKey()),
+            psUtil.get(db, baseNotes, basePatchSetId));
       }
     }
 
     // Try parsing base as a change number (assume current patch set).
     Integer baseChangeId = Ints.tryParse(base);
     if (baseChangeId != null) {
-      ChangeControl baseCtl = controlFor(rsrc, new Change.Id(baseChangeId));
-      if (baseCtl != null) {
-        return Base.create(baseCtl, psUtil.current(db, baseCtl.getNotes()));
+      ChangeNotes baseNotes = notesFor(rsrc, new Change.Id(baseChangeId));
+      if (baseNotes != null) {
+        return Base.create(baseNotes, psUtil.current(db, baseNotes));
       }
     }
 
@@ -124,19 +123,18 @@
           continue;
         }
         if (ret == null || ret.patchSet().getId().get() < ps.getId().get()) {
-          ret = Base.create(rsrc.getControl().getProjectControl().controlFor(cd.notes()), ps);
+          ret = Base.create(cd.notes(), ps);
         }
       }
     }
     return ret;
   }
 
-  private ChangeControl controlFor(RevisionResource rsrc, Change.Id id) throws OrmException {
+  private ChangeNotes notesFor(RevisionResource rsrc, Change.Id id) throws OrmException {
     if (rsrc.getChange().getId().equals(id)) {
-      return rsrc.getControl();
+      return rsrc.getNotes();
     }
-    ChangeNotes notes = notesFactory.createChecked(dbProvider.get(), rsrc.getProject(), id);
-    return rsrc.getControl().getProjectControl().controlFor(notes);
+    return notesFactory.createChecked(dbProvider.get(), rsrc.getProject(), id);
   }
 
   /**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index 941f4dc..cfb588f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -47,6 +47,8 @@
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
@@ -93,6 +95,7 @@
   private final PersonIdent serverIdent;
   private final ApprovalsUtil approvalsUtil;
   private final ChangeReverted changeReverted;
+  private final ProjectControl.GenericFactory projectControlFactory;
 
   @Inject
   Revert(
@@ -108,7 +111,8 @@
       ChangeJson.Factory json,
       @GerritPersonIdent PersonIdent serverIdent,
       ApprovalsUtil approvalsUtil,
-      ChangeReverted changeReverted) {
+      ChangeReverted changeReverted,
+      ProjectControl.GenericFactory projectControlFactory) {
     super(retryHelper);
     this.db = db;
     this.permissionBackend = permissionBackend;
@@ -122,19 +126,20 @@
     this.serverIdent = serverIdent;
     this.approvalsUtil = approvalsUtil;
     this.changeReverted = changeReverted;
+    this.projectControlFactory = projectControlFactory;
   }
 
   @Override
   public ChangeInfo applyImpl(
       BatchUpdate.Factory updateFactory, ChangeResource rsrc, RevertInput input)
       throws IOException, OrmException, RestApiException, UpdateException, NoSuchChangeException,
-          PermissionBackendException {
+          PermissionBackendException, NoSuchProjectException {
     Change change = rsrc.getChange();
     if (change.getStatus() != Change.Status.MERGED) {
       throw new ResourceConflictException("change is " + ChangeUtil.status(change));
     }
 
-    CreateChange.checkValidCLA(rsrc.getControl().getProjectControl());
+    CreateChange.checkValidCLA(projectControlFactory.controlFor(rsrc.getProject(), rsrc.getUser()));
     permissionBackend.user(rsrc.getUser()).ref(change.getDest()).check(CREATE_CHANGE);
 
     Change.Id revertId =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
index a582e2c..b9b2d1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RevisionResource.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.inject.TypeLiteral;
 import java.util.Optional;
 
@@ -62,12 +61,8 @@
     return change;
   }
 
-  public ChangeControl getControl() {
-    return getChangeResource().getControl();
-  }
-
   public Change getChange() {
-    return getControl().getChange();
+    return getChangeResource().getChange();
   }
 
   public Project.NameKey getProject() {
@@ -100,7 +95,7 @@
   }
 
   CurrentUser getUser() {
-    return getControl().getUser();
+    return getChangeResource().getUser();
   }
 
   RevisionResource doNotCache() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index 47edc48..fae911f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.edit.ChangeEdit;
 import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -45,17 +46,20 @@
   private final Provider<ReviewDb> dbProvider;
   private final ChangeEditUtil editUtil;
   private final PatchSetUtil psUtil;
+  private final ChangeControl.GenericFactory changeControlFactory;
 
   @Inject
   Revisions(
       DynamicMap<RestView<RevisionResource>> views,
       Provider<ReviewDb> dbProvider,
       ChangeEditUtil editUtil,
-      PatchSetUtil psUtil) {
+      PatchSetUtil psUtil,
+      ChangeControl.GenericFactory changeControlFactory) {
     this.views = views;
     this.dbProvider = dbProvider;
     this.editUtil = editUtil;
     this.psUtil = psUtil;
+    this.changeControlFactory = changeControlFactory;
   }
 
   @Override
@@ -97,7 +101,9 @@
   }
 
   private boolean visible(ChangeResource change, PatchSet ps) throws OrmException {
-    return change.getControl().isPatchVisible(ps, dbProvider.get());
+    return changeControlFactory
+        .controlFor(change.getNotes(), change.getUser())
+        .isPatchVisible(ps, dbProvider.get());
   }
 
   private List<RevisionResource> find(ChangeResource change, String id)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 74fd2b1..78aae96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -55,6 +55,7 @@
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -321,7 +322,12 @@
     }
 
     ReviewDb db = dbProvider.get();
-    ChangeData cd = changeDataFactory.create(db, resource.getControl());
+    ChangeData cd;
+    try {
+      cd = changeDataFactory.create(db, resource.getChangeResource());
+    } catch (NoSuchChangeException e) {
+      return null; // submit not visible
+    }
     try {
       MergeOp.checkSubmitRule(cd, false);
     } catch (ResourceConflictException e) {
@@ -333,7 +339,7 @@
 
     ChangeSet cs;
     try {
-      cs = mergeSuperSet.get().completeChangeSet(db, cd.change(), resource.getControl().getUser());
+      cs = mergeSuperSet.get().completeChangeSet(db, cd.change(), resource.getUser());
     } catch (OrmException | IOException | PermissionBackendException e) {
       throw new OrmRuntimeException(
           "Could not determine complete set of changes to be submitted", e);
@@ -508,17 +514,20 @@
     private final Submit submit;
     private final ChangeJson.Factory json;
     private final PatchSetUtil psUtil;
+    private final ChangeControl.GenericFactory changeControlFactory;
 
     @Inject
     CurrentRevision(
         Provider<ReviewDb> dbProvider,
         Submit submit,
         ChangeJson.Factory json,
-        PatchSetUtil psUtil) {
+        PatchSetUtil psUtil,
+        ChangeControl.GenericFactory changeControlFactory) {
       this.dbProvider = dbProvider;
       this.submit = submit;
       this.json = json;
       this.psUtil = psUtil;
+      this.changeControlFactory = changeControlFactory;
     }
 
     @Override
@@ -528,7 +537,9 @@
       PatchSet ps = psUtil.current(dbProvider.get(), rsrc.getNotes());
       if (ps == null) {
         throw new ResourceConflictException("current revision is missing");
-      } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
+      } else if (!changeControlFactory
+          .controlFor(rsrc.getNotes(), rsrc.getUser())
+          .isPatchVisible(ps, dbProvider.get())) {
         throw new AuthException("current revision not accessible");
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
index 75b60c7..bb4a357 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -50,6 +51,7 @@
 
   private final PermissionBackend permissionBackend;
   private final Provider<CurrentUser> self;
+  private final ProjectCache projectCache;
 
   @Inject
   SuggestChangeReviewers(
@@ -59,10 +61,12 @@
       PermissionBackend permissionBackend,
       Provider<CurrentUser> self,
       @GerritServerConfig Config cfg,
-      ReviewersUtil reviewersUtil) {
+      ReviewersUtil reviewersUtil,
+      ProjectCache projectCache) {
     super(av, identifiedUserFactory, dbProvider, cfg, reviewersUtil);
     this.permissionBackend = permissionBackend;
     this.self = self;
+    this.projectCache = projectCache;
   }
 
   @Override
@@ -74,7 +78,7 @@
     return reviewersUtil.suggestReviewers(
         rsrc.getNotes(),
         this,
-        rsrc.getControl().getProjectControl().getProjectState(),
+        projectCache.checkedGet(rsrc.getProject()),
         getVisibility(rsrc),
         excludeGroups);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index 9e93465..ad716e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -80,7 +80,10 @@
     input.filters = MoreObjects.firstNonNull(input.filters, filters);
     SubmitRuleEvaluator evaluator =
         new SubmitRuleEvaluator(
-            accountCache, accounts, emails, changeDataFactory.create(db.get(), rsrc.getControl()));
+            accountCache,
+            accounts,
+            emails,
+            changeDataFactory.create(db.get(), rsrc.getChangeResource()));
 
     List<SubmitRecord> records =
         evaluator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
index 5fb37e6..48b657e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitType.java
@@ -74,7 +74,10 @@
     input.filters = MoreObjects.firstNonNull(input.filters, filters);
     SubmitRuleEvaluator evaluator =
         new SubmitRuleEvaluator(
-            accountCache, accounts, emails, changeDataFactory.create(db.get(), rsrc.getControl()));
+            accountCache,
+            accounts,
+            emails,
+            changeDataFactory.create(db.get(), rsrc.getChangeResource()));
 
     SubmitTypeRecord rec =
         evaluator
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 917c005..565ed9e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -39,7 +39,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.RepoContext;
@@ -149,7 +148,8 @@
    * Promote change edit to patch set, by squashing the edit into its parent.
    *
    * @param updateFactory factory for creating updates.
-   * @param ctl the {@code ChangeControl} of the change to which the change edit belongs
+   * @param notes the {@code ChangeNotes} of the change to which the change edit belongs
+   * @param user the current user
    * @param edit change edit to publish
    * @param notify Notify handling that defines to whom email notifications should be sent after the
    *     change edit is published.
@@ -161,7 +161,8 @@
    */
   public void publish(
       BatchUpdate.Factory updateFactory,
-      ChangeControl ctl,
+      ChangeNotes notes,
+      CurrentUser user,
       final ChangeEdit edit,
       NotifyHandling notify,
       ListMultimap<RecipientType, Account.Id> accountsToNotify)
@@ -180,7 +181,7 @@
       PatchSet.Id psId = ChangeUtil.nextPatchSetId(repo, change.currentPatchSetId());
       PatchSetInserter inserter =
           patchSetInserterFactory
-              .create(ctl.getNotes(), psId, squashed)
+              .create(notes, psId, squashed)
               .setNotify(notify)
               .setAccountsToNotify(accountsToNotify);
 
@@ -202,7 +203,7 @@
       }
 
       try (BatchUpdate bu =
-          updateFactory.create(db.get(), change.getProject(), ctl.getUser(), TimeUtil.nowTs())) {
+          updateFactory.create(db.get(), change.getProject(), user, TimeUtil.nowTs())) {
         bu.setRepository(repo, rw, oi);
         bu.addOp(
             change.getId(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 8c67c72..de95f61 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -299,7 +299,7 @@
     recipients.add(
         getRecipientsFromFooters(ctx.getDb(), accountResolver, draft, commit.getFooterLines()));
     recipients.remove(ctx.getAccountId());
-    ChangeData cd = changeDataFactory.create(ctx.getDb(), ctx.getControl());
+    ChangeData cd = changeDataFactory.create(ctx.getDb(), ctx);
     MailRecipients oldRecipients = getRecipientsFromReviewers(cd.reviewers());
     Iterable<PatchSetApproval> newApprovals =
         approvalsUtil.addApprovalsForNewPatchSet(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 3a1c60a..ce68398 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -60,6 +60,7 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.account.Emails;
+import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.MergeabilityCache;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.TrackingFooters;
@@ -76,6 +77,7 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.update.ChangeContext;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
@@ -282,10 +284,12 @@
 
   public static class Factory {
     private final AssistedFactory assistedFactory;
+    private final ChangeControl.GenericFactory changeControlFactory;
 
     @Inject
-    Factory(AssistedFactory assistedFactory) {
+    Factory(AssistedFactory assistedFactory, ChangeControl.GenericFactory changeControlFactory) {
       this.assistedFactory = assistedFactory;
+      this.changeControlFactory = changeControlFactory;
     }
 
     public ChangeData create(ReviewDb db, Project.NameKey project, Change.Id id) {
@@ -310,6 +314,15 @@
           control.getNotes(),
           control);
     }
+
+    // TODO(hiesel): Remove these after ChangeControl is removed from ChangeData
+    public ChangeData create(ReviewDb db, ChangeResource rsrc) throws NoSuchChangeException {
+      return create(db, changeControlFactory.controlFor(rsrc.getNotes(), rsrc.getUser()));
+    }
+
+    public ChangeData create(ReviewDb db, ChangeContext ctx) throws NoSuchChangeException {
+      return create(db, changeControlFactory.controlFor(ctx.getNotes(), ctx.getUser()));
+    }
   }
 
   public interface AssistedFactory {