Merge branch 'stable-2.15'

* stable-2.15:
  GroupField: Change UUID fields' type to KEYWORD
  Add keyword type to index type system
  Elasticsearch: Encapsulate supported versions in an enum
  setup_gjf.sh: amend SHA1 for GJF 1.6
  Elasticsearch: Tidy up Javadoc in builders package
  setup_gjf.sh: Add support for google-java-format 1.6
  ElasticRestClientProvider: Detect Elasticsearch version
  Convert ElasticRestClientBuilder to a provider
  WorkQueue: rename prefix to queueName
  Remove outdated Elasticsearch/Lucene comments from WORKSPACE
  AbstractElasticIndex: Move generation of index name to ElasticConfiguration

ElasticProjectIndex is adapted to the changes done in:

- Ie4696b4d5 (AbstractElasticIndex: Move generation of index name to ElasticConfiguration)
- I4747114e2 (Convert ElasticRestClientBuilder to a provider)

Change-Id: I5ee243cb696c5e3e14b8c881950eb27000efce5c
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticException.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticException.java
new file mode 100644
index 0000000..d4baf75
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticException.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+class ElasticException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
+
+  ElasticException(String message) {
+    super(message);
+  }
+
+  ElasticException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
new file mode 100644
index 0000000..91f938c
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gson.JsonParser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestClientBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+class ElasticRestClientProvider implements Provider<RestClient>, LifecycleListener {
+  private static final Logger log = LoggerFactory.getLogger(ElasticRestClientProvider.class);
+
+  private final HttpHost[] hosts;
+  private final String username;
+  private final String password;
+
+  private RestClient client;
+
+  @Inject
+  ElasticRestClientProvider(ElasticConfiguration cfg) {
+    hosts = cfg.urls.toArray(new HttpHost[cfg.urls.size()]);
+    username = cfg.username;
+    password = cfg.password;
+  }
+
+  public static LifecycleModule module() {
+    return new LifecycleModule() {
+      @Override
+      protected void configure() {
+        listener().to(ElasticRestClientProvider.class);
+      }
+    };
+  }
+
+  @Override
+  public RestClient get() {
+    if (client == null) {
+      synchronized (this) {
+        if (client == null) {
+          client = build();
+          ElasticVersion version = getVersion();
+          log.info("Elasticsearch integration version {}", version);
+        }
+      }
+    }
+    return client;
+  }
+
+  @Override
+  public void start() {}
+
+  @Override
+  public void stop() {
+    if (client != null) {
+      try {
+        client.close();
+      } catch (IOException e) {
+        // Ignore. We can't do anything about it.
+      }
+    }
+  }
+
+  public static class FailedToGetVersion extends ElasticException {
+    private static final long serialVersionUID = 1L;
+    private static final String MESSAGE = "Failed to get Elasticsearch version";
+
+    FailedToGetVersion(StatusLine status) {
+      super(String.format("%s: %d %s", MESSAGE, status.getStatusCode(), status.getReasonPhrase()));
+    }
+
+    FailedToGetVersion(Throwable cause) {
+      super(MESSAGE, cause);
+    }
+  }
+
+  private ElasticVersion getVersion() throws ElasticException {
+    try {
+      Response response = client.performRequest("GET", "");
+      StatusLine statusLine = response.getStatusLine();
+      if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
+        throw new FailedToGetVersion(statusLine);
+      }
+      String version =
+          new JsonParser()
+              .parse(AbstractElasticIndex.getContent(response))
+              .getAsJsonObject()
+              .get("version")
+              .getAsJsonObject()
+              .get("number")
+              .getAsString();
+      log.info("Connected to Elasticsearch version {}", version);
+      return ElasticVersion.forVersion(version);
+    } catch (IOException e) {
+      throw new FailedToGetVersion(e);
+    }
+  }
+
+  private RestClient build() {
+    RestClientBuilder builder = RestClient.builder(hosts);
+    setConfiguredCredentialsIfAny(builder);
+    return builder.build();
+  }
+
+  private void setConfiguredCredentialsIfAny(RestClientBuilder builder) {
+    if (username != null && password != null) {
+      CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+      credentialsProvider.setCredentials(
+          AuthScope.ANY, new UsernamePasswordCredentials(username, password));
+      builder.setHttpClientConfigCallback(
+          (HttpAsyncClientBuilder httpClientBuilder) ->
+              httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
+    }
+  }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
new file mode 100644
index 0000000..ff26382
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+import com.google.common.base.Joiner;
+import java.util.regex.Pattern;
+
+public enum ElasticVersion {
+  V2_4("2.4.*"),
+  V5_6("5.6.*"),
+  V6_2("6.2.*");
+
+  private final String version;
+  private final Pattern pattern;
+
+  private ElasticVersion(String version) {
+    this.version = version;
+    this.pattern = Pattern.compile(version);
+  }
+
+  public static class InvalidVersion extends ElasticException {
+    private static final long serialVersionUID = 1L;
+
+    InvalidVersion(String version) {
+      super(
+          String.format(
+              "Invalid version: [%s]. Supported versions: %s", version, supportedVersions()));
+    }
+  }
+
+  public static ElasticVersion forVersion(String version) throws InvalidVersion {
+    for (ElasticVersion value : ElasticVersion.values()) {
+      if (value.pattern.matcher(version).matches()) {
+        return value;
+      }
+    }
+    throw new InvalidVersion(version);
+  }
+
+  public static String supportedVersions() {
+    return Joiner.on(", ").join(ElasticVersion.values());
+  }
+
+  @Override
+  public String toString() {
+    return version;
+  }
+}
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
new file mode 100644
index 0000000..e0da86a
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class ElasticVersionTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void supportedVersion() throws Exception {
+    assertThat(ElasticVersion.forVersion("2.4.0")).isEqualTo(ElasticVersion.V2_4);
+    assertThat(ElasticVersion.forVersion("2.4.6")).isEqualTo(ElasticVersion.V2_4);
+
+    assertThat(ElasticVersion.forVersion("5.6.0")).isEqualTo(ElasticVersion.V5_6);
+    assertThat(ElasticVersion.forVersion("5.6.9")).isEqualTo(ElasticVersion.V5_6);
+
+    assertThat(ElasticVersion.forVersion("6.2.0")).isEqualTo(ElasticVersion.V6_2);
+    assertThat(ElasticVersion.forVersion("6.2.4")).isEqualTo(ElasticVersion.V6_2);
+  }
+
+  @Test
+  public void unsupportedVersion() throws Exception {
+    exception.expect(ElasticVersion.InvalidVersion.class);
+    exception.expectMessage(
+        "Invalid version: [4.0.0]. Supported versions: " + ElasticVersion.supportedVersions());
+    ElasticVersion.forVersion("4.0.0");
+  }
+}
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 3ae89d1..320ebfa 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -19,7 +19,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.commons.codec.binary.Base64.decodeBase64;
 
