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-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
deleted file mode 100644
index 37207ff..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ /dev/null
@@ -1,60 +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.acceptance.pgm;
-
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.elasticsearch.ElasticContainer;
-import com.google.gerrit.elasticsearch.ElasticTestUtils;
-import com.google.gerrit.elasticsearch.ElasticTestUtils.ElasticNodeInfo;
-import com.google.gerrit.testutil.ConfigSuite;
-import com.google.inject.Injector;
-import java.util.UUID;
-import org.eclipse.jgit.lib.Config;
-import org.junit.After;
-import org.junit.Ignore;
-
-@NoHttpd
-@Ignore
-public class ElasticReindexIT extends AbstractReindexTests {
- private static ElasticContainer<?> container;
-
- @ConfigSuite.Default
- public static Config elasticsearch() {
- ElasticNodeInfo elasticNodeInfo;
- try {
- container = ElasticContainer.createAndStart();
- elasticNodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
- } catch (Throwable t) {
- return null;
- }
- String indicesPrefix = UUID.randomUUID().toString();
- Config cfg = new Config();
- ElasticTestUtils.configure(cfg, elasticNodeInfo.port, indicesPrefix);
- return cfg;
- }
-
- @Override
- public void configureIndex(Injector injector) throws Exception {
- ElasticTestUtils.createAllIndexes(injector);
- }
-
- @After
- public void stopElasticServer() {
- if (container != null) {
- container.stop();
- container = null;
- }
- }
-}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
deleted file mode 100644
index 9d25308..0000000
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright (C) 2014 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.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.codec.binary.Base64.decodeBase64;
-
-import com.google.common.collect.FluentIterable;
-import com.google.common.io.CharStreams;
-import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
-import com.google.gerrit.elasticsearch.bulk.DeleteRequest;
-import com.google.gerrit.index.Index;
-import com.google.gerrit.index.Schema;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.IndexUtils;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gwtorm.protobuf.ProtobufCodec;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpStatus;
-import org.apache.http.entity.ContentType;
-import org.apache.http.nio.entity.NStringEntity;
-import org.elasticsearch.client.Response;
-
-abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
- protected static final String BULK = "_bulk";
- protected static final String IGNORE_UNMAPPED = "ignore_unmapped";
- protected static final String ORDER = "order";
- protected static final String SEARCH = "_search";
-
- protected static <T> List<T> decodeProtos(
- JsonObject doc, String fieldName, ProtobufCodec<T> codec) {
- JsonArray field = doc.getAsJsonArray(fieldName);
- if (field == null) {
- return null;
- }
- return FluentIterable.from(field)
- .transform(i -> codec.decode(decodeBase64(i.toString())))
- .toList();
- }
-
- static String getContent(Response response) throws IOException {
- HttpEntity responseEntity = response.getEntity();
- String content = "";
- if (responseEntity != null) {
- InputStream contentStream = responseEntity.getContent();
- try (Reader reader = new InputStreamReader(contentStream)) {
- content = CharStreams.toString(reader);
- }
- }
- return content;
- }
-
- private final Schema<V> schema;
- private final SitePaths sitePaths;
- private final String indexNameRaw;
- private final ElasticRestClientProvider client;
-
- protected final String indexName;
- protected final Gson gson;
- protected final ElasticQueryBuilder queryBuilder;
-
- AbstractElasticIndex(
- ElasticConfiguration cfg,
- SitePaths sitePaths,
- Schema<V> schema,
- 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 = cfg.getIndexName(indexName, schema.getVersion());
- this.indexNameRaw = indexName;
- this.client = client;
- }
-
- @Override
- public Schema<V> getSchema() {
- return schema;
- }
-
- @Override
- public void close() {
- // Do nothing. Client is closed by the provider.
- }
-
- @Override
- public void markReady(boolean ready) throws IOException {
- IndexUtils.setReady(sitePaths, indexNameRaw, schema.getVersion(), ready);
- }
-
- @Override
- public void delete(K c) throws IOException {
- String uri = getURI(indexNameRaw, BULK);
- Response response = postRequest(addActions(c), uri, getRefreshParam());
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- throw new IOException(
- String.format("Failed to delete %s from index %s: %s", c, indexName, statusCode));
- }
- }
-
- @Override
- public void deleteAll() throws IOException {
- // Delete the index, if it exists.
- Response response = client.get().performRequest("HEAD", indexName);
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode == HttpStatus.SC_OK) {
- response = client.get().performRequest("DELETE", indexName);
- statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- throw new IOException(
- String.format("Failed to delete index %s: %s", indexName, statusCode));
- }
- }
-
- // Recreate the index.
- response = performRequest("PUT", getMappings(), indexName, Collections.emptyMap());
- 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);
- }
- }
-
- protected abstract String addActions(K c);
-
- protected abstract String getMappings();
-
- protected abstract String getId(V v);
-
- protected String delete(String type, K c) {
- String id = c.toString();
- return new DeleteRequest(id, indexNameRaw, type).toString();
- }
-
- protected void addNamedElement(String name, JsonObject element, JsonArray array) {
- JsonObject arrayElement = new JsonObject();
- arrayElement.add(name, element);
- array.add(arrayElement);
- }
-
- protected Map<String, String> getRefreshParam() {
- Map<String, String> params = new HashMap<>();
- params.put("refresh", "true");
- return params;
- }
-
- protected String getSearch(SearchSourceBuilder searchSource, JsonArray sortArray) {
- JsonObject search = new JsonParser().parse(searchSource.toString()).getAsJsonObject();
- search.add("sort", sortArray);
- return gson.toJson(search);
- }
-
- protected JsonArray getSortArray(String idFieldName) {
- JsonObject properties = new JsonObject();
- properties.addProperty(ORDER, "asc");
- properties.addProperty(IGNORE_UNMAPPED, true);
-
- JsonArray sortArray = new JsonArray();
- addNamedElement(idFieldName, properties, sortArray);
- return sortArray;
- }
-
- protected String getURI(String type, String request) throws UnsupportedEncodingException {
- String encodedType = URLEncoder.encode(type, UTF_8.toString());
- String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
- return encodedIndexName + "/" + encodedType + "/" + request;
- }
-
- protected Response postRequest(Object payload, String uri, Map<String, String> params)
- throws IOException {
- return performRequest("POST", payload, uri, params);
- }
-
- private Response performRequest(
- 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.get().performRequest(method, uri, params, entity);
- }
-}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
deleted file mode 100644
index 28fc181..0000000
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.elasticsearch;
-
-import static com.google.gerrit.server.index.account.AccountField.ID;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
-import com.google.gerrit.elasticsearch.builders.QueryBuilder;
-import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
-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.index.QueryOptions;
-import com.google.gerrit.index.Schema;
-import com.google.gerrit.index.query.DataSource;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-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.SitePaths;
-import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.account.AccountField;
-import com.google.gerrit.server.index.account.AccountIndex;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.AssistedInject;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import org.apache.http.HttpStatus;
-import org.apache.http.StatusLine;
-import org.elasticsearch.client.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, AccountState>
- implements AccountIndex {
- public static class AccountMapping {
- MappingProperties accounts;
-
- public AccountMapping(Schema<AccountState> schema) {
- this.accounts = ElasticMapping.createMapping(schema);
- }
- }
-
- public static final String ACCOUNTS = "accounts";
-
- private static final Logger log = LoggerFactory.getLogger(ElasticAccountIndex.class);
-
- private final AccountMapping mapping;
- private final Provider<AccountCache> accountCache;
- private final Schema<AccountState> schema;
-
- @AssistedInject
- ElasticAccountIndex(
- ElasticConfiguration cfg,
- SitePaths sitePaths,
- Provider<AccountCache> accountCache,
- ElasticRestClientProvider client,
- @Assisted Schema<AccountState> schema) {
- super(cfg, sitePaths, schema, client, ACCOUNTS);
- this.accountCache = accountCache;
- this.mapping = new AccountMapping(schema);
- this.schema = schema;
- }
-
- @Override
- public void replace(AccountState as) throws IOException {
- BulkRequest bulk =
- new IndexRequest(getId(as), indexName, ACCOUNTS).add(new UpdateRequest<>(schema, as));
-
- String uri = getURI(ACCOUNTS, BULK);
- Response response = postRequest(bulk, uri, getRefreshParam());
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- throw new IOException(
- String.format(
- "Failed to replace account %s in index %s: %s",
- as.getAccount().getId(), indexName, statusCode));
- }
- }
-
- @Override
- public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts)
- throws QueryParseException {
- return new QuerySource(p, opts);
- }
-
- @Override
- protected String addActions(Account.Id c) {
- return delete(ACCOUNTS, c);
- }
-
- @Override
- protected String getMappings() {
- ImmutableMap<String, AccountMapping> mappings = ImmutableMap.of("mappings", mapping);
- return gson.toJson(mappings);
- }
-
- @Override
- protected String getId(AccountState as) {
- return as.getAccount().getId().toString();
- }
-
- private class QuerySource implements DataSource<AccountState> {
- private final String search;
- private final Set<String> fields;
-
- QuerySource(Predicate<AccountState> p, QueryOptions opts) throws QueryParseException {
- QueryBuilder qb = queryBuilder.toQueryBuilder(p);
- fields = IndexUtils.accountFields(opts);
- SearchSourceBuilder searchSource =
- new SearchSourceBuilder()
- .query(qb)
- .from(opts.start())
- .size(opts.limit())
- .fields(Lists.newArrayList(fields));
-
- JsonArray sortArray = getSortArray(AccountField.ID.getName());
- search = getSearch(searchSource, sortArray);
- }
-
- @Override
- public int getCardinality() {
- return 10;
- }
-
- @Override
- public ResultSet<AccountState> read() throws OrmException {
- try {
- List<AccountState> results = Collections.emptyList();
- String uri = getURI(ACCOUNTS, SEARCH);
- Response response = postRequest(search, uri, Collections.emptyMap());
- StatusLine statusLine = response.getStatusLine();
- if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
- String content = getContent(response);
- JsonObject obj =
- new JsonParser().parse(content).getAsJsonObject().getAsJsonObject("hits");
- if (obj.get("hits") != null) {
- JsonArray json = obj.getAsJsonArray("hits");
- results = Lists.newArrayListWithCapacity(json.size());
- for (int i = 0; i < json.size(); i++) {
- results.add(toAccountState(json.get(i)));
- }
- }
- } else {
- log.error(statusLine.getReasonPhrase());
- }
- final List<AccountState> r = Collections.unmodifiableList(results);
- return new ResultSet<AccountState>() {
- @Override
- public Iterator<AccountState> iterator() {
- return r.iterator();
- }
-
- @Override
- public List<AccountState> toList() {
- return r;
- }
-
- @Override
- public void close() {
- // Do nothing.
- }
- };
- } catch (IOException e) {
- throw new OrmException(e);
- }
- }
-
- private AccountState toAccountState(JsonElement json) {
- JsonElement source = json.getAsJsonObject().get("_source");
- if (source == null) {
- source = json.getAsJsonObject().get("fields");
- }
-
- Account.Id id = new Account.Id(source.getAsJsonObject().get(ID.getName()).getAsInt());
- // Use the AccountCache rather than depending on any stored fields in the
- // document (of which there shouldn't be any). The most expensive part to
- // compute anyway is the effective group IDs, and we don't have a good way
- // to reindex when those change.
- return accountCache.get().get(id);
- }
- }
-}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
deleted file mode 100644
index f5a9bc0..0000000
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ /dev/null
@@ -1,447 +0,0 @@
-// Copyright (C) 2014 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.base.Preconditions.checkNotNull;
-import static com.google.gerrit.server.index.change.ChangeField.APPROVAL_CODEC;
-import static com.google.gerrit.server.index.change.ChangeField.CHANGE_CODEC;
-import static com.google.gerrit.server.index.change.ChangeField.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;
-import static org.apache.commons.codec.binary.Base64.decodeBase64;
-
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Sets;
-import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
-import com.google.gerrit.elasticsearch.builders.QueryBuilder;
-import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
-import com.google.gerrit.elasticsearch.bulk.BulkRequest;
-import com.google.gerrit.elasticsearch.bulk.DeleteRequest;
-import com.google.gerrit.elasticsearch.bulk.IndexRequest;
-import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
-import com.google.gerrit.index.QueryOptions;
-import com.google.gerrit.index.Schema;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Change.Id;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ReviewerByEmailSet;
-import com.google.gerrit.server.ReviewerSet;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.index.change.ChangeIndexRewriter;
-import com.google.gerrit.server.project.SubmitRuleOptions;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeDataSource;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.http.HttpStatus;
-import org.apache.http.StatusLine;
-import org.elasticsearch.client.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** Secondary index implementation using Elasticsearch. */
-class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
- implements ChangeIndex {
- private static final Logger log = LoggerFactory.getLogger(ElasticChangeIndex.class);
-
- static class ChangeMapping {
- MappingProperties openChanges;
- MappingProperties closedChanges;
-
- ChangeMapping(Schema<ChangeData> schema) {
- MappingProperties mapping = ElasticMapping.createMapping(schema);
- this.openChanges = mapping;
- this.closedChanges = mapping;
- }
- }
-
- static final String CHANGES = "changes";
- static final String OPEN_CHANGES = "open_" + CHANGES;
- 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 client,
- @Assisted Schema<ChangeData> schema) {
- super(cfg, sitePaths, schema, client, CHANGES);
- this.db = db;
- this.changeDataFactory = changeDataFactory;
- this.schema = schema;
- mapping = new ChangeMapping(schema);
- }
-
- @Override
- public void replace(ChangeData cd) throws IOException {
- String deleteIndex;
- String insertIndex;
-
- try {
- if (cd.change().getStatus().isOpen()) {
- insertIndex = OPEN_CHANGES;
- deleteIndex = CLOSED_CHANGES;
- } else {
- insertIndex = CLOSED_CHANGES;
- deleteIndex = OPEN_CHANGES;
- }
- } catch (OrmException e) {
- throw new IOException(e);
- }
-
- BulkRequest bulk =
- new IndexRequest(getId(cd), indexName, insertIndex)
- .add(new UpdateRequest<>(schema, cd))
- .add(new DeleteRequest(cd.getId().toString(), indexName, deleteIndex));
-
- String uri = getURI(CHANGES, BULK);
- Response response = postRequest(bulk, uri, getRefreshParam());
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- throw new IOException(
- String.format(
- "Failed to replace change %s in index %s: %s", cd.getId(), indexName, statusCode));
- }
- }
-
- @Override
- public ChangeDataSource getSource(Predicate<ChangeData> p, QueryOptions opts)
- throws QueryParseException {
- Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
- List<String> indexes = Lists.newArrayListWithCapacity(2);
- if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
- indexes.add(OPEN_CHANGES);
- }
- if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
- indexes.add(CLOSED_CHANGES);
- }
- return new QuerySource(indexes, p, opts);
- }
-
- @Override
- protected String addActions(Id c) {
- return delete(OPEN_CHANGES, c) + delete(CLOSED_CHANGES, c);
- }
-
- @Override
- protected String getMappings() {
- return gson.toJson(ImmutableMap.of("mappings", mapping));
- }
-
- @Override
- protected String getId(ChangeData cd) {
- return cd.getId().toString();
- }
-
- private class QuerySource implements ChangeDataSource {
- private final String search;
- private final Set<String> fields;
- private final List<String> types;
-
- QuerySource(List<String> types, Predicate<ChangeData> p, QueryOptions opts)
- throws QueryParseException {
- QueryBuilder qb = queryBuilder.toQueryBuilder(p);
- fields = IndexUtils.changeFields(opts);
- SearchSourceBuilder searchSource =
- new SearchSourceBuilder()
- .query(qb)
- .from(opts.start())
- .size(opts.limit())
- .fields(Lists.newArrayList(fields));
-
- search = getSearch(searchSource, getSortArray());
- this.types = types;
- }
-
- @Override
- public int getCardinality() {
- return 10;
- }
-
- @Override
- public ResultSet<ChangeData> read() throws OrmException {
- try {
- List<ChangeData> results = Collections.emptyList();
- String uri = getURI(types);
- Response response = postRequest(search, uri, Collections.emptyMap());
- StatusLine statusLine = response.getStatusLine();
- if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
- String content = getContent(response);
- JsonObject obj =
- new JsonParser().parse(content).getAsJsonObject().getAsJsonObject("hits");
- if (obj.get("hits") != null) {
- JsonArray json = obj.getAsJsonArray("hits");
- results = Lists.newArrayListWithCapacity(json.size());
- for (int i = 0; i < json.size(); i++) {
- results.add(toChangeData(json.get(i)));
- }
- }
- } else {
- log.error(statusLine.getReasonPhrase());
- }
- final List<ChangeData> r = Collections.unmodifiableList(results);
- return new ResultSet<ChangeData>() {
- @Override
- public Iterator<ChangeData> iterator() {
- return r.iterator();
- }
-
- @Override
- public List<ChangeData> toList() {
- return r;
- }
-
- @Override
- public void close() {
- // Do nothing.
- }
- };
- } catch (IOException e) {
- throw new OrmException(e);
- }
- }
-
- @Override
- public boolean hasChange() {
- return false;
- }
-
- private ChangeData toChangeData(JsonElement json) {
- JsonElement sourceElement = json.getAsJsonObject().get("_source");
- if (sourceElement == null) {
- sourceElement = json.getAsJsonObject().get("fields");
- }
- JsonObject source = sourceElement.getAsJsonObject();
- JsonElement c = source.get(ChangeField.CHANGE.getName());
-
- if (c == null) {
- int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
- // IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
- String projectName = checkNotNull(source.get(ChangeField.PROJECT.getName()).getAsString());
- return changeDataFactory.create(
- db.get(), new Project.NameKey(projectName), new Change.Id(id));
- }
-
- ChangeData cd =
- changeDataFactory.create(
- db.get(), CHANGE_CODEC.decode(Base64.decodeBase64(c.getAsString())));
-
- // Patch sets.
- cd.setPatchSets(decodeProtos(source, ChangeField.PATCH_SET.getName(), PATCH_SET_CODEC));
-
- // Approvals.
- if (source.get(ChangeField.APPROVAL.getName()) != null) {
- cd.setCurrentApprovals(
- decodeProtos(source, ChangeField.APPROVAL.getName(), APPROVAL_CODEC));
- } else if (fields.contains(ChangeField.APPROVAL.getName())) {
- cd.setCurrentApprovals(Collections.emptyList());
- }
-
- JsonElement addedElement = source.get(ChangeField.ADDED.getName());
- JsonElement deletedElement = source.get(ChangeField.DELETED.getName());
- if (addedElement != null && deletedElement != null) {
- // Changed lines.
- int added = addedElement.getAsInt();
- int deleted = deletedElement.getAsInt();
- cd.setChangedLines(added, deleted);
- }
-
- // Star.
- JsonElement starredElement = source.get(ChangeField.STAR.getName());
- if (starredElement != null) {
- ListMultimap<Account.Id, String> stars =
- MultimapBuilder.hashKeys().arrayListValues().build();
- JsonArray starBy = starredElement.getAsJsonArray();
- if (starBy.size() > 0) {
- for (int i = 0; i < starBy.size(); i++) {
- String[] indexableFields = starBy.get(i).getAsString().split(":");
- Account.Id id = Account.Id.parse(indexableFields[0]);
- stars.put(id, indexableFields[1]);
- }
- }
- cd.setStars(stars);
- }
-
- // Mergeable.
- JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
- if (mergeableElement != null) {
- String mergeable = mergeableElement.getAsString();
- if ("1".equals(mergeable)) {
- cd.setMergeable(true);
- } else if ("0".equals(mergeable)) {
- cd.setMergeable(false);
- }
- }
-
- // Reviewed-by.
- if (source.get(ChangeField.REVIEWEDBY.getName()) != null) {
- JsonArray reviewedBy = source.get(ChangeField.REVIEWEDBY.getName()).getAsJsonArray();
- if (reviewedBy.size() > 0) {
- Set<Account.Id> accounts = Sets.newHashSetWithExpectedSize(reviewedBy.size());
- for (int i = 0; i < reviewedBy.size(); i++) {
- int aId = reviewedBy.get(i).getAsInt();
- if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
- break;
- }
- accounts.add(new Account.Id(aId));
- }
- cd.setReviewedBy(accounts);
- }
- } else if (fields.contains(ChangeField.REVIEWEDBY.getName())) {
- cd.setReviewedBy(Collections.emptySet());
- }
-
- if (source.get(ChangeField.REVIEWER.getName()) != null) {
- cd.setReviewers(
- ChangeField.parseReviewerFieldValues(
- FluentIterable.from(source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.REVIEWER.getName())) {
- cd.setReviewers(ReviewerSet.empty());
- }
-
- if (source.get(ChangeField.REVIEWER_BY_EMAIL.getName()) != null) {
- cd.setReviewersByEmail(
- ChangeField.parseReviewerByEmailFieldValues(
- FluentIterable.from(
- source.get(ChangeField.REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.REVIEWER_BY_EMAIL.getName())) {
- cd.setReviewersByEmail(ReviewerByEmailSet.empty());
- }
-
- if (source.get(ChangeField.PENDING_REVIEWER.getName()) != null) {
- cd.setPendingReviewers(
- ChangeField.parseReviewerFieldValues(
- FluentIterable.from(
- source.get(ChangeField.PENDING_REVIEWER.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.PENDING_REVIEWER.getName())) {
- cd.setPendingReviewers(ReviewerSet.empty());
- }
-
- if (source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()) != null) {
- cd.setPendingReviewersByEmail(
- ChangeField.parseReviewerByEmailFieldValues(
- FluentIterable.from(
- source
- .get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())
- .getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())) {
- cd.setPendingReviewersByEmail(ReviewerByEmailSet.empty());
- }
- decodeSubmitRecords(
- source,
- ChangeField.STORED_SUBMIT_RECORD_STRICT.getName(),
- ChangeField.SUBMIT_RULE_OPTIONS_STRICT,
- cd);
- decodeSubmitRecords(
- source,
- ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
- ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
- cd);
- decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
-
- if (fields.contains(ChangeField.REF_STATE.getName())) {
- cd.setRefStates(getByteArray(source, ChangeField.REF_STATE.getName()));
- }
- if (fields.contains(ChangeField.REF_STATE_PATTERN.getName())) {
- cd.setRefStatePatterns(getByteArray(source, ChangeField.REF_STATE_PATTERN.getName()));
- }
-
- return cd;
- }
-
- private Iterable<byte[]> getByteArray(JsonObject source, String name) {
- JsonElement element = source.get(name);
- return element != null
- ? Iterables.transform(element.getAsJsonArray(), e -> Base64.decodeBase64(e.getAsString()))
- : Collections.emptyList();
- }
-
- private void decodeSubmitRecords(
- JsonObject doc, String fieldName, SubmitRuleOptions opts, ChangeData out) {
- JsonArray records = doc.getAsJsonArray(fieldName);
- if (records == null) {
- return;
- }
- ChangeField.parseSubmitRecords(
- FluentIterable.from(records)
- .transform(i -> new String(decodeBase64(i.toString()), UTF_8))
- .toList(),
- opts,
- out);
- }
-
- private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) {
- JsonElement count = doc.get(fieldName);
- if (count == null) {
- return;
- }
- out.setUnresolvedCommentCount(count.getAsInt());
- }
-
- private JsonArray getSortArray() {
- JsonObject properties = new JsonObject();
- properties.addProperty(ORDER, "desc");
- properties.addProperty(IGNORE_UNMAPPED, true);
-
- JsonArray sortArray = new JsonArray();
- addNamedElement(ChangeField.UPDATED.getName(), properties, sortArray);
- addNamedElement(ChangeField.LEGACY_ID.getName(), properties, sortArray);
- return sortArray;
- }
- }
-
- private String getURI(List<String> types) throws UnsupportedEncodingException {
- String joinedTypes = String.join(",", types);
- return getURI(joinedTypes, SEARCH);
- }
-}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
deleted file mode 100644
index 03a958b..0000000
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright (C) 2017 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.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
-import com.google.gerrit.elasticsearch.builders.QueryBuilder;
-import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
-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.index.QueryOptions;
-import com.google.gerrit.index.Schema;
-import com.google.gerrit.index.query.DataSource;
-import com.google.gerrit.index.query.Predicate;
-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.SitePaths;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.group.GroupField;
-import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
-import com.google.inject.assistedinject.Assisted;
-import com.google.inject.assistedinject.AssistedInject;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import org.apache.http.HttpStatus;
-import org.apache.http.StatusLine;
-import org.elasticsearch.client.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, InternalGroup>
- implements GroupIndex {
- static class GroupMapping {
- MappingProperties groups;
-
- GroupMapping(Schema<InternalGroup> schema) {
- this.groups = ElasticMapping.createMapping(schema);
- }
- }
-
- public static final String GROUPS = "groups";
-
- private static final Logger log = LoggerFactory.getLogger(ElasticGroupIndex.class);
-
- private final GroupMapping mapping;
- private final Provider<GroupCache> groupCache;
- private final Schema<InternalGroup> schema;
-
- @AssistedInject
- ElasticGroupIndex(
- ElasticConfiguration cfg,
- SitePaths sitePaths,
- Provider<GroupCache> groupCache,
- ElasticRestClientProvider client,
- @Assisted Schema<InternalGroup> schema) {
- super(cfg, sitePaths, schema, client, GROUPS);
- this.groupCache = groupCache;
- this.mapping = new GroupMapping(schema);
- this.schema = schema;
- }
-
- @Override
- public void replace(InternalGroup group) throws IOException {
- BulkRequest bulk =
- new IndexRequest(getId(group), indexName, GROUPS).add(new UpdateRequest<>(schema, group));
-
- String uri = getURI(GROUPS, BULK);
- Response response = postRequest(bulk, uri, getRefreshParam());
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- throw new IOException(
- String.format(
- "Failed to replace group %s in index %s: %s",
- group.getGroupUUID().get(), indexName, statusCode));
- }
- }
-
- @Override
- public DataSource<InternalGroup> getSource(Predicate<InternalGroup> p, QueryOptions opts)
- throws QueryParseException {
- return new QuerySource(p, opts);
- }
-
- @Override
- protected String addActions(AccountGroup.UUID c) {
- return delete(GROUPS, c);
- }
-
- @Override
- protected String getMappings() {
- ImmutableMap<String, GroupMapping> mappings = ImmutableMap.of("mappings", mapping);
- return gson.toJson(mappings);
- }
-
- @Override
- protected String getId(InternalGroup group) {
- return group.getGroupUUID().get();
- }
-
- private class QuerySource implements DataSource<InternalGroup> {
- private final String search;
- private final Set<String> fields;
-
- QuerySource(Predicate<InternalGroup> p, QueryOptions opts) throws QueryParseException {
- QueryBuilder qb = queryBuilder.toQueryBuilder(p);
- fields = IndexUtils.groupFields(opts);
- SearchSourceBuilder searchSource =
- new SearchSourceBuilder()
- .query(qb)
- .from(opts.start())
- .size(opts.limit())
- .fields(Lists.newArrayList(fields));
-
- JsonArray sortArray = getSortArray(GroupField.UUID.getName());
- search = getSearch(searchSource, sortArray);
- }
-
- @Override
- public int getCardinality() {
- return 10;
- }
-
- @Override
- public ResultSet<InternalGroup> read() throws OrmException {
- try {
- List<InternalGroup> results = Collections.emptyList();
- String uri = getURI(GROUPS, SEARCH);
- Response response = postRequest(search, uri, Collections.emptyMap());
- StatusLine statusLine = response.getStatusLine();
- if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
- String content = getContent(response);
- JsonObject obj =
- new JsonParser().parse(content).getAsJsonObject().getAsJsonObject("hits");
- if (obj.get("hits") != null) {
- JsonArray json = obj.getAsJsonArray("hits");
- results = Lists.newArrayListWithCapacity(json.size());
- for (int i = 0; i < json.size(); i++) {
- results.add(toAccountGroup(json.get(i)).get());
- }
- }
- } else {
- log.error(statusLine.getReasonPhrase());
- }
- final List<InternalGroup> r = Collections.unmodifiableList(results);
- return new ResultSet<InternalGroup>() {
- @Override
- public Iterator<InternalGroup> iterator() {
- return r.iterator();
- }
-
- @Override
- public List<InternalGroup> toList() {
- return r;
- }
-
- @Override
- public void close() {
- // Do nothing.
- }
- };
- } catch (IOException e) {
- throw new OrmException(e);
- }
- }
-
- private Optional<InternalGroup> toAccountGroup(JsonElement json) {
- JsonElement source = json.getAsJsonObject().get("_source");
- if (source == null) {
- source = json.getAsJsonObject().get("fields");
- }
-
- AccountGroup.UUID uuid =
- new AccountGroup.UUID(
- source.getAsJsonObject().get(GroupField.UUID.getName()).getAsString());
- // Use the GroupCache rather than depending on any stored fields in the
- // document (of which there shouldn't be any).
- return groupCache.get().get(uuid);
- }
- }
-}
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
new file mode 100644
index 0000000..320ebfa
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -0,0 +1,359 @@
+// Copyright (C) 2014 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.base.Preconditions.checkArgument;
+import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.codec.binary.Base64.decodeBase64;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.io.CharStreams;
+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.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.QueryOptions;
+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.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+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 java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+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;
+import java.util.function.Function;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.nio.entity.NStringEntity;
+import org.elasticsearch.client.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
+ private static final Logger log = LoggerFactory.getLogger(AbstractElasticIndex.class);
+
+ protected static final String BULK = "_bulk";
+ protected static final String IGNORE_UNMAPPED = "ignore_unmapped";
+ protected static final String ORDER = "order";
+ protected static final String SEARCH = "_search";
+
+ protected static <T> List<T> decodeProtos(
+ JsonObject doc, String fieldName, ProtobufCodec<T> codec) {
+ JsonArray field = doc.getAsJsonArray(fieldName);
+ if (field == null) {
+ return null;
+ }
+ return FluentIterable.from(field)
+ .transform(i -> codec.decode(decodeBase64(i.toString())))
+ .toList();
+ }
+
+ static String getContent(Response response) throws IOException {
+ HttpEntity responseEntity = response.getEntity();
+ String content = "";
+ if (responseEntity != null) {
+ InputStream contentStream = responseEntity.getContent();
+ try (Reader reader = new InputStreamReader(contentStream)) {
+ content = CharStreams.toString(reader);
+ }
+ }
+ return content;
+ }
+
+ private final Schema<V> schema;
+ private final SitePaths sitePaths;
+ private final String indexNameRaw;
+ private final ElasticRestClientProvider client;
+
+ protected final String indexName;
+ protected final Gson gson;
+ protected final ElasticQueryBuilder queryBuilder;
+
+ AbstractElasticIndex(
+ ElasticConfiguration cfg,
+ SitePaths sitePaths,
+ Schema<V> schema,
+ 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 = cfg.getIndexName(indexName, schema.getVersion());
+ this.indexNameRaw = indexName;
+ this.client = client;
+ }
+
+ @Override
+ public Schema<V> getSchema() {
+ return schema;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing. Client is closed by the provider.
+ }
+
+ @Override
+ public void markReady(boolean ready) throws IOException {
+ IndexUtils.setReady(sitePaths, indexNameRaw, schema.getVersion(), ready);
+ }
+
+ @Override
+ public void delete(K c) throws IOException {
+ String uri = getURI(indexNameRaw, BULK);
+ Response response = postRequest(addActions(c), uri, getRefreshParam());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException(
+ String.format("Failed to delete %s from index %s: %s", c, indexName, statusCode));
+ }
+ }
+
+ @Override
+ public void deleteAll() throws IOException {
+ // Delete the index, if it exists.
+ Response response = client.get().performRequest("HEAD", indexName);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_OK) {
+ response = client.get().performRequest("DELETE", indexName);
+ statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException(
+ String.format("Failed to delete index %s: %s", indexName, statusCode));
+ }
+ }
+
+ // Recreate the index.
+ response = performRequest("PUT", getMappings(), indexName, Collections.emptyMap());
+ 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);
+ }
+ }
+
+ protected abstract String addActions(K c);
+
+ protected abstract String getMappings();
+
+ protected abstract String getId(V v);
+
+ protected String delete(String type, K c) {
+ String id = c.toString();
+ return new DeleteRequest(id, indexNameRaw, type).toString();
+ }
+
+ protected abstract V fromDocument(JsonObject doc, Set<String> fields);
+
+ protected FieldBundle toFieldBundle(JsonObject doc) {
+ Map<String, FieldDef<V, ?>> allFields = getSchema().getFields();
+ ListMultimap<String, Object> rawFields = ArrayListMultimap.create();
+ for (Map.Entry<String, JsonElement> element : doc.get("fields").getAsJsonObject().entrySet()) {
+ checkArgument(
+ allFields.containsKey(element.getKey()), "Unrecognized field " + element.getKey());
+ FieldType<?> type = allFields.get(element.getKey()).getType();
+ Iterable<JsonElement> innerItems =
+ element.getValue().isJsonArray()
+ ? element.getValue().getAsJsonArray()
+ : Collections.singleton(element.getValue());
+ for (JsonElement inner : innerItems) {
+ if (type == FieldType.EXACT || type == FieldType.FULL_TEXT || type == FieldType.PREFIX) {
+ rawFields.put(element.getKey(), inner.getAsString());
+ } else if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
+ rawFields.put(element.getKey(), inner.getAsInt());
+ } else if (type == FieldType.LONG) {
+ rawFields.put(element.getKey(), inner.getAsLong());
+ } else if (type == FieldType.TIMESTAMP) {
+ rawFields.put(element.getKey(), new Timestamp(inner.getAsLong()));
+ } else if (type == FieldType.STORED_ONLY) {
+ rawFields.put(element.getKey(), Base64.decodeBase64(inner.getAsString()));
+ } else {
+ throw FieldType.badFieldType(type);
+ }
+ }
+ }
+ return new FieldBundle(rawFields);
+ }
+
+ protected String toAction(String type, String id, String action) {
+ JsonObject properties = new JsonObject();
+ properties.addProperty("_id", id);
+ properties.addProperty("_index", indexName);
+ properties.addProperty("_type", type);
+
+ JsonObject jsonAction = new JsonObject();
+ jsonAction.add(action, properties);
+ return jsonAction.toString() + System.lineSeparator();
+ }
+
+ protected void addNamedElement(String name, JsonObject element, JsonArray array) {
+ JsonObject arrayElement = new JsonObject();
+ arrayElement.add(name, element);
+ array.add(arrayElement);
+ }
+
+ protected Map<String, String> getRefreshParam() {
+ Map<String, String> params = new HashMap<>();
+ params.put("refresh", "true");
+ return params;
+ }
+
+ protected String getSearch(SearchSourceBuilder searchSource, JsonArray sortArray) {
+ JsonObject search = new JsonParser().parse(searchSource.toString()).getAsJsonObject();
+ search.add("sort", sortArray);
+ return gson.toJson(search);
+ }
+
+ protected JsonArray getSortArray(String idFieldName) {
+ JsonObject properties = new JsonObject();
+ properties.addProperty(ORDER, "asc");
+ properties.addProperty(IGNORE_UNMAPPED, true);
+
+ JsonArray sortArray = new JsonArray();
+ addNamedElement(idFieldName, properties, sortArray);
+ return sortArray;
+ }
+
+ protected String getURI(String type, String request) throws UnsupportedEncodingException {
+ String encodedType = URLEncoder.encode(type, UTF_8.toString());
+ String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
+ return encodedIndexName + "/" + encodedType + "/" + request;
+ }
+
+ protected Response postRequest(Object payload, String uri, Map<String, String> params)
+ throws IOException {
+ return performRequest("POST", payload, uri, params);
+ }
+
+ private Response performRequest(
+ 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.get().performRequest(method, uri, params, entity);
+ }
+
+ protected class ElasticQuerySource implements DataSource<V> {
+ private final QueryOptions opts;
+ private final String search;
+ private final String index;
+
+ ElasticQuerySource(Predicate<V> p, QueryOptions opts, String index, JsonArray sortArray)
+ throws QueryParseException {
+ this.opts = opts;
+ this.index = index;
+ QueryBuilder qb = queryBuilder.toQueryBuilder(p);
+ SearchSourceBuilder searchSource =
+ new SearchSourceBuilder()
+ .query(qb)
+ .from(opts.start())
+ .size(opts.limit())
+ .fields(Lists.newArrayList(opts.fields()));
+ search = getSearch(searchSource, sortArray);
+ }
+
+ @Override
+ public int getCardinality() {
+ return 10;
+ }
+
+ @Override
+ public ResultSet<V> read() throws OrmException {
+ return readImpl((doc) -> AbstractElasticIndex.this.fromDocument(doc, opts.fields()));
+ }
+
+ @Override
+ public ResultSet<FieldBundle> readRaw() throws OrmException {
+ return readImpl(AbstractElasticIndex.this::toFieldBundle);
+ }
+
+ private <T> ResultSet<T> readImpl(Function<JsonObject, T> mapper) throws OrmException {
+ try {
+ List<T> results = Collections.emptyList();
+ String uri = getURI(index, SEARCH);
+ Response response =
+ performRequest(HttpPost.METHOD_NAME, search, uri, Collections.emptyMap());
+ StatusLine statusLine = response.getStatusLine();
+ if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
+ String content = getContent(response);
+ JsonObject obj =
+ new JsonParser().parse(content).getAsJsonObject().getAsJsonObject("hits");
+ if (obj.get("hits") != null) {
+ JsonArray json = obj.getAsJsonArray("hits");
+ results = Lists.newArrayListWithCapacity(json.size());
+ for (int i = 0; i < json.size(); i++) {
+ T mapperResult = mapper.apply(json.get(i).getAsJsonObject());
+ if (mapperResult != null) {
+ results.add(mapperResult);
+ }
+ }
+ }
+ } else {
+ log.error(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.
+ }
+ };
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+ }
+}
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
new file mode 100644
index 0000000..b771cf5
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/BUILD
@@ -0,0 +1,30 @@
+java_library(
+ name = "elasticsearch",
+ srcs = glob(["**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//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/reviewdb:server",
+ "//java/com/google/gerrit/server",
+ "//lib:gson",
+ "//lib:guava",
+ "//lib:gwtorm",
+ "//lib:protobuf",
+ "//lib/commons:codec",
+ "//lib/commons:lang",
+ "//lib/elasticsearch-rest-client",
+ "//lib/guice",
+ "//lib/guice:guice-assistedinject",
+ "//lib/httpcomponents:httpasyncclient",
+ "//lib/httpcomponents:httpclient",
+ "//lib/httpcomponents:httpcore",
+ "//lib/httpcomponents:httpcore-nio",
+ "//lib/jackson:jackson-core",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib/log:api",
+ ],
+)
diff --git a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
new file mode 100644
index 0000000..1abdee9
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -0,0 +1,131 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.elasticsearch;
+
+import static com.google.gerrit.server.index.account.AccountField.ID;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+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.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+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.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.account.AccountField;
+import com.google.gerrit.server.index.account.AccountIndex;
+import com.google.gson.JsonArray;
+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.Set;
+import org.apache.http.HttpStatus;
+import org.elasticsearch.client.Response;
+
+public class ElasticAccountIndex extends AbstractElasticIndex<Account.Id, AccountState>
+ implements AccountIndex {
+ static class AccountMapping {
+ MappingProperties accounts;
+
+ AccountMapping(Schema<AccountState> schema) {
+ this.accounts = ElasticMapping.createMapping(schema);
+ }
+ }
+
+ static final String ACCOUNTS = "accounts";
+
+ private final AccountMapping mapping;
+ private final Provider<AccountCache> accountCache;
+ private final Schema<AccountState> schema;
+
+ @Inject
+ ElasticAccountIndex(
+ ElasticConfiguration cfg,
+ SitePaths sitePaths,
+ Provider<AccountCache> accountCache,
+ ElasticRestClientProvider client,
+ @Assisted Schema<AccountState> schema) {
+ super(cfg, sitePaths, schema, client, ACCOUNTS);
+ this.accountCache = accountCache;
+ this.mapping = new AccountMapping(schema);
+ this.schema = schema;
+ }
+
+ @Override
+ public void replace(AccountState as) throws IOException {
+ BulkRequest bulk =
+ new IndexRequest(getId(as), indexName, ACCOUNTS).add(new UpdateRequest<>(schema, as));
+
+ String uri = getURI(ACCOUNTS, BULK);
+ Response response = postRequest(bulk, uri, getRefreshParam());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException(
+ String.format(
+ "Failed to replace account %s in index %s: %s",
+ as.getAccount().getId(), indexName, statusCode));
+ }
+ }
+
+ @Override
+ public DataSource<AccountState> getSource(Predicate<AccountState> p, QueryOptions opts)
+ throws QueryParseException {
+ JsonArray sortArray = getSortArray(AccountField.ID.getName());
+ return new ElasticQuerySource(
+ p, opts.filterFields(IndexUtils::accountFields), ACCOUNTS, sortArray);
+ }
+
+ @Override
+ protected String addActions(Account.Id c) {
+ return delete(ACCOUNTS, c);
+ }
+
+ @Override
+ protected String getMappings() {
+ ImmutableMap<String, AccountMapping> mappings = ImmutableMap.of("mappings", mapping);
+ return gson.toJson(mappings);
+ }
+
+ @Override
+ protected String getId(AccountState as) {
+ return as.getAccount().getId().toString();
+ }
+
+ @Override
+ protected AccountState fromDocument(JsonObject json, Set<String> fields) {
+ JsonElement source = json.get("_source");
+ if (source == null) {
+ source = json.getAsJsonObject().get("fields");
+ }
+
+ Account.Id id = new Account.Id(source.getAsJsonObject().get(ID.getName()).getAsInt());
+ // Use the AccountCache rather than depending on any stored fields in the document (of which
+ // there shouldn't be any). The most expensive part to compute anyway is the effective group
+ // IDs, and we don't have a good way to reindex when those change.
+ // If the account doesn't exist return an empty AccountState to represent the missing account
+ // to account the fact that the account exists in the index.
+ return accountCache.get().getEvenIfMissing(id);
+ }
+}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
new file mode 100644
index 0000000..c7b0fb5
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -0,0 +1,416 @@
+// Copyright (C) 2014 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.base.Preconditions.checkNotNull;
+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;
+import static org.apache.commons.codec.binary.Base64.decodeBase64;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.Sets;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.elasticsearch.bulk.BulkRequest;
+import com.google.gerrit.elasticsearch.bulk.DeleteRequest;
+import com.google.gerrit.elasticsearch.bulk.IndexRequest;
+import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Change.Id;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ReviewerByEmailSet;
+import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.index.change.ChangeIndex;
+import com.google.gerrit.server.index.change.ChangeIndexRewriter;
+import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gwtorm.server.OrmException;
+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;
+import java.util.Set;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.HttpStatus;
+import org.elasticsearch.client.Response;
+
+/** Secondary index implementation using Elasticsearch. */
+class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
+ implements ChangeIndex {
+ static class ChangeMapping {
+ MappingProperties openChanges;
+ MappingProperties closedChanges;
+
+ ChangeMapping(Schema<ChangeData> schema) {
+ MappingProperties mapping = ElasticMapping.createMapping(schema);
+ this.openChanges = mapping;
+ this.closedChanges = mapping;
+ }
+ }
+
+ static final String CHANGES = "changes";
+ static final String OPEN_CHANGES = "open_" + CHANGES;
+ 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);
+ }
+
+ @Override
+ public void replace(ChangeData cd) throws IOException {
+ String deleteIndex;
+ String insertIndex;
+
+ try {
+ if (cd.change().getStatus().isOpen()) {
+ insertIndex = OPEN_CHANGES;
+ deleteIndex = CLOSED_CHANGES;
+ } else {
+ insertIndex = CLOSED_CHANGES;
+ deleteIndex = OPEN_CHANGES;
+ }
+ } catch (OrmException e) {
+ throw new IOException(e);
+ }
+
+ BulkRequest bulk =
+ new IndexRequest(getId(cd), indexName, insertIndex)
+ .add(new UpdateRequest<>(schema, cd))
+ .add(new DeleteRequest(cd.getId().toString(), indexName, deleteIndex));
+
+ String uri = getURI(CHANGES, BULK);
+ Response response = postRequest(bulk, uri, getRefreshParam());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException(
+ String.format(
+ "Failed to replace change %s in index %s: %s", cd.getId(), indexName, statusCode));
+ }
+ }
+
+ @Override
+ public DataSource<ChangeData> getSource(Predicate<ChangeData> p, QueryOptions opts)
+ throws QueryParseException {
+ Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
+ List<String> indexes = Lists.newArrayListWithCapacity(2);
+ if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
+ indexes.add(OPEN_CHANGES);
+ }
+ if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
+ indexes.add(CLOSED_CHANGES);
+ }
+
+ QueryOptions filteredOpts = opts.filterFields(IndexUtils::changeFields);
+ return new ElasticQuerySource(p, filteredOpts, getURI(indexes), getSortArray());
+ }
+
+ private JsonArray getSortArray() {
+ JsonObject properties = new JsonObject();
+ properties.addProperty(ORDER, "desc");
+ properties.addProperty(IGNORE_UNMAPPED, true);
+
+ JsonArray sortArray = new JsonArray();
+ addNamedElement(ChangeField.UPDATED.getName(), properties, sortArray);
+ addNamedElement(ChangeField.LEGACY_ID.getName(), properties, sortArray);
+ return sortArray;
+ }
+
+ private String getURI(List<String> types) {
+ return String.join(",", types);
+ }
+
+ @Override
+ protected String addActions(Id c) {
+ return delete(OPEN_CHANGES, c) + delete(CLOSED_CHANGES, c);
+ }
+
+ @Override
+ protected String getMappings() {
+ return gson.toJson(ImmutableMap.of("mappings", mapping));
+ }
+
+ @Override
+ protected String getId(ChangeData cd) {
+ return cd.getId().toString();
+ }
+
+ @Override
+ protected ChangeData fromDocument(JsonObject json, Set<String> fields) {
+ JsonElement sourceElement = json.get("_source");
+ if (sourceElement == null) {
+ sourceElement = json.getAsJsonObject().get("fields");
+ }
+ JsonObject source = sourceElement.getAsJsonObject();
+ JsonElement c = source.get(ChangeField.CHANGE.getName());
+
+ if (c == null) {
+ int id = source.get(ChangeField.LEGACY_ID.getName()).getAsInt();
+ // IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
+ String projectName = checkNotNull(source.get(ChangeField.PROJECT.getName()).getAsString());
+ return changeDataFactory.create(
+ db.get(), new Project.NameKey(projectName), new Change.Id(id));
+ }
+
+ ChangeData cd =
+ changeDataFactory.create(
+ db.get(), CHANGE_CODEC.decode(Base64.decodeBase64(c.getAsString())));
+
+ // 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));
+
+ // Approvals.
+ if (source.get(ChangeField.APPROVAL.getName()) != null) {
+ cd.setCurrentApprovals(decodeProtos(source, ChangeField.APPROVAL.getName(), APPROVAL_CODEC));
+ } else if (fields.contains(ChangeField.APPROVAL.getName())) {
+ cd.setCurrentApprovals(Collections.emptyList());
+ }
+
+ // Added & Deleted.
+ JsonElement addedElement = source.get(ChangeField.ADDED.getName());
+ JsonElement deletedElement = source.get(ChangeField.DELETED.getName());
+ if (addedElement != null && deletedElement != null) {
+ // Changed lines.
+ int added = addedElement.getAsInt();
+ int deleted = deletedElement.getAsInt();
+ cd.setChangedLines(added, deleted);
+ }
+
+ // Star.
+ JsonElement starredElement = source.get(ChangeField.STAR.getName());
+ if (starredElement != null) {
+ ListMultimap<Account.Id, String> stars = MultimapBuilder.hashKeys().arrayListValues().build();
+ JsonArray starBy = starredElement.getAsJsonArray();
+ if (starBy.size() > 0) {
+ for (int i = 0; i < starBy.size(); i++) {
+ String[] indexableFields = starBy.get(i).getAsString().split(":");
+ Optional<Account.Id> id = Account.Id.tryParse(indexableFields[0]);
+ if (id.isPresent()) {
+ stars.put(id.get(), indexableFields[1]);
+ }
+ }
+ }
+ cd.setStars(stars);
+ }
+
+ // Mergeable.
+ JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
+ if (mergeableElement != null) {
+ String mergeable = mergeableElement.getAsString();
+ if ("1".equals(mergeable)) {
+ cd.setMergeable(true);
+ } else if ("0".equals(mergeable)) {
+ cd.setMergeable(false);
+ }
+ }
+
+ // Reviewed-by.
+ if (source.get(ChangeField.REVIEWEDBY.getName()) != null) {
+ JsonArray reviewedBy = source.get(ChangeField.REVIEWEDBY.getName()).getAsJsonArray();
+ if (reviewedBy.size() > 0) {
+ Set<Account.Id> accounts = Sets.newHashSetWithExpectedSize(reviewedBy.size());
+ for (int i = 0; i < reviewedBy.size(); i++) {
+ int aId = reviewedBy.get(i).getAsInt();
+ if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
+ break;
+ }
+ accounts.add(new Account.Id(aId));
+ }
+ cd.setReviewedBy(accounts);
+ }
+ } else if (fields.contains(ChangeField.REVIEWEDBY.getName())) {
+ cd.setReviewedBy(Collections.emptySet());
+ }
+
+ // Hashtag.
+ if (source.get(ChangeField.HASHTAG.getName()) != null) {
+ JsonArray hashtagArray = source.get(ChangeField.HASHTAG.getName()).getAsJsonArray();
+ if (hashtagArray.size() > 0) {
+ Set<String> hashtags = Sets.newHashSetWithExpectedSize(hashtagArray.size());
+ for (int i = 0; i < hashtagArray.size(); i++) {
+ hashtags.add(hashtagArray.get(i).getAsString());
+ }
+ cd.setHashtags(hashtags);
+ }
+ } else if (fields.contains(ChangeField.HASHTAG.getName())) {
+ cd.setHashtags(Collections.emptySet());
+ }
+
+ // Star.
+ if (source.get(ChangeField.STAR.getName()) != null) {
+ JsonArray starArray = source.get(ChangeField.STAR.getName()).getAsJsonArray();
+ if (starArray.size() > 0) {
+ ListMultimap<Account.Id, String> stars =
+ MultimapBuilder.hashKeys().arrayListValues().build();
+ for (int i = 0; i < starArray.size(); i++) {
+ StarredChangesUtil.StarField starField =
+ StarredChangesUtil.StarField.parse(starArray.get(i).getAsString());
+ stars.put(starField.accountId(), starField.label());
+ }
+ cd.setStars(stars);
+ }
+ } else if (fields.contains(ChangeField.STAR.getName())) {
+ cd.setStars(ImmutableListMultimap.of());
+ }
+
+ // Reviewer.
+ if (source.get(ChangeField.REVIEWER.getName()) != null) {
+ cd.setReviewers(
+ ChangeField.parseReviewerFieldValues(
+ cd.getId(),
+ FluentIterable.from(source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
+ .transform(JsonElement::getAsString)));
+ } else if (fields.contains(ChangeField.REVIEWER.getName())) {
+ cd.setReviewers(ReviewerSet.empty());
+ }
+
+ // Reviewer-by-email.
+ if (source.get(ChangeField.REVIEWER_BY_EMAIL.getName()) != null) {
+ cd.setReviewersByEmail(
+ ChangeField.parseReviewerByEmailFieldValues(
+ cd.getId(),
+ FluentIterable.from(
+ source.get(ChangeField.REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
+ .transform(JsonElement::getAsString)));
+ } else if (fields.contains(ChangeField.REVIEWER_BY_EMAIL.getName())) {
+ cd.setReviewersByEmail(ReviewerByEmailSet.empty());
+ }
+
+ // Pending-reviewer.
+ if (source.get(ChangeField.PENDING_REVIEWER.getName()) != null) {
+ cd.setPendingReviewers(
+ ChangeField.parseReviewerFieldValues(
+ cd.getId(),
+ FluentIterable.from(
+ source.get(ChangeField.PENDING_REVIEWER.getName()).getAsJsonArray())
+ .transform(JsonElement::getAsString)));
+ } else if (fields.contains(ChangeField.PENDING_REVIEWER.getName())) {
+ cd.setPendingReviewers(ReviewerSet.empty());
+ }
+
+ // Pending-reviewer-by-email.
+ if (source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()) != null) {
+ cd.setPendingReviewersByEmail(
+ ChangeField.parseReviewerByEmailFieldValues(
+ cd.getId(),
+ FluentIterable.from(
+ source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
+ .transform(JsonElement::getAsString)));
+ } else if (fields.contains(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())) {
+ cd.setPendingReviewersByEmail(ReviewerByEmailSet.empty());
+ }
+
+ // Stored-submit-record-strict.
+ decodeSubmitRecords(
+ source,
+ ChangeField.STORED_SUBMIT_RECORD_STRICT.getName(),
+ ChangeField.SUBMIT_RULE_OPTIONS_STRICT,
+ cd);
+
+ // Stored-submit-record-leniant.
+ decodeSubmitRecords(
+ source,
+ ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
+ ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
+ cd);
+
+ // Ref-state.
+ if (fields.contains(ChangeField.REF_STATE.getName())) {
+ cd.setRefStates(getByteArray(source, ChangeField.REF_STATE.getName()));
+ }
+
+ // Ref-state-pattern.
+ if (fields.contains(ChangeField.REF_STATE_PATTERN.getName())) {
+ cd.setRefStatePatterns(getByteArray(source, ChangeField.REF_STATE_PATTERN.getName()));
+ }
+
+ // Unresolved-comment-count.
+ decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
+
+ return cd;
+ }
+
+ private Iterable<byte[]> getByteArray(JsonObject source, String name) {
+ JsonElement element = source.get(name);
+ return element != null
+ ? Iterables.transform(element.getAsJsonArray(), e -> Base64.decodeBase64(e.getAsString()))
+ : Collections.emptyList();
+ }
+
+ private void decodeSubmitRecords(
+ JsonObject doc, String fieldName, SubmitRuleOptions opts, ChangeData out) {
+ JsonArray records = doc.getAsJsonArray(fieldName);
+ if (records == null) {
+ return;
+ }
+ ChangeField.parseSubmitRecords(
+ FluentIterable.from(records)
+ .transform(i -> new String(decodeBase64(i.toString()), UTF_8))
+ .toList(),
+ opts,
+ out);
+ }
+
+ private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) {
+ JsonElement count = doc.get(fieldName);
+ if (count == null) {
+ return;
+ }
+ out.setUnresolvedCommentCount(count.getAsInt());
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
rename to java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
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
new file mode 100644
index 0000000..d1eab7c
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2017 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.collect.ImmutableMap;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+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.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+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.SitePaths;
+import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.group.GroupField;
+import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.gson.JsonArray;
+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.Set;
+import org.apache.http.HttpStatus;
+import org.elasticsearch.client.Response;
+
+public class ElasticGroupIndex extends AbstractElasticIndex<AccountGroup.UUID, InternalGroup>
+ implements GroupIndex {
+ static class GroupMapping {
+ MappingProperties groups;
+
+ GroupMapping(Schema<InternalGroup> schema) {
+ this.groups = ElasticMapping.createMapping(schema);
+ }
+ }
+
+ static final String GROUPS = "groups";
+
+ private final GroupMapping mapping;
+ private final Provider<GroupCache> groupCache;
+ private final Schema<InternalGroup> schema;
+
+ @Inject
+ ElasticGroupIndex(
+ ElasticConfiguration cfg,
+ SitePaths sitePaths,
+ Provider<GroupCache> groupCache,
+ ElasticRestClientProvider client,
+ @Assisted Schema<InternalGroup> schema) {
+ super(cfg, sitePaths, schema, client, GROUPS);
+ this.groupCache = groupCache;
+ this.mapping = new GroupMapping(schema);
+ this.schema = schema;
+ }
+
+ @Override
+ public void replace(InternalGroup group) throws IOException {
+ BulkRequest bulk =
+ new IndexRequest(getId(group), indexName, GROUPS).add(new UpdateRequest<>(schema, group));
+
+ String uri = getURI(GROUPS, BULK);
+ Response response = postRequest(bulk, uri, getRefreshParam());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException(
+ String.format(
+ "Failed to replace group %s in index %s: %s",
+ group.getGroupUUID().get(), indexName, statusCode));
+ }
+ }
+
+ @Override
+ public DataSource<InternalGroup> getSource(Predicate<InternalGroup> p, QueryOptions opts)
+ throws QueryParseException {
+ JsonArray sortArray = getSortArray(GroupField.UUID.getName());
+ return new ElasticQuerySource(p, opts.filterFields(IndexUtils::groupFields), GROUPS, sortArray);
+ }
+
+ @Override
+ protected String addActions(AccountGroup.UUID c) {
+ return delete(GROUPS, c);
+ }
+
+ @Override
+ protected String getMappings() {
+ ImmutableMap<String, GroupMapping> mappings = ImmutableMap.of("mappings", mapping);
+ return gson.toJson(mappings);
+ }
+
+ @Override
+ protected String getId(InternalGroup group) {
+ return group.getGroupUUID().get();
+ }
+
+ @Override
+ protected InternalGroup fromDocument(JsonObject json, Set<String> fields) {
+ JsonElement source = json.get("_source");
+ if (source == null) {
+ source = json.getAsJsonObject().get("fields");
+ }
+
+ AccountGroup.UUID uuid =
+ new AccountGroup.UUID(
+ source.getAsJsonObject().get(GroupField.UUID.getName()).getAsString());
+ // Use the GroupCache rather than depending on any stored fields in the
+ // document (of which there shouldn't be any).
+ return groupCache.get().get(uuid).orElse(null);
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
similarity index 77%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
rename to java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index e78416d..1e41985 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -14,6 +14,7 @@
package com.google.gerrit.elasticsearch;
+import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.server.index.AbstractIndexModule;
import com.google.gerrit.server.index.VersionManager;
import com.google.gerrit.server.index.account.AccountIndex;
@@ -22,23 +23,22 @@
import java.util.Map;
public class ElasticIndexModule extends AbstractIndexModule {
-
public static ElasticIndexModule singleVersionWithExplicitVersions(
- Map<String, Integer> versions, int threads) {
- return new ElasticIndexModule(versions, threads, false);
+ Map<String, Integer> versions, int threads, boolean slave) {
+ return new ElasticIndexModule(versions, threads, false, slave);
}
- public static ElasticIndexModule latestVersionWithOnlineUpgrade() {
- return new ElasticIndexModule(null, 0, true);
+ public static ElasticIndexModule latestVersionWithOnlineUpgrade(boolean slave) {
+ return new ElasticIndexModule(null, 0, true, slave);
}
- public static ElasticIndexModule latestVersionWithoutOnlineUpgrade() {
- return new ElasticIndexModule(null, 0, false);
+ public static ElasticIndexModule latestVersionWithoutOnlineUpgrade(boolean slave) {
+ return new ElasticIndexModule(null, 0, false, slave);
}
private ElasticIndexModule(
- Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade) {
- super(singleVersions, threads, onlineUpgrade);
+ Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
+ super(singleVersions, threads, onlineUpgrade, slave);
}
@Override
@@ -63,6 +63,11 @@
}
@Override
+ protected Class<? extends ProjectIndex> getProjectIndex() {
+ return ElasticProjectIndex.class;
+ }
+
+ @Override
protected Class<? extends VersionManager> getVersionManager() {
return ElasticIndexVersionManager.class;
}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
rename to java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
similarity index 91%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
rename to java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
index 1ddff77..612402e 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
@@ -14,11 +14,13 @@
package com.google.gerrit.elasticsearch;
+import com.google.common.base.Strings;
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;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.GerritIndexStatus;
import com.google.gerrit.server.index.OnlineUpgradeListener;
@@ -28,6 +30,7 @@
import java.io.IOException;
import java.util.Collection;
import java.util.TreeMap;
+import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,14 +43,14 @@
@Inject
ElasticIndexVersionManager(
- ElasticConfiguration cfg,
+ @GerritServerConfig Config cfg,
SitePaths sitePaths,
DynamicSet<OnlineUpgradeListener> listeners,
Collection<IndexDefinition<?, ?, ?>> defs,
ElasticIndexVersionDiscovery versionDiscovery) {
- super(sitePaths, listeners, defs, VersionManager.getOnlineUpgrade(cfg.getConfig()));
+ super(sitePaths, listeners, defs, VersionManager.getOnlineUpgrade(cfg));
this.versionDiscovery = versionDiscovery;
- prefix = cfg.prefix;
+ prefix = Strings.nullToEmpty(cfg.getString("elasticsearch", null, "prefix"));
}
@Override
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/java/com/google/gerrit/elasticsearch/ElasticMapping.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
rename to java/com/google/gerrit/elasticsearch/ElasticMapping.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
new file mode 100644
index 0000000..a536b41
--- /dev/null
+++ b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2017 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.collect.ImmutableMap;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+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.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.index.project.ProjectField;
+import com.google.gerrit.index.project.ProjectIndex;
+import com.google.gerrit.index.query.DataSource;
+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.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gson.JsonArray;
+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.Set;
+import org.apache.http.HttpStatus;
+import org.elasticsearch.client.Response;
+
+public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectData>
+ implements ProjectIndex {
+ static class ProjectMapping {
+ MappingProperties projects;
+
+ ProjectMapping(Schema<ProjectData> schema) {
+ this.projects = ElasticMapping.createMapping(schema);
+ }
+ }
+
+ static final String PROJECTS = "projects";
+
+ private final ProjectMapping mapping;
+ private final Provider<ProjectCache> projectCache;
+ private final Schema<ProjectData> schema;
+
+ @Inject
+ ElasticProjectIndex(
+ ElasticConfiguration cfg,
+ SitePaths sitePaths,
+ Provider<ProjectCache> projectCache,
+ ElasticRestClientProvider clientBuilder,
+ @Assisted Schema<ProjectData> schema) {
+ super(cfg, sitePaths, schema, clientBuilder, PROJECTS);
+ this.projectCache = projectCache;
+ this.schema = schema;
+ this.mapping = new ProjectMapping(schema);
+ }
+
+ @Override
+ public void replace(ProjectData projectState) throws IOException {
+ BulkRequest bulk =
+ new IndexRequest(projectState.getProject().getName(), indexName, PROJECTS)
+ .add(new UpdateRequest<>(schema, projectState));
+
+ String uri = getURI(PROJECTS, BULK);
+ Response response = postRequest(bulk, uri, getRefreshParam());
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new IOException(
+ String.format(
+ "Failed to replace project %s in index %s: %s",
+ projectState.getProject().getName(), indexName, statusCode));
+ }
+ }
+
+ @Override
+ public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
+ throws QueryParseException {
+ JsonArray sortArray = getSortArray(ProjectField.NAME.getName());
+ return new ElasticQuerySource(
+ p, opts.filterFields(IndexUtils::projectFields), PROJECTS, sortArray);
+ }
+
+ @Override
+ protected String addActions(Project.NameKey nameKey) {
+ return delete(PROJECTS, nameKey);
+ }
+
+ @Override
+ protected String getMappings() {
+ ImmutableMap<String, ProjectMapping> mappings = ImmutableMap.of("mappings", mapping);
+ return gson.toJson(mappings);
+ }
+
+ @Override
+ protected String getId(ProjectData projectState) {
+ return projectState.getProject().getName();
+ }
+
+ @Override
+ protected ProjectData fromDocument(JsonObject json, Set<String> fields) {
+ JsonElement source = json.get("_source");
+ if (source == null) {
+ source = json.getAsJsonObject().get("fields");
+ }
+
+ Project.NameKey nameKey =
+ new Project.NameKey(
+ source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
+ return projectCache.get().get(nameKey).toProjectData();
+ }
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
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/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java b/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
rename to java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java b/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java b/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java b/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java b/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
rename to java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java b/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
rename to java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java b/java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java
rename to java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java b/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
rename to java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java b/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
rename to java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java b/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
similarity index 100%
rename from gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
rename to java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java b/java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java
similarity index 100%
rename from gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java
rename to java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
new file mode 100644
index 0000000..49c34dd
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2014 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.acceptance.pgm;
+
+import com.google.gerrit.acceptance.NoHttpd;
+import org.junit.Ignore;
+
+@NoHttpd
+@Ignore
+public class ElasticReindexIT extends AbstractReindexTests {}
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
new file mode 100644
index 0000000..1249909
--- /dev/null
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -0,0 +1,58 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+java_library(
+ name = "elasticsearch_test_utils",
+ testonly = 1,
+ srcs = [
+ "ElasticContainer.java",
+ "ElasticTestUtils.java",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/elasticsearch",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/index",
+ "//java/com/google/gerrit/index/project",
+ "//java/com/google/gerrit/reviewdb:server",
+ "//java/com/google/gerrit/server",
+ "//lib:gson",
+ "//lib:guava",
+ "//lib:junit",
+ "//lib/guice",
+ "//lib/httpcomponents:httpcore",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib/testcontainers",
+ "//lib/truth",
+ ],
+)
+
+ELASTICSEARCH_TESTS = {i: "ElasticQuery" + i.capitalize() + "sTest.java" for i in [
+ "account",
+ "change",
+ "group",
+ "project",
+]}
+
+[junit_tests(
+ name = "elasticsearch_%ss_test" % name,
+ size = "large",
+ srcs = [src],
+ tags = [
+ "docker",
+ "elastic",
+ ],
+ deps = [
+ ":elasticsearch_test_utils",
+ "//java/com/google/gerrit/elasticsearch",
+ "//java/com/google/gerrit/server",
+ "//java/com/google/gerrit/server/project/testing:project-test-util",
+ "//java/com/google/gerrit/testing:gerrit-test-util",
+ "//javatests/com/google/gerrit/server/query/%s:abstract_query_tests" % name,
+ "//lib/guice",
+ "//lib/httpcomponents:httpcore",
+ "//lib/jgit/org.eclipse.jgit:jgit",
+ "//lib/jgit/org.eclipse.jgit.junit:junit",
+ "//lib/testcontainers",
+ ],
+) for name, src in ELASTICSEARCH_TESTS.items()]
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
similarity index 95%
rename from gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index 6df742c..a7a23c3 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -15,10 +15,9 @@
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;
+import org.junit.AssumptionViolatedException;
import org.testcontainers.containers.GenericContainer;
/* Helper class for running ES integration tests in docker container */
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
similarity index 89%
rename from gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
index 4bf1a46..4f0f8b0 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
@@ -16,7 +16,9 @@
import com.google.gerrit.elasticsearch.ElasticTestUtils.ElasticNodeInfo;
import com.google.gerrit.server.query.account.AbstractQueryAccountsTest;
-import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.gerrit.testing.IndexConfig;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.eclipse.jgit.lib.Config;
@@ -24,6 +26,11 @@
import org.junit.BeforeClass;
public class ElasticQueryAccountsTest extends AbstractQueryAccountsTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return IndexConfig.createForElasticsearch();
+ }
+
private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
similarity index 88%
rename from gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
index eed7d9e..2aef875 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
@@ -16,8 +16,10 @@
import com.google.gerrit.elasticsearch.ElasticTestUtils.ElasticNodeInfo;
import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
-import com.google.gerrit.testutil.InMemoryModule;
-import com.google.gerrit.testutil.InMemoryRepositoryManager.Repo;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
+import com.google.gerrit.testing.IndexConfig;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.eclipse.jgit.junit.TestRepository;
@@ -27,6 +29,11 @@
import org.junit.Test;
public class ElasticQueryChangesTest extends AbstractQueryChangesTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return IndexConfig.createForElasticsearch();
+ }
+
private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
similarity index 88%
rename from gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
index 779dd91..f13c491 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
@@ -16,7 +16,9 @@
import com.google.gerrit.elasticsearch.ElasticTestUtils.ElasticNodeInfo;
import com.google.gerrit.server.query.group.AbstractQueryGroupsTest;
-import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.gerrit.testing.IndexConfig;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.eclipse.jgit.lib.Config;
@@ -24,6 +26,11 @@
import org.junit.BeforeClass;
public class ElasticQueryGroupsTest extends AbstractQueryGroupsTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return IndexConfig.createForElasticsearch();
+ }
+
private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
similarity index 82%
copy from gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
copy to javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
index 779dd91..dd04010 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
@@ -15,15 +15,22 @@
package com.google.gerrit.elasticsearch;
import com.google.gerrit.elasticsearch.ElasticTestUtils.ElasticNodeInfo;
-import com.google.gerrit.server.query.group.AbstractQueryGroupsTest;
-import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.server.query.project.AbstractQueryProjectsTest;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.gerrit.testing.IndexConfig;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
-public class ElasticQueryGroupsTest extends AbstractQueryGroupsTest {
+public class ElasticQueryProjectsTest extends AbstractQueryProjectsTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return IndexConfig.createForElasticsearch();
+ }
+
private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
similarity index 96%
rename from gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index 54a3895..ca52e2a 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -38,7 +38,7 @@
config.setString("elasticsearch", "test", "hostname", "localhost");
config.setInt("elasticsearch", "test", "port", port);
config.setString("elasticsearch", null, "prefix", prefix);
- config.setInt("index", null, "maxLimit", 10000);
+ config.setString("index", null, "maxLimit", "10000");
}
public static void createAllIndexes(Injector injector) throws IOException {