Add intersection to GroupMembership

Support slower group backends by allowing a lookup of a lot of
groups at once to use intersection(). This method signature allows
the backend to filter the input query set and return the subset of
groups that the user is a member of.

Using a batch style interface can decrease latency when resolving
a large number of external groups identified through the internal
group system's nested includes feature.

Change-Id: I6926a3373589f439c8d694d36cdbe5fa8396a040
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
index d536c09..d7a97fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
@@ -40,6 +40,20 @@
   boolean containsAnyOf(Iterable<AccountGroup.UUID> groupIds);
 
   /**
+   * Returns a set containing an input member of {@code contains(id)} is true.
+   * <p>
+   * This is batch form of contains that returns specific group information.
+   * Implementors may implement the method as:
+   *
+   * <pre>
+   * Set&lt;AccountGroup.UUID&gt; r = Sets.newHashSet();
+   * for (AccountGroup.UUID id : groupIds)
+   *   if (contains(id)) r.add(id);
+   * </pre>
+   */
+  Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupIds);
+
+  /**
    * Returns the set of groups that can be determined by the implementation.
    * This may not return all groups the {@link #contains(AccountGroup.UUID)}
    * would return {@code true} for, but will at least contain all top level
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index d448fff..75b7f32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -70,6 +70,17 @@
     return findIncludedGroup(query);
   }
 
+  @Override
+  public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupIds) {
+    Set<AccountGroup.UUID> r = Sets.newHashSet();
+    for (AccountGroup.UUID id : groupIds) {
+      if (contains(id)) {
+        r.add(id);
+      }
+    }
+    return r;
+  }
+
   private boolean findIncludedGroup(Set<AccountGroup.UUID> query) {
     boolean found = false;
     while (!found && !groupQueue.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java
index 346f406..118940f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ListGroupMembership.java
@@ -24,7 +24,6 @@
  * GroupMembership over an explicit list.
  */
 public class ListGroupMembership implements GroupMembership {
-
   private final Set<AccountGroup.UUID> groups;
 
   public ListGroupMembership(Iterable<AccountGroup.UUID> groupIds) {
@@ -47,6 +46,11 @@
   }
 
   @Override
+  public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupIds) {
+    return Sets.intersection(ImmutableSet.copyOf(groupIds), groups);
+  }
+
+  @Override
   public Set<AccountGroup.UUID> getKnownGroups() {
     return Sets.newHashSet(groups);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index 1f670f6..d9c9257 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -156,13 +156,33 @@
       return false;
     }
 
-   @Override
-   public Set<AccountGroup.UUID> getKnownGroups() {
-     Set<AccountGroup.UUID> groups = Sets.newHashSet();
-     for (GroupMembership m : memberships.values()) {
-       groups.addAll(m.getKnownGroups());
-     }
-     return groups;
-   }
+    @Override
+    public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> uuids) {
+      Multimap<GroupMembership, AccountGroup.UUID> lookups =
+          ArrayListMultimap.create();
+      for (AccountGroup.UUID uuid : uuids) {
+        GroupMembership m = membership(uuid);
+        if (m == null) {
+          log.warn("Unknown GroupMembership for UUID: " + uuid);
+          continue;
+        }
+        lookups.put(m, uuid);
+      }
+      Set<AccountGroup.UUID> groups = Sets.newHashSet();
+      for (Map.Entry<GroupMembership, Collection<AccountGroup.UUID>> entry
+          : lookups.asMap().entrySet()) {
+        groups.addAll(entry.getKey().intersection(entry.getValue()));
+      }
+      return groups;
+    }
+
+    @Override
+    public Set<AccountGroup.UUID> getKnownGroups() {
+      Set<AccountGroup.UUID> groups = Sets.newHashSet();
+      for (GroupMembership m : memberships.values()) {
+        groups.addAll(m.getKnownGroups());
+      }
+      return groups;
+    }
   }
 }