-import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ListMultimap;
@@ -37,7 +36,6 @@
 import com.google.gerrit.index.query.FieldBundle;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gson.Gson;
@@ -70,9 +68,7 @@
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.ContentType;
 import org.apache.http.nio.entity.NStringEntity;
-import org.eclipse.jgit.lib.Config;
 import org.elasticsearch.client.Response;
-import org.elasticsearch.client.RestClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -110,30 +106,25 @@
   private final Schema<V> schema;
   private final SitePaths sitePaths;
   private final String indexNameRaw;
-  private final RestClient client;
+  private final ElasticRestClientProvider client;
 
   protected final String indexName;
   protected final Gson gson;
   protected final ElasticQueryBuilder queryBuilder;
 
   AbstractElasticIndex(
-      @GerritServerConfig Config cfg,
+      ElasticConfiguration cfg,
       SitePaths sitePaths,
       Schema<V> schema,
-      ElasticRestClientBuilder clientBuilder,
+      ElasticRestClientProvider client,
       String indexName) {
     this.sitePaths = sitePaths;
     this.schema = schema;
     this.gson = new GsonBuilder().setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
     this.queryBuilder = new ElasticQueryBuilder();
-    this.indexName =
-        String.format(
-            "%s%s_%04d",
-            Strings.nullToEmpty(cfg.getString("elasticsearch", null, "prefix")),
-            indexName,
-            schema.getVersion());
+    this.indexName = cfg.getIndexName(indexName, schema.getVersion());
     this.indexNameRaw = indexName;
-    this.client = clientBuilder.build();
+    this.client = client;
   }
 
   @Override
