Use persistent cache for GitHub groups

Retrieval of GitHub groups is a very expensive operation
and causes the start-up of Gerrit to be very sloppy.

By using a persistent cache we should avoid to rescan all
the GitHub groups when rescanning the users and changes.

Change-Id: Ib1c67f17d45ca63c54f5c801859cced42b756b54
diff --git a/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/groups/OrganizationStructure.java b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/groups/OrganizationStructure.java
new file mode 100644
index 0000000..d577a62
--- /dev/null
+++ b/github-oauth/src/main/java/com/googlesource/gerrit/plugins/github/groups/OrganizationStructure.java
@@ -0,0 +1,55 @@
+// Copyright (C) 2016 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.googlesource.gerrit.plugins.github.groups;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.common.base.MoreObjects;
+
+public class OrganizationStructure implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private HashMap<String, HashSet<String>> teams = new HashMap<>();
+
+  public Set<String> put(String organisation, String team) {
+    HashSet<String> userTeams =
+        MoreObjects.firstNonNull(teams.get(organisation),
+            new HashSet<String>());
+    userTeams.add(team);
+    return teams.put(organisation, userTeams);
+  }
+
+  public Set<String> keySet() {
+    return teams.keySet();
+  }
+
+  public Iterable<String> get(String organization) {
+    return teams.get(organization);
+  }
+
+  @Override
+  public String toString() {
+    return teams
+        .entrySet()
+        .stream()
+        .map(
+            org -> "Organization " + org.getKey() + " Teams: " + org.getValue())
+        .collect(Collectors.joining(" : "));
+  }
+}
\ No newline at end of file
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java
index 97eeff4..5370f03 100644
--- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java
+++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/group/GitHubGroupsCache.java
@@ -18,9 +18,7 @@
 
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
 import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.cache.CacheModule;
@@ -28,9 +26,8 @@
 import com.google.inject.Module;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
 import com.google.inject.name.Named;
-
+import com.googlesource.gerrit.plugins.github.groups.OrganizationStructure;
 import com.googlesource.gerrit.plugins.github.oauth.GitHubLogin;
 import com.googlesource.gerrit.plugins.github.oauth.UserScopedProvider;
 
@@ -55,7 +52,7 @@
   public static final String EVERYONE_TEAM_NAME = "Everyone";
 
   public static class OrganisationLoader extends
-      CacheLoader<String, Multimap<String, String>> {
+      CacheLoader<String, OrganizationStructure> {
     private static final Logger logger = LoggerFactory
         .getLogger(OrganisationLoader.class);
     private final UserScopedProvider<GitHubLogin> ghLoginProvider;
@@ -66,8 +63,8 @@
     }
 
     @Override
-    public Multimap<String, String> load(String username) throws Exception {
-      Multimap<String, String> orgsTeams = HashMultimap.create();
+    public OrganizationStructure load(String username) throws Exception {
+      OrganizationStructure orgsTeams = new OrganizationStructure();
       GitHubLogin ghLogin = ghLoginProvider.get(username);
       if (ghLogin == null) {
         logger.warn("Cannot login to GitHub on behalf of '{}'", username);
@@ -87,9 +84,11 @@
       return orgsTeams;
     }
 
-    private void loadOrganisationsAndTeams(String username, Multimap<String, String> orgsTeams,
-        GitHubLogin ghLogin) throws IOException {
-      logger.debug("Getting list of organisations/teams for user '{}'", username);
+    private void loadOrganisationsAndTeams(String username,
+        OrganizationStructure orgsTeams, GitHubLogin ghLogin)
+        throws IOException {
+      logger.debug("Getting list of organisations/teams for user '{}'",
+          username);
       Map<String, Set<GHTeam>> myOrganisationsLogins =
           ghLogin.getHub().getMyTeams();
       for (Entry<String, Set<GHTeam>> teamsOrg : myOrganisationsLogins
@@ -102,8 +101,10 @@
     }
 
     private void loadOrganisations(String username,
-        Multimap<String, String> orgsTeams, GitHubLogin ghLogin) throws IOException {
-      logger.debug("Getting list of public organisations for user '{}'", username);
+        OrganizationStructure orgsTeams, GitHubLogin ghLogin)
+        throws IOException {
+      logger.debug("Getting list of public organisations for user '{}'",
+          username);
       Set<String> organisations = ghLogin.getMyOrganisationsLogins();
       for (String org : organisations) {
         orgsTeams.put(org, EVERYONE_TEAM_NAME);
@@ -115,20 +116,20 @@
     return new CacheModule() {
       @Override
       protected void configure() {
-        cache(ORGS_CACHE_NAME, String.class,
-            new TypeLiteral<Multimap<String, String>>() {}).expireAfterWrite(
-            GROUPS_CACHE_TTL_MINS, MINUTES).loader(OrganisationLoader.class);
+        persist(ORGS_CACHE_NAME, String.class, OrganizationStructure.class)
+            .expireAfterWrite(GROUPS_CACHE_TTL_MINS, MINUTES).loader(
+                OrganisationLoader.class);
         bind(GitHubGroupsCache.class);
       }
     };
   }
 
-  private final LoadingCache<String, Multimap<String, String>> orgTeamsByUsername;
+  private final LoadingCache<String, OrganizationStructure> orgTeamsByUsername;
   private final Provider<IdentifiedUser> userProvider;
 
   @Inject
   GitHubGroupsCache(
-      @Named(ORGS_CACHE_NAME) LoadingCache<String, Multimap<String, String>> byUsername,
+      @Named(ORGS_CACHE_NAME) LoadingCache<String, OrganizationStructure> byUsername,
       Provider<IdentifiedUser> userProvider) {
     this.orgTeamsByUsername = byUsername;
     this.userProvider = userProvider;