From 929d97f25532a793bedadb62680e72a8d7e94ce6 Mon Sep 17 00:00:00 2001 From: gpopides <46693847+gpopides@users.noreply.github.com> Date: Sat, 20 Feb 2021 17:01:12 +0000 Subject: [PATCH] Add function to get index information Original Pull Request #1693 Closes #1646 --- .../DefaultReactiveElasticsearchClient.java | 32 +-- .../reactive/ReactiveElasticsearchClient.java | 3 + .../client/reactive/RequestCreator.java | 2 + .../client/util/RequestConverters.java | 16 ++ .../core/AbstractDefaultIndexOperations.java | 4 + .../core/DefaultIndexOperations.java | 18 +- .../core/DefaultReactiveIndexOperations.java | 39 ++-- .../core/DefaultTransportIndexOperations.java | 20 +- .../elasticsearch/core/IndexOperations.java | 10 + .../core/ReactiveIndexOperations.java | 13 +- .../elasticsearch/core/RequestFactory.java | 35 ++-- .../elasticsearch/core/ResponseConverter.java | 195 ++++++++++++++++++ .../core/mapping/IndexInformation.java | 78 +++++++ ...ElasticsearchTemplateIntegrationTests.java | 87 +++++++- .../core/index/IndexOperationTests.java | 107 ++++++++++ .../index/IndexOperationTransportTests.java | 12 ++ 16 files changed, 610 insertions(+), 61 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexInformation.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTransportTests.java diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java index 149caf258..bd3748aa4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java @@ -15,18 +15,6 @@ */ package org.springframework.data.elasticsearch.client.reactive; -import io.netty.channel.ChannelOption; -import io.netty.handler.ssl.ApplicationProtocolConfig; -import io.netty.handler.ssl.ClientAuth; -import io.netty.handler.ssl.IdentityCipherSuiteFilter; -import io.netty.handler.ssl.JdkSslContext; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutHandler; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; -import reactor.netty.transport.ProxyProvider; - import java.io.IOException; import java.lang.reflect.Method; import java.net.ConnectException; @@ -86,6 +74,7 @@ import org.elasticsearch.client.GetAliasesResponse; import org.elasticsearch.client.Request; import org.elasticsearch.client.indices.GetFieldMappingsRequest; import org.elasticsearch.client.indices.GetFieldMappingsResponse; +import org.elasticsearch.client.indices.GetIndexResponse; import org.elasticsearch.client.indices.GetIndexTemplatesRequest; import org.elasticsearch.client.indices.GetIndexTemplatesResponse; import org.elasticsearch.client.indices.IndexTemplatesExistRequest; @@ -131,6 +120,18 @@ import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.ApplicationProtocolConfig; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.IdentityCipherSuiteFilter; +import io.netty.handler.ssl.JdkSslContext; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import reactor.netty.transport.ProxyProvider; + /** * A {@link WebClient} based {@link ReactiveElasticsearchClient} that connects to an Elasticsearch cluster using HTTP. * @@ -144,6 +145,7 @@ import org.springframework.web.reactive.function.client.WebClient.RequestBodySpe * @author Thomas Geese * @author Brian Clozel * @author Farid Faoudi + * @author George Popides * @since 3.2 * @see ClientConfiguration * @see ReactiveRestClients @@ -757,6 +759,12 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch .map(AcknowledgedResponse::isAcknowledged).next(); } + @Override + public Mono getIndex(HttpHeaders headers, org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) { + return sendRequest(getIndexRequest, requestCreator.getIndex(), GetIndexResponse.class, headers) + .next(); + } + // endregion // region helper functions diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java index f5716c187..bb0ddaaf8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.client.reactive; +import org.elasticsearch.client.indices.GetIndexResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -1456,5 +1457,7 @@ public interface ReactiveElasticsearchClient { * @since 4.1 */ Mono deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest); + + Mono getIndex(HttpHeaders headers, org.elasticsearch.client.indices.GetIndexRequest getIndexRequest); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/RequestCreator.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/RequestCreator.java index 4d39143fe..878c0bf35 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/RequestCreator.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/RequestCreator.java @@ -40,6 +40,7 @@ import org.springframework.data.elasticsearch.client.util.RequestConverters; /** * @author Roman Puchkovskiy * @author Farid Faoudi + * @author George Popides * @since 4.0 */ public interface RequestCreator { @@ -149,6 +150,7 @@ public interface RequestCreator { return RequestConverters::count; } + default Function getIndex() { return RequestConverters::getIndex; } /** * @since 4.1 */ diff --git a/src/main/java/org/springframework/data/elasticsearch/client/util/RequestConverters.java b/src/main/java/org/springframework/data/elasticsearch/client/util/RequestConverters.java index bf6e40d40..4fefaaabb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/util/RequestConverters.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/util/RequestConverters.java @@ -692,6 +692,22 @@ public class RequestConverters { return request; } + public static Request getIndex(org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) { + String[] indices = getIndexRequest.indices() == null ? Strings.EMPTY_ARRAY : getIndexRequest.indices(); + + String endpoint = endpoint(indices); + Request request = new Request(HttpMethod.GET.name(), endpoint); + + Params params = new Params(request); + params.withIndicesOptions(getIndexRequest.indicesOptions()); + params.withLocal(getIndexRequest.local()); + params.withIncludeDefaults(getIndexRequest.includeDefaults()); + params.withHuman(getIndexRequest.humanReadable()); + params.withMasterTimeout(getIndexRequest.masterNodeTimeout()); + + return request; + } + public static Request indexDelete(DeleteIndexRequest deleteIndexRequest) { String endpoint = RequestConverters.endpoint(deleteIndexRequest.indices()); Request request = new Request(HttpMethod.DELETE.name(), endpoint); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java index 7559fc52b..5eeb4a827 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -54,10 +54,12 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { protected final ElasticsearchConverter elasticsearchConverter; protected final RequestFactory requestFactory; + protected final ResponseConverter responseConverter; @Nullable protected final Class boundClass; @Nullable private final IndexCoordinates boundIndex; + public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class boundClass) { Assert.notNull(boundClass, "boundClass may not be null"); @@ -66,6 +68,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { requestFactory = new RequestFactory(elasticsearchConverter); this.boundClass = boundClass; this.boundIndex = null; + this.responseConverter = new ResponseConverter(); } public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) { @@ -76,6 +79,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { requestFactory = new RequestFactory(elasticsearchConverter); this.boundClass = null; this.boundIndex = boundIndex; + this.responseConverter = new ResponseConverter(); } protected Class checkForBoundClass() { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java index 5f5860b5e..246204700 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java @@ -32,6 +32,7 @@ import org.elasticsearch.client.GetAliasesResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.client.indices.GetIndexResponse; import org.elasticsearch.client.indices.GetIndexTemplatesRequest; import org.elasticsearch.client.indices.GetIndexTemplatesResponse; import org.elasticsearch.client.indices.GetMappingsRequest; @@ -51,6 +52,7 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; import org.springframework.data.elasticsearch.core.query.AliasQuery; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -60,6 +62,7 @@ import org.springframework.util.Assert; * * @author Peter-Josef Meisch * @author Sascha Woo + * @author George Popides * @since 4.0 */ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations { @@ -175,7 +178,7 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames); - return restTemplate.execute(client -> requestFactory + return restTemplate.execute(client -> responseConverter .convertAliasesResponse(client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT).getAliases())); } @@ -256,5 +259,18 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I client -> client.indices().deleteTemplate(deleteIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged()); } + @Override + public List getInformation() { + IndexCoordinates indexCoordinates = getIndexCoordinates(); + GetIndexRequest request = requestFactory.getIndexRequest(indexCoordinates); + + return restTemplate.execute( + client -> { + GetIndexResponse getIndexResponse = client.indices().get(request, RequestOptions.DEFAULT); + return responseConverter.indexInformationCollection(getIndexResponse); + }); + } + // endregion + } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java index af5259632..e40195ff5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -18,8 +18,6 @@ package org.springframework.data.elasticsearch.core; import static org.elasticsearch.client.Requests.*; import static org.springframework.util.StringUtils.*; -import reactor.core.publisher.Mono; - import java.util.Map; import java.util.Set; @@ -38,7 +36,6 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.NoSuchIndexException; import org.springframework.data.elasticsearch.annotations.Mapping; @@ -55,11 +52,17 @@ import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; +import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + /** * @author Peter-Josef Meisch + * @author George Popides * @since 4.1 */ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { @@ -71,6 +74,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { private final RequestFactory requestFactory; private final ReactiveElasticsearchOperations operations; private final ElasticsearchConverter converter; + private final ResponseConverter responseConverter; public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) { @@ -80,6 +84,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { this.operations = operations; this.converter = operations.getElasticsearchConverter(); this.requestFactory = new RequestFactory(operations.getElasticsearchConverter()); + this.responseConverter = new ResponseConverter(); this.boundClass = null; this.boundIndex = index; } @@ -92,6 +97,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { this.operations = operations; this.converter = operations.getElasticsearchConverter(); this.requestFactory = new RequestFactory(operations.getElasticsearchConverter()); + this.responseConverter = new ResponseConverter(); this.boundClass = clazz; this.boundIndex = getIndexCoordinatesFor(clazz); } @@ -159,11 +165,11 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { @Override public Mono createMapping(Class clazz) { - Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); + Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); - if (mappingAnnotation != null) { - return loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); - } + if (mappingAnnotation != null) { + return loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); + } String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz); return Mono.just(Document.parse(mapping)); @@ -201,11 +207,11 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { @Override public Mono createSettings(Class clazz) { - Setting setting = AnnotatedElementUtils.findMergedAnnotation(clazz, Setting.class); + Setting setting = AnnotatedElementUtils.findMergedAnnotation(clazz, Setting.class); - if (setting != null) { - return loadDocument(setting.settingPath(), "@Setting"); - } + if (setting != null) { + return loadDocument(setting.settingPath(), "@Setting"); + } return Mono.just(getRequiredPersistentEntity(clazz).getDefaultSettings()); } @@ -244,7 +250,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames); return Mono.from(operations.executeWithIndicesClient(client -> client.getAliases(getAliasesRequest))) - .map(GetAliasesResponse::getAliases).map(requestFactory::convertAliasesResponse); + .map(GetAliasesResponse::getAliases).map(responseConverter::convertAliasesResponse); } // endregion @@ -304,6 +310,15 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex; } + @Override + public Flux getInformation() { + org.elasticsearch.client.indices.GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(getIndexCoordinates()); + + return Mono.from(operations.executeWithIndicesClient(client -> + client.getIndex(HttpHeaders.EMPTY, getIndexRequest).map(responseConverter::indexInformationCollection))) + .flatMapMany(Flux::fromIterable); + } + private IndexCoordinates getIndexCoordinatesFor(Class clazz) { return operations.getElasticsearchConverter().getMappingContext().getRequiredPersistentEntity(clazz) .getIndexCoordinates(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java index b81d28f99..d420eb1b4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java @@ -29,6 +29,8 @@ import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; +import org.elasticsearch.action.admin.indices.get.GetIndexRequest; +import org.elasticsearch.action.admin.indices.get.GetIndexResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; @@ -57,6 +59,7 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; import org.springframework.data.elasticsearch.core.query.AliasQuery; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -66,6 +69,7 @@ import org.springframework.util.Assert; * * @author Peter-Josef Meisch * @author Sascha Woo + * @author George Popides * @since 4.0 */ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations { @@ -177,7 +181,7 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp Map> aliasesResponse = new LinkedHashMap<>(); aliases.keysIt().forEachRemaining(index -> aliasesResponse.put(index, new HashSet<>(aliases.get(index)))); - return requestFactory.convertAliasesResponse(aliasesResponse); + return responseConverter.convertAliasesResponse(aliasesResponse); } @Override @@ -243,7 +247,7 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp Iterator keysItAliases = aliasesResponse.keysIt(); while (keysItAliases.hasNext()) { String key = keysItAliases.next(); - aliases.put(key, requestFactory.convertAliasMetadata(aliasesResponse.get(key))); + aliases.put(key, responseConverter.convertAliasMetadata(aliasesResponse.get(key))); } Map mappingsDoc = new LinkedHashMap<>(); @@ -296,4 +300,16 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp deleteTemplateRequest); return client.admin().indices().deleteTemplate(deleteIndexTemplateRequest).actionGet().isAcknowledged(); } + + @Override + public List getInformation() { + GetIndexRequest getIndexRequest = new GetIndexRequest(); + IndexCoordinates index = getIndexCoordinates(); + + getIndexRequest.indices(index.getIndexNames()); + + GetIndexResponse getIndexResponse = client.admin().indices().getIndex(getIndexRequest).actionGet(); + + return responseConverter.indexInformationCollection(getIndexResponse); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java index a04c85f28..3a0ef7af3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -29,6 +29,7 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; import org.springframework.data.elasticsearch.core.query.AliasQuery; import org.springframework.lang.Nullable; @@ -41,6 +42,7 @@ import org.springframework.lang.Nullable; * * @author Peter-Josef Meisch * @author Sascha Woo + * @author George Popides * @since 4.0 */ public interface IndexOperations { @@ -317,5 +319,13 @@ public interface IndexOperations { */ IndexCoordinates getIndexCoordinates(); + + /** + * + * @return a list of {@link IndexInformation} + * @since 4.2 + */ + List getInformation(); + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java index 07411cdcb..6b1b25d8c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java @@ -15,8 +15,6 @@ */ package org.springframework.data.elasticsearch.core; -import reactor.core.publisher.Mono; - import java.util.Map; import java.util.Set; @@ -29,11 +27,16 @@ import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.PutTemplateRequest; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; /** * Interface defining operations on indexes for the reactive stack. * * @author Peter-Josef Meisch + * @author George Popides * @since 4.1 */ public interface ReactiveIndexOperations { @@ -284,5 +287,11 @@ public interface ReactiveIndexOperations { */ IndexCoordinates getIndexCoordinates(); + /** + * + * @return a flux of {@link IndexInformation} + * @since 4.2 + */ + Flux getInformation(); // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java index 33418fdf8..d43cea95a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -18,7 +18,15 @@ package org.springframework.data.elasticsearch.core; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.util.CollectionUtils.*; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.admin.indices.alias.Alias; @@ -62,7 +70,6 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.client.indices.PutMappingRequest; import org.elasticsearch.cluster.metadata.AliasMetadata; import org.elasticsearch.common.collect.ImmutableOpenMap; -import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.DistanceUnit; @@ -492,26 +499,6 @@ class RequestFactory { return new org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest().indices(indexNames); } - public Map> convertAliasesResponse(Map> aliasesResponse) { - Map> converted = new LinkedHashMap<>(); - aliasesResponse.forEach((index, aliasMetaDataSet) -> { - Set aliasDataSet = new LinkedHashSet<>(); - aliasMetaDataSet.forEach(aliasMetaData -> aliasDataSet.add(convertAliasMetadata(aliasMetaData))); - converted.put(index, aliasDataSet); - }); - return converted; - } - - public AliasData convertAliasMetadata(AliasMetadata aliasMetaData) { - Document filter = null; - CompressedXContent aliasMetaDataFilter = aliasMetaData.getFilter(); - if (aliasMetaDataFilter != null) { - filter = Document.parse(aliasMetaDataFilter.string()); - } - AliasData aliasData = AliasData.of(aliasMetaData.alias(), filter, aliasMetaData.indexRouting(), - aliasMetaData.getSearchRouting(), aliasMetaData.writeIndex(), aliasMetaData.isHidden()); - return aliasData; - } public PutIndexTemplateRequest putIndexTemplateRequest(PutTemplateRequest putTemplateRequest) { @@ -672,7 +659,7 @@ class RequestFactory { Iterator keysIt = aliasesResponse.keysIt(); while (keysIt.hasNext()) { String key = keysIt.next(); - aliases.put(key, convertAliasMetadata(aliasesResponse.get(key))); + aliases.put(key, ResponseConverter.convertAliasMetadata(aliasesResponse.get(key))); } TemplateData templateData = TemplateData.builder() .withIndexPatterns(indexTemplateMetadata.patterns().toArray(new String[0])) // @@ -1848,5 +1835,7 @@ class RequestFactory { return settings; } + + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java new file mode 100644 index 000000000..62d2fc96f --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java @@ -0,0 +1,195 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.elasticsearch.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.elasticsearch.client.indices.GetIndexResponse; +import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.cluster.metadata.MappingMetadata; +import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; + +/** + * Factory class to elasticsearch responses to different type of data classes. + * @author George Popides + * @since 4.2 + */ +public class ResponseConverter { + public ResponseConverter() { + } + + // region alias + + public static AliasData convertAliasMetadata(AliasMetadata aliasMetaData) { + Document filter = null; + CompressedXContent aliasMetaDataFilter = aliasMetaData.getFilter(); + if (aliasMetaDataFilter != null) { + filter = Document.parse(aliasMetaDataFilter.string()); + } + AliasData aliasData = AliasData.of(aliasMetaData.alias(), filter, aliasMetaData.indexRouting(), + aliasMetaData.getSearchRouting(), aliasMetaData.writeIndex(), aliasMetaData.isHidden()); + return aliasData; + } + + public List indexInformationCollection(GetIndexResponse getIndexResponse) { + List indexInformationList = new ArrayList<>(); + + for (String indexName : getIndexResponse.getIndices()) { + Document settings = settingsFromGetIndexResponse(getIndexResponse, indexName); + Document mappings = mappingsFromGetIndexResponse(getIndexResponse, indexName); + List aliases = mappingsFromIndexResponse(getIndexResponse, indexName); + + + indexInformationList.add(IndexInformation.create(indexName, settings, mappings, aliases)); + } + + return indexInformationList; + } + + public List indexInformationCollection(org.elasticsearch.action.admin.indices.get.GetIndexResponse getIndexResponse) { + List indexInformationList = new ArrayList<>(); + + for (String indexName : getIndexResponse.getIndices()) { + Document settings = settingsFromGetIndexResponse(getIndexResponse, indexName); + Document mappings = mappingsFromGetIndexResponse(getIndexResponse, indexName); + List aliases = mappingsFromIndexResponse(getIndexResponse, indexName); + + indexInformationList.add(IndexInformation.create(indexName, settings, mappings, aliases)); + } + + return indexInformationList; + } + + public Map> convertAliasesResponse(Map> aliasesResponse) { + Map> converted = new LinkedHashMap<>(); + aliasesResponse.forEach((index, aliasMetaDataSet) -> { + Set aliasDataSet = new LinkedHashSet<>(); + aliasMetaDataSet.forEach(aliasMetaData -> aliasDataSet.add(convertAliasMetadata(aliasMetaData))); + converted.put(index, aliasDataSet); + }); + return converted; + } + + + // end region + + + /** + * extract the index settings information from a given index + * @param getIndexResponse the elastic GetIndexResponse + * @param indexName the index name + * @return a document that represents {@link Settings} + */ + private Document settingsFromGetIndexResponse(GetIndexResponse getIndexResponse, String indexName) { + Document document = Document.create(); + + Settings indexSettings = getIndexResponse.getSettings().get(indexName); + + if (!indexSettings.isEmpty()) { + for (String key : indexSettings.keySet()) { + document.put(key, indexSettings.get(key)); + } + } + + return document; + } + + /** + * extract the mappings information from a given index + * @param getIndexResponse the elastic GetIndexResponse + * @param indexName the index name + * @return a document that represents {@link MappingMetadata} + */ + private Document mappingsFromGetIndexResponse(GetIndexResponse getIndexResponse, String indexName) { + Document document = Document.create(); + + if (getIndexResponse.getMappings().containsKey(indexName)) { + MappingMetadata mappings = getIndexResponse.getMappings().get(indexName); + document = Document.from(mappings.getSourceAsMap()); + } + + return document; + } + + private Document settingsFromGetIndexResponse(org.elasticsearch.action.admin.indices.get.GetIndexResponse getIndexResponse, String indexName) { + Document document = Document.create(); + + if (getIndexResponse.getSettings().containsKey(indexName)) { + Settings indexSettings = getIndexResponse.getSettings().get(indexName); + + for (String key : indexSettings.keySet()) { + document.put(key, indexSettings.get(key)); + } + } + + return document; + } + + private Document mappingsFromGetIndexResponse(org.elasticsearch.action.admin.indices.get.GetIndexResponse getIndexResponse, String indexName) { + Document document = Document.create(); + + boolean responseHasMappings = getIndexResponse.getMappings().containsKey(indexName) && + (getIndexResponse.getMappings().get(indexName).get("_doc") != null); + + if (responseHasMappings) { + MappingMetadata mappings = getIndexResponse.getMappings().get(indexName).get("_doc"); + document = Document.from(mappings.getSourceAsMap()); + } + + return document; + } + + private List mappingsFromIndexResponse(GetIndexResponse getIndexResponse, String indexName) { + List aliases = Collections.emptyList(); + + if (getIndexResponse.getAliases().get(indexName) != null) { + aliases = getIndexResponse + .getAliases() + .get(indexName) + .stream() + .map(ResponseConverter::convertAliasMetadata) + .collect(Collectors.toList()); + } + return aliases; + } + + private List mappingsFromIndexResponse(org.elasticsearch.action.admin.indices.get.GetIndexResponse getIndexResponse, String indexName) { + List aliases = Collections.emptyList(); + + if (getIndexResponse.getAliases().get(indexName) != null) { + aliases = getIndexResponse + .getAliases() + .get(indexName) + .stream() + .map(ResponseConverter::convertAliasMetadata) + .collect(Collectors.toList()); + } + return aliases; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexInformation.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexInformation.java new file mode 100644 index 000000000..81f44a86e --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/IndexInformation.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.elasticsearch.core.mapping; + +import java.util.List; + +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.lang.Nullable; + +/** + * Immutable object that holds information(name, settings, mappings, aliases) about an Index + * + * @author George Popides + * @since 4.2 + */ +public class IndexInformation { + private final String name; + @Nullable + private final Document settings; + @Nullable + private final Document mappings; + @Nullable + private final List aliases; + + + public static IndexInformation create( + String indexName, + @Nullable Document settings, + @Nullable Document mappings, + @Nullable List aliases + ) { + return new IndexInformation(indexName, settings, mappings, aliases); + } + + private IndexInformation( + String indexName, + @Nullable Document settings, + @Nullable Document mappings, + @Nullable List aliases + ) { + this.name = indexName; + this.settings = settings; + this.mappings = mappings; + this.aliases = aliases; + } + + public Document getMappings() { + return mappings; + } + + public String getName() { + return name; + } + + public Document getSettings() { + return settings; + } + + public List getAliases() { + return aliases; + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index ff50200ab..7d63f83d5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -20,15 +20,6 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import org.springframework.data.elasticsearch.core.document.Explanation; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - import java.lang.Boolean; import java.lang.Long; import java.lang.Object; @@ -51,10 +42,12 @@ import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; +import org.json.JSONException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -68,13 +61,28 @@ import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; +import org.springframework.data.elasticsearch.core.document.Explanation; +import org.springframework.data.elasticsearch.core.index.AliasAction; +import org.springframework.data.elasticsearch.core.index.AliasActionParameters; +import org.springframework.data.elasticsearch.core.index.AliasActions; +import org.springframework.data.elasticsearch.core.index.AliasData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.util.StringUtils; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + /** * Integration tests for {@link ReactiveElasticsearchTemplate}. * @@ -86,6 +94,7 @@ import org.springframework.util.StringUtils; * @author Aleksei Arsenev * @author Russell Parry * @author Roman Puchkovskiy + * @author George Popides */ @SpringIntegrationTest public class ReactiveElasticsearchTemplateIntegrationTests { @@ -1104,6 +1113,56 @@ public class ReactiveElasticsearchTemplateIntegrationTests { }) .verifyComplete(); } + + + @Test // #1646 + @DisplayName("should return a list of info for specific index using reactive template") + void shouldReturnInformationListOfAllIndices() { + String indexName = "test-index-reactive-information-list"; + String aliasName = "testindexinformationindex"; + ReactiveIndexOperations indexOps = template.indexOps(EntityWithSettingsAndMappingsReactive.class); + + indexOps.create().block(); + indexOps.putMapping().block(); + + AliasActionParameters parameters = AliasActionParameters.builder() + .withAliases(aliasName) + .withIndices(indexName) + .withIsHidden(false) + .withIsWriteIndex(false) + .withRouting("indexrouting") + .withSearchRouting("searchrouting") + .build(); + indexOps.alias(new AliasActions(new AliasAction.Add(parameters))).block(); + + indexOps + .getInformation() + .as(StepVerifier::create) + .consumeNextWith(indexInformation -> { + assertThat(indexInformation.getName()).isEqualTo(indexName); + assertThat(indexInformation.getSettings().get("index.number_of_shards")).isEqualTo("1"); + assertThat(indexInformation.getSettings().get("index.number_of_replicas")).isEqualTo("0"); + assertThat(indexInformation.getSettings().get("index.analysis.analyzer.emailAnalyzer.type")).isEqualTo("custom"); + assertThat(indexInformation.getAliases()).hasSize(1); + + AliasData aliasData = indexInformation.getAliases().get(0); + + assertThat(aliasData.getAlias()).isEqualTo(aliasName); + assertThat(aliasData.isHidden()).isEqualTo(false); + assertThat(aliasData.isWriteIndex()).isEqualTo(false); + assertThat(aliasData.getIndexRouting()).isEqualTo("indexrouting"); + assertThat(aliasData.getSearchRouting()).isEqualTo("searchrouting"); + + String expectedMappings = "{\"properties\":{\"email\":{\"type\":\"text\",\"analyzer\":\"emailAnalyzer\"}}}"; + try { + JSONAssert.assertEquals(expectedMappings, indexInformation.getMappings().toJson(), false); + } catch (JSONException e) { + e.printStackTrace(); + } + }) + .verifyComplete(); + } + // endregion // region Helper functions @@ -1135,6 +1194,7 @@ public class ReactiveElasticsearchTemplateIntegrationTests { template.saveAll(Mono.just(Arrays.asList(entities)), indexCoordinates).then(indexOperations.refresh()).block(); } } + // endregion // region Entities @@ -1201,5 +1261,14 @@ public class ReactiveElasticsearchTemplateIntegrationTests { @Version private Long version; } + @Data + @Document(indexName = "test-index-reactive-information-list", createIndex = false) + @Setting(settingPath = "settings/test-settings.json") + @Mapping(mappingPath = "mappings/test-mappings.json") + private static class EntityWithSettingsAndMappingsReactive { + @Id + String id; + } + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTests.java new file mode 100644 index 000000000..94040dc08 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.elasticsearch.core.index; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.json.JSONException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.annotations.Setting; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexInformation; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.test.context.ContextConfiguration; + +import lombok.Data; + +/** + * @author George Popides + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) +public class IndexOperationTests { + @Autowired + protected ElasticsearchOperations operations; + + @BeforeEach + void setUp() { + operations.indexOps(EntityWithSettingsAndMappings.class).delete(); + } + + @Test // #1646 + @DisplayName("should return a list of info for specific index") + void shouldReturnInformationList() throws JSONException { + IndexOperations indexOps = operations.indexOps(EntityWithSettingsAndMappings.class); + + String aliasName = "testindexinformationindex"; + String indexName = "test-index-information-list"; + + indexOps.create(); + indexOps.putMapping(); + + AliasActionParameters parameters = AliasActionParameters.builder() + .withAliases(aliasName) + .withIndices(indexName) + .withIsHidden(false) + .withIsWriteIndex(false) + .withRouting("indexrouting") + .withSearchRouting("searchrouting") + .build(); + indexOps.alias(new AliasActions(new AliasAction.Add(parameters))); + + List indexInformationList = indexOps.getInformation(); + + IndexInformation indexInformation = indexInformationList.get(0); + + assertThat(indexInformationList.size()).isEqualTo(1); + assertThat(indexInformation.getName()).isEqualTo(indexName); + assertThat(indexInformation.getSettings().get("index.number_of_shards")).isEqualTo("1"); + assertThat(indexInformation.getSettings().get("index.number_of_replicas")).isEqualTo("0"); + assertThat(indexInformation.getSettings().get("index.analysis.analyzer.emailAnalyzer.type")).isEqualTo("custom"); + assertThat(indexInformation.getAliases()).hasSize(1); + + AliasData aliasData = indexInformation.getAliases().get(0); + + assertThat(aliasData.getAlias()).isEqualTo(aliasName); + assertThat(aliasData.isHidden()).isEqualTo(false); + assertThat(aliasData.isWriteIndex()).isEqualTo(false); + assertThat(aliasData.getIndexRouting()).isEqualTo("indexrouting"); + assertThat(aliasData.getSearchRouting()).isEqualTo("searchrouting"); + + String expectedMappings = "{\"properties\":{\"email\":{\"type\":\"text\",\"analyzer\":\"emailAnalyzer\"}}}"; + JSONAssert.assertEquals(expectedMappings, indexInformation.getMappings().toJson(), false); + } + + @Data + @Document(indexName = "test-index-information-list") + @Setting(settingPath = "settings/test-settings.json") + @Mapping(mappingPath = "mappings/test-mappings.json") + protected static class EntityWithSettingsAndMappings { + @Id + String id; + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTransportTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTransportTests.java new file mode 100644 index 000000000..9ec1d04b5 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationTransportTests.java @@ -0,0 +1,12 @@ +package org.springframework.data.elasticsearch.core.index; + +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author George Popides + */ + +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +public class IndexOperationTransportTests extends IndexOperationTests { +}