@@ -143,11 +134,7 @@
 
   @Override
   public void close() {
-    try {
-      client.close();
-    } catch (IOException e) {
-      // Ignored.
-    }
+    // Do nothing. Client is closed by the provider.
   }
 
   @Override
@@ -169,10 +156,10 @@
   @Override
   public void deleteAll() throws IOException {
     // Delete the index, if it exists.
-    Response response = client.performRequest("HEAD", indexName);
+    Response response = client.get().performRequest("HEAD", indexName);
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode == HttpStatus.SC_OK) {
-      response = client.performRequest("DELETE", indexName);
+      response = client.get().performRequest("DELETE", indexName);
       statusCode = response.getStatusLine().getStatusCode();
       if (statusCode != HttpStatus.SC_OK) {
         throw new IOException(
@@ -286,7 +273,7 @@
       String method, Object payload, String uri, Map<String, String> params) throws IOException {
     String payloadStr = payload instanceof String ? (String) payload : payload.toString();
     HttpEntity entity = new NStringEntity(payloadStr, ContentType.APPLICATION_JSON);
-    return client.performRequest(method, uri, params, entity);
+    return client.get().performRequest(method, uri, params, entity);
   }
 
   protected class ElasticQuerySource implements DataSource<V> {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index 1bfb586..1abdee9 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.index.account.AccountField;
@@ -43,7 +42,6 @@
 import java.io.IOException;
 import java.util.Set;
 import org.apache.http.HttpStatus;
-import org.eclipse.jgit.lib.Config;
 import org.elasticsearch.client.Response;
 
 public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, AccountState>
@@ -64,12 +62,12 @@
 
   @Inject
   ElasticAccountIndex(
-      @GerritServerConfig Config cfg,
+      ElasticConfiguration cfg,
       SitePaths sitePaths,
       Provider<AccountCache> accountCache,
-      ElasticRestClientBuilder clientBuilder,
+      ElasticRestClientProvider client,
       @Assisted Schema<AccountState> schema) {
-    super(cfg, sitePaths, schema, clientBuilder, ACCOUNTS);
+    super(cfg, sitePaths, schema, client, ACCOUNTS);
     this.accountCache = accountCache;
     this.mapping = new AccountMapping(schema);
     this.schema = schema;
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index e31e270..c7b0fb5 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -49,7 +49,6 @@
 import com.google.gerrit.server.ReviewerByEmailSet;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.index.change.ChangeField;
@@ -71,7 +70,6 @@
 import java.util.Set;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.HttpStatus;
-import org.eclipse.jgit.lib.Config;
 import org.elasticsearch.client.Response;
 
 /** Secondary index implementation using Elasticsearch. */
@@ -99,11 +97,11 @@
 
   @Inject
   ElasticChangeIndex(
-      @GerritServerConfig Config cfg,
+      ElasticConfiguration cfg,
       Provider<ReviewDb> db,
       ChangeData.Factory changeDataFactory,
       SitePaths sitePaths,
-      ElasticRestClientBuilder clientBuilder,
+      ElasticRestClientProvider clientBuilder,
       @Assisted Schema<ChangeData> schema) {
     super(cfg, sitePaths, schema, clientBuilder, CHANGES);
     this.db = db;
diff --git a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
index 4cca280..84dae7f 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.elasticsearch;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -32,6 +33,8 @@
   private static final String DEFAULT_PORT = "9200";
   private static final String DEFAULT_PROTOCOL = "http";
 
+  private final Config cfg;
+
   final List<HttpHost> urls;
   final String username;
   final String password;
@@ -41,9 +44,11 @@
   final TimeUnit maxConnectionIdleUnit = TimeUnit.MILLISECONDS;
   final int maxTotalConnection;
   final int readTimeout;
+  final String prefix;
 
   @Inject
   ElasticConfiguration(@GerritServerConfig Config cfg) {
+    this.cfg = cfg;
     this.username = cfg.getString("elasticsearch", null, "username");
     this.password = cfg.getString("elasticsearch", null, "password");
     this.requestCompression = cfg.getBoolean("elasticsearch", null, "requestCompression", false);
@@ -55,6 +60,7 @@
     this.maxTotalConnection = cfg.getInt("elasticsearch", null, "maxTotalConnection", 1);
     this.readTimeout =
         (int) cfg.getTimeUnit("elasticsearch", null, "readTimeout", 3000, TimeUnit.MICROSECONDS);
+    this.prefix = Strings.nullToEmpty(cfg.getString("elasticsearch", null, "prefix"));
 
     Set<String> subsections = cfg.getSubsections("elasticsearch");
     if (subsections.isEmpty()) {
@@ -74,6 +80,14 @@
     }
   }
 
+  public Config getConfig() {
+    return cfg;
+  }
+
+  public String getIndexName(String name, int schemaVersion) {
+    return String.format("%s%s_%04d", prefix, name, schemaVersion);
+  }
+
   private String getString(Config cfg, String subsection, String name, String defaultValue) {
     return MoreObjects.firstNonNull(cfg.getString("elasticsearch", subsection, name), defaultValue);
   }
diff --git a/java/com/google/gerrit/elasticsearch/ElasticException.java b/java/com/google/gerrit/elasticsearch/ElasticException.java
new file mode 100644
index 0000000..d4baf75
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticException.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+class ElasticException extends RuntimeException {
+  private static final long serialVersionUID = 1L;
+
+  ElasticException(String message) {
+    super(message);
+  }
+
+  ElasticException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index 633a0d2..d1eab7c 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -26,7 +26,6 @@
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.index.IndexUtils;
@@ -41,7 +40,6 @@
 import java.io.IOException;
 import java.util.Set;
 import org.apache.http.HttpStatus;
-import org.eclipse.jgit.lib.Config;
 import org.elasticsearch.client.Response;
 
 public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, InternalGroup>
@@ -62,12 +60,12 @@
 
   @Inject
   ElasticGroupIndex(
-      @GerritServerConfig Config cfg,
+      ElasticConfiguration cfg,
       SitePaths sitePaths,
       Provider<GroupCache> groupCache,
-      ElasticRestClientBuilder clientBuilder,
+      ElasticRestClientProvider client,
       @Assisted Schema<InternalGroup> schema) {
-    super(cfg, sitePaths, schema, clientBuilder, GROUPS);
+    super(cfg, sitePaths, schema, client, GROUPS);
     this.groupCache = groupCache;
     this.mapping = new GroupMapping(schema);
     this.schema = schema;
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 76fdfea..1e41985 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -42,6 +42,12 @@
   }
 
   @Override
+  public void configure() {
+    super.configure();
+    install(ElasticRestClientProvider.module());
+  }
+
+  @Override
   protected Class<? extends AccountIndex> getAccountIndex() {
     return ElasticAccountIndex.class;
   }
@@ -63,6 +69,6 @@
 
   @Override
   protected Class<? extends VersionManager> getVersionManager() {
-    return ElasticVersionManager.class;
+    return ElasticIndexVersionManager.class;
   }
 }
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
index 85fa2c1..3314a38 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
@@ -25,20 +25,19 @@
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.HttpGet;
 import org.elasticsearch.client.Response;
-import org.elasticsearch.client.RestClient;
 
 @Singleton
 class ElasticIndexVersionDiscovery {
-  private final RestClient client;
+  private final ElasticRestClientProvider client;
 
   @Inject
-  ElasticIndexVersionDiscovery(ElasticRestClientBuilder clientBuilder) {
-    this.client = clientBuilder.build();
+  ElasticIndexVersionDiscovery(ElasticRestClientProvider client) {
+    this.client = client;
   }
 
   List<String> discover(String prefix, String indexName) throws IOException {
     String name = prefix + indexName + "_";
-    Response response = client.performRequest(HttpGet.METHOD_NAME, name + "*/_aliases");
+    Response response = client.get().performRequest(HttpGet.METHOD_NAME, name + "*/_aliases");
 
     if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
       return new JsonParser()
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
similarity index 95%
rename from java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
rename to java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
index 36d40f5..612402e 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
@@ -35,14 +35,14 @@
 import org.slf4j.LoggerFactory;
 
 @Singleton
-public class ElasticVersionManager extends VersionManager {
-  private static final Logger log = LoggerFactory.getLogger(ElasticVersionManager.class);
+public class ElasticIndexVersionManager extends VersionManager {
+  private static final Logger log = LoggerFactory.getLogger(ElasticIndexVersionManager.class);
 
   private final String prefix;
   private final ElasticIndexVersionDiscovery versionDiscovery;
 
   @Inject
-  ElasticVersionManager(
+  ElasticIndexVersionManager(
       @GerritServerConfig Config cfg,
       SitePaths sitePaths,
       DynamicSet<OnlineUpgradeListener> listeners,
diff --git a/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/java/com/google/gerrit/elasticsearch/ElasticMapping.java
index 9e1c729..bf6da5c 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticMapping.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticMapping.java
@@ -26,7 +26,7 @@
     for (FieldDef<?, ?> field : schema.getFields().values()) {
       String name = field.getName();
       FieldType<?> fieldType = field.getType();
-      if (fieldType == FieldType.EXACT) {
+      if (fieldType == FieldType.EXACT || fieldType == FieldType.KEYWORD) {
         mapping.addExactField(name);
       } else if (fieldType == FieldType.TIMESTAMP) {
         mapping.addTimestamp(name);
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index d53c290..a536b41 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -28,7 +28,6 @@
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.project.ProjectCache;
@@ -41,7 +40,6 @@
 import java.io.IOException;
 import java.util.Set;
 import org.apache.http.HttpStatus;
-import org.eclipse.jgit.lib.Config;
 import org.elasticsearch.client.Response;
 
 public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectData>
@@ -62,10 +60,10 @@
 
   @Inject
   ElasticProjectIndex(
-      @GerritServerConfig Config cfg,
+      ElasticConfiguration cfg,
       SitePaths sitePaths,
       Provider<ProjectCache> projectCache,
-      ElasticRestClientBuilder clientBuilder,
+      ElasticRestClientProvider clientBuilder,
       @Assisted Schema<ProjectData> schema) {
     super(cfg, sitePaths, schema, clientBuilder, PROJECTS);
     this.projectCache = projectCache;
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
index 2a97e2e..c8c6345 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
@@ -94,7 +94,7 @@
       return intRangeQuery(p);
     } else if (type == FieldType.TIMESTAMP) {
       return timestampQuery(p);
-    } else if (type == FieldType.EXACT) {
+    } else if (type == FieldType.EXACT || type == FieldType.KEYWORD) {
       return exactQuery(p);
     } else if (type == FieldType.PREFIX) {
       return QueryBuilders.matchPhrasePrefixQuery(name, value);
diff --git a/java/com/google/gerrit/elasticsearch/ElasticRestClientBuilder.java b/java/com/google/gerrit/elasticsearch/ElasticRestClientBuilder.java
deleted file mode 100644
index faf1c71..0000000
--- a/java/com/google/gerrit/elasticsearch/ElasticRestClientBuilder.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (C) 2018 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.elasticsearch;
-
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import org.apache.http.HttpHost;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
-import org.elasticsearch.client.RestClient;
-import org.elasticsearch.client.RestClientBuilder;
-
-@Singleton
-class ElasticRestClientBuilder {
-
-  private final HttpHost[] hosts;
-  private final String username;
-  private final String password;
-
-  @Inject
-  ElasticRestClientBuilder(ElasticConfiguration cfg) {
-    hosts = cfg.urls.toArray(new HttpHost[cfg.urls.size()]);
-    username = cfg.username;
-    password = cfg.password;
-  }
-
-  RestClient build() {
-    RestClientBuilder builder = RestClient.builder(hosts);
-    setConfiguredCredentialsIfAny(builder);
-    return builder.build();
-  }
-
-  private void setConfiguredCredentialsIfAny(RestClientBuilder builder) {
-    if (username != null && password != null) {
-      CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
-      credentialsProvider.setCredentials(
-          AuthScope.ANY, new UsernamePasswordCredentials(username, password));
-      builder.setHttpClientConfigCallback(
-          (HttpAsyncClientBuilder httpClientBuilder) ->
-              httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
-    }
-  }
-}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
new file mode 100644
index 0000000..91f938c
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -0,0 +1,143 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gson.JsonParser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestClientBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+class ElasticRestClientProvider implements Provider<RestClient>, LifecycleListener {
+  private static final Logger log = LoggerFactory.getLogger(ElasticRestClientProvider.class);
+
+  private final HttpHost[] hosts;
+  private final String username;
+  private final String password;
+
+  private RestClient client;
+
+  @Inject
+  ElasticRestClientProvider(ElasticConfiguration cfg) {
+    hosts = cfg.urls.toArray(new HttpHost[cfg.urls.size()]);
+    username = cfg.username;
+    password = cfg.password;
+  }
+
+  public static LifecycleModule module() {
+    return new LifecycleModule() {
+      @Override
+      protected void configure() {
+        listener().to(ElasticRestClientProvider.class);
+      }
+    };
+  }
+
+  @Override
+  public RestClient get() {
+    if (client == null) {
+      synchronized (this) {
+        if (client == null) {
+          client = build();
+          ElasticVersion version = getVersion();
+          log.info("Elasticsearch integration version {}", version);
+        }
+      }
+    }
+    return client;
+  }
+
+  @Override
+  public void start() {}
+
+  @Override
+  public void stop() {
+    if (client != null) {
+      try {
+        client.close();
+      } catch (IOException e) {
+        // Ignore. We can't do anything about it.
+      }
+    }
+  }
+
+  public static class FailedToGetVersion extends ElasticException {
+    private static final long serialVersionUID = 1L;
+    private static final String MESSAGE = "Failed to get Elasticsearch version";
+
+    FailedToGetVersion(StatusLine status) {
+      super(String.format("%s: %d %s", MESSAGE, status.getStatusCode(), status.getReasonPhrase()));
+    }
+
+    FailedToGetVersion(Throwable cause) {
+      super(MESSAGE, cause);
+    }
+  }
+
+  private ElasticVersion getVersion() throws ElasticException {
+    try {
+      Response response = client.performRequest("GET", "");
+      StatusLine statusLine = response.getStatusLine();
+      if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
+        throw new FailedToGetVersion(statusLine);
+      }
+      String version =
+          new JsonParser()
+              .parse(AbstractElasticIndex.getContent(response))
+              .getAsJsonObject()
+              .get("version")
+              .getAsJsonObject()
+              .get("number")
+              .getAsString();
+      log.info("Connected to Elasticsearch version {}", version);
+      return ElasticVersion.forVersion(version);
+    } catch (IOException e) {
+      throw new FailedToGetVersion(e);
+    }
+  }
+
+  private RestClient build() {
+    RestClientBuilder builder = RestClient.builder(hosts);
+    setConfiguredCredentialsIfAny(builder);
+    return builder.build();
+  }
+
+  private void setConfiguredCredentialsIfAny(RestClientBuilder builder) {
+    if (username != null && password != null) {
+      CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+      credentialsProvider.setCredentials(
+          AuthScope.ANY, new UsernamePasswordCredentials(username, password));
+      builder.setHttpClientConfigCallback(
+          (HttpAsyncClientBuilder httpClientBuilder) ->
+              httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
+    }
+  }
+}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
new file mode 100644
index 0000000..ff26382
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2018 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.elasticsearch;
+
+import com.google.common.base.Joiner;
+import java.util.regex.Pattern;
+
+public enum ElasticVersion {
+  V2_4("2.4.*"),
+  V5_6("5.6.*"),
+  V6_2("6.2.*");
+
+  private final String version;
+  private final Pattern pattern;
+
+  private ElasticVersion(String version) {
+    this.version = version;
+    this.pattern = Pattern.compile(version);
+  }
+
+  public static class InvalidVersion extends ElasticException {
+    private static final long serialVersionUID = 1L;
+
+    InvalidVersion(String version) {
+      super(
+          String.format(
+              "Invalid version: [%s]. Supported versions: %s", version, supportedVersions()));
+    }
+  }
+
+  public static ElasticVersion forVersion(String version) throws InvalidVersion {
+    for (ElasticVersion value : ElasticVersion.values()) {
+      if (value.pattern.matcher(version).matches()) {
+        return value;
+      }
+    }
+    throw new InvalidVersion(version);
+  }
+
+  public static String supportedVersions() {
+    return Joiner.on(", ").join(ElasticVersion.values());
+  }
+
+  @Override
+  public String toString() {
+    return version;
+  }
+}
diff --git a/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
index 4b1adbd..a204919 100644
--- a/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
@@ -19,8 +19,9 @@
 import java.util.List;
 
 /**
- * A Query that matches documents matching boolean combinations of other queries. A trimmed down
- * version of org.elasticsearch.index.query.BoolQueryBuilder for this very package.
+ * A Query that matches documents matching boolean combinations of other queries.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.BoolQueryBuilder.
  */
 public class BoolQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
index b1b3b1b..1b058d7 100644
--- a/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
@@ -17,8 +17,9 @@
 import java.io.IOException;
 
 /**
- * Constructs a query that only match on documents that the field has a value in them. A trimmed
- * down version of org.elasticsearch.index.query.ExistsQueryBuilder for this very package.
+ * Constructs a query that only match on documents that the field has a value in them.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.ExistsQueryBuilder.
  */
 class ExistsQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
index b2411a2..a3b303c 100644
--- a/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
@@ -17,8 +17,9 @@
 import java.io.IOException;
 
 /**
- * A query that matches on all documents. A trimmed down version of
- * org.elasticsearch.index.query.MatchAllQueryBuilder for this very package.
+ * A query that matches on all documents.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.MatchAllQueryBuilder.
  */
 class MatchAllQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
index e0c9a65..7a12080 100644
--- a/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
@@ -19,8 +19,9 @@
 
 /**
  * Match query is a query that analyzes the text and constructs a query as the result of the
- * analysis. It can construct different queries based on the type provided. A trimmed down version
- * of org.elasticsearch.index.query.MatchQueryBuilder for this very package.
+ * analysis. It can construct different queries based on the type provided.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.MatchQueryBuilder.
  */
 class MatchQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
index b56db0b..d6f154e 100644
--- a/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
@@ -16,7 +16,7 @@
 
 import java.io.IOException;
 
-/** A trimmed down version of org.elasticsearch.index.query.QueryBuilder for this very package. */
+/** A trimmed down version of org.elasticsearch.index.query.QueryBuilder. */
 public abstract class QueryBuilder {
 
   protected QueryBuilder() {}
diff --git a/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java b/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
index 38e6acd..26fac4c 100644
--- a/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
+++ b/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
@@ -15,8 +15,9 @@
 package com.google.gerrit.elasticsearch.builders;
 
 /**
- * A static factory for simple "import static" usage. A trimmed down version of
- * org.elasticsearch.index.query.QueryBuilders for this very package.
+ * A static factory for simple "import static" usage.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.QueryBuilders.
  */
 public abstract class QueryBuilders {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java b/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
index 7920157..1cb5c82 100644
--- a/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
@@ -16,10 +16,7 @@
 
 import java.io.IOException;
 
-/**
- * A trimmed down and further altered version of org.elasticsearch.action.support.QuerySourceBuilder
- * for this very package.
- */
+/** A trimmed down and modified version of org.elasticsearch.action.support.QuerySourceBuilder. */
 class QuerySourceBuilder {
 
   private final QueryBuilder queryBuilder;
diff --git a/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
index 5487198..32dbc0e 100644
--- a/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
@@ -17,8 +17,9 @@
 import java.io.IOException;
 
 /**
- * A Query that matches documents within an range of terms. A trimmed down version of
- * org.elasticsearch.index.query.RangeQueryBuilder for this very package.
+ * A Query that matches documents within an range of terms.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.RangeQueryBuilder.
  */
 public class RangeQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
index e88e8ef..b81ec20 100644
--- a/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
@@ -17,8 +17,9 @@
 import java.io.IOException;
 
 /**
- * A Query that does fuzzy matching for a specific value. A trimmed down version of
- * org.elasticsearch.index.query.RegexpQueryBuilder for this very package.
+ * A Query that does fuzzy matching for a specific value.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.RegexpQueryBuilder.
  */
 class RegexpQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java b/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
index 3df2a7c..e72e9fa 100644
--- a/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
@@ -18,8 +18,9 @@
 import java.util.List;
 
 /**
- * A search source builder allowing to easily build search source. A trimmed down and further
- * altered version of org.elasticsearch.search.builder.SearchSourceBuilder for this very package.
+ * A search source builder allowing to easily build search source.
+ *
+ * <p>A trimmed down and modified version of org.elasticsearch.search.builder.SearchSourceBuilder.
  */
 public class SearchSourceBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
index e290006..2b407c6 100644
--- a/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
@@ -17,8 +17,9 @@
 import java.io.IOException;
 
 /**
- * A Query that matches documents containing a term. A trimmed down version of
- * org.elasticsearch.index.query.TermQueryBuilder for this very package.
+ * A Query that matches documents containing a term.
+ *
+ * <p>A trimmed down version of org.elasticsearch.index.query.TermQueryBuilder.
  */
 class TermQueryBuilder extends QueryBuilder {
 
diff --git a/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java b/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
index 3c1d114..06427f1 100644
--- a/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
+++ b/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
@@ -26,10 +26,7 @@
 import java.io.IOException;
 import java.util.Date;
 
-/**
- * A trimmed down and further altered version of org.elasticsearch.common.xcontent.XContentBuilder
- * for this very package.
- */
+/** A trimmed down and modified version of org.elasticsearch.common.xcontent.XContentBuilder. */
 public final class XContentBuilder implements Closeable {
 
   private final JsonGenerator generator;
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index d5edaa1..a7a23c3 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -24,13 +24,7 @@
 public class ElasticContainer<SELF extends ElasticContainer<SELF>> extends GenericContainer<SELF> {
   private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;
 
-  public enum Version {
-    V2,
-    V5,
-    V6
-  }
-
-  public static ElasticContainer<?> createAndStart(Version version) {
+  public static ElasticContainer<?> createAndStart(ElasticVersion version) {
     // Assumption violation is not natively supported by Testcontainers.
     // See https://github.com/testcontainers/testcontainers-java/issues/343
     try {
@@ -43,22 +37,22 @@
   }
 
   public static ElasticContainer<?> createAndStart() {
-    return createAndStart(Version.V2);
+    return createAndStart(ElasticVersion.V2_4);
   }
 
-  private static String getImageName(Version version) {
+  private static String getImageName(ElasticVersion version) {
     switch (version) {
-      case V2:
+      case V2_4:
         return "elasticsearch:2.4.6-alpine";
-      case V5:
+      case V5_6:
         return "elasticsearch:5.6.9-alpine";
-      case V6:
+      case V6_2:
         return "docker.elastic.co/elasticsearch/elasticsearch:6.2.4";
     }
-    throw new IllegalStateException("Unsupported version: " + version.name());
+    throw new IllegalStateException("No tests for version: " + version.name());
   }
 
-  private ElasticContainer(Version version) {
+  private ElasticContainer(ElasticVersion version) {
     super(getImageName(version));
   }