Merge branch 'stable-2.16' into stable-3.0

* stable-2.16:
  Update git submodules
  Update git submodules
  Apply diff preferences immediately after clicking save
  Grant the InternalUser back access to changes
  Rewrite upload archive tests as real integration tests
  Add support for Elasticsearch version 7.7.*

Change-Id: I531f7336bdbe191b5d88bbc1282a59861250c897
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 2ab9c92..8ed9729 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -15,14 +15,16 @@
 package com.google.gerrit.elasticsearch;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Streams;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.io.CharStreams;
 import com.google.gerrit.common.Nullable;
@@ -30,6 +32,7 @@
 import com.google.gerrit.elasticsearch.builders.QueryBuilder;
 import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
 import com.google.gerrit.elasticsearch.bulk.DeleteRequest;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.FieldType;
 import com.google.gerrit.index.Index;
@@ -37,8 +40,12 @@
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.DataSource;
 import com.google.gerrit.index.query.FieldBundle;
+import com.google.gerrit.index.query.ListResultSet;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.ResultSet;
+import com.google.gerrit.proto.Protos;
+import com.google.gerrit.reviewdb.converter.ProtoConverter;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gson.Gson;
@@ -47,9 +54,7 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
-import com.google.gwtorm.protobuf.ProtobufCodec;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
+import com.google.protobuf.MessageLite;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -59,7 +64,6 @@
 import java.sql.Timestamp;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -88,14 +92,22 @@
   }
 
   protected static <T> List<T> decodeProtos(
-      JsonObject doc, String fieldName, ProtobufCodec<T> codec) {
+      JsonObject doc, String fieldName, ProtoConverter<?, T> converter) {
     JsonArray field = doc.getAsJsonArray(fieldName);
     if (field == null) {
       return null;
     }
-    return FluentIterable.from(field)
-        .transform(i -> codec.decode(decodeBase64(i.getAsString())))
-        .toList();
+    return Streams.stream(field)
+        .map(JsonElement::getAsString)
+        .map(AbstractElasticIndex::decodeBase64)
+        .map(bytes -> parseProtoFrom(bytes, converter))
+        .collect(toImmutableList());
+  }
+
+  protected static <P extends MessageLite, T> T parseProtoFrom(
+      byte[] bytes, ProtoConverter<P, T> converter) {
+    P message = Protos.parseUnchecked(converter.getParser(), bytes);
+    return converter.fromProto(message);
   }
 
   static String getContent(Response response) throws IOException {
@@ -149,23 +161,23 @@
   }
 
   @Override
