Merge "Fix "stream has already been operated upon or closed"" into stable-2.16
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index a1d90ef..ebf7107 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -392,6 +392,7 @@
     cfg.setBoolean("httpd", null, "requestLog", false);
     cfg.setBoolean("sshd", null, "requestLog", false);
     cfg.setBoolean("index", "lucene", "testInmemory", true);
+    cfg.setBoolean("index", null, "onlineUpgrade", false);
     cfg.setString("gitweb", null, "cgi", "");
     daemon.setEnableHttpd(desc.httpd());
     daemon.setLuceneModule(LuceneIndexModule.singleVersionAllLatest(0, isSlave(baseConfig)));
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 1e41985..15d6126 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -25,20 +25,15 @@
 public class ElasticIndexModule extends AbstractIndexModule {
   public static ElasticIndexModule singleVersionWithExplicitVersions(
       Map<String, Integer> versions, int threads, boolean slave) {
-    return new ElasticIndexModule(versions, threads, false, slave);
+    return new ElasticIndexModule(versions, threads, slave);
   }
 
-  public static ElasticIndexModule latestVersionWithOnlineUpgrade(boolean slave) {
-    return new ElasticIndexModule(null, 0, true, slave);
+  public static ElasticIndexModule latestVersion(boolean slave) {
+    return new ElasticIndexModule(null, 0, slave);
   }
 
-  public static ElasticIndexModule latestVersionWithoutOnlineUpgrade(boolean slave) {
-    return new ElasticIndexModule(null, 0, false, slave);
-  }
-
-  private ElasticIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
-    super(singleVersions, threads, onlineUpgrade, slave);
+  private ElasticIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
+    super(singleVersions, threads, slave);
   }
 
   @Override
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index db218d8..aadfeba 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -74,6 +74,8 @@
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.gerrit.server.index.OnlineUpgrader;
+import com.google.gerrit.server.index.VersionManager;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.receive.MailReceiver;
 import com.google.gerrit.server.mail.send.SmtpEmailSender;
@@ -339,21 +341,6 @@
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new LocalMergeSuperSetComputation.Module());
     modules.add(new AuditModule());
-
-    // Plugin module needs to be inserted *before* the index module.
-    // There is the concept of LifecycleModule, in Gerrit's own extension
-    // to Guice, which has these:
-    //  listener().to(SomeClassImplementingLifecycleListener.class);
-    // and the start() methods of each such listener are executed in the
-    // order they are declared.
-    // Makes sure that PluginLoader.start() is executed before the
-    // LuceneIndexModule.start() so that plugins get loaded and the respective
-    // Guice modules installed so that the on-line reindexing will happen
-    // with the proper classes (e.g. group backends, custom Prolog
-    // predicates) and the associated rules ready to be evaluated.
-    modules.add(new PluginModule());
-
-    modules.add(new RestApiModule());
     modules.add(new GpgModule(config));
     modules.add(new StartupChecks.Module());
 
@@ -361,6 +348,12 @@
     // work queue can get stuck waiting on index futures that will never return.
     modules.add(createIndexModule());
 
