From bdcecd095002e7bf2ab74abd5370e750eafd5c3a Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 17 Jul 2020 21:11:40 +0200 Subject: [PATCH] DATAES-612 - Add support for index templates. Original PR: #495 --- .../asciidoc/reference/elasticsearch-new.adoc | 1 + .../elasticsearch/ElasticsearchException.java | 2 +- .../DefaultReactiveElasticsearchClient.java | 54 +++- .../reactive/ReactiveElasticsearchClient.java | 145 ++++++++++ .../client/reactive/RequestCreator.java | 35 +++ .../client/util/RequestConverters.java | 62 ++++- .../core/AbstractDefaultIndexOperations.java | 67 ++--- .../core/DefaultIndexOperations.java | 62 ++++- .../core/DefaultReactiveIndexOperations.java | 95 ++++++- .../core/DefaultTransportIndexOperations.java | 104 ++++++- .../elasticsearch/core/IndexOperations.java | 107 ++++++- .../core/ReactiveIndexOperations.java | 103 ++++++- .../elasticsearch/core/RequestFactory.java | 261 ++++++++++++++++-- .../core/index/AliasActionParameters.java | 12 +- .../core/index/DeleteTemplateRequest.java | 36 +++ .../core/index/ExistsTemplateRequest.java | 36 +++ .../core/index/GetTemplateRequest.java | 36 +++ .../core/index/PutTemplateRequest.java | 136 +++++++++ .../core/index/TemplateData.java | 123 +++++++++ .../core/query/FetchSourceFilter.java | 11 +- .../core/query/MoreLikeThisQuery.java | 8 +- .../data/elasticsearch/core/query/Query.java | 6 +- .../core/query/SourceFilter.java | 7 +- .../core/ReactiveIndexOperationsTest.java | 155 +++++++++++ .../core/RequestFactoryTests.java | 67 +++++ .../ElasticsearchDateConverterTests.java | 4 +- .../core/index/TemplateTests.java | 192 +++++++++++++ .../core/index/TemplateTransportTests.java | 25 ++ 28 files changed, 1836 insertions(+), 116 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/DeleteTemplateRequest.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/ExistsTemplateRequest.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/GetTemplateRequest.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/PutTemplateRequest.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/TemplateData.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTransportTests.java diff --git a/src/main/asciidoc/reference/elasticsearch-new.adoc b/src/main/asciidoc/reference/elasticsearch-new.adoc index bd7816177..68159d431 100644 --- a/src/main/asciidoc/reference/elasticsearch-new.adoc +++ b/src/main/asciidoc/reference/elasticsearch-new.adoc @@ -7,6 +7,7 @@ * Upgrade to Elasticsearch 7.8.0 * Improved API for alias management * Introduction of `ReactiveIndexOperations` for index management +* Index templates support [[new-features.4-0-0]] == New in Spring Data Elasticsearch 4.0 diff --git a/src/main/java/org/springframework/data/elasticsearch/ElasticsearchException.java b/src/main/java/org/springframework/data/elasticsearch/ElasticsearchException.java index aed85fa99..126a6830c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/ElasticsearchException.java +++ b/src/main/java/org/springframework/data/elasticsearch/ElasticsearchException.java @@ -25,7 +25,7 @@ import org.springframework.lang.Nullable; * @author Rizwan Idrees * @author Mohsin Husen * @author Peter-Josef Meisch - * @deprecated since 4.0, use {@link org.springframework.dao.UncategorizedDataAccessException} + * @deprecated since 4.0, use {@link UncategorizedElasticsearchException} */ @Deprecated public class ElasticsearchException extends RuntimeException { 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 c5edd1e71..321f9f4ae 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 @@ -64,6 +64,7 @@ import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshResponse; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; @@ -86,6 +87,10 @@ import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.GetAliasesResponse; import org.elasticsearch.client.Request; +import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesResponse; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.DeprecationHandler; import org.elasticsearch.common.xcontent.NamedXContentRegistry; @@ -266,10 +271,9 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch Optional sslContext = clientConfiguration.getSslContext(); if (sslContext.isPresent()) { - httpClient = httpClient.secure(sslContextSpec -> { - sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, IdentityCipherSuiteFilter.INSTANCE, - ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false)); - }); + httpClient = httpClient + .secure(sslContextSpec -> sslContextSpec.sslContext(new JdkSslContext(sslContext.get(), true, null, + IdentityCipherSuiteFilter.INSTANCE, ApplicationProtocolConfig.DISABLED, ClientAuth.NONE, null, false))); } else { httpClient = httpClient.secure(); } @@ -589,8 +593,8 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch // --> - private Flux sendRequest(Req request, Function converter, - Class responseType, HttpHeaders headers) { + private Flux sendRequest(REQ request, Function converter, Class responseType, + HttpHeaders headers) { return sendRequest(converter.apply(request), responseType, headers); } @@ -737,6 +741,32 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch return sendRequest(getAliasesRequest, requestCreator.getAlias(), GetAliasesResponse.class, headers).publishNext(); } + @Override + public Mono putTemplate(HttpHeaders headers, PutIndexTemplateRequest putIndexTemplateRequest) { + return sendRequest(putIndexTemplateRequest, requestCreator.putTemplate(), AcknowledgedResponse.class, headers) + .map(AcknowledgedResponse::isAcknowledged).next(); + } + + @Override + public Mono getTemplate(HttpHeaders headers, + GetIndexTemplatesRequest getIndexTemplatesRequest) { + return (sendRequest(getIndexTemplatesRequest, requestCreator.getTemplates(), GetIndexTemplatesResponse.class, + headers)).next(); + } + + @Override + public Mono existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest) { + return sendRequest(indexTemplatesExistRequest, requestCreator.templatesExist(), RawActionResponse.class, headers) // + .flatMap(response -> response.releaseBody().thenReturn(response.statusCode().is2xxSuccessful())) // + .next(); + } + + @Override + public Mono deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest) { + return sendRequest(deleteIndexTemplateRequest, requestCreator.deleteTemplate(), AcknowledgedResponse.class, headers) + .map(AcknowledgedResponse::isAcknowledged).next(); + } + // endregion // region helper functions @@ -758,7 +788,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch if (response.statusCode().is4xxClientError()) { ClientLogger.logRawResponse(logId, response.statusCode()); - return handleClientError(logId, request, response, responseType); + return handleClientError(logId, response, responseType); } return response.body(BodyExtractors.toMono(byte[].class)) // @@ -825,8 +855,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch request.getMethod(), request.getEndpoint(), statusCode), status))); } - private Publisher handleClientError(String logId, Request request, ClientResponse response, - Class responseType) { + private Publisher handleClientError(String logId, ClientResponse response, Class responseType) { int statusCode = response.statusCode().value(); RestStatus status = RestStatus.fromCode(statusCode); @@ -874,12 +903,13 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch try { XContentParser parser = createParser(mediaType, content); // we have a JSON object with an error and a status field - XContentParser.Token token = parser.nextToken(); // Skip START_OBJECT + parser.nextToken(); // Skip START_OBJECT + XContentParser.Token token; do { token = parser.nextToken(); - if (parser.currentName().equals("error")) { + if ("error".equals(parser.currentName())) { return ElasticsearchException.failureFromXContent(parser); } } while (token == XContentParser.Token.FIELD_NAME); @@ -906,7 +936,7 @@ public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearch * * @author Christoph Strobl */ - class ClientStatus implements Status { + static class ClientStatus implements Status { private final Collection connectedHosts; 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 170d065a1..61f1e6005 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 @@ -36,6 +36,7 @@ import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; @@ -50,6 +51,10 @@ import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.GetAliasesResponse; +import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesResponse; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.reindex.BulkByScrollResponse; import org.elasticsearch.index.reindex.DeleteByQueryRequest; @@ -610,6 +615,7 @@ public interface ReactiveElasticsearchClient { * @param callback the {@link ReactiveElasticsearchClientCallback callback} wielding the actual command to run. * @return the {@link Mono} emitting the {@link ClientResponse} once subscribed. */ + @SuppressWarnings("JavaDoc") Mono execute(ReactiveElasticsearchClientCallback callback); /** @@ -1196,5 +1202,144 @@ public interface ReactiveElasticsearchClient { * @since 4.1 */ Mono getAliases(HttpHeaders headers, GetAliasesRequest getAliasesRequest); + + /** + * Execute the given {@link PutIndexTemplateRequest} against the {@literal indices} API. + * + * @param consumer never {@literal null}. + * @return a {@link Mono} signalling operation completion. + * @since 4.1 + */ + default Mono putTemplate(Consumer consumer, String templateName) { + PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest(templateName); + consumer.accept(putIndexTemplateRequest); + return putTemplate(putIndexTemplateRequest); + } + + /** + * Execute the given {@link PutIndexTemplateRequest} against the {@literal indices} API. + * + * @param putIndexTemplateRequest must not be {@literal null} + * @return a {@link Mono} signalling operation completion. + * @since 4.1 + */ + default Mono putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) { + return putTemplate(HttpHeaders.EMPTY, putIndexTemplateRequest); + } + + /** + * Execute the given {@link PutIndexTemplateRequest} against the {@literal indices} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param putIndexTemplateRequest must not be {@literal null} + * @return a {@link Mono} signalling operation completion. + * @since 4.1 + */ + Mono putTemplate(HttpHeaders headers, PutIndexTemplateRequest putIndexTemplateRequest); + + /** + * Execute the given {@link GetIndexTemplatesRequest} against the {@literal indices} API. + * + * @param consumer never {@literal null}. + * @return a {@link Mono} with the GetIndexTemplatesResponse. + * @since 4.1 + */ + default Mono getTemplate(Consumer consumer) { + + GetIndexTemplatesRequest getIndexTemplatesRequest = new GetIndexTemplatesRequest(); + consumer.accept(getIndexTemplatesRequest); + return getTemplate(getIndexTemplatesRequest); + } + + /** + * Execute the given {@link GetIndexTemplatesRequest} against the {@literal indices} API. + * + * @param getIndexTemplatesRequest must not be {@literal null} + * @return a {@link Mono} with the GetIndexTemplatesResponse. + * @since 4.1 + */ + default Mono getTemplate(GetIndexTemplatesRequest getIndexTemplatesRequest) { + return getTemplate(HttpHeaders.EMPTY, getIndexTemplatesRequest); + } + + /** + * Execute the given {@link GetIndexTemplatesRequest} against the {@literal indices} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param getIndexTemplatesRequest must not be {@literal null} + * @return a {@link Mono} with the GetIndexTemplatesResponse. + * @since 4.1 + */ + Mono getTemplate(HttpHeaders headers, GetIndexTemplatesRequest getIndexTemplatesRequest); + + /** + * Execute the given {@link IndexTemplatesExistRequest} against the {@literal indices} API. + * + * @param consumer never {@literal null}. + * @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise. + * @since 4.1 + */ + default Mono existsTemplate(Consumer consumer) { + + IndexTemplatesExistRequest indexTemplatesExistRequest = new IndexTemplatesExistRequest(); + consumer.accept(indexTemplatesExistRequest); + return existsTemplate(indexTemplatesExistRequest); + } + + /** + * Execute the given {@link IndexTemplatesExistRequest} against the {@literal indices} API. + * + * @param indexTemplatesExistRequest must not be {@literal null} + * @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise. + * @since 4.1 + */ + default Mono existsTemplate(IndexTemplatesExistRequest indexTemplatesExistRequest) { + return existsTemplate(HttpHeaders.EMPTY, indexTemplatesExistRequest); + } + + /** + * Execute the given {@link IndexTemplatesExistRequest} against the {@literal indices} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param indexTemplatesExistRequest must not be {@literal null} + * @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise. + * @since 4.1 + */ + Mono existsTemplate(HttpHeaders headers, IndexTemplatesExistRequest indexTemplatesExistRequest); + + /** + * Execute the given {@link DeleteIndexTemplateRequest} against the {@literal indices} API. + * + * @param consumer never {@literal null}. + * @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise. + * @since 4.1 + */ + default Mono deleteTemplate(Consumer consumer) { + + DeleteIndexTemplateRequest deleteIndexTemplateRequest = new DeleteIndexTemplateRequest(); + consumer.accept(deleteIndexTemplateRequest); + return deleteTemplate(deleteIndexTemplateRequest); + } + + /** + * Execute the given {@link DeleteIndexTemplateRequest} against the {@literal indices} API. + * + * @param deleteIndexTemplateRequest must not be {@literal null} + * @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise. + * @since 4.1 + */ + default Mono deleteTemplate(DeleteIndexTemplateRequest deleteIndexTemplateRequest) { + return deleteTemplate(HttpHeaders.EMPTY, deleteIndexTemplateRequest); + } + + /** + * Execute the given {@link DeleteIndexTemplateRequest} against the {@literal indices} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param deleteIndexTemplateRequest must not be {@literal null} + * @return the {@link Mono} emitting {@literal true} if the template exists, {@literal false} otherwise. + * @since 4.1 + */ + Mono deleteTemplate(HttpHeaders headers, DeleteIndexTemplateRequest deleteIndexTemplateRequest); } } 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 d3bda5488..2c3fb0ae6 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 @@ -15,6 +15,7 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.get.GetRequest; @@ -27,6 +28,9 @@ import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.client.Request; import org.elasticsearch.client.core.CountRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.client.util.RequestConverters; @@ -156,7 +160,38 @@ public interface RequestCreator { return RequestConverters::updateAliases; } + /** + * @since 4.1 + */ default Function getAlias() { return RequestConverters::getAlias; } + + /** + * @since 4.1 + */ + default Function putTemplate() { + return RequestConverters::putTemplate; + } + + /** + * @since 4.1 + */ + default Function getTemplates() { + return RequestConverters::getTemplates; + } + + /** + * @since 4.1 + */ + default Function templatesExist() { + return RequestConverters::templatesExist; + } + + /** + * @since 4.1 + */ + default Function deleteTemplate() { + return RequestConverters::deleteTemplate; + } } 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 5519871d9..8297a6748 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 @@ -25,8 +25,11 @@ import java.util.Locale; import java.util.StringJoiner; import org.apache.http.HttpEntity; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.lucene.util.BytesRef; @@ -47,6 +50,7 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.admin.indices.open.OpenIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.explain.ExplainRequest; @@ -67,6 +71,9 @@ import org.elasticsearch.client.Requests; import org.elasticsearch.client.RethrottleRequest; import org.elasticsearch.client.core.CountRequest; import org.elasticsearch.client.indices.AnalyzeRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.Priority; import org.elasticsearch.common.Strings; @@ -743,7 +750,9 @@ public class RequestConverters { public static Request indexRefresh(RefreshRequest refreshRequest) { String[] indices = refreshRequest.indices() == null ? Strings.EMPTY_ARRAY : refreshRequest.indices(); - Request request = new Request(HttpMethod.POST.name(), RequestConverters.endpoint(indices, "_refresh")); + // using a GET here as reactor-netty set the transfer-encoding to chunked on POST requests which blocks on + // Elasticsearch when no body is sent. + Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_refresh")); Params parameters = new Params(request); parameters.withIndicesOptions(refreshRequest.indicesOptions()); @@ -831,6 +840,57 @@ public class RequestConverters { return request; } + public static Request putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) { + String endpoint = (new RequestConverters.EndpointBuilder()) // + .addPathPartAsIs("_template") // + .addPathPart(putIndexTemplateRequest.name()) // + .build(); // + + Request request = new Request(HttpPut.METHOD_NAME, endpoint); + RequestConverters.Params params = new RequestConverters.Params(request); + params.withMasterTimeout(putIndexTemplateRequest.masterNodeTimeout()); + if (putIndexTemplateRequest.create()) { + params.putParam("create", Boolean.TRUE.toString()); + } + + if (Strings.hasText(putIndexTemplateRequest.cause())) { + params.putParam("cause", putIndexTemplateRequest.cause()); + } + + request.setEntity( + RequestConverters.createEntity(putIndexTemplateRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE)); + return request; + } + + public static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) { + final String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template") + .addCommaSeparatedPathParts(getIndexTemplatesRequest.names()).build(); + final Request request = new Request(HttpGet.METHOD_NAME, endpoint); + RequestConverters.Params params = new RequestConverters.Params(request); + params.withLocal(getIndexTemplatesRequest.isLocal()); + params.withMasterTimeout(getIndexTemplatesRequest.getMasterNodeTimeout()); + return request; + } + + public static Request templatesExist(IndexTemplatesExistRequest indexTemplatesExistRequest) { + final String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template") + .addCommaSeparatedPathParts(indexTemplatesExistRequest.names()).build(); + final Request request = new Request(HttpHead.METHOD_NAME, endpoint); + final RequestConverters.Params params = new RequestConverters.Params(request); + params.withLocal(indexTemplatesExistRequest.isLocal()); + params.withMasterTimeout(indexTemplatesExistRequest.getMasterNodeTimeout()); + return request; + } + + public static Request deleteTemplate(DeleteIndexTemplateRequest deleteIndexTemplateRequest) { + String name = deleteIndexTemplateRequest.name(); + String endpoint = new RequestConverters.EndpointBuilder().addPathPartAsIs("_template").addPathPart(name).build(); + Request request = new Request(HttpDelete.METHOD_NAME, endpoint); + RequestConverters.Params params = new RequestConverters.Params(request); + params.withMasterTimeout(deleteIndexTemplateRequest.masterNodeTimeout()); + return request; + } + static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) { try { 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 866671eff..3f9ac3b35 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -19,15 +19,14 @@ import static org.springframework.util.StringUtils.*; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; import org.elasticsearch.cluster.metadata.AliasMetadata; -import org.elasticsearch.common.settings.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.elasticsearch.ElasticsearchException; +import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; @@ -93,21 +92,31 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { Document settings = null; if (boundClass != null) { - Class clazz = boundClass; - - if (clazz.isAnnotationPresent(Setting.class)) { - String settingPath = clazz.getAnnotation(Setting.class).settingPath(); - settings = loadSettings(settingPath); - } - - if (settings == null) { - settings = getRequiredPersistentEntity(clazz).getDefaultSettings(); - } + settings = createSettings(boundClass); } return doCreate(getIndexCoordinates(), settings); } + @Override + public Document createSettings(Class clazz) { + + Assert.notNull(clazz, "clazz must not be null"); + + Document settings = null; + + if (clazz.isAnnotationPresent(Setting.class)) { + String settingPath = clazz.getAnnotation(Setting.class).settingPath(); + settings = loadSettings(settingPath); + } + + if (settings == null) { + settings = getRequiredPersistentEntity(clazz).getDefaultSettings(); + } + + return settings; + } + @Override public boolean create(Document settings) { return doCreate(getIndexCoordinates(), settings); @@ -234,9 +243,15 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz); return Document.parse(mapping); } catch (Exception e) { - throw new ElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e); + throw new UncategorizedElasticsearchException("Failed to build mapping for " + clazz.getSimpleName(), e); } } + + @Override + public Document createSettings() { + return createSettings(checkForBoundClass()); + } + // endregion // region Helper functions @@ -246,7 +261,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { @Override public IndexCoordinates getIndexCoordinates() { - return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : boundIndex; + return (boundClass != null) ? getIndexCoordinatesFor(boundClass) : Objects.requireNonNull(boundIndex); } public IndexCoordinates getIndexCoordinatesFor(Class clazz) { @@ -266,27 +281,5 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { } return null; } - - protected Document convertSettingsResponseToMap(GetSettingsResponse response, String indexName) { - - Document settings = Document.create(); - - if (!response.getIndexToDefaultSettings().isEmpty()) { - Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName); - for (String key : defaultSettings.keySet()) { - settings.put(key, defaultSettings.get(key)); - } - } - - if (!response.getIndexToSettings().isEmpty()) { - Settings customSettings = response.getIndexToSettings().get(indexName); - for (String key : customSettings.keySet()) { - settings.put(key, customSettings.get(key)); - } - } - - return settings; - } // endregion - } 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 305d06dd1..f30c9aafa 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java @@ -16,7 +16,6 @@ package org.springframework.data.elasticsearch.core; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -27,17 +26,27 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; 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.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesResponse; import org.elasticsearch.client.indices.GetMappingsRequest; import org.elasticsearch.client.indices.GetMappingsResponse; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.client.indices.PutMappingRequest; import org.elasticsearch.cluster.metadata.AliasMetadata; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasActions; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +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.query.AliasQuery; import org.springframework.lang.Nullable; @@ -111,7 +120,6 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I GetMappingsResponse mapping = client.indices().getMapping(mappingsRequest, RequestOptions.DEFAULT); // we only return data for the first index name that was requested (always have done so) String index1 = mappingsRequest.indices()[0]; - Map result = new LinkedHashMap<>(); return mapping.mappings().get(index1).getSourceAsMap(); }); } @@ -174,7 +182,7 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I GetSettingsResponse response = restTemplate.execute(client -> client.indices() // .getSettings(getSettingsRequest, RequestOptions.DEFAULT)); - return convertSettingsResponseToMap(response, getSettingsRequest.indices()[0]); + return requestFactory.fromSettingsResponse(response, getSettingsRequest.indices()[0]); } @Override @@ -186,5 +194,53 @@ class DefaultIndexOperations extends AbstractDefaultIndexOperations implements I restTemplate.execute(client -> client.indices().refresh(refreshRequest, RequestOptions.DEFAULT)); } + @Override + public boolean putTemplate(PutTemplateRequest putTemplateRequest) { + + Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null"); + + PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest); + return restTemplate.execute( + client -> client.indices().putTemplate(putIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged()); + } + + @Override + public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) { + + Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null"); + + // getIndexTemplate throws an error on non-existing template names + if (!existsTemplate(new ExistsTemplateRequest(getTemplateRequest.getTemplateName()))) { + return null; + } + + GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(getTemplateRequest); + GetIndexTemplatesResponse getIndexTemplatesResponse = restTemplate + .execute(client -> client.indices().getIndexTemplate(getIndexTemplatesRequest, RequestOptions.DEFAULT)); + return requestFactory.getTemplateData(getIndexTemplatesResponse, getTemplateRequest.getTemplateName()); + } + + @Override + public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + IndexTemplatesExistRequest putIndexTemplateRequest = requestFactory + .indexTemplatesExistsRequest(existsTemplateRequest); + return restTemplate + .execute(client -> client.indices().existsTemplate(putIndexTemplateRequest, RequestOptions.DEFAULT)); + } + + @Override + public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) { + + Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null"); + + DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory + .deleteIndexTemplateRequest(deleteTemplateRequest); + return restTemplate.execute( + client -> client.indices().deleteTemplate(deleteIndexTemplateRequest, RequestOptions.DEFAULT).isAcknowledged()); + } + // 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 3c97e450f..9c7379eb6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -30,7 +30,11 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.elasticsearch.client.GetAliasesResponse; +import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -41,7 +45,12 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverte import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasActions; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +import org.springframework.data.elasticsearch.core.index.GetTemplateRequest; import org.springframework.data.elasticsearch.core.index.MappingBuilder; +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.lang.Nullable; @@ -90,21 +99,12 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { public Mono create() { String indexName = getIndexCoordinates().getIndexName(); - Document settings = null; if (boundClass != null) { - Class clazz = boundClass; - - if (clazz.isAnnotationPresent(Setting.class)) { - String settingPath = clazz.getAnnotation(Setting.class).settingPath(); - - return loadDocument(settingPath, "@Setting").flatMap(document -> doCreate(indexName, document)); - } - - settings = getRequiredPersistentEntity(clazz).getDefaultSettings(); + return createSettings(boundClass).flatMap(settings -> doCreate(indexName, settings)); + } else { + return doCreate(indexName, null); } - - return doCreate(indexName, settings); } @Override @@ -113,8 +113,8 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { } private Mono doCreate(String indexName, @Nullable Document settings) { - CreateIndexRequest request = requestFactory.createIndexRequestReactive(getIndexCoordinates().getIndexName(), - settings); + + CreateIndexRequest request = requestFactory.createIndexRequestReactive(indexName, settings); return Mono.from(operations.executeWithIndicesClient(client -> client.createIndex(request))); } @@ -189,6 +189,24 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { // endregion // region settings + + @Override + public Mono createSettings() { + return createSettings(checkForBoundClass()); + } + + @Override + public Mono createSettings(Class clazz) { + + if (clazz.isAnnotationPresent(Setting.class)) { + String settingPath = clazz.getAnnotation(Setting.class).settingPath(); + + return loadDocument(settingPath, "@Setting"); + } + + return Mono.just(getRequiredPersistentEntity(clazz).getDefaultSettings()); + } + @Override public Mono getSettings(boolean includeDefaults) { @@ -225,6 +243,55 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { return Mono.from(operations.executeWithIndicesClient(client -> client.getAliases(getAliasesRequest))) .map(GetAliasesResponse::getAliases).map(requestFactory::convertAliasesResponse); } + // endregion + + // region templates + @Override + public Mono putTemplate(PutTemplateRequest putTemplateRequest) { + + Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null"); + + PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest); + return Mono.from(operations.executeWithIndicesClient(client -> client.putTemplate(putIndexTemplateRequest))); + } + + @Override + public Mono getTemplate(GetTemplateRequest getTemplateRequest) { + + Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null"); + + GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(getTemplateRequest); + return Mono.from(operations.executeWithIndicesClient(client -> client.getTemplate(getIndexTemplatesRequest))) + .flatMap(response -> { + if (response != null) { + TemplateData templateData = requestFactory.getTemplateData(response, getTemplateRequest.getTemplateName()); + if (templateData != null) { + return Mono.just(templateData); + } + } + return Mono.empty(); + }); + } + + @Override + public Mono existsTemplate(ExistsTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + IndexTemplatesExistRequest indexTemplatesExistRequest = requestFactory + .indexTemplatesExistsRequest(existsTemplateRequest); + return Mono.from(operations.executeWithIndicesClient(client -> client.existsTemplate(indexTemplatesExistRequest))); + } + + @Override + public Mono deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) { + + Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null"); + + DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory + .deleteIndexTemplateRequest(deleteTemplateRequest); + return Mono.from(operations.executeWithIndicesClient(client -> client.deleteTemplate(deleteIndexTemplateRequest))); + } // endregion 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 fc7a4da9f..5bdce272d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.core; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -32,13 +33,27 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuild import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest; +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.cluster.metadata.IndexTemplateMetadata; import org.elasticsearch.common.collect.ImmutableOpenMap; +import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.settings.Settings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasActions; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +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.query.AliasQuery; import org.springframework.lang.Nullable; @@ -53,6 +68,8 @@ import org.springframework.util.Assert; */ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTransportIndexOperations.class); + private final Client client; public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter, @@ -140,7 +157,7 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp } @Override - protected Map> doGetAliases(String[] aliasNames, String[] indexNames) { + protected Map> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) { GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(aliasNames, indexNames); @@ -183,4 +200,89 @@ class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations imp client.admin().indices().refresh(request).actionGet(); } + @Override + public boolean putTemplate(PutTemplateRequest putTemplateRequest) { + + Assert.notNull(putTemplateRequest, "putTemplateRequest must not be null"); + + PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(client, + putTemplateRequest); + return client.admin().indices().putTemplate(putIndexTemplateRequest).actionGet().isAcknowledged(); + } + + @Override + public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) { + + Assert.notNull(getTemplateRequest, "getTemplateRequest must not be null"); + + GetIndexTemplatesRequest getIndexTemplatesRequest = requestFactory.getIndexTemplatesRequest(client, + getTemplateRequest); + GetIndexTemplatesResponse getIndexTemplatesResponse = client.admin().indices() + .getTemplates(getIndexTemplatesRequest).actionGet(); + for (IndexTemplateMetadata indexTemplateMetadata : getIndexTemplatesResponse.getIndexTemplates()) { + + if (indexTemplateMetadata.getName().equals(getTemplateRequest.getTemplateName())) { + + Document settings = Document.create(); + Settings templateSettings = indexTemplateMetadata.settings(); + templateSettings.keySet().forEach(key -> settings.put(key, templateSettings.get(key))); + + Map aliases = new LinkedHashMap<>(); + ImmutableOpenMap aliasesResponse = indexTemplateMetadata.aliases(); + Iterator keysItAliases = aliasesResponse.keysIt(); + while (keysItAliases.hasNext()) { + String key = keysItAliases.next(); + aliases.put(key, requestFactory.convertAliasMetadata(aliasesResponse.get(key))); + } + + Map mappingsDoc = new LinkedHashMap<>(); + ImmutableOpenMap mappingsResponse = indexTemplateMetadata.mappings(); + Iterator keysItMappings = mappingsResponse.keysIt(); + while (keysItMappings.hasNext()) { + String key = keysItMappings.next(); + mappingsDoc.put(key, mappingsResponse.get(key).string()); + } + String mappingsJson = mappingsDoc.get("_doc"); + Document mapping = null; + if (mappingsJson != null) { + try { + mapping = Document.from((Map) Document.parse(mappingsJson).get("_doc")); + } catch (Exception e) { + LOGGER.warn("Got invalid mappings JSON: {}", mappingsJson); + } + } + + TemplateData templateData = TemplateData.builder() + .withIndexPatterns(indexTemplateMetadata.patterns().toArray(new String[0])) // + .withSettings(settings) // + .withMapping(mapping) // + .withAliases(aliases) // + .withOrder(indexTemplateMetadata.order()) // + .withVersion(indexTemplateMetadata.version()).build(); + + return templateData; + } + } + + return null; + } + + @Override + public boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest) { + + Assert.notNull(existsTemplateRequest, "existsTemplateRequest must not be null"); + + // client.admin().indices() has no method for checking the existence + return getTemplate(new GetTemplateRequest(existsTemplateRequest.getTemplateName())) != null; + } + + @Override + public boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest) { + + Assert.notNull(deleteTemplateRequest, "deleteTemplateRequest must not be null"); + + DeleteIndexTemplateRequest deleteIndexTemplateRequest = requestFactory.deleteIndexTemplateRequest(client, + deleteTemplateRequest); + return client.admin().indices().deleteTemplate(deleteIndexTemplateRequest).actionGet().isAcknowledged(); + } } 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 8631f4611..2d5cc8b92 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -23,8 +23,14 @@ import org.elasticsearch.cluster.metadata.AliasMetadata; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasActions; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +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.query.AliasQuery; +import org.springframework.lang.Nullable; /** * The operations for the @@ -122,6 +128,23 @@ public interface IndexOperations { // endregion // region settings + /** + * Creates the index settings for the entity this IndexOperations is bound to. + * + * @return a settings document. + * @since 4.1 + */ + Document createSettings(); + + /** + * Creates the index settings from the annotations on the given class + * + * @param clazz the class to create the index settings from + * @return a settings document. + * @since 4.1 + */ + Document createSettings(Class clazz); + /** * Get mapping for an index defined by a class. * @@ -157,7 +180,7 @@ public interface IndexOperations { boolean addAlias(AliasQuery query); /** - * Get the alias informations for a specified index. + * Get the alias information for a specified index. * * @return alias information * @deprecated since 4.1, use {@link #getAliases(String...)} or {@link #getAliasesForIndex(String...)}. @@ -203,6 +226,87 @@ public interface IndexOperations { Map> getAliasesForIndex(String... indexNames); // endregion + // region templates + /** + * Creates an index template using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html). + * + * @param putTemplateRequest template request parameters + * @return true if successful + * @since 4.1 + */ + boolean putTemplate(PutTemplateRequest putTemplateRequest); + + /** + * gets an index template using the legacy Elasticsearch + * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * + * @param templateName the template name + * @return TemplateData, {@literal null} if no template with the given name exists. + * @since 4.1 + */ + @Nullable + default TemplateData getTemplate(String templateName) { + return getTemplate(new GetTemplateRequest(templateName)); + } + + /** + * gets an index template using the legacy Elasticsearch + * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * + * @param getTemplateRequest the request parameters + * @return TemplateData, {@literal null} if no template with the given name exists. + * @since 4.1 + */ + @Nullable + TemplateData getTemplate(GetTemplateRequest getTemplateRequest); + + /** + * check if an index template exists using the legacy Elasticsearch + * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * + * @param templateName the template name + * @return {@literal true} if the index exists + * @since 4.1 + */ + default boolean existsTemplate(String templateName) { + return existsTemplate(new ExistsTemplateRequest(templateName)); + } + + /** + * check if an index template exists using the legacy Elasticsearch + * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * + * @param existsTemplateRequest the request parameters + * @return {@literal true} if the index exists + * @since 4.1 + */ + boolean existsTemplate(ExistsTemplateRequest existsTemplateRequest); + + /** + * Deletes an index template using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html). + * + * @param templateName the template name + * @return true if successful + * @since 4.1 + */ + default boolean deleteTemplate(String templateName) { + return deleteTemplate(new DeleteTemplateRequest(templateName)); + } + + /** + * Deletes an index template using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html). + * + * @param deleteTemplateRequest template request parameters + * @return true if successful + * @since 4.1 + */ + boolean deleteTemplate(DeleteTemplateRequest deleteTemplateRequest); + + // endregion + // region helper functions /** * get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed @@ -212,5 +316,6 @@ public interface IndexOperations { * @since 4.1 */ IndexCoordinates getIndexCoordinates(); + // 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 8fb7b5885..2896e3e86 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java @@ -23,6 +23,11 @@ import java.util.Set; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasActions; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +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; /** @@ -126,6 +131,23 @@ public interface ReactiveIndexOperations { // endregion // region settings + /** + * Creates the index settings for the entity this IndexOperations is bound to. + * + * @return a settings document. + * @since 4.1 + */ + Mono createSettings(); + + /** + * Creates the index settings from the annotations on the given class + * + * @param clazz the class to create the index settings from + * @return a settings document. + * @since 4.1 + */ + Mono createSettings(Class clazz); + /** * get the settings for the index * @@ -173,13 +195,92 @@ public interface ReactiveIndexOperations { Mono>> getAliasesForIndex(String... indexNames); // endregion + // region templates + /** + * Creates an index template using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) + * + * @param putTemplateRequest template request parameters + * @return Mono of {@literal true} if the template could be stored + * @since 4.1 + */ + Mono putTemplate(PutTemplateRequest putTemplateRequest); + + /** + * gets an index template using the legacy Elasticsearch + * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * + * @param templateName the template name + * @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists. + * @since 4.1 + */ + default Mono getTemplate(String templateName) { + return getTemplate(new GetTemplateRequest(templateName)); + } + + /** + * gets an index template using the legacy Elasticsearch + * interface/Users/peter/Entwicklung/Projekte/spring-data-elasticsearch/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java. + * + * @param getTemplateRequest the request parameters + * @return Mono of TemplateData, {@literal Mono.empty()} if no template with the given name exists. + * @since 4.1 + */ + Mono getTemplate(GetTemplateRequest getTemplateRequest); + + /** + * Checks if an index template exists using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) + * + * @param templateName the template name + * @return Mono of {@literal true} if the template exists + * @since 4.1 + */ + default Mono existsTemplate(String templateName) { + return existsTemplate(new ExistsTemplateRequest(templateName)); + } + + /** + * Checks if an index template exists using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) + * + * @param existsTemplateRequest template request parameters + * @return Mono of {@literal true} if the template exists + * @since 4.1 + */ + Mono existsTemplate(ExistsTemplateRequest existsTemplateRequest); + + /** + * Deletes an index template using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) + * + * @param templateName the template name + * @return Mono of {@literal true} if the template could be deleted + * @since 4.1 + */ + default Mono deleteTemplate(String templateName) { + return deleteTemplate(new DeleteTemplateRequest(templateName)); + } + + /** + * Deletes an index template using the legacy Elasticsearch interface (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) + * + * @param deleteTemplateRequest template request parameters + * @return Mono of {@literal true} if the template could be deleted + * @since 4.1 + */ + Mono deleteTemplate(DeleteTemplateRequest deleteTemplateRequest); + + // endregion + // region helper functions /** * get the current {@link IndexCoordinates}. These may change over time when the entity class has a SpEL constructed * index name. When this IndexOperations is not bound to a class, the bound IndexCoordinates are returned. * * @return IndexCoordinates - * @ince 4.1 + * @since 4.1 */ IndexCoordinates getIndexCoordinates(); 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 a8761b740..fc6c9688a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -19,14 +19,17 @@ import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.util.CollectionUtils.*; 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.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; @@ -37,6 +40,7 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuild import org.elasticsearch.action.admin.indices.refresh.RefreshRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.delete.DeleteRequest; @@ -56,9 +60,15 @@ import org.elasticsearch.client.Client; import org.elasticsearch.client.Requests; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.GetIndexTemplatesResponse; import org.elasticsearch.client.indices.GetMappingsRequest; +import org.elasticsearch.client.indices.IndexTemplateMetadata; +import org.elasticsearch.client.indices.IndexTemplatesExistRequest; +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; @@ -92,6 +102,11 @@ 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.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +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.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -466,8 +481,8 @@ class RequestFactory { return new GetMappingsRequest().indices(indexNames); } - public org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest(Client client, - IndexCoordinates index) { + public org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest( + @SuppressWarnings("unused") Client client, IndexCoordinates index) { String[] indexNames = index.getIndexNames(); return new org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest().indices(indexNames); @@ -477,22 +492,217 @@ class RequestFactory { Map> converted = new LinkedHashMap<>(); aliasesResponse.forEach((index, aliasMetaDataSet) -> { Set aliasDataSet = new LinkedHashSet<>(); - - aliasMetaDataSet.forEach(aliasMetaData -> { - Document filter = null; - CompressedXContent aliasMetaDataFilter = aliasMetaData.getFilter(); - if (aliasMetaDataFilter != null) { - filter = Document.parse(aliasMetaDataFilter.string()); - } - aliasDataSet.add(AliasData.of(aliasMetaData.alias(), filter, aliasMetaData.indexRouting(), - aliasMetaData.getSearchRouting(), aliasMetaData.writeIndex(), aliasMetaData.isHidden())); - }); - + 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) { + + PutIndexTemplateRequest request = new PutIndexTemplateRequest(putTemplateRequest.getName()) + .patterns(Arrays.asList(putTemplateRequest.getIndexPatterns())); + + if (putTemplateRequest.getSettings() != null) { + request.settings(putTemplateRequest.getSettings()); + } + + if (putTemplateRequest.getMappings() != null) { + request.mapping(putTemplateRequest.getMappings()); + } + + request.order(putTemplateRequest.getOrder()).version(putTemplateRequest.getVersion()); + + AliasActions aliasActions = putTemplateRequest.getAliasActions(); + + if (aliasActions != null) { + aliasActions.getActions().forEach(aliasAction -> { + AliasActionParameters parameters = aliasAction.getParameters(); + String[] parametersAliases = parameters.getAliases(); + + if (parametersAliases != null) { + for (String aliasName : parametersAliases) { + Alias alias = new Alias(aliasName); + + if (parameters.getRouting() != null) { + alias.routing(parameters.getRouting()); + } + + if (parameters.getIndexRouting() != null) { + alias.indexRouting(parameters.getIndexRouting()); + } + + if (parameters.getSearchRouting() != null) { + alias.searchRouting(parameters.getSearchRouting()); + } + + if (parameters.getHidden() != null) { + alias.isHidden(parameters.getHidden()); + } + + if (parameters.getWriteIndex() != null) { + alias.writeIndex(parameters.getWriteIndex()); + } + + Query filterQuery = parameters.getFilterQuery(); + + if (filterQuery != null) { + elasticsearchConverter.updateQuery(filterQuery, parameters.getFilterQueryClass()); + QueryBuilder queryBuilder = getFilter(filterQuery); + + if (queryBuilder == null) { + queryBuilder = getQuery(filterQuery); + } + + alias.filter(queryBuilder); + } + + request.alias(alias); + } + } + }); + } + + return request; + } + + /** + * The version for the transport client needs a different PutIndexTemplateRequest class + */ + public org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest putIndexTemplateRequest( + @SuppressWarnings("unused") Client client, PutTemplateRequest putTemplateRequest) { + org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest request = new org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest( + putTemplateRequest.getName()).patterns(Arrays.asList(putTemplateRequest.getIndexPatterns())); + + if (putTemplateRequest.getSettings() != null) { + request.settings(putTemplateRequest.getSettings()); + } + + if (putTemplateRequest.getMappings() != null) { + request.mapping("_doc", putTemplateRequest.getMappings()); + } + + request.order(putTemplateRequest.getOrder()).version(putTemplateRequest.getVersion()); + + AliasActions aliasActions = putTemplateRequest.getAliasActions(); + + if (aliasActions != null) { + aliasActions.getActions().forEach(aliasAction -> { + AliasActionParameters parameters = aliasAction.getParameters(); + String[] parametersAliases = parameters.getAliases(); + if (parametersAliases != null) { + + for (String aliasName : parametersAliases) { + Alias alias = new Alias(aliasName); + + if (parameters.getRouting() != null) { + alias.routing(parameters.getRouting()); + } + + if (parameters.getIndexRouting() != null) { + alias.indexRouting(parameters.getIndexRouting()); + } + + if (parameters.getSearchRouting() != null) { + alias.searchRouting(parameters.getSearchRouting()); + } + + if (parameters.getHidden() != null) { + alias.isHidden(parameters.getHidden()); + } + + if (parameters.getWriteIndex() != null) { + alias.writeIndex(parameters.getWriteIndex()); + } + + Query filterQuery = parameters.getFilterQuery(); + + if (filterQuery != null) { + elasticsearchConverter.updateQuery(filterQuery, parameters.getFilterQueryClass()); + QueryBuilder queryBuilder = getFilter(filterQuery); + + if (queryBuilder == null) { + queryBuilder = getQuery(filterQuery); + } + + alias.filter(queryBuilder); + } + + request.alias(alias); + } + } + }); + } + + return request; + } + + public GetIndexTemplatesRequest getIndexTemplatesRequest(GetTemplateRequest getTemplateRequest) { + return new GetIndexTemplatesRequest(getTemplateRequest.getTemplateName()); + } + + @Nullable + public TemplateData getTemplateData(GetIndexTemplatesResponse getIndexTemplatesResponse, String templateName) { + for (IndexTemplateMetadata indexTemplateMetadata : getIndexTemplatesResponse.getIndexTemplates()) { + + if (indexTemplateMetadata.name().equals(templateName)) { + + Document settings = Document.create(); + Settings templateSettings = indexTemplateMetadata.settings(); + templateSettings.keySet().forEach(key -> settings.put(key, templateSettings.get(key))); + + Map aliases = new LinkedHashMap<>(); + + ImmutableOpenMap aliasesResponse = indexTemplateMetadata.aliases(); + Iterator keysIt = aliasesResponse.keysIt(); + while (keysIt.hasNext()) { + String key = keysIt.next(); + aliases.put(key, convertAliasMetadata(aliasesResponse.get(key))); + } + TemplateData templateData = TemplateData.builder() + .withIndexPatterns(indexTemplateMetadata.patterns().toArray(new String[0])) // + .withSettings(settings) // + .withMapping(Document.from(indexTemplateMetadata.mappings().getSourceAsMap())) // + .withAliases(aliases) // + .withOrder(indexTemplateMetadata.order()) // + .withVersion(indexTemplateMetadata.version()).build(); + + return templateData; + } + } + return null; + } + + public org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest getIndexTemplatesRequest( + Client client, GetTemplateRequest getTemplateRequest) { + return new org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesRequest( + getTemplateRequest.getTemplateName()); + } + + public IndexTemplatesExistRequest indexTemplatesExistsRequest(ExistsTemplateRequest existsTemplateRequest) { + return new IndexTemplatesExistRequest(existsTemplateRequest.getTemplateName()); + } + + public DeleteIndexTemplateRequest deleteIndexTemplateRequest(DeleteTemplateRequest deleteTemplateRequest) { + return new DeleteIndexTemplateRequest(deleteTemplateRequest.getTemplateName()); + } + + public org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest deleteIndexTemplateRequest( + Client client, DeleteTemplateRequest deleteTemplateRequest) { + return new org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest( + deleteTemplateRequest.getTemplateName()); + } // endregion // region delete @@ -524,10 +734,12 @@ class RequestFactory { .setRefresh(true); if (query.isLimiting()) { + // noinspection ConstantConditions deleteByQueryRequest.setBatchSize(query.getMaxResults()); } if (query.hasScrollTime()) { + // noinspection ConstantConditions deleteByQueryRequest.setScroll(TimeValue.timeValueMillis(query.getScrollTime().toMillis())); } @@ -752,10 +964,7 @@ class RequestFactory { String indexName = index.getIndexName(); MoreLikeThisQueryBuilder.Item item = new MoreLikeThisQueryBuilder.Item(indexName, query.getId()); - String[] fields = null; - if (query.getFields() != null) { - fields = query.getFields().toArray(new String[] {}); - } + String[] fields = query.getFields().toArray(new String[] {}); MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = QueryBuilders.moreLikeThisQuery(fields, null, new MoreLikeThisQueryBuilder.Item[] { item }); @@ -817,11 +1026,7 @@ class RequestFactory { QueryBuilder elasticsearchQuery = getQuery(query); QueryBuilder elasticsearchFilter = getFilter(query); - if (elasticsearchQuery != null) { - searchRequest.source().query(elasticsearchQuery); - } else { - searchRequest.source().query(QueryBuilders.matchAllQuery()); - } + searchRequest.source().query(elasticsearchQuery); if (elasticsearchFilter != null) { searchRequest.source().postFilter(elasticsearchFilter); @@ -839,11 +1044,7 @@ class RequestFactory { QueryBuilder elasticsearchQuery = getQuery(query); QueryBuilder elasticsearchFilter = getFilter(query); - if (elasticsearchQuery != null) { - searchRequestBuilder.setQuery(elasticsearchQuery); - } else { - searchRequestBuilder.setQuery(QueryBuilders.matchAllQuery()); - } + searchRequestBuilder.setQuery(elasticsearchQuery); if (elasticsearchFilter != null) { searchRequestBuilder.setPostFilter(elasticsearchFilter); @@ -888,6 +1089,7 @@ class RequestFactory { } if (query.isLimiting()) { + // noinspection ConstantConditions sourceBuilder.size(query.getMaxResults()); } @@ -899,9 +1101,7 @@ class RequestFactory { request.preference(query.getPreference()); } - if (query.getSearchType() != null) { - request.searchType(query.getSearchType()); - } + request.searchType(query.getSearchType()); prepareSort(query, sourceBuilder, getPersistentEntity(clazz)); @@ -965,6 +1165,7 @@ class RequestFactory { } if (query.isLimiting()) { + // noinspection ConstantConditions searchRequestBuilder.setSize(query.getMaxResults()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/AliasActionParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/AliasActionParameters.java index d9634a433..79cd8ccfc 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/AliasActionParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/AliasActionParameters.java @@ -20,7 +20,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Value class capturing the arguments for an {@link AliasAction}.   + * Value class capturing the arguments for an {@link AliasAction}. * * @author Peter-Josef Meisch * @since 4.1 @@ -54,6 +54,14 @@ public class AliasActionParameters { return new Builder(); } + /** + * a Builder to create AliasActionParameters to be used when creating index templates. Automatically sets the index + * name to an empty string, as this is not used in templates + */ + public static Builder builderForTemplate() { + return new Builder().withIndices(""); + } + public String[] getIndices() { return indices; } @@ -158,7 +166,7 @@ public class AliasActionParameters { public AliasActionParameters build() { - Assert.notNull(indices, "indices must bes set"); + Assert.notNull(indices, "indices must be set"); return new AliasActionParameters(indices, aliases, isHidden, isWriteIndex, routing, indexRouting, searchRouting, filterQuery, filterQueryClass); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/DeleteTemplateRequest.java b/src/main/java/org/springframework/data/elasticsearch/core/index/DeleteTemplateRequest.java new file mode 100644 index 000000000..34ac13695 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/DeleteTemplateRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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 org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + */ +public class DeleteTemplateRequest { + private final String templateName; + + public DeleteTemplateRequest(String templateName) { + + Assert.notNull(templateName, "templateName must not be null"); + + this.templateName = templateName; + } + + public String getTemplateName() { + return templateName; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/ExistsTemplateRequest.java b/src/main/java/org/springframework/data/elasticsearch/core/index/ExistsTemplateRequest.java new file mode 100644 index 000000000..87f641246 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/ExistsTemplateRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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 org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + */ +public class ExistsTemplateRequest { + private final String templateName; + + public ExistsTemplateRequest(String templateName) { + + Assert.notNull(templateName, "templateName must not be null"); + + this.templateName = templateName; + } + + public String getTemplateName() { + return templateName; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/GetTemplateRequest.java b/src/main/java/org/springframework/data/elasticsearch/core/index/GetTemplateRequest.java new file mode 100644 index 000000000..48fe1d9fa --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/GetTemplateRequest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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 org.springframework.util.Assert; + +/** + * @author Peter-Josef Meisch + */ +public class GetTemplateRequest { + private final String templateName; + + public GetTemplateRequest(String templateName) { + + Assert.notNull(templateName, "templateName must not be null"); + + this.templateName = templateName; + } + + public String getTemplateName() { + return templateName; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/PutTemplateRequest.java b/src/main/java/org/springframework/data/elasticsearch/core/index/PutTemplateRequest.java new file mode 100644 index 000000000..e12ab3d94 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/PutTemplateRequest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2020 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 org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Request to create an index template. This is to create legacy templates (@see + * https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates-v1.html) + * + * @author Peter-Josef Meisch + * @since 4.1 + */ +public class PutTemplateRequest { + private final String name; + private final String[] indexPatterns; + @Nullable final private Document settings; + @Nullable final private Document mappings; + @Nullable final AliasActions aliasActions; + private final int order; + @Nullable final Integer version; + + private PutTemplateRequest(String name, String[] indexPatterns, @Nullable Document settings, + @Nullable Document mappings, @Nullable AliasActions aliasActions, int order, @Nullable Integer version) { + this.name = name; + this.indexPatterns = indexPatterns; + this.settings = settings; + this.mappings = mappings; + this.aliasActions = aliasActions; + this.order = order; + this.version = version; + } + + public String getName() { + return name; + } + + public String[] getIndexPatterns() { + return indexPatterns; + } + + @Nullable + public Document getSettings() { + return settings; + } + + @Nullable + public Document getMappings() { + return mappings; + } + + @Nullable + public AliasActions getAliasActions() { + return aliasActions; + } + + public int getOrder() { + return order; + } + + @Nullable + public Integer getVersion() { + return version; + } + + public static TemplateRequestBuilder builder(String name, String... indexPatterns) { + return new TemplateRequestBuilder(name, indexPatterns); + } + + public static final class TemplateRequestBuilder { + private final String name; + private final String[] indexPatterns; + @Nullable private Document settings; + @Nullable private Document mappings; + @Nullable AliasActions aliasActions; + private int order = 0; + @Nullable Integer version; + + private TemplateRequestBuilder(String name, String... indexPatterns) { + + Assert.notNull(name, "name must not be null"); + Assert.notNull(indexPatterns, "indexPatterns must not be null"); + + this.name = name; + this.indexPatterns = indexPatterns; + } + + public TemplateRequestBuilder withSettings(Document settings) { + this.settings = settings; + return this; + } + + public TemplateRequestBuilder withMappings(Document mappings) { + this.mappings = mappings; + return this; + } + + public TemplateRequestBuilder withAliasActions(AliasActions aliasActions) { + + aliasActions.getActions().forEach(action -> Assert.isTrue(action instanceof AliasAction.Add, + "only alias add actions are allowed in templates")); + + this.aliasActions = aliasActions; + return this; + } + + public TemplateRequestBuilder withOrder(int order) { + this.order = order; + return this; + } + + public TemplateRequestBuilder withVersion(@Nullable Integer version) { + this.version = version; + return this; + } + + public PutTemplateRequest build() { + return new PutTemplateRequest(name, indexPatterns, settings, mappings, aliasActions, order, version); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/TemplateData.java b/src/main/java/org/springframework/data/elasticsearch/core/index/TemplateData.java new file mode 100644 index 000000000..a8322fb95 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/TemplateData.java @@ -0,0 +1,123 @@ +/* + * Copyright 2020 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 java.util.Map; + +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.lang.Nullable; + +/** + * Data returned for template information retrieval. + * + * @author Peter-Josef Meisch + */ +public class TemplateData { + @Nullable private final String[] indexPatterns; + @Nullable Document settings; + @Nullable Document mapping; + @Nullable private final Map aliases; + int order; + @Nullable Integer version; + + private TemplateData(@Nullable String[] indexPatterns, @Nullable Document settings, @Nullable Document mapping, + @Nullable Map aliases, int order, @Nullable Integer version) { + this.indexPatterns = indexPatterns; + this.settings = settings; + this.mapping = mapping; + this.order = order; + this.version = version; + this.aliases = aliases; + } + + public static TemplateDataBuilder builder() { + return new TemplateDataBuilder(); + } + + @Nullable + public String[] getIndexPatterns() { + return indexPatterns; + } + + @Nullable + public Document getSettings() { + return settings; + } + + @Nullable + public Document getMapping() { + return mapping; + } + + @Nullable + public Map getAliases() { + return aliases; + } + + public int getOrder() { + return order; + } + + @Nullable + public Integer getVersion() { + return version; + } + + public static final class TemplateDataBuilder { + @Nullable Document settings; + @Nullable Document mapping; + int order; + @Nullable Integer version; + @Nullable private String[] indexPatterns; + @Nullable private Map aliases; + + private TemplateDataBuilder() {} + + public TemplateDataBuilder withIndexPatterns(String[] indexPatterns) { + this.indexPatterns = indexPatterns; + return this; + } + + public TemplateDataBuilder withSettings(Document settings) { + this.settings = settings; + return this; + } + + public TemplateDataBuilder withMapping(Document mapping) { + this.mapping = mapping; + return this; + } + + public TemplateDataBuilder withOrder(int order) { + this.order = order; + return this; + } + + public TemplateDataBuilder withVersion(Integer version) { + this.version = version; + return this; + } + + public TemplateDataBuilder withAliases(Map aliases) { + this.aliases = aliases; + return this; + } + + public TemplateData build() { + return new TemplateData(indexPatterns, settings, mapping, aliases, order, version); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/FetchSourceFilter.java b/src/main/java/org/springframework/data/elasticsearch/core/query/FetchSourceFilter.java index 93bc6e495..6e89746a9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/FetchSourceFilter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/FetchSourceFilter.java @@ -15,17 +15,20 @@ */ package org.springframework.data.elasticsearch.core.query; +import org.springframework.lang.Nullable; + /** * SourceFilter implementation for providing includes and excludes. * - * @Author Jon Tsiros + * @author Jon Tsiros + * @author Peter-Josef Meisch */ public class FetchSourceFilter implements SourceFilter { - private final String[] includes; - private final String[] excludes; + @Nullable private final String[] includes; + @Nullable private final String[] excludes; - public FetchSourceFilter(final String[] includes, final String[] excludes) { + public FetchSourceFilter(@Nullable final String[] includes, @Nullable final String[] excludes) { this.includes = includes; this.excludes = excludes; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java index 025a7b576..90976e72c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/MoreLikeThisQuery.java @@ -34,14 +34,14 @@ import org.springframework.lang.Nullable; public class MoreLikeThisQuery { @Nullable private String id; - private List searchIndices = new ArrayList<>(); - private List searchTypes = new ArrayList<>(); - private List fields = new ArrayList<>(); + private final List searchIndices = new ArrayList<>(); + private final List searchTypes = new ArrayList<>(); + private final List fields = new ArrayList<>(); @Nullable private String routing; @Nullable private Float percentTermsToMatch; @Nullable private Integer minTermFreq; @Nullable private Integer maxQueryTerms; - private List stopWords = new ArrayList<>(); + private final List stopWords = new ArrayList<>(); @Nullable private Integer minDocFreq; @Nullable private Integer maxDocFreq; @Nullable private Integer minWordLen; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java index 62656dd29..47f5d216c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Query.java @@ -232,7 +232,8 @@ public interface Query { boolean getTrackTotalHits(); /** - * For queries that are used in delete request, these are internally handled by Elasticsearch as scroll/bulk delete queries. + * For queries that are used in delete request, these are internally handled by Elasticsearch as scroll/bulk delete + * queries. Must not return {@literal null} when {@link #hasScrollTime()} returns {@literal true}. * * @return the scrolltime settings * @since 4.0 @@ -241,7 +242,8 @@ public interface Query { Duration getScrollTime(); /** - * For queries that are used in delete request, these are internally handled by Elasticsearch as scroll/bulk delete queries. + * For queries that are used in delete request, these are internally handled by Elasticsearch as scroll/bulk delete + * queries. * * @param scrollTime the scrolltime settings * @since 4.0 diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java b/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java index cb7d9a33e..3591cfbe9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/SourceFilter.java @@ -15,14 +15,19 @@ */ package org.springframework.data.elasticsearch.core.query; +import org.springframework.lang.Nullable; + /** * SourceFilter for providing includes and excludes. * - * @Author Jon Tsiros + * @author Jon Tsiros + * @author Peter-Josef Meisch */ public interface SourceFilter { + @Nullable String[] getIncludes(); + @Nullable String[] getExcludes(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java index 9edf8a414..178711c5c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java @@ -23,7 +23,9 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import java.time.LocalDate; +import java.util.Map; import java.util.Set; +import java.util.UUID; import org.json.JSONException; import org.junit.jupiter.api.AfterEach; @@ -44,10 +46,16 @@ 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.index.DeleteTemplateRequest; +import org.springframework.data.elasticsearch.core.index.ExistsTemplateRequest; +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.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -378,6 +386,128 @@ public class ReactiveIndexOperationsTest { .verifyComplete(); } + @Test // DATAES-612 + void shouldPutTemplate() { + + ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); + + org.springframework.data.elasticsearch.core.document.Document mapping = indexOps.createMapping(TemplateClass.class) + .block(); + org.springframework.data.elasticsearch.core.document.Document settings = indexOps + .createSettings(TemplateClass.class).block(); + + AliasActions aliasActions = new AliasActions( + new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder("test-template", "log-*") // + .withSettings(settings) // + .withMappings(mapping) // + .withAliasActions(aliasActions) // + .withOrder(11) // + .withVersion(42) // + .build(); + + Boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + assertThat(acknowledged).isTrue(); + } + + @Test // DATAES-612 + void shouldReturnNullOnNonExistingGetTemplate() { + + String templateName = "template" + UUID.randomUUID().toString(); + ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); + + GetTemplateRequest getTemplateRequest = new GetTemplateRequest(templateName); + indexOps.getTemplate(getTemplateRequest).as(StepVerifier::create).verifyComplete(); + } + + @Test // DATAES-612 + void shouldGetTemplate() throws JSONException { + ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); + + org.springframework.data.elasticsearch.core.document.Document mapping = indexOps.createMapping(TemplateClass.class) + .block(); + org.springframework.data.elasticsearch.core.document.Document settings = indexOps + .createSettings(TemplateClass.class).block(); + + AliasActions aliasActions = new AliasActions( + new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder("test-template", "log-*") // + .withSettings(settings) // + .withMappings(mapping) // + .withAliasActions(aliasActions) // + .withOrder(11) // + .withVersion(42) // + .build(); + + Boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + assertThat(acknowledged).isTrue(); + + GetTemplateRequest getTemplateRequest = new GetTemplateRequest(putTemplateRequest.getName()); + TemplateData templateData = indexOps.getTemplate(getTemplateRequest).block(); + + assertThat(templateData).isNotNull(); + assertThat(templateData.getIndexPatterns()).containsExactlyInAnyOrder(putTemplateRequest.getIndexPatterns()); + assertEquals(settings.toJson(), templateData.getSettings().toJson(), false); + assertEquals(mapping.toJson(), templateData.getMapping().toJson(), false); + Map aliases = templateData.getAliases(); + assertThat(aliases).hasSize(2); + AliasData alias1 = aliases.get("alias1"); + assertThat(alias1.getAlias()).isEqualTo("alias1"); + AliasData alias2 = aliases.get("alias2"); + assertThat(alias2.getAlias()).isEqualTo("alias2"); + assertThat(templateData.getOrder()).isEqualTo(putTemplateRequest.getOrder()); + assertThat(templateData.getVersion()).isEqualTo(putTemplateRequest.getVersion()); + } + + @Test // DATAES-612 + void shouldCheckExists() { + + ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); + + String templateName = "template" + UUID.randomUUID().toString(); + ExistsTemplateRequest existsTemplateRequest = new ExistsTemplateRequest(templateName); + + boolean exists = indexOps.existsTemplate(existsTemplateRequest).block(); + assertThat(exists).isFalse(); + + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder(templateName, "log-*") // + .withOrder(11) // + .withVersion(42) // + .build(); + + boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + assertThat(acknowledged).isTrue(); + + exists = indexOps.existsTemplate(existsTemplateRequest).block(); + assertThat(exists).isTrue(); + } + + @Test // DATAES-612 + void shouldDeleteTemplate() { + + ReactiveIndexOperations indexOps = operations.indexOps(Entity.class); + + String templateName = "template" + UUID.randomUUID().toString(); + ExistsTemplateRequest existsTemplateRequest = new ExistsTemplateRequest(templateName); + + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder(templateName, "log-*") // + .withOrder(11) // + .withVersion(42) // + .build(); + + boolean acknowledged = indexOps.putTemplate(putTemplateRequest).block(); + assertThat(acknowledged).isTrue(); + + boolean exists = indexOps.existsTemplate(existsTemplateRequest).block(); + assertThat(exists).isTrue(); + + acknowledged = indexOps.deleteTemplate(new DeleteTemplateRequest(templateName)).block(); + assertThat(acknowledged).isTrue(); + + exists = indexOps.existsTemplate(existsTemplateRequest).block(); + assertThat(exists).isFalse(); + } + @Data @Document(indexName = TESTINDEX, shards = 3, replicas = 2, refreshInterval = "4s") static class Entity { @@ -400,4 +530,29 @@ public class ReactiveIndexOperationsTest { static class EntityWithAnnotatedSettingsAndMappings { @Id private String id; } + + @Document(indexName = "test-template", shards = 3, replicas = 2, refreshInterval = "5s") + static class TemplateClass { + @Id @Nullable private String id; + @Field(type = FieldType.Text) @Nullable private String message; + + @Nullable + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java index acfc5084d..df8a9ef87 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java @@ -32,6 +32,7 @@ import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.client.Client; +import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; @@ -51,6 +52,7 @@ import org.springframework.data.elasticsearch.core.geo.GeoPoint; 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.PutTemplateRequest; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.Criteria; @@ -371,6 +373,71 @@ class RequestFactoryTests { assertEquals(expected, json, false); } + @Test // DATAES-612 + void shouldCreatePutIndexTemplateRequest() throws JSONException, IOException { + + String expected = "{\n" + // + " \"index_patterns\": [\n" + // + " \"test-*\"\n" + // + " ],\n" + // + " \"order\": 42,\n" + // + " \"version\": 7,\n" + // + " \"settings\": {\n" + // + " \"index\": {\n" + // + " \"number_of_replicas\": \"2\",\n" + // + " \"number_of_shards\": \"3\",\n" + // + " \"refresh_interval\": \"7s\",\n" + // + " \"store\": {\n" + // + " \"type\": \"oops\"\n" + // + " }\n" + // + " }\n" + // + " },\n" + // + " \"mappings\": {\n" + // + " \"properties\": {\n" + // + " \"price\": {\n" + // + " \"type\": \"double\"\n" + // + " }\n" + // + " }\n" + // + " },\n" + // + " \"aliases\":{\n" + // + " \"alias1\": {},\n" + // + " \"alias2\": {},\n" + // + " \"alias3\": {\n" + // + " \"routing\": \"11\"\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + org.springframework.data.elasticsearch.core.document.Document settings = org.springframework.data.elasticsearch.core.document.Document + .create(); + settings.put("index.number_of_replicas", 2); + settings.put("index.number_of_shards", 3); + settings.put("index.refresh_interval", "7s"); + settings.put("index.store.type", "oops"); + + org.springframework.data.elasticsearch.core.document.Document mappings = org.springframework.data.elasticsearch.core.document.Document + .parse("{\"properties\":{\"price\":{\"type\":\"double\"}}}"); + + AliasActions aliasActions = new AliasActions( + new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build()), + new AliasAction.Add( + AliasActionParameters.builderForTemplate().withAliases("alias3").withRouting("11").build())); + + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder("test-template", "test-*") // + .withSettings(settings) // + .withMappings(mappings) // + .withAliasActions(aliasActions) // + .withOrder(42) // + .withVersion(7) // + .build(); // + + PutIndexTemplateRequest putIndexTemplateRequest = requestFactory.putIndexTemplateRequest(putTemplateRequest); + + String json = requestToString(putIndexTemplateRequest); + System.out.println(json); + assertEquals(expected, json, false); + } + private String requestToString(ToXContent request) throws IOException { return XContentHelper.toXContent(request, XContentType.JSON, true).utf8ToString(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterTests.java index a99f9ec2c..b2e991395 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterTests.java @@ -58,7 +58,7 @@ class ElasticsearchDateConverterTests { @Test // DATAES-792 void shouldConvertLegacyDateToString() { GregorianCalendar calendar = GregorianCalendar - .from(ZonedDateTime.of(LocalDateTime.of(2020, 04, 19, 19, 44), ZoneId.of("UTC"))); + .from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC"))); Date legacyDate = calendar.getTime(); ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date_time); @@ -70,7 +70,7 @@ class ElasticsearchDateConverterTests { @Test // DATAES-792 void shouldParseLegacyDateFromString() { GregorianCalendar calendar = GregorianCalendar - .from(ZonedDateTime.of(LocalDateTime.of(2020, 04, 19, 19, 44), ZoneId.of("UTC"))); + .from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC"))); Date legacyDate = calendar.getTime(); ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(DateFormat.basic_date_time); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTests.java new file mode 100644 index 000000000..af298e9e0 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTests.java @@ -0,0 +1,192 @@ +/* + * Copyright 2020 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 static org.skyscreamer.jsonassert.JSONAssert.*; + +import java.util.Map; +import java.util.UUID; + +import org.json.JSONException; +import org.junit.jupiter.api.Test; +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.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.lang.Nullable; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) +public class TemplateTests { + + @Autowired ElasticsearchOperations operations; + + @Test // DATAES-612 + void shouldCreateTemplate() { + + IndexOperations indexOps = operations.indexOps(IndexCoordinates.of("dont-care")); + + org.springframework.data.elasticsearch.core.document.Document mapping = indexOps.createMapping(TemplateClass.class); + org.springframework.data.elasticsearch.core.document.Document settings = indexOps + .createSettings(TemplateClass.class); + + AliasActions aliasActions = new AliasActions( + new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder("test-template", "log-*") // + .withSettings(settings) // + .withMappings(mapping) // + .withAliasActions(aliasActions) // + .build(); + + boolean acknowledged = indexOps.putTemplate(putTemplateRequest); + + assertThat(acknowledged).isTrue(); + } + + @Test // DATAES-612 + void shouldReturnNullOnNonExistingGetTemplate() { + + String templateName = "template" + UUID.randomUUID().toString(); + IndexOperations indexOps = operations.indexOps(IndexCoordinates.of("dont-care")); + + GetTemplateRequest getTemplateRequest = new GetTemplateRequest(templateName); + TemplateData templateData = indexOps.getTemplate(getTemplateRequest); + + assertThat(templateData).isNull(); + } + + @Test // DATAES-612 + void shouldGetTemplate() throws JSONException { + IndexOperations indexOps = operations.indexOps(IndexCoordinates.of("dont-care")); + + org.springframework.data.elasticsearch.core.document.Document mapping = indexOps.createMapping(TemplateClass.class); + org.springframework.data.elasticsearch.core.document.Document settings = indexOps + .createSettings(TemplateClass.class); + + AliasActions aliasActions = new AliasActions( + new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder("test-template", "log-*") // + .withSettings(settings) // + .withMappings(mapping) // + .withAliasActions(aliasActions) // + .withOrder(11) // + .withVersion(42) // + .build(); + + boolean acknowledged = indexOps.putTemplate(putTemplateRequest); + assertThat(acknowledged).isTrue(); + + GetTemplateRequest getTemplateRequest = new GetTemplateRequest(putTemplateRequest.getName()); + TemplateData templateData = indexOps.getTemplate(getTemplateRequest); + + assertThat(templateData).isNotNull(); + assertThat(templateData.getIndexPatterns()).containsExactlyInAnyOrder(putTemplateRequest.getIndexPatterns()); + assertEquals(settings.toJson(), templateData.getSettings().toJson(), false); + assertEquals(mapping.toJson(), templateData.getMapping().toJson(), false); + Map aliases = templateData.getAliases(); + assertThat(aliases).hasSize(2); + AliasData alias1 = aliases.get("alias1"); + assertThat(alias1.getAlias()).isEqualTo("alias1"); + AliasData alias2 = aliases.get("alias2"); + assertThat(alias2.getAlias()).isEqualTo("alias2"); + assertThat(templateData.getOrder()).isEqualTo(putTemplateRequest.getOrder()); + assertThat(templateData.getVersion()).isEqualTo(putTemplateRequest.getVersion()); + } + + @Test // DATAES-612 + void shouldCheckExists() { + IndexOperations indexOps = operations.indexOps(IndexCoordinates.of("dont-care")); + + String templateName = "template" + UUID.randomUUID().toString(); + ExistsTemplateRequest existsTemplateRequest = new ExistsTemplateRequest(templateName); + + boolean exists = indexOps.existsTemplate(existsTemplateRequest); + assertThat(exists).isFalse(); + + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder(templateName, "log-*") // + .withOrder(11) // + .withVersion(42) // + .build(); + + boolean acknowledged = indexOps.putTemplate(putTemplateRequest); + assertThat(acknowledged).isTrue(); + + exists = indexOps.existsTemplate(existsTemplateRequest); + assertThat(exists).isTrue(); + } + + @Test // DATAES-612 + void shouldDeleteTemplate() { + + IndexOperations indexOps = operations.indexOps(IndexCoordinates.of("dont-care")); + + String templateName = "template" + UUID.randomUUID().toString(); + ExistsTemplateRequest existsTemplateRequest = new ExistsTemplateRequest(templateName); + + PutTemplateRequest putTemplateRequest = PutTemplateRequest.builder(templateName, "log-*") // + .withOrder(11) // + .withVersion(42) // + .build(); + + boolean acknowledged = indexOps.putTemplate(putTemplateRequest); + assertThat(acknowledged).isTrue(); + + boolean exists = indexOps.existsTemplate(existsTemplateRequest); + assertThat(exists).isTrue(); + + acknowledged = indexOps.deleteTemplate(new DeleteTemplateRequest(templateName)); + assertThat(acknowledged).isTrue(); + + exists = indexOps.existsTemplate(existsTemplateRequest); + assertThat(exists).isFalse(); + + } + + @Document(indexName = "test-template", shards = 3, replicas = 2, refreshInterval = "5s") + static class TemplateClass { + @Id @Nullable private String id; + @Field(type = FieldType.Text) @Nullable private String message; + + @Nullable + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTransportTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTransportTests.java new file mode 100644 index 000000000..dc23cd1f4 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTransportTests.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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 org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +public class TemplateTransportTests extends TemplateTests {}