-  public void markReady(boolean ready) throws IOException {
+  public void markReady(boolean ready) {
     IndexUtils.setReady(sitePaths, indexNameRaw, schema.getVersion(), ready);
   }
 
   @Override
-  public void delete(K id) throws IOException {
+  public void delete(K id) {
     String uri = getURI(type, BULK);
     Response response = postRequest(uri, getDeleteActions(id), getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
-      throw new IOException(
+      throw new StorageException(
           String.format("Failed to delete %s from index %s: %s", id, indexName, statusCode));
     }
   }
 
   @Override
-  public void deleteAll() throws IOException {
+  public void deleteAll() {
     // Delete the index, if it exists.
     String endpoint = indexName + client.adapter().indicesExistParams();
     Response response = performRequest("HEAD", endpoint);
@@ -174,7 +186,7 @@
       response = performRequest("DELETE", indexName);
       statusCode = response.getStatusLine().getStatusCode();
       if (statusCode != HttpStatus.SC_OK) {
-        throw new IOException(
+        throw new StorageException(
             String.format("Failed to delete index %s: %s", indexName, statusCode));
       }
     }
@@ -187,7 +199,7 @@
     statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       String error = String.format("Failed to create index %s: %s", indexName, statusCode);
-      throw new IOException(error);
+      throw new StorageException(error);
     }
   }
 
@@ -293,22 +305,25 @@
     return sortArray;
   }
 
-  protected String getURI(String type, String request) throws UnsupportedEncodingException {
-    String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
-    if (SEARCH.equals(request) && client.adapter().omitType()) {
-      return encodedIndexName + "/" + request;
+  protected String getURI(String type, String request) {
+    try {
+      String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
+      if (SEARCH.equals(request) && client.adapter().omitType()) {
+        return encodedIndexName + "/" + request;
+      }
+      String encodedTypeIfAny =
+          client.adapter().omitType() ? "" : "/" + URLEncoder.encode(type, UTF_8.toString());
+      return encodedIndexName + encodedTypeIfAny + "/" + request;
+    } catch (UnsupportedEncodingException e) {
+      throw new StorageException(e);
     }
-    String encodedTypeIfAny =
-        client.adapter().omitType() ? "" : "/" + URLEncoder.encode(type, UTF_8.toString());
-    return encodedIndexName + encodedTypeIfAny + "/" + request;
   }
 
-  protected Response postRequest(String uri, Object payload) throws IOException {
+  protected Response postRequest(String uri, Object payload) {
     return performRequest("POST", uri, payload);
   }
 
-  protected Response postRequest(String uri, Object payload, Map<String, String> params)
-      throws IOException {
+  protected Response postRequest(String uri, Object payload, Map<String, String> params) {
     return performRequest("POST", uri, payload, params);
   }
 
@@ -316,18 +331,16 @@
     return target.substring(0, target.length() - 1) + "," + addition.substring(1);
   }
 
-  private Response performRequest(String method, String uri) throws IOException {
+  private Response performRequest(String method, String uri) {
     return performRequest(method, uri, null);
   }
 
-  private Response performRequest(String method, String uri, @Nullable Object payload)
-      throws IOException {
+  private Response performRequest(String method, String uri, @Nullable Object payload) {
     return performRequest(method, uri, payload, Collections.emptyMap());
   }
 
   private Response performRequest(
-      String method, String uri, @Nullable Object payload, Map<String, String> params)
-      throws IOException {
+      String method, String uri, @Nullable Object payload, Map<String, String> params) {
     Request request = new Request(method, uri.startsWith("/") ? uri : "/" + uri);
     if (payload != null) {
       String payloadStr = payload instanceof String ? (String) payload : payload.toString();
@@ -336,7 +349,11 @@
     for (Map.Entry<String, String> entry : params.entrySet()) {
       request.addParameter(entry.getKey(), entry.getValue());
     }
-    return client.get().performRequest(request);
+    try {
+      return client.get().performRequest(request);
+    } catch (IOException e) {
+      throw new StorageException(e);
+    }
   }
 
   protected class ElasticQuerySource implements DataSource<V> {
@@ -364,18 +381,17 @@
     }
 
     @Override
-    public ResultSet<V> read() throws OrmException {
+    public ResultSet<V> read() {
       return readImpl(doc -> AbstractElasticIndex.this.fromDocument(doc, opts.fields()));
     }
 
     @Override
-    public ResultSet<FieldBundle> readRaw() throws OrmException {
+    public ResultSet<FieldBundle> readRaw() {
       return readImpl(AbstractElasticIndex.this::toFieldBundle);
     }
 
-    private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) throws OrmException {
+    private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) {
       try {
-        List<T> results = Collections.emptyList();
         String uri = getURI(index, SEARCH);
         Response response =
             performRequest(HttpPost.METHOD_NAME, uri, search, Collections.emptyMap());
@@ -386,36 +402,21 @@
               new JsonParser().parse(content).getAsJsonObject().getAsJsonObject("hits");
           if (obj.get("hits") != null) {
             JsonArray json = obj.getAsJsonArray("hits");
-            results = Lists.newArrayListWithCapacity(json.size());
+            ImmutableList.Builder<T> results = ImmutableList.builderWithExpectedSize(json.size());
             for (int i = 0; i < json.size(); i++) {
               T mapperResult = mapper.apply(json.get(i).getAsJsonObject());
               if (mapperResult != null) {
                 results.add(mapperResult);
               }
             }
+            return new ListResultSet<>(results.build());
           }
         } else {
           logger.atSevere().log(statusLine.getReasonPhrase());
         }
-        final List<T> r = Collections.unmodifiableList(results);
-        return new ResultSet<T>() {
-          @Override
-          public Iterator<T> iterator() {
-            return r.iterator();
-          }
-
-          @Override
-          public List<T> toList() {
-            return r;
-          }
-
-          @Override
-          public void close() {
-            // Do nothing.
-          }
-        };
+        return new ListResultSet<>(ImmutableList.of());
       } catch (IOException e) {
-        throw new OrmException(e);
+        throw new StorageException(e);
       }
     }
   }
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
index c71d536..a9b145b 100644
--- a/java/com/google/gerrit/elasticsearch/BUILD
+++ b/java/com/google/gerrit/elasticsearch/BUILD
@@ -6,16 +6,17 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/common:annotations",
+        "//java/com/google/gerrit/exceptions",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
         "//java/com/google/gerrit/index/project",
         "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/proto",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//lib:gson",
         "//lib:guava",
-        "//lib:gwtorm",
         "//lib:protobuf",
         "//lib/commons:codec",
         "//lib/commons:lang",
diff --git a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index 7f05817..f646efc 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
 import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.DataSource;
@@ -38,7 +39,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
 import java.util.Set;
 import org.apache.http.HttpStatus;
 import org.elasticsearch.client.Response;
@@ -73,14 +73,14 @@
   }
 
   @Override
-  public void replace(AccountState as) throws IOException {
+  public void replace(AccountState as) {
     BulkRequest bulk = new IndexRequest(getId(as), indexName).add(new UpdateRequest<>(schema, as));
 
     String uri = getURI(type, BULK);
     Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
-      throw new IOException(
+      throw new StorageException(
           String.format(
               "Failed to replace account %s in index %s: %s",
               as.getAccount().getId(), indexName, statusCode));
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index 6086b2d..ca2b1a8 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -14,9 +14,6 @@
 
 package com.google.gerrit.elasticsearch;
 
-import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.APPROVAL_CODEC;
-import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.CHANGE_CODEC;
-import static com.google.gerrit.reviewdb.server.ReviewDbCodecs.PATCH_SET_CODEC;
 import static com.google.gerrit.server.index.change.ChangeIndexRewriter.CLOSED_STATUSES;
 import static com.google.gerrit.server.index.change.ChangeIndexRewriter.OPEN_STATUSES;
 import static java.nio.charset.StandardCharsets.UTF_8;
@@ -33,6 +30,7 @@
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
 import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.DataSource;
@@ -41,7 +39,9 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.reviewdb.converter.ChangeProtoConverter;
+import com.google.gerrit.reviewdb.converter.PatchSetApprovalProtoConverter;
+import com.google.gerrit.reviewdb.converter.PatchSetProtoConverter;
 import com.google.gerrit.server.ReviewerByEmailSet;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.StarredChangesUtil;
@@ -56,9 +56,7 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
@@ -87,34 +85,31 @@
   private static final String CLOSED_CHANGES = "closed_" + CHANGES;
 
   private final ChangeMapping mapping;
-  private final Provider<ReviewDb> db;
   private final ChangeData.Factory changeDataFactory;
   private final Schema<ChangeData> schema;
 
   @Inject
   ElasticChangeIndex(
       ElasticConfiguration cfg,
-      Provider<ReviewDb> db,
       ChangeData.Factory changeDataFactory,
       SitePaths sitePaths,
       ElasticRestClientProvider clientBuilder,
       @Assisted Schema<ChangeData> schema) {
     super(cfg, sitePaths, schema, clientBuilder, CHANGES);
-    this.db = db;
     this.changeDataFactory = changeDataFactory;
     this.schema = schema;
     mapping = new ChangeMapping(schema, client.adapter());
   }
 
   @Override
-  public void replace(ChangeData cd) throws IOException {
+  public void replace(ChangeData cd) {
     BulkRequest bulk = new IndexRequest(getId(cd), indexName).add(new UpdateRequest<>(schema, cd));
 
     String uri = getURI(type, BULK);
     Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
-      throw new IOException(
+      throw new StorageException(
           String.format(
               "Failed to replace change %s in index %s: %s", cd.getId(), indexName, statusCode));
     }
@@ -187,21 +182,24 @@
       int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
       // IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
       String projectName = requireNonNull(source.get(ChangeField.PROJECT.getName()).getAsString());
-      return changeDataFactory.create(
-          db.get(), new Project.NameKey(projectName), new Change.Id(id));
+      return changeDataFactory.create(new Project.NameKey(projectName), new Change.Id(id));
     }
 
     ChangeData cd =
-        changeDataFactory.create(db.get(), CHANGE_CODEC.decode(decodeBase64(c.getAsString())));
+        changeDataFactory.create(
+            parseProtoFrom(decodeBase64(c.getAsString()), ChangeProtoConverter.INSTANCE));
 
     // Any decoding that is done here must also be done in {@link LuceneChangeIndex}.
 
     // Patch sets.
-    cd.setPatchSets(decodeProtos(source, ChangeField.PATCH_SET.getName(), PATCH_SET_CODEC));
+    cd.setPatchSets(
+        decodeProtos(source, ChangeField.PATCH_SET.getName(), PatchSetProtoConverter.INSTANCE));
 
     // Approvals.
     if (source.get(ChangeField.APPROVAL.getName()) != null) {
-      cd.setCurrentApprovals(decodeProtos(source, ChangeField.APPROVAL.getName(), APPROVAL_CODEC));
+      cd.setCurrentApprovals(
+          decodeProtos(
+              source, ChangeField.APPROVAL.getName(), PatchSetApprovalProtoConverter.INSTANCE));
     } else if (fields.contains(ChangeField.APPROVAL.getName())) {
       cd.setCurrentApprovals(Collections.emptyList());
     }
diff --git a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index 2c10a2c..eff3d52 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
 import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.DataSource;
@@ -36,7 +37,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
 import java.util.Set;
 import org.apache.http.HttpStatus;
 import org.elasticsearch.client.Response;
@@ -71,7 +71,7 @@
   }
 
   @Override
-  public void replace(InternalGroup group) throws IOException {
+  public void replace(InternalGroup group) {
     BulkRequest bulk =
         new IndexRequest(getId(group), indexName).add(new UpdateRequest<>(schema, group));
 
@@ -79,7 +79,7 @@
     Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
-      throw new IOException(
+      throw new StorageException(
           String.format(
               "Failed to replace group %s in index %s: %s",
               group.getGroupUUID().get(), indexName, statusCode));
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
index 8011efa..b9d86d5 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.Schema;
@@ -26,6 +25,7 @@
 import com.google.gerrit.server.index.GerritIndexStatus;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
 import com.google.gerrit.server.index.VersionManager;
+import com.google.gerrit.server.plugincontext.PluginSetContext;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -45,7 +45,7 @@
   ElasticIndexVersionManager(
       @GerritServerConfig Config cfg,
       SitePaths sitePaths,
-      DynamicSet<OnlineUpgradeListener> listeners,
+      PluginSetContext<OnlineUpgradeListener> listeners,
       Collection<IndexDefinition<?, ?, ?>> defs,
       ElasticIndexVersionDiscovery versionDiscovery) {
     super(sitePaths, listeners, defs, VersionManager.getOnlineUpgrade(cfg));
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index d8c53eb..a0ebb07 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
 import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.QueryOptions;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.project.ProjectData;
@@ -36,7 +37,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
 import java.util.Set;
 import org.apache.http.HttpStatus;
 import org.elasticsearch.client.Response;
@@ -71,7 +71,7 @@
   }
 
   @Override
-  public void replace(ProjectData projectState) throws IOException {
+  public void replace(ProjectData projectState) {
     BulkRequest bulk =
         new IndexRequest(projectState.getProject().getName(), indexName)
             .add(new UpdateRequest<>(schema, projectState));
@@ -80,7 +80,7 @@
     Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
-      throw new IOException(
+      throw new StorageException(
           String.format(
               "Failed to replace project %s in index %s: %s",
               projectState.getProject().getName(), indexName, statusCode));
diff --git a/java/com/google/gerrit/server/query/change/FileWithNoExtensionInElasticPredicate.java b/java/com/google/gerrit/server/query/change/FileWithNoExtensionInElasticPredicate.java
new file mode 100644
index 0000000..d886baf
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/FileWithNoExtensionInElasticPredicate.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 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.google.gerrit.server.query.change;
+
+import com.google.gerrit.index.query.PostFilterPredicate;
+import com.google.gerrit.server.index.change.ChangeField;
+
+public class FileWithNoExtensionInElasticPredicate extends PostFilterPredicate<ChangeData> {
+
+  private static final String NO_EXT = "";
+
+  public FileWithNoExtensionInElasticPredicate() {
+    super(ChangeField.EXTENSION.getName(), NO_EXT);
+  }
+
+  @Override
+  public boolean match(ChangeData cd) {
+    return ChangeField.getExtensions(cd).contains(NO_EXT);
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index 214861c..38b7e0e 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -36,7 +36,7 @@
   }
 
   @Override
-  public void configureIndex(Injector injector) throws Exception {
+  public void configureIndex(Injector injector) {
     createAllIndexes(injector);
   }
 
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 74f25e4..620cd09 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -35,7 +35,7 @@
   }
 
   @Override
-  public void configureIndex(Injector injector) throws Exception {
+  public void configureIndex(Injector injector) {
     createAllIndexes(injector);
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
index b657545..c7ae0d0 100644
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -84,8 +84,10 @@
     tags = ["elastic"],
     deps = [
         "//java/com/google/gerrit/elasticsearch",
+        "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:guava",
         "//lib/guice",
+        "//lib/httpcomponents:httpcore",
         "//lib/jgit/org.eclipse.jgit:jgit",
         "//lib/truth",
     ],
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
index ff7b5ca..9ce1456 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
@@ -24,16 +24,14 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.testing.GerritBaseTests;
 import com.google.inject.ProvisionException;
 import java.util.Arrays;
+import org.apache.http.HttpHost;
 import org.eclipse.jgit.lib.Config;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
-public class ElasticConfigurationTest {
-  @Rule public ExpectedException exception = ExpectedException.none();
-
+public class ElasticConfigurationTest extends GerritBaseTests {
   @Test
   public void singleServerNoOtherConfig() throws Exception {
     Config cfg = newConfig();
@@ -119,7 +117,7 @@
   }
 
   private void assertHosts(ElasticConfiguration cfg, Object... hostURIs) throws Exception {
-    assertThat(Arrays.asList(cfg.getHosts()).stream().map(h -> h.toURI()).collect(toList()))
+    assertThat(Arrays.asList(cfg.getHosts()).stream().map(HttpHost::toURI).collect(toList()))
         .containsExactly(hostURIs);
   }
 
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index a976aae..0203a22 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -19,7 +19,6 @@
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.TypeLiteral;
-import java.io.IOException;
 import java.util.Collection;
 import java.util.UUID;
 import org.eclipse.jgit.lib.Config;
@@ -34,7 +33,7 @@
     config.setInt("index", null, "maxLimit", 10000);
   }
 
-  public static void createAllIndexes(Injector injector) throws IOException {
+  public static void createAllIndexes(Injector injector) {
     Collection<IndexDefinition<?, ?, ?>> indexDefs =
         injector.getInstance(Key.get(new TypeLiteral<Collection<IndexDefinition<?, ?, ?>>>() {}));
     for (IndexDefinition<?, ?, ?> indexDef : indexDefs) {
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
index bb79655..d4d321d 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -59,6 +59,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
index 3f59d30..68c4b71 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -82,6 +82,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
index a966dfa..99c07f4 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -59,6 +59,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
index 40bee58..89c7774 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
@@ -59,6 +59,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
index bbddaa2..52752fb 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
@@ -59,6 +59,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
index e12df9c..7bf72bd 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
@@ -82,6 +82,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
index 5fec685..96fe274 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
@@ -59,6 +59,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
index 3e8bab4..76ec1a2 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
@@ -59,6 +59,6 @@
     InMemoryModule.setDefaults(elasticsearchConfig);
     String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
-    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
   }
 }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 309c63a..e05320a 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -16,13 +16,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import org.junit.Rule;
+import com.google.gerrit.testing.GerritBaseTests;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
-public class ElasticVersionTest {
-  @Rule public ExpectedException exception = ExpectedException.none();
-
+public class ElasticVersionTest extends GerritBaseTests {
   @Test
   public void supportedVersion() throws Exception {
     assertThat(ElasticVersion.forVersion("6.6.0")).isEqualTo(ElasticVersion.V6_6);