+    modules.add(new PluginModule());
+    if (VersionManager.getOnlineUpgrade(config)) {
+      modules.add(new OnlineUpgrader.Module());
+    }
+
+    modules.add(new RestApiModule());
     modules.add(new WorkQueue.Module());
     modules.add(new GerritInstanceNameModule());
     modules.add(
@@ -392,9 +385,9 @@
   private Module createIndexModule() {
     switch (indexType) {
       case LUCENE:
-        return LuceneIndexModule.latestVersionWithOnlineUpgrade(false);
+        return LuceneIndexModule.latestVersion(false);
       case ELASTICSEARCH:
-        return ElasticIndexModule.latestVersionWithOnlineUpgrade(false);
+        return ElasticIndexModule.latestVersion(false);
       default:
         throw new IllegalStateException("unsupported index.type = " + indexType);
     }
diff --git a/java/com/google/gerrit/lucene/LuceneIndexModule.java b/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 121b96b..302a2da 100644
--- a/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -29,29 +29,24 @@
 
 public class LuceneIndexModule extends AbstractIndexModule {
   public static LuceneIndexModule singleVersionAllLatest(int threads, boolean slave) {
-    return new LuceneIndexModule(ImmutableMap.of(), threads, false, slave);
+    return new LuceneIndexModule(ImmutableMap.of(), threads, slave);
   }
 
   public static LuceneIndexModule singleVersionWithExplicitVersions(
       Map<String, Integer> versions, int threads, boolean slave) {
-    return new LuceneIndexModule(versions, threads, false, slave);
+    return new LuceneIndexModule(versions, threads, slave);
   }
 
-  public static LuceneIndexModule latestVersionWithOnlineUpgrade(boolean slave) {
-    return new LuceneIndexModule(null, 0, true, slave);
-  }
-
-  public static LuceneIndexModule latestVersionWithoutOnlineUpgrade(boolean slave) {
-    return new LuceneIndexModule(null, 0, false, slave);
+  public static LuceneIndexModule latestVersion(boolean slave) {
+    return new LuceneIndexModule(null, 0, slave);
   }
 
   static boolean isInMemoryTest(Config cfg) {
     return cfg.getBoolean("index", "lucene", "testInmemory", false);
   }
 
-  private LuceneIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
-    super(singleVersions, threads, onlineUpgrade, slave);
+  private LuceneIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
+    super(singleVersions, threads, slave);
   }
 
   @Override
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index fa7662d..95b2af0 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -81,6 +81,7 @@
 import com.google.gerrit.server.group.PeriodicGroupIndexer;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.gerrit.server.index.OnlineUpgrader;
 import com.google.gerrit.server.index.VersionManager;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.receive.MailReceiver;
@@ -401,19 +402,6 @@
     modules.add(new DropWizardMetricMaker.RestModule());
     modules.add(new LogFileCompressor.Module());
 
-    // Plugin module needs to be inserted *before* the index module.
-    // There is the concept of LifecycleModule, in Gerrit's own extension
-    // to Guice, which has these:
-    //  listener().to(SomeClassImplementingLifecycleListener.class);
-    // and the start() methods of each such listener are executed in the
-    // order they are declared.
-    // Makes sure that PluginLoader.start() is executed before the
-    // LuceneIndexModule.start() so that plugins get loaded and the respective
-    // Guice modules installed so that the on-line reindexing will happen
-    // with the proper classes (e.g. group backends, custom Prolog
-    // predicates) and the associated rules ready to be evaluated.
-    modules.add(new PluginModule());
-
     // Index module shutdown must happen before work queue shutdown, otherwise
     // work queue can get stuck waiting on index futures that will never return.
     modules.add(createIndexModule());
@@ -449,6 +437,12 @@
       modules.add(new AuditModule());
     }
     modules.add(new SignedTokenEmailTokenVerifier.Module());
+    modules.add(new PluginModule());
+    if (VersionManager.getOnlineUpgrade(config)
+        // Schema upgrade is handled by OnlineNoteDbMigrator in this case.
+        && !migrateToNoteDb()) {
+      modules.add(new OnlineUpgrader.Module());
+    }
     modules.add(new RestApiModule());
     modules.add(new GpgModule(config));
     modules.add(new StartupChecks.Module());
@@ -517,19 +511,11 @@
     if (luceneModule != null) {
       return luceneModule;
     }
-    boolean onlineUpgrade =
-        VersionManager.getOnlineUpgrade(config)
-            // Schema upgrade is handled by OnlineNoteDbMigrator in this case.
-            && !migrateToNoteDb();
     switch (indexType) {
       case LUCENE:
-        return onlineUpgrade
-            ? LuceneIndexModule.latestVersionWithOnlineUpgrade(slave)
-            : LuceneIndexModule.latestVersionWithoutOnlineUpgrade(slave);
+        return LuceneIndexModule.latestVersion(slave);
       case ELASTICSEARCH:
-        return onlineUpgrade
-            ? ElasticIndexModule.latestVersionWithOnlineUpgrade(slave)
-            : ElasticIndexModule.latestVersionWithoutOnlineUpgrade(slave);
+        return ElasticIndexModule.latestVersion(slave);
       default:
         throw new IllegalStateException("unsupported index.type = " + indexType);
     }
diff --git a/java/com/google/gerrit/server/index/AbstractIndexModule.java b/java/com/google/gerrit/server/index/AbstractIndexModule.java
index 12aedfd..352ea4b 100644
--- a/java/com/google/gerrit/server/index/AbstractIndexModule.java
+++ b/java/com/google/gerrit/server/index/AbstractIndexModule.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.index;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.project.ProjectIndex;
@@ -35,17 +33,11 @@
 
   private final int threads;
   private final Map<String, Integer> singleVersions;
-  private final boolean onlineUpgrade;
   private final boolean slave;
 
-  protected AbstractIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
-    if (singleVersions != null) {
-      checkArgument(!onlineUpgrade, "online upgrade is incompatible with single version map");
-    }
+  protected AbstractIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
     this.singleVersions = singleVersions;
     this.threads = threads;
-    this.onlineUpgrade = onlineUpgrade;
     this.slave = slave;
   }
 
@@ -113,9 +105,6 @@
       Class<? extends VersionManager> versionManagerClass = getVersionManager();
       bind(VersionManager.class).to(versionManagerClass);
       listener().to(versionManagerClass);
-      if (onlineUpgrade) {
-        listener().to(OnlineUpgrader.class);
-      }
     }
   }
 }
