Merge branch 'stable-2.14' into stable-2.15

* stable-2.14:
  GroupField: Change UUID fields' type to KEYWORD
  Add keyword type to index type system
  Elasticsearch: Encapsulate supported versions in an enum

Change-Id: Ic30a5bc0c69623a22f1305c96bfa70e3daeed054
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index fa87a3f..e78416d 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -64,6 +64,6 @@
 
   @Override
   protected Class<? extends VersionManager> getVersionManager() {
-    return ElasticVersionManager.class;
+    return ElasticIndexVersionManager.class;
   }
 }
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
similarity index 95%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
rename to gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
index 4f7fbe3..1ddff77 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
@@ -32,14 +32,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(
       ElasticConfiguration cfg,
       SitePaths sitePaths,
       DynamicSet<OnlineUpgradeListener> listeners,
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
index 9e1c729..bf6da5c 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
+++ b/gerrit-elasticsearch/src/main/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/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
index 2a97e2e..c8c6345 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
+++ b/gerrit-elasticsearch/src/main/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/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
index a81cae6..91f938c 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -67,8 +67,8 @@
       synchronized (this) {
         if (client == null) {
           client = build();
-          String version = getVersion();
-          log.info("Connected to Elasticsearch version {}", version);
+          ElasticVersion version = getVersion();
+          log.info("Elasticsearch integration version {}", version);
         }
       }
     }
@@ -102,20 +102,23 @@
     }
   }
 
-  private String getVersion() throws ElasticException {
+  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);
       }
-      return new JsonParser()
-          .parse(AbstractElasticIndex.getContent(response))
-          .getAsJsonObject()
-          .get("version")
-          .getAsJsonObject()
-          .get("number")
-          .getAsString();
+      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);
     }
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/ElasticContainer.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
index 862c340..6df742c 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.elasticsearch;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.elasticsearch.ElasticVersion;
 import java.util.Set;
 import org.apache.http.HttpHost;
 import org.junit.internal.AssumptionViolatedException;
@@ -24,13 +25,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 +38,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));
   }
 
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");
+  }
+}