Rely on Gerrit change_notes cache for filtering

The git-refs-filter makes sense only when it provides
a significant performance improvement from a network
and CPU perspective.

Do not scan for all refs on the repository because it
would consume too much CPU and I/O. Rely on Gerrit's
pre-defined caching abilities on change notes.

Change-Id: I2ad7496e2f8f5a1860245de6460fc64bfe8c0905
diff --git a/src/main/java/com/googlesource/gerrit/modules/gitrefsfilter/ForProjectWrapper.java b/src/main/java/com/googlesource/gerrit/modules/gitrefsfilter/ForProjectWrapper.java
index 0544665..426519e 100644
--- a/src/main/java/com/googlesource/gerrit/modules/gitrefsfilter/ForProjectWrapper.java
+++ b/src/main/java/com/googlesource/gerrit/modules/gitrefsfilter/ForProjectWrapper.java
@@ -14,36 +14,25 @@
 
 package com.googlesource.gerrit.modules.gitrefsfilter;
 
-import static com.google.common.collect.ImmutableList.toImmutableList;
-
-import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.api.access.CoreOrPluginProjectPermission;
 import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
 import com.google.gerrit.server.permissions.PermissionBackend.ForProject;
 import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.Set;
-import java.util.stream.Stream;
+import java.util.stream.Collectors;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 
 public class ForProjectWrapper extends ForProject {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private final ForProject defaultForProject;
   private final Project.NameKey project;
   private final ChangeNotes.Factory changeNotesFactory;
@@ -82,20 +71,13 @@
   @Override
   public Collection<Ref> filter(Collection<Ref> refs, Repository repo, RefFilterOptions opts)
       throws PermissionBackendException {
-    Collection<Ref> filteredRefs = new ArrayList<>();
-    Collection<Ref> defaultFilteredRefs =
-        defaultForProject.filter(refs, repo, opts); // FIXME: can we filter the closed refs here?
-    Set<String> openChangesRefs = openChangesByScan(repo);
-
-    for (Ref ref : defaultFilteredRefs) {
-      String refName = ref.getName();
-      if (!isChangeRef(refName)
-          || (isOpen(openChangesRefs, refName) && !isChangeMetaRef(refName))) {
-        filteredRefs.add(ref);
-      }
-    }
-
-    return filteredRefs;
+    return defaultForProject.filter(refs, repo, opts).parallelStream()
+        .filter(
+            (ref) -> {
+              String refName = ref.getName();
+              return (!isChangeRef(refName) || (!isChangeMetaRef(refName) && isOpen(refName)));
+            })
+        .collect(Collectors.toList());
   }
 
   private boolean isChangeRef(String changeKey) {
@@ -106,10 +88,10 @@
     return isChangeRef(changeKey) && changeKey.endsWith("/meta");
   }
 
-  private boolean isOpen(Set<String> openChangesRefs, String changeKey) {
-    // Parse changeKey as refs/changes/NN/<change num>/PP
-    String changeRefWithoutPatchset = changeKey.substring(0, changeKey.lastIndexOf('/') + 1);
-    return openChangesRefs.contains(changeRefWithoutPatchset);
+  private boolean isOpen(String refName) {
+    Change.Id changeId = Change.Id.fromRef(refName);
+    ChangeNotes changeNotes = changeNotesFactory.create(project, changeId);
+    return changeNotes.getChange().getStatus().isOpen();
   }
 
   @Override
@@ -121,34 +103,4 @@
   public String resourcePath() {
     return defaultForProject.resourcePath();
   }
-
-  private Set<String> openChangesByScan(Repository repo) {
-    Set<String> result = new HashSet<>();
-    Stream<ChangeNotesResult> s;
-    try {
-      s = changeNotesFactory.scan(repo, project);
-    } catch (IOException e) {
-      logger.atSevere().withCause(e).log(
-          "Cannot load changes for project %s, assuming no changes are visible", project);
-      return Collections.emptySet();
-    }
-
-    for (ChangeNotesResult notesResult : s.collect(toImmutableList())) {
-      Change change = toNotes(notesResult).getChange();
-      if (change.getStatus().isOpen()) {
-        result.add(change.getId().toRefPrefix());
-      }
-    }
-    return result;
-  }
-
-  @Nullable
-  private ChangeNotes toNotes(ChangeNotesResult r) {
-    if (r.error().isPresent()) {
-      logger.atWarning().withCause(r.error().get()).log(
-          "Failed to load change %s in %s", r.id(), project);
-      return null;
-    }
-    return r.notes();
-  }
 }