diff --git a/java/com/google/gerrit/server/index/OnlineUpgrader.java b/java/com/google/gerrit/server/index/OnlineUpgrader.java
index 9fc3aa9..bfcf55f 100644
--- a/java/com/google/gerrit/server/index/OnlineUpgrader.java
+++ b/java/com/google/gerrit/server/index/OnlineUpgrader.java
@@ -15,10 +15,18 @@
 package com.google.gerrit.server.index;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.Inject;
 
 /** Listener to handle upgrading index schema versions at startup. */
 public class OnlineUpgrader implements LifecycleListener {
+  public static class Module extends LifecycleModule {
+    @Override
+    protected void configure() {
+      listener().to(OnlineUpgrader.class);
+    }
+  }
+
   private final VersionManager versionManager;
 
   @Inject
diff --git a/plugins/replication b/plugins/replication
index fe4162b..3e97cd9 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit fe4162b8c38d2a817a98effd0748d045872edef7
+Subproject commit 3e97cd967fc9a6413ae4265a4d7cb45a3730463a
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index 738d29e..a77c66d 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -179,11 +179,16 @@
     },
 
     _computeLinks(defaultLinks, userLinks, adminLinks, topMenus, docBaseUrl) {
-      const links = defaultLinks.slice();
+      const links = defaultLinks.map(menu => {
+        return {
+          title: menu.title,
+          links: menu.links.slice(),
+        };
+      });
       if (userLinks && userLinks.length > 0) {
         links.push({
           title: 'Your',
-          links: userLinks,
+          links: userLinks.slice(),
         });
       }
       const docLinks = this._getDocLinks(docBaseUrl, DOCUMENTATION_LINKS);
@@ -196,13 +201,20 @@
       }
       links.push({
         title: 'Browse',
-        links: adminLinks,
+        links: adminLinks.slice(),
       });
+      const topMenuLinks = [];
+      links.forEach(link => { topMenuLinks[link.title] = link.links; });
       for (const m of topMenus) {
-        links.push({
-          title: m.name,
-          links: m.items.map(this._fixCustomMenuItem),
-        });
+        const items = m.items.map(this._fixCustomMenuItem);
+        if (m.name in topMenuLinks) {
+          items.forEach(link => { topMenuLinks[m.name].push(link); });
+        } else {
+          links.push({
+            title: m.name,
+            links: topMenuLinks[m.name] = items,
+          });
+        }
       }
       return links;
     },
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index b6e64ec..582ca61 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -191,6 +191,121 @@
       }]);
     });
 
+    test('merge top menus', () => {
+      const adminLinks = [{
+        name: 'Repos',
+        url: '/repos',
+      }];
+      const topMenus = [{
+        name: 'Plugins',
+        items: [{
+          name: 'Manage',
+          target: '_blank',
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }],
+      }, {
+        name: 'Plugins',
+        items: [{
+          name: 'Create',
+          target: '_blank',
+          url: 'https://gerrit/plugins/plugin-manager/static/create.html',
+        }],
+      }];
+      assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+        title: 'Browse',
+        links: adminLinks,
+      }, {
+        title: 'Plugins',
+        links: [{
+          name: 'Manage',
+          external: true,
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }, {
+          name: 'Create',
+          external: true,
+          url: 'https://gerrit/plugins/plugin-manager/static/create.html',
+        }],
+      }]);
+    });
+
+    test('merge top menus in default links', () => {
+      const defaultLinks = [{
+        title: 'Faves',
+        links: [{
+          name: 'Pinterest',
+          url: 'https://pinterest.com',
+        }],
+      }];
+      const topMenus = [{
+        name: 'Faves',
+        items: [{
+          name: 'Manage',
+          target: '_blank',
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }],
+      }];
+      assert.deepEqual(element._computeLinks(defaultLinks, [], [], topMenus), [{
+        title: 'Faves',
+        links: defaultLinks[0].links.concat([{
+          name: 'Manage',
+          external: true,
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }]),
+      }, {
+        title: 'Browse',
+        links: [],
+      }]);
+    });
+
+    test('merge top menus in user links', () => {
+      const userLinks = [{
+        name: 'Facebook',
+        url: 'https://facebook.com',
+      }];
+      const topMenus = [{
+        name: 'Your',
+        items: [{
+          name: 'Manage',
+          target: '_blank',
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }],
+      }];
+      assert.deepEqual(element._computeLinks([], userLinks, [], topMenus), [{
+        title: 'Your',
+        links: userLinks.concat([{
+          name: 'Manage',
+          external: true,
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }]),
+      }, {
+        title: 'Browse',
+        links: [],
+      }]);
+    });
+
+    test('merge top menus in admin links', () => {
+      const adminLinks = [{
+        name: 'Repos',
+        url: '/repos',
+      }];
+      const topMenus = [{
+        name: 'Browse',
+        items: [{
+          name: 'Manage',
+          target: '_blank',
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }],
+      }];
+      assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+        title: 'Browse',
+        links: adminLinks.concat([{
+          name: 'Manage',
+          external: true,
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }]),
+      }]);
+    });
+
     test('register URL', () => {
       const config = {
         auth: {