From 3f2ab4b06a592b978442b0d01371b9dd3151be0b Mon Sep 17 00:00:00 2001 From: Morgan <11651971+morganlutz@users.noreply.github.com> Date: Thu, 25 Feb 2021 23:25:27 -0800 Subject: [PATCH 001/776] Add support for dense_vector type Original Pull Request #1708 Closes #1700 --- .../data/elasticsearch/annotations/Field.java | 9 ++++++ .../elasticsearch/annotations/FieldType.java | 6 +++- .../elasticsearch/annotations/InnerField.java | 9 ++++++ .../core/index/MappingParameters.java | 16 ++++++++++ .../index/MappingBuilderIntegrationTests.java | 19 ++++++++++++ .../core/index/MappingBuilderUnitTests.java | 26 ++++++++++++++++ .../core/index/MappingParametersTest.java | 31 +++++++++++++++++++ 7 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index 1c4147db9..4f0d5aaa9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -34,6 +34,8 @@ * @author Peter-Josef Meisch * @author Xiao Yu * @author Aleksei Arsenev + * @author Brian Kimmig + * @author Morgan Lutz */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @@ -185,4 +187,11 @@ * @since 4.1 */ NullValueType nullValueType() default NullValueType.String; + + /** + * to be used in combination with {@link FieldType#Dense_Vector} + * + * @since 4.2 + */ + int dims() default -1; } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java b/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java index 5ef154e34..94233e190 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java @@ -22,6 +22,8 @@ * @author Zeng Zetang * @author Peter-Josef Meisch * @author Aleksei Arsenev + * @author Brian Kimmig + * @author Morgan Lutz */ public enum FieldType { Auto, // @@ -57,5 +59,7 @@ public enum FieldType { /** @since 4.1 */ Rank_Features, // /** since 4.2 */ - Wildcard // + Wildcard, // + /** @since 4.2 */ + Dense_Vector // } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java index 4bcb071c9..c2d8b803f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java @@ -27,6 +27,8 @@ * @author Xiao Yu * @author Peter-Josef Meisch * @author Aleksei Arsenev + * @author Brian Kimmig + * @author Morgan Lutz */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) @@ -140,4 +142,11 @@ * @since 4.1 */ NullValueType nullValueType() default NullValueType.String; + + /** + * to be used in combination with {@link FieldType#Dense_Vector} + * + * @since 4.2 + */ + int dims() default -1; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java index a6294c0fe..6d7d7104a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java @@ -39,6 +39,8 @@ * * @author Peter-Josef Meisch * @author Aleksei Arsenev + * @author Brian Kimmig + * @author Morgan Lutz * @since 4.0 */ public final class MappingParameters { @@ -65,6 +67,7 @@ public final class MappingParameters { static final String FIELD_PARAM_NULL_VALUE = "null_value"; static final String FIELD_PARAM_POSITION_INCREMENT_GAP = "position_increment_gap"; static final String FIELD_PARAM_POSITIVE_SCORE_IMPACT = "positive_score_impact"; + static final String FIELD_PARAM_DIMS = "dims"; static final String FIELD_PARAM_SCALING_FACTOR = "scaling_factor"; static final String FIELD_PARAM_SEARCH_ANALYZER = "search_analyzer"; static final String FIELD_PARAM_STORE = "store"; @@ -94,6 +97,7 @@ public final class MappingParameters { private final NullValueType nullValueType; private final Integer positionIncrementGap; private final boolean positiveScoreImpact; + private final Integer dims; private final String searchAnalyzer; private final double scalingFactor; private final Similarity similarity; @@ -153,6 +157,10 @@ private MappingParameters(Field field) { || (maxShingleSize >= 2 && maxShingleSize <= 4), // "maxShingleSize must be in inclusive range from 2 to 4 for field type search_as_you_type"); positiveScoreImpact = field.positiveScoreImpact(); + dims = field.dims(); + if (type == FieldType.Dense_Vector) { + Assert.isTrue(dims >= 1 && dims <= 2048, "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048."); + } Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object"); enabled = field.enabled(); eagerGlobalOrdinals = field.eagerGlobalOrdinals(); @@ -191,6 +199,10 @@ private MappingParameters(InnerField field) { || (maxShingleSize >= 2 && maxShingleSize <= 4), // "maxShingleSize must be in inclusive range from 2 to 4 for field type search_as_you_type"); positiveScoreImpact = field.positiveScoreImpact(); + dims = field.dims(); + if (type == FieldType.Dense_Vector) { + Assert.isTrue(dims >= 1 && dims <= 2048, "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048."); + } enabled = true; eagerGlobalOrdinals = field.eagerGlobalOrdinals(); } @@ -323,6 +335,10 @@ public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException builder.field(FIELD_PARAM_POSITIVE_SCORE_IMPACT, positiveScoreImpact); } + if (type == FieldType.Dense_Vector) { + builder.field(FIELD_PARAM_DIMS, dims); + } + if (!enabled) { builder.field(FIELD_PARAM_ENABLED, enabled); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index 8d4fcc471..9abd53802 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -82,6 +82,8 @@ * @author Peter-Josef Meisch * @author Xiao Yu * @author Roman Puchkovskiy + * @author Brian Kimmig + * @author Morgan Lutz */ @SpringIntegrationTest @ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) @@ -271,6 +273,16 @@ void shouldWriteWildcardFieldMapping() { indexOps.putMapping(); } + @Test // #1700 + @DisplayName("should write dense_vector field mapping") + void shouldWriteDenseVectorFieldMapping() { + + IndexOperations indexOps = operations.indexOps(DenseVectorEntity.class); + indexOps.create(); + indexOps.putMapping(); + indexOps.delete(); + } + @Test // #1370 @DisplayName("should write mapping for disabled entity") void shouldWriteMappingForDisabledEntity() { @@ -657,4 +669,11 @@ static class DisabledMappingProperty { @Field(type = Text) private String text; @Mapping(enabled = false) @Field(type = Object) private Object object; } + + @Data + @Document(indexName = "densevector-test") + static class DenseVectorEntity { + @Id private String id; + @Field(type = Dense_Vector, dims = 3) private float[] dense_vector; + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 14764c460..347344db3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -69,6 +69,8 @@ * @author Peter-Josef Meisch * @author Xiao Yu * @author Roman Puchkovskiy + * @author Brian Kimmig + * @author Morgan Lutz */ public class MappingBuilderUnitTests extends MappingContextBaseTests { @@ -506,6 +508,23 @@ void shouldWriteRankFeatureProperties() throws JSONException { assertEquals(expected, mapping, false); } + @Test // #1700 + @DisplayName("should write dense_vector properties") + void shouldWriteDenseVectorProperties() throws JSONException { + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"my_vector\": {\n" + // + " \"type\": \"dense_vector\",\n" + // + " \"dims\": 16\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DenseVectorEntity.class); + + assertEquals(expected, mapping, false); + } + @Test // #1370 @DisplayName("should not write mapping when enabled is false on entity") void shouldNotWriteMappingWhenEnabledIsFalseOnEntity() throws JSONException { @@ -963,6 +982,13 @@ static class RankFeatureEntity { @Field(type = FieldType.Rank_Features) private Map topics; } + @Data + static class DenseVectorEntity { + + @Id private String id; + @Field(type = FieldType.Dense_Vector, dims = 16) private float[] my_vector; + } + @Data @Mapping(enabled = false) static class DisabledMappingEntity { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java index d1fbac06e..f99b1d2e0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java @@ -1,6 +1,7 @@ package org.springframework.data.elasticsearch.core.index; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.elasticsearch.annotations.FieldType.Dense_Vector; import static org.springframework.data.elasticsearch.annotations.FieldType.Object; import java.lang.annotation.Annotation; @@ -17,6 +18,8 @@ /** * @author Peter-Josef Meisch + * @author Brian Kimmig + * @author Morgan Lutz */ public class MappingParametersTest extends MappingContextBaseTests { @@ -66,6 +69,26 @@ void shouldAllowEnabledFalseOnlyOnObjectFields() { assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class); } + @Test // #1700 + @DisplayName("should not allow dims length greater than 2048 for dense_vector type") + void shouldNotAllowDimsLengthGreaterThan2048ForDenseVectorType() { + ElasticsearchPersistentEntity failEntity = elasticsearchConverter.get().getMappingContext() + .getRequiredPersistentEntity(DenseVectorInvalidDimsClass.class); + Annotation annotation = failEntity.getRequiredPersistentProperty("dense_vector").findAnnotation(Field.class); + + assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class); + } + + @Test // #1700 + @DisplayName("should require dims parameter for dense_vector type") + void shouldRequireDimsParameterForDenseVectorType() { + ElasticsearchPersistentEntity failEntity = elasticsearchConverter.get().getMappingContext() + .getRequiredPersistentEntity(DenseVectorMissingDimsClass.class); + Annotation annotation = failEntity.getRequiredPersistentProperty("dense_vector").findAnnotation(Field.class); + + assertThatThrownBy(() -> MappingParameters.from(annotation)).isInstanceOf(IllegalArgumentException.class); + } + static class AnnotatedClass { @Nullable @Field private String field; @Nullable @MultiField(mainField = @Field, @@ -79,4 +102,12 @@ static class AnnotatedClass { static class InvalidEnabledFieldClass { @Nullable @Field(type = FieldType.Text, enabled = false) private String disabledObject; } + + static class DenseVectorInvalidDimsClass { + @Field(type = Dense_Vector, dims = 2049) private float[] dense_vector; + } + + static class DenseVectorMissingDimsClass { + @Field(type = Dense_Vector) private float[] dense_vector; + } } From dd3d01eab6f62f7f6c21ec4abe1612c0b8a157a9 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 26 Feb 2021 08:28:45 +0100 Subject: [PATCH 002/776] Polishing --- .../data/elasticsearch/annotations/Field.java | 6 +++--- .../elasticsearch/annotations/FieldType.java | 4 ++-- .../core/index/MappingBuilderUnitTests.java | 16 +++++++--------- .../core/index/MappingParametersTest.java | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index 4f0d5aaa9..b97b243ad 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -159,14 +159,14 @@ /** * if true, the field will be stored in Elasticsearch even if it has a null value - * + * * @since 4.1 */ boolean storeNullValue() default false; /** * to be used in combination with {@link FieldType#Rank_Feature} - * + * * @since 4.1 */ boolean positiveScoreImpact() default true; @@ -189,7 +189,7 @@ NullValueType nullValueType() default NullValueType.String; /** - * to be used in combination with {@link FieldType#Dense_Vector} + * to be used in combination with {@link FieldType#Dense_Vector} * * @since 4.2 */ diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java b/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java index 94233e190..80c283841 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/FieldType.java @@ -60,6 +60,6 @@ public enum FieldType { Rank_Features, // /** since 4.2 */ Wildcard, // - /** @since 4.2 */ - Dense_Vector // + /** @since 4.2 */ + Dense_Vector // } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 347344db3..76b11d8aa 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -31,6 +31,7 @@ import java.lang.Boolean; import java.lang.Double; import java.lang.Integer; +import java.lang.Object; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Collection; @@ -508,7 +509,7 @@ void shouldWriteRankFeatureProperties() throws JSONException { assertEquals(expected, mapping, false); } - @Test // #1700 + @Test // #1700 @DisplayName("should write dense_vector properties") void shouldWriteDenseVectorProperties() throws JSONException { String expected = "{\n" + // @@ -563,9 +564,8 @@ void shouldWriteDisabledPropertyMapping() throws JSONException { @DisplayName("should only allow disabled properties on type object") void shouldOnlyAllowDisabledPropertiesOnTypeObject() { - assertThatThrownBy(() -> - getMappingBuilder().buildPropertyMapping(InvalidDisabledMappingProperty.class) - ).isInstanceOf(MappingException.class); + assertThatThrownBy(() -> getMappingBuilder().buildPropertyMapping(InvalidDisabledMappingProperty.class)) + .isInstanceOf(MappingException.class); } @Setter @@ -982,7 +982,7 @@ static class RankFeatureEntity { @Field(type = FieldType.Rank_Features) private Map topics; } - @Data + @Data static class DenseVectorEntity { @Id private String id; @@ -999,15 +999,13 @@ static class DisabledMappingEntity { @Data static class InvalidDisabledMappingProperty { @Id private String id; - @Mapping(enabled = false) - @Field(type = Text) private String text; + @Mapping(enabled = false) @Field(type = Text) private String text; } @Data static class DisabledMappingProperty { @Id private String id; @Field(type = Text) private String text; - @Mapping(enabled = false) - @Field(type = Object) private Object object; + @Mapping(enabled = false) @Field(type = Object) private Object object; } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java index f99b1d2e0..29c329411 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingParametersTest.java @@ -1,7 +1,7 @@ package org.springframework.data.elasticsearch.core.index; import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.elasticsearch.annotations.FieldType.Dense_Vector; +import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Object; import java.lang.annotation.Annotation; From f08c34ec5da7df71d7d3540c5b1f70e4f911de8e Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 27 Feb 2021 18:16:54 +0100 Subject: [PATCH 003/776] Improve multiget return. Original Pull Request #1710 Closes #1678 --- ...elasticsearch-migration-guide-4.1-4.2.adoc | 7 + .../DefaultReactiveElasticsearchClient.java | 11 +- .../reactive/ReactiveElasticsearchClient.java | 175 ++++++------ .../core/AbstractElasticsearchTemplate.java | 2 +- .../core/DocumentOperations.java | 8 +- .../core/ElasticsearchRestTemplate.java | 7 +- .../core/ElasticsearchTemplate.java | 10 +- .../data/elasticsearch/core/MultiGetItem.java | 96 +++++++ .../core/ReactiveDocumentOperations.java | 8 +- .../core/ReactiveElasticsearchTemplate.java | 13 +- .../elasticsearch/core/ResponseConverter.java | 136 +++++---- .../core/document/DocumentAdapters.java | 22 +- .../SimpleElasticsearchRepository.java | 12 +- ...SimpleReactiveElasticsearchRepository.java | 6 +- ...veElasticsearchClientIntegrationTests.java | 270 ++++++++++-------- .../ReactiveElasticsearchClientUnitTests.java | 45 +-- ...actElasticsearchTemplateCallbackTests.java | 8 +- .../core/ElasticsearchTemplateTests.java | 42 ++- ...iveElasticsearchTemplateCallbackTests.java | 24 +- ...ElasticsearchTemplateIntegrationTests.java | 15 +- .../core/SourceFilterIntegrationTests.java | 18 +- 21 files changed, 574 insertions(+), 361 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc index 756b39ca1..58bb48f10 100644 --- a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc @@ -53,3 +53,10 @@ Previously the reactive code initialized this to `IMMEDIATE`, now reactive and n ==== delete methods that take a Query The reactive methods previously returned a `Mono` with the number of deleted documents, the non reactive versions were void. They now return a `Mono` which contains much more detailed information about the deleted documents and errors that might have occurred. + +==== multiget methods + +The implementations of _multiget_ previousl only returned the found entities in a `List` for non-reactive implementations and in a `Flux` for reactive implementations. If the request contained ids that were not found, the information that these are missing was not available. The user needed to compare the returned ids to the requested ones to find +which ones were missing. + +Now the `multiget` methods return a `MultiGetItem` for every requested id. This contains information about failures (like non existing indices) and the information if the item existed (then it is contained in the `MultiGetItem) or not. 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 e98d9647a..ce3ab80c7 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 @@ -22,6 +22,7 @@ import io.netty.handler.ssl.JdkSslContext; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; +import org.elasticsearch.action.get.MultiGetItemResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; @@ -330,18 +331,12 @@ public Mono get(HttpHeaders headers, GetRequest getRequest) { .next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#multiGet(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.MultiGetRequest) - */ @Override - public Flux multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) { + public Flux multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest) { return sendRequest(multiGetRequest, requestCreator.multiGet(), MultiGetResponse.class, headers) .map(MultiGetResponse::getResponses) // - .flatMap(Flux::fromArray) // - .filter(it -> !it.isFailed() && it.getResponse().isExists()) // - .map(it -> DefaultReactiveElasticsearchClient.getResponseToGetResult(it.getResponse())); + .flatMap(Flux::fromArray); // } /* 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 1d1cca41d..dcc430758 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClient.java @@ -15,11 +15,6 @@ */ package org.springframework.data.elasticsearch.client.reactive; -import org.elasticsearch.client.indices.CreateIndexRequest; -import org.elasticsearch.client.indices.GetIndexRequest; -import org.elasticsearch.client.indices.GetMappingsRequest; -import org.elasticsearch.client.indices.GetMappingsResponse; -import org.elasticsearch.client.indices.PutMappingRequest; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -42,6 +37,7 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; @@ -51,13 +47,7 @@ import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.GetAliasesResponse; -import org.elasticsearch.client.indices.GetFieldMappingsRequest; -import org.elasticsearch.client.indices.GetFieldMappingsResponse; -import org.elasticsearch.client.indices.GetIndexResponse; -import org.elasticsearch.client.indices.GetIndexTemplatesRequest; -import org.elasticsearch.client.indices.GetIndexTemplatesResponse; -import org.elasticsearch.client.indices.IndexTemplatesExistRequest; -import org.elasticsearch.client.indices.PutIndexTemplateRequest; +import org.elasticsearch.client.indices.*; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.reindex.BulkByScrollResponse; import org.elasticsearch.index.reindex.DeleteByQueryRequest; @@ -167,9 +157,9 @@ default Mono get(GetRequest getRequest) { * @param consumer never {@literal null}. * @see Multi Get API on * elastic.co - * @return the {@link Flux} emitting the {@link GetResult result}. + * @return the {@link Flux} emitting the {@link MultiGetItemResponse result}. */ - default Flux multiGet(Consumer consumer) { + default Flux multiGet(Consumer consumer) { MultiGetRequest request = new MultiGetRequest(); consumer.accept(request); @@ -183,9 +173,9 @@ default Flux multiGet(Consumer consumer) { * @param multiGetRequest must not be {@literal null}. * @see Multi Get API on * elastic.co - * @return the {@link Flux} emitting the {@link GetResult result}. + * @return the {@link Flux} emitting the {@link MultiGetItemResponse result}. */ - default Flux multiGet(MultiGetRequest multiGetRequest) { + default Flux multiGet(MultiGetRequest multiGetRequest) { return multiGet(HttpHeaders.EMPTY, multiGetRequest); } @@ -197,9 +187,9 @@ default Flux multiGet(MultiGetRequest multiGetRequest) { * @param multiGetRequest must not be {@literal null}. * @see Multi Get API on * elastic.co - * @return the {@link Flux} emitting the {@link GetResult result}. + * @return the {@link Flux} emitting the {@link MultiGetItemResponse result}. */ - Flux multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest); + Flux multiGet(HttpHeaders headers, MultiGetRequest multiGetRequest); /** * Checks for the existence of a document. Emits {@literal true} if it exists, {@literal false} otherwise. @@ -748,31 +738,32 @@ default boolean isOk() { interface Indices { /** - * Execute the given {@link org.elasticsearch.action.admin.indices.get.GetIndexRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.get.GetIndexRequest} against the + * {@literal indices} API. * * @param consumer never {@literal null}. * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. * @see Indices * Exists API on elastic.co - * @deprecated since 4.2 + * @deprecated since 4.2 */ @Deprecated default Mono existsIndex(Consumer consumer) { - org.elasticsearch.action.admin.indices.get.GetIndexRequest request = - new org.elasticsearch.action.admin.indices.get.GetIndexRequest(); + org.elasticsearch.action.admin.indices.get.GetIndexRequest request = new org.elasticsearch.action.admin.indices.get.GetIndexRequest(); consumer.accept(request); return existsIndex(request); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.get.GetIndexRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.get.GetIndexRequest} against the + * {@literal indices} API. * * @param getIndexRequest must not be {@literal null}. * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. * @see Indices * Exists API on elastic.co - * @deprecated since 4.2, use {@link #existsIndex(GetIndexRequest)} + * @deprecated since 4.2, use {@link #existsIndex(GetIndexRequest)} */ @Deprecated default Mono existsIndex(org.elasticsearch.action.admin.indices.get.GetIndexRequest getIndexRequest) { @@ -780,42 +771,44 @@ default Mono existsIndex(org.elasticsearch.action.admin.indices.get.Get } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.get.GetIndexRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.get.GetIndexRequest} against the + * {@literal indices} API. * * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. * @param getIndexRequest must not be {@literal null}. * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. * @see Indices * Exists API on elastic.co - * @deprecated since 4.2, use {@link #existsIndex(HttpHeaders, GetIndexRequest)} + * @deprecated since 4.2, use {@link #existsIndex(HttpHeaders, GetIndexRequest)} */ @Deprecated - Mono existsIndex(HttpHeaders headers, org.elasticsearch.action.admin.indices.get.GetIndexRequest getIndexRequest); - - /** - * Execute the given {@link GetIndexRequest} against the {@literal indices} API. - * - * @param getIndexRequest must not be {@literal null}. - * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. - * @see Indices - * Exists API on elastic.co - * @since 4.2 - */ - default Mono existsIndex(GetIndexRequest getIndexRequest) { - return existsIndex(HttpHeaders.EMPTY, getIndexRequest); - } - - /** - * Execute the given {@link GetIndexRequest} against the {@literal indices} API. - * - * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. - * @param getIndexRequest must not be {@literal null}. - * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. - * @see Indices - * Exists API on elastic.co - * @since 4.2 - */ - Mono existsIndex(HttpHeaders headers, GetIndexRequest getIndexRequest); + Mono existsIndex(HttpHeaders headers, + org.elasticsearch.action.admin.indices.get.GetIndexRequest getIndexRequest); + + /** + * Execute the given {@link GetIndexRequest} against the {@literal indices} API. + * + * @param getIndexRequest must not be {@literal null}. + * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. + * @see Indices + * Exists API on elastic.co + * @since 4.2 + */ + default Mono existsIndex(GetIndexRequest getIndexRequest) { + return existsIndex(HttpHeaders.EMPTY, getIndexRequest); + } + + /** + * Execute the given {@link GetIndexRequest} against the {@literal indices} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param getIndexRequest must not be {@literal null}. + * @return the {@link Mono} emitting {@literal true} if the index exists, {@literal false} otherwise. + * @see Indices + * Exists API on elastic.co + * @since 4.2 + */ + Mono existsIndex(HttpHeaders headers, GetIndexRequest getIndexRequest); /** * Execute the given {@link DeleteIndexRequest} against the {@literal indices} API. @@ -859,7 +852,8 @@ default Mono deleteIndex(DeleteIndexRequest deleteIndexRequest) { Mono deleteIndex(HttpHeaders headers, DeleteIndexRequest deleteIndexRequest); /** - * Execute the given {@link org.elasticsearch.action.admin.indices.create.CreateIndexRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.create.CreateIndexRequest} against the + * {@literal indices} API. * * @param consumer never {@literal null}. * @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if @@ -869,16 +863,17 @@ default Mono deleteIndex(DeleteIndexRequest deleteIndexRequest) { * @deprecated since 4.2 */ @Deprecated - default Mono createIndex(Consumer consumer) { + default Mono createIndex( + Consumer consumer) { - org.elasticsearch.action.admin.indices.create.CreateIndexRequest request = - new org.elasticsearch.action.admin.indices.create.CreateIndexRequest(); + org.elasticsearch.action.admin.indices.create.CreateIndexRequest request = new org.elasticsearch.action.admin.indices.create.CreateIndexRequest(); consumer.accept(request); return createIndex(request); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.create.CreateIndexRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.create.CreateIndexRequest} against the + * {@literal indices} API. * * @param createIndexRequest must not be {@literal null}. * @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if @@ -888,12 +883,14 @@ default Mono createIndex(Consumer createIndex(org.elasticsearch.action.admin.indices.create.CreateIndexRequest createIndexRequest) { + default Mono createIndex( + org.elasticsearch.action.admin.indices.create.CreateIndexRequest createIndexRequest) { return createIndex(HttpHeaders.EMPTY, createIndexRequest); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.create.CreateIndexRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.create.CreateIndexRequest} against the + * {@literal indices} API. * * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. * @param createIndexRequest must not be {@literal null}. @@ -904,7 +901,8 @@ default Mono createIndex(org.elasticsearch.action.admin.indices.create. * @deprecated since 4.2, use {@link #createIndex(HttpHeaders, CreateIndexRequest)} */ @Deprecated - Mono createIndex(HttpHeaders headers, org.elasticsearch.action.admin.indices.create.CreateIndexRequest createIndexRequest); + Mono createIndex(HttpHeaders headers, + org.elasticsearch.action.admin.indices.create.CreateIndexRequest createIndexRequest); /** * Execute the given {@link CreateIndexRequest} against the {@literal indices} API. @@ -1057,7 +1055,8 @@ default Mono refreshIndex(RefreshRequest refreshRequest) { Mono refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest); /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the + * {@literal indices} API. * * @param consumer never {@literal null}. * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index @@ -1067,12 +1066,14 @@ default Mono refreshIndex(RefreshRequest refreshRequest) { * @deprecated since 4.1, use {@link #putMapping(Consumer)} */ @Deprecated - default Mono updateMapping(Consumer consumer) { + default Mono updateMapping( + Consumer consumer) { return putMapping(consumer); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the + * {@literal indices} API. * * @param putMappingRequest must not be {@literal null}. * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index @@ -1082,12 +1083,14 @@ default Mono updateMapping(Consumer updateMapping(org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { + default Mono updateMapping( + org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { return putMapping(putMappingRequest); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the + * {@literal indices} API. * * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. * @param putMappingRequest must not be {@literal null}. @@ -1098,12 +1101,14 @@ default Mono updateMapping(org.elasticsearch.action.admin.indices.mappi * @deprecated since 4.1, use {@link #putMapping(HttpHeaders, PutMappingRequest)} */ @Deprecated - default Mono updateMapping(HttpHeaders headers, org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { + default Mono updateMapping(HttpHeaders headers, + org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { return putMapping(headers, putMappingRequest); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the + * {@literal indices} API. * * @param consumer never {@literal null}. * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index @@ -1113,16 +1118,17 @@ default Mono updateMapping(HttpHeaders headers, org.elasticsearch.actio * @deprecated since 4.2 */ @Deprecated - default Mono putMapping(Consumer consumer) { + default Mono putMapping( + Consumer consumer) { - org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest request = - new org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest(); + org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest request = new org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest(); consumer.accept(request); return putMapping(request); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the + * {@literal indices} API. * * @param putMappingRequest must not be {@literal null}. * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index @@ -1132,12 +1138,14 @@ default Mono putMapping(Consumer putMapping(org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { + default Mono putMapping( + org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { return putMapping(HttpHeaders.EMPTY, putMappingRequest); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the + * {@literal indices} API. * * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. * @param putMappingRequest must not be {@literal null}. @@ -1148,7 +1156,8 @@ default Mono putMapping(org.elasticsearch.action.admin.indices.mapping. * @deprecated since 4.2, use {@link #putMapping(HttpHeaders, PutMappingRequest)} */ @Deprecated - Mono putMapping(HttpHeaders headers, org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest); + Mono putMapping(HttpHeaders headers, + org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest); /** * Execute the given {@link PutMappingRequest} against the {@literal indices} API. @@ -1263,7 +1272,8 @@ default Mono getSettings(GetSettingsRequest getSettingsRequ Mono getSettings(HttpHeaders headers, GetSettingsRequest getSettingsRequest); /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest} against the + * {@literal indices} API. * * @param consumer never {@literal null}. * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index @@ -1275,16 +1285,16 @@ default Mono getSettings(GetSettingsRequest getSettingsRequ */ @Deprecated default Mono getMapping( - Consumer consumer) { + Consumer consumer) { - org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest request = - new org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest(); + org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest request = new org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest(); consumer.accept(request); return getMapping(request); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest} against the + * {@literal indices} API. * * @param getMappingsRequest must not be {@literal null}. * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index @@ -1296,12 +1306,13 @@ default Mono getMapping( - org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest) { + org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest) { return getMapping(HttpHeaders.EMPTY, getMappingsRequest); } /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest} against the {@literal indices} API. + * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest} against the + * {@literal indices} API. * * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. * @param getMappingsRequest must not be {@literal null}. @@ -1314,7 +1325,7 @@ default Mono getMapping(HttpHeaders headers, - org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest); + org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest); /** * Execute the given {@link GetMappingsRequest} against the {@literal indices} API. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index c802c044a..4caf7f13b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -250,7 +250,7 @@ public T get(String id, Class clazz) { } @Override - public List multiGet(Query query, Class clazz) { + public List> multiGet(Query query, Class clazz) { return multiGet(query, clazz, getIndexCoordinatesFor(clazz)); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java index 91412c611..c5ddbd597 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java @@ -120,10 +120,10 @@ public interface DocumentOperations { * * @param query the query defining the ids of the objects to get * @param clazz the type of the object to be returned - * @return list of objects, contains null values for ids that are not found + * @return list of {@link MultiGetItem}s * @since 4.1 */ - List multiGet(Query query, Class clazz); + List> multiGet(Query query, Class clazz); /** * Execute a multiGet against elasticsearch for the given ids. @@ -131,9 +131,9 @@ public interface DocumentOperations { * @param query the query defining the ids of the objects to get * @param clazz the type of the object to be returned * @param index the index(es) from which the objects are read. - * @return list of objects, contains null values for ids that are not found + * @return list of {@link MultiGetItem}s */ - List multiGet(Query query, Class clazz, IndexCoordinates index); + List> multiGet(Query query, Class clazz, IndexCoordinates index); /** * Check if an entity with given {@literal id} exists. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 2b2583f87..18360dfc6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -168,7 +168,7 @@ public T get(String id, Class clazz, IndexCoordinates index) { } @Override - public List multiGet(Query query, Class clazz, IndexCoordinates index) { + public List> multiGet(Query query, Class clazz, IndexCoordinates index) { Assert.notNull(index, "index must not be null"); Assert.notEmpty(query.getIds(), "No Id defined for Query"); @@ -177,7 +177,10 @@ public List multiGet(Query query, Class clazz, IndexCoordinates index) MultiGetResponse result = execute(client -> client.mget(request, RequestOptions.DEFAULT)); DocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); - return DocumentAdapters.from(result).stream().map(callback::doWith).collect(Collectors.toList()); + return DocumentAdapters.from(result).stream() // + .map(multiGetItem -> MultiGetItem.of( // + multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()), multiGetItem.getFailure())) // + .collect(Collectors.toList()); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 0123a5fac..d0d4852a5 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -42,7 +42,6 @@ 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.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -190,7 +189,7 @@ public T get(String id, Class clazz, IndexCoordinates index) { } @Override - public List multiGet(Query query, Class clazz, IndexCoordinates index) { + public List> multiGet(Query query, Class clazz, IndexCoordinates index) { Assert.notNull(index, "index must not be null"); Assert.notEmpty(query.getIds(), "No Ids defined for Query"); @@ -198,8 +197,11 @@ public List multiGet(Query query, Class clazz, IndexCoordinates index) MultiGetRequestBuilder builder = requestFactory.multiGetRequestBuilder(client, query, clazz, index); DocumentCallback callback = new ReadDocumentCallback<>(elasticsearchConverter, clazz, index); - List documents = DocumentAdapters.from(builder.execute().actionGet()); - return documents.stream().map(callback::doWith).collect(Collectors.toList()); + + return DocumentAdapters.from(builder.execute().actionGet()).stream() // + .map(multiGetItem -> MultiGetItem.of(multiGetItem.isFailed() ? null : callback.doWith(multiGetItem.getItem()), + multiGetItem.getFailure())) + .collect(Collectors.toList()); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java b/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java new file mode 100644 index 000000000..09bf3c933 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/MultiGetItem.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import org.springframework.lang.Nullable; + +/** + * Response object for items returned from multiget requests, encapsulating the returned data and potential error + * information. + * + * @param the entity type + * @author Peter-Josef Meisch + * @since 4.2 + */ +public class MultiGetItem { + @Nullable private final T item; + @Nullable private final Failure failure; + + private MultiGetItem(@Nullable T item, @Nullable Failure failure) { + this.item = item; + this.failure = failure; + } + + public static MultiGetItem of(@Nullable T item, @Nullable Failure failure) { + return new MultiGetItem<>(item, failure); + } + + public boolean hasItem() { + return item != null; + } + + @Nullable + public T getItem() { + return item; + } + + public boolean isFailed() { + return failure != null; + } + + @Nullable + public Failure getFailure() { + return failure; + } + + public static class Failure { + @Nullable private final String index; + @Nullable private final String type; + @Nullable private final String id; + @Nullable private final Exception exception; + + private Failure(@Nullable String index, @Nullable String type, @Nullable String id, @Nullable Exception exception) { + this.index = index; + this.type = type; + this.id = id; + this.exception = exception; + } + + public static Failure of(String index, String type, String id, Exception exception) { + return new Failure(index, type, id, exception); + } + + @Nullable + public String getIndex() { + return index; + } + + @Nullable + public String getType() { + return type; + } + + @Nullable + public String getId() { + return id; + } + + @Nullable + public Exception getException() { + return exception; + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java index c3917e3e4..566fee06f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveDocumentOperations.java @@ -147,10 +147,10 @@ default Flux saveAll(Iterable entities, IndexCoordinates index) { * * @param query the query defining the ids of the objects to get * @param clazz the type of the object to be returned, used to determine the index - * @return flux with list of nullable objects + * @return flux with list of {@link MultiGetItem}s that contain the entities * @since 4.1 */ - Flux multiGet(Query query, Class clazz); + Flux> multiGet(Query query, Class clazz); /** * Execute a multiGet against elasticsearch for the given ids. @@ -158,10 +158,10 @@ default Flux saveAll(Iterable entities, IndexCoordinates index) { * @param query the query defining the ids of the objects to get * @param clazz the type of the object to be returned * @param index the index(es) from which the objects are read. - * @return flux with list of nullable objects + * @return flux with list of {@link MultiGetItem}s that contain the entities * @since 4.0 */ - Flux multiGet(Query query, Class clazz, IndexCoordinates index); + Flux> multiGet(Query query, Class clazz, IndexCoordinates index); /** * Bulk update all objects. Will do update. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 7d2cf7b18..925f6c948 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -71,10 +71,10 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.BulkOptions; +import org.springframework.data.elasticsearch.core.query.ByQueryResponse; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; -import org.springframework.data.elasticsearch.core.query.ByQueryResponse; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver; @@ -298,12 +298,12 @@ private T updateIndexedObject(T entity, IndexedObjectInformation indexedObje } @Override - public Flux multiGet(Query query, Class clazz) { + public Flux> multiGet(Query query, Class clazz) { return multiGet(query, clazz, getIndexCoordinatesFor(clazz)); } @Override - public Flux multiGet(Query query, Class clazz, IndexCoordinates index) { + public Flux> multiGet(Query query, Class clazz, IndexCoordinates index) { Assert.notNull(index, "Index must not be null"); Assert.notNull(clazz, "Class must not be null"); @@ -314,7 +314,12 @@ public Flux multiGet(Query query, Class clazz, IndexCoordinates index) MultiGetRequest request = requestFactory.multiGetRequest(query, clazz, index); return Flux.from(execute(client -> client.multiGet(request))) // - .concatMap(result -> callback.toEntity(DocumentAdapters.from(result))); + .map(DocumentAdapters::from) // + .flatMap(multiGetItem -> multiGetItem.isFailed() // + ? Mono.just(MultiGetItem.of(null, multiGetItem.getFailure())) // + : callback.toEntity(multiGetItem.getItem()) + .map((T item) -> MultiGetItem.of(item, multiGetItem.getFailure())) // + ); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java index 0ca86324d..c2328aad2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java @@ -27,6 +27,8 @@ import java.util.stream.Collectors; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; +import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.client.indices.GetIndexResponse; import org.elasticsearch.client.indices.GetIndexTemplatesResponse; import org.elasticsearch.client.indices.IndexTemplateMetadata; @@ -213,67 +215,77 @@ private static List aliasDataFromIndexResponse( // endregion - //region templates - @Nullable - public static 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, ResponseConverter.toAliasData(aliasesResponse.get(key))); - } - - return 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 null; - } - //endregion - - //region settings - /** - * extract the index settings information for a given index - * - * @param response the Elasticsearch response - * @param indexName the index name - * @return settings as {@link Document} - */ - public static Document fromSettingsResponse(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 + // region templates + @Nullable + public static 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, ResponseConverter.toAliasData(aliasesResponse.get(key))); + } + + return 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 null; + } + // endregion + + // region settings + /** + * extract the index settings information for a given index + * + * @param response the Elasticsearch response + * @param indexName the index name + * @return settings as {@link Document} + */ + public static Document fromSettingsResponse(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 + + // region multiget + + @Nullable + public static MultiGetItem.Failure getFailure(MultiGetItemResponse itemResponse) { + + MultiGetResponse.Failure responseFailure = itemResponse.getFailure(); + return responseFailure != null ? MultiGetItem.Failure.of(responseFailure.getIndex(), responseFailure.getType(), + responseFailure.getId(), responseFailure.getFailure()) : null; + } + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index e42057fd3..ae4dc09fa 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.document.DocumentField; @@ -39,6 +40,8 @@ import org.elasticsearch.index.get.GetResult; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; +import org.springframework.data.elasticsearch.core.MultiGetItem; +import org.springframework.data.elasticsearch.core.ResponseConverter; import org.springframework.data.mapping.MappingException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -126,21 +129,32 @@ public static Document from(GetResult source) { } /** - * Creates a List of {@link Document}s from {@link MultiGetResponse}. + * Creates a List of {@link MultiGetItem}s from {@link MultiGetResponse}. * * @param source the source {@link MultiGetResponse}, not {@literal null}. * @return a list of Documents, contains null values for not found Documents. */ - public static List from(MultiGetResponse source) { + public static List> from(MultiGetResponse source) { Assert.notNull(source, "MultiGetResponse must not be null"); - // noinspection ReturnOfNull return Arrays.stream(source.getResponses()) // - .map(itemResponse -> itemResponse.isFailed() ? null : DocumentAdapters.from(itemResponse.getResponse())) // + .map(DocumentAdapters::from) // .collect(Collectors.toList()); } + /** + * Creates a {@link MultiGetItem} from a {@link MultiGetItemResponse}. + * + * @param itemResponse the response, must not be {@literal null} + * @return the MultiGetItem + */ + public static MultiGetItem from(MultiGetItemResponse itemResponse) { + + MultiGetItem.Failure failure = ResponseConverter.getFailure(itemResponse); + return MultiGetItem.of(itemResponse.isFailed() ? null : DocumentAdapters.from(itemResponse.getResponse()), failure); + } + /** * Create a {@link SearchDocument} from {@link SearchHit}. *

diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java index 80c61a48b..7e8a4c252 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java @@ -34,6 +34,7 @@ import org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; +import org.springframework.data.elasticsearch.core.MultiGetItem; import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHitSupport; @@ -154,13 +155,14 @@ public Iterable findAllById(Iterable ids) { return result; } - List multiGetEntities = execute(operations -> operations.multiGet(idQuery, entityClass, getIndexCoordinates())); + List> multiGetItems = execute( + operations -> operations.multiGet(idQuery, entityClass, getIndexCoordinates())); - if (multiGetEntities != null) { - multiGetEntities.forEach(entity -> { + if (multiGetItems != null) { + multiGetItems.forEach(multiGetItem -> { - if (entity != null) { - result.add(entity); + if (multiGetItem.hasItem()) { + result.add(multiGetItem.getItem()); } }); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java index f93693bea..c863333e4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java @@ -22,6 +22,7 @@ import org.reactivestreams.Publisher; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.elasticsearch.core.MultiGetItem; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; @@ -161,9 +162,10 @@ public Flux findAllById(Publisher idStream) { .collectList() // .map(ids -> new NativeSearchQueryBuilder().withIds(ids).build()) // .flatMapMany(query -> { - IndexCoordinates index = entityInformation.getIndexCoordinates(); - return operations.multiGet(query, entityInformation.getJavaType(), index); + return operations.multiGet(query, entityInformation.getJavaType(), index) // + .filter(MultiGetItem::hasItem) // + .map(MultiGetItem::getItem); }); } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java index fea6d3395..1bfa6053a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java @@ -18,10 +18,6 @@ import static org.assertj.core.api.Assertions.*; import lombok.SneakyThrows; -import org.elasticsearch.client.indices.CreateIndexRequest; -import org.elasticsearch.client.indices.GetIndexRequest; -import org.elasticsearch.client.indices.GetMappingsRequest; -import org.elasticsearch.common.xcontent.XContentType; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -45,8 +41,10 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.GetIndexRequest; +import org.elasticsearch.client.indices.GetMappingsRequest; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.reindex.BulkByScrollResponse; import org.elasticsearch.index.reindex.DeleteByQueryRequest; @@ -197,7 +195,7 @@ public void getShouldCompleteForNonExistingDocuments() { .verifyComplete(); } - @Test // DATAES-488 + @Test // DATAES-488, #1678 public void multiGetShouldReturnAllDocumentsFromSameCollection() { String id1 = addSourceDocument().to(INDEX_I); @@ -209,12 +207,18 @@ public void multiGetShouldReturnAllDocumentsFromSameCollection() { client.multiGet(request) // .as(StepVerifier::create) // - .consumeNextWith(it -> assertThat(it.getId()).isEqualTo(id1)) // - .consumeNextWith(it -> assertThat(it.getId()).isEqualTo(id2)) // + .consumeNextWith(it -> { + assertThat(it.getResponse().isExists()).isTrue(); // + assertThat(it.getId()).isEqualTo(id1); // + }) // + .consumeNextWith(it -> { + assertThat(it.getResponse().isExists()).isTrue(); // + assertThat(it.getId()).isEqualTo(id2); // + }) // .verifyComplete(); } - @Test // DATAES-488 + @Test // DATAES-488, #1678 public void multiGetShouldReturnAllExistingDocumentsFromSameCollection() { String id1 = addSourceDocument().to(INDEX_I); @@ -226,12 +230,20 @@ public void multiGetShouldReturnAllExistingDocumentsFromSameCollection() { client.multiGet(request) // .as(StepVerifier::create) // - .consumeNextWith(it -> assertThat(it.getId()).isEqualTo(id1)) // + .consumeNextWith(it -> { + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getResponse().isExists()).isTrue(); // + assertThat(it.getId()).isEqualTo(id1); // + }) // + .consumeNextWith(it -> { + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getResponse().isExists()).isFalse(); // + }) // .verifyComplete(); } - @Test // DATAES-488 - public void multiGetShouldSkipNonExistingDocuments() { + @Test // DATAES-488, #1678 + public void multiGetShouldNotSkipNonExistingDocuments() { String id1 = addSourceDocument().to(INDEX_I); String id2 = addSourceDocument().to(INDEX_I); @@ -242,9 +254,21 @@ public void multiGetShouldSkipNonExistingDocuments() { .add(INDEX_I, id2); // client.multiGet(request) // - .map(GetResult::getId) // .as(StepVerifier::create) // - .expectNext(id1, id2) // + .consumeNextWith(it -> { + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getResponse().isExists()).isTrue(); // + assertThat(it.getId()).isEqualTo(id1); // + }) // + .consumeNextWith(it -> { + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getResponse().isExists()).isFalse(); + }) // + .consumeNextWith(it -> { + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getResponse().isExists()).isTrue(); // + assertThat(it.getId()).isEqualTo(id2); // + }) // .verifyComplete(); } @@ -257,10 +281,12 @@ public void multiGetShouldCompleteIfNothingFound() { client.multiGet(new MultiGetRequest() // .add(INDEX_II, id1).add(INDEX_II, id2)) // .as(StepVerifier::create) // + .consumeNextWith(it -> assertThat(it.isFailed()).isTrue()) // + .consumeNextWith(it -> assertThat(it.isFailed()).isTrue()) // .verifyComplete(); } - @Test // DATAES-488 + @Test // DATAES-488, #1678 public void multiGetShouldReturnAllExistingDocumentsFromDifferentCollection() { String id1 = addSourceDocument().to(INDEX_I); @@ -271,10 +297,16 @@ public void multiGetShouldReturnAllExistingDocumentsFromDifferentCollection() { .add(INDEX_II, id2); client.multiGet(request) // - .map(GetResult::getId) // .as(StepVerifier::create) // - .expectNext(id1, id2) // - .verifyComplete(); + .consumeNextWith(it -> { // + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getId()).isEqualTo(id1); // + }) // + .consumeNextWith(it -> { // + assertThat(it.isFailed()).isFalse(); // + assertThat(it.getId()).isEqualTo(id2); // + }) // + .verifyComplete(); // } @Test // DATAES-488 @@ -587,27 +619,27 @@ public void indexExistsShouldReturnFalseIfNot() { .verifyComplete(); } - @Test // #1658 - public void indexExistsShouldReturnTrueIfExists() { + @Test // #1658 + public void indexExistsShouldReturnTrueIfExists() { - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - client.indices().existsIndex(new GetIndexRequest(INDEX_I)) // - .as(StepVerifier::create) // - .expectNext(true) // - .verifyComplete(); - } + client.indices().existsIndex(new GetIndexRequest(INDEX_I)) // + .as(StepVerifier::create) // + .expectNext(true) // + .verifyComplete(); + } - @Test // #1658 - public void indexExistsShouldReturnFalseIfNotExists() { + @Test // #1658 + public void indexExistsShouldReturnFalseIfNotExists() { - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - client.indices().existsIndex(new GetIndexRequest(INDEX_II)) // - .as(StepVerifier::create) // - .expectNext(false) // - .verifyComplete(); - } + client.indices().existsIndex(new GetIndexRequest(INDEX_II)) // + .as(StepVerifier::create) // + .expectNext(false) // + .verifyComplete(); + } @Test // DATAES-569, DATAES-678 public void createIndex() { @@ -633,54 +665,47 @@ public void createExistingIndexErrors() { .verifyError(ElasticsearchStatusException.class); } - @Test // #1658 - public void createIndex_() { + @Test // #1658 + public void createIndex_() { - client.indices().createIndex(new CreateIndexRequest(INDEX_I)) - .as(StepVerifier::create) - .expectNext(true) - .verifyComplete(); - - operations.indexOps(IndexCoordinates.of(INDEX_I)).exists() // - .as(StepVerifier::create) // - .expectNext(true) // - .verifyComplete(); - } + client.indices().createIndex(new CreateIndexRequest(INDEX_I)).as(StepVerifier::create).expectNext(true) + .verifyComplete(); - @Test // #1658 - public void createExistingIndexErrors_() { + operations.indexOps(IndexCoordinates.of(INDEX_I)).exists() // + .as(StepVerifier::create) // + .expectNext(true) // + .verifyComplete(); + } - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + @Test // #1658 + public void createExistingIndexErrors_() { - client.indices().createIndex(new CreateIndexRequest(INDEX_I)) // - .as(StepVerifier::create) // - .verifyError(ElasticsearchStatusException.class); - } + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - @Test // #1658 - public void getIndex() { + client.indices().createIndex(new CreateIndexRequest(INDEX_I)) // + .as(StepVerifier::create) // + .verifyError(ElasticsearchStatusException.class); + } - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + @Test // #1658 + public void getIndex() { - client.indices().getIndex(new GetIndexRequest(INDEX_I)) - .as(StepVerifier::create) - .consumeNextWith(it -> { - assertThat(it.getIndices().length).isOne(); - assertThat(it.getIndices()[0]).isEqualTo(INDEX_I); - }) - .verifyComplete(); - } + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - @Test // #1658 - public void getIndexError() { + client.indices().getIndex(new GetIndexRequest(INDEX_I)).as(StepVerifier::create).consumeNextWith(it -> { + assertThat(it.getIndices().length).isOne(); + assertThat(it.getIndices()[0]).isEqualTo(INDEX_I); + }).verifyComplete(); + } - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + @Test // #1658 + public void getIndexError() { - client.indices().getIndex(new GetIndexRequest(INDEX_II)) - .as(StepVerifier::create) - .verifyError(ElasticsearchStatusException.class); - } + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + client.indices().getIndex(new GetIndexRequest(INDEX_II)).as(StepVerifier::create) + .verifyError(ElasticsearchStatusException.class); + } @Test // DATAES-569, DATAES-678 public void deleteExistingIndex() { @@ -761,81 +786,80 @@ public void refreshNonExistingIndex() { .verifyError(ElasticsearchStatusException.class); } - @Test // #1640 - void putMapping() { + @Test // #1640 + void putMapping() { - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - Map jsonMap = Collections.singletonMap("properties", - Collections.singletonMap("message", Collections.singletonMap("type", "text"))); + Map jsonMap = Collections.singletonMap("properties", + Collections.singletonMap("message", Collections.singletonMap("type", "text"))); - org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = - new org.elasticsearch.client.indices.PutMappingRequest(INDEX_I).source(jsonMap); + org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = new org.elasticsearch.client.indices.PutMappingRequest( + INDEX_I).source(jsonMap); - client.indices().putMapping(putMappingRequest) // - .as(StepVerifier::create) // - .expectNext(true) // - .verifyComplete(); - } + client.indices().putMapping(putMappingRequest) // + .as(StepVerifier::create) // + .expectNext(true) // + .verifyComplete(); + } - @Test // #1640 - void putMappingError() { + @Test // #1640 + void putMappingError() { - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - Map jsonMap = Collections.singletonMap("properties", - Collections.singletonMap("message", Collections.singletonMap("type", "text"))); + Map jsonMap = Collections.singletonMap("properties", + Collections.singletonMap("message", Collections.singletonMap("type", "text"))); - org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = - new org.elasticsearch.client.indices.PutMappingRequest(INDEX_II).source(jsonMap); + org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = new org.elasticsearch.client.indices.PutMappingRequest( + INDEX_II).source(jsonMap); - client.indices().putMapping(putMappingRequest) // - .as(StepVerifier::create) // - .verifyError(ElasticsearchStatusException.class); - } + client.indices().putMapping(putMappingRequest) // + .as(StepVerifier::create) // + .verifyError(ElasticsearchStatusException.class); + } - @Test // #1640 - void getMapping() { + @Test // #1640 + void getMapping() { - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - Map jsonMap = Collections.singletonMap("properties", - Collections.singletonMap("message", Collections.singletonMap("type", "text"))); + Map jsonMap = Collections.singletonMap("properties", + Collections.singletonMap("message", Collections.singletonMap("type", "text"))); - org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = - new org.elasticsearch.client.indices.PutMappingRequest(INDEX_I).source(jsonMap); + org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = new org.elasticsearch.client.indices.PutMappingRequest( + INDEX_I).source(jsonMap); - client.indices().putMapping(putMappingRequest).block(); + client.indices().putMapping(putMappingRequest).block(); - final GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(INDEX_I); + final GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(INDEX_I); - client.indices().getMapping(getMappingsRequest) // - .as(StepVerifier::create) // - .consumeNextWith(it -> { - assertThat(it.mappings().get(INDEX_I).getSourceAsMap()).isEqualTo(jsonMap); - }) - .verifyComplete(); - } + client.indices().getMapping(getMappingsRequest) // + .as(StepVerifier::create) // + .consumeNextWith(it -> { + assertThat(it.mappings().get(INDEX_I).getSourceAsMap()).isEqualTo(jsonMap); + }).verifyComplete(); + } - @Test // #1640 - void getMappingError() { + @Test // #1640 + void getMappingError() { - operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); + operations.indexOps(IndexCoordinates.of(INDEX_I)).create().block(); - Map jsonMap = Collections.singletonMap("properties", - Collections.singletonMap("message", Collections.singletonMap("type", "text"))); + Map jsonMap = Collections.singletonMap("properties", + Collections.singletonMap("message", Collections.singletonMap("type", "text"))); - org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = - new org.elasticsearch.client.indices.PutMappingRequest(INDEX_I).source(jsonMap); + org.elasticsearch.client.indices.PutMappingRequest putMappingRequest = new org.elasticsearch.client.indices.PutMappingRequest( + INDEX_I).source(jsonMap); - client.indices().putMapping(putMappingRequest).block(); + client.indices().putMapping(putMappingRequest).block(); - final GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(INDEX_II); + final GetMappingsRequest getMappingsRequest = new GetMappingsRequest().indices(INDEX_II); - client.indices().getMapping(getMappingsRequest) // - .as(StepVerifier::create) // - .verifyError(ElasticsearchStatusException.class); - } + client.indices().getMapping(getMappingsRequest) // + .as(StepVerifier::create) // + .verifyError(ElasticsearchStatusException.class); + } @Test // DATAES-569 public void updateMapping() { diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientUnitTests.java index b4fab9390..ac30ab48f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientUnitTests.java @@ -242,30 +242,30 @@ public void multiGetShouldHitMGetEndpoint() { assertThat(uri.getRawPath()).isEqualTo("/_mget"); } - @Test // DATAES-488 + @Test // DATAES-488, #1678 public void multiGetShouldReturnExistingDocuments() { hostProvider.when(HOST) // .receiveJsonFromFile("multi-get-ok-2-hits"); - client.multiGet(new MultiGetRequest().add("twitter", "_doc", "1").add("twitter", "_doc", "2")) // + client.multiGet(new MultiGetRequest().add("twitter", "1").add("twitter", "2")) // .as(StepVerifier::create) // .consumeNextWith(result -> { - assertThat(result.isExists()).isTrue(); + assertThat(result.isFailed()).isFalse(); assertThat(result.getIndex()).isEqualTo("twitter"); assertThat(result.getId()).isEqualTo("1"); - assertThat(result.getSource()) // + assertThat(result.getResponse().getSource()) // .containsEntry("user", "kimchy") // .containsEntry("message", "Trying out Elasticsearch, so far so good?") // .containsKey("post_date"); }) // .consumeNextWith(result -> { - assertThat(result.isExists()).isTrue(); + assertThat(result.isFailed()).isFalse(); assertThat(result.getIndex()).isEqualTo("twitter"); assertThat(result.getId()).isEqualTo("2"); - assertThat(result.getSource()) // + assertThat(result.getResponse().getSource()) // .containsEntry("user", "kimchy") // .containsEntry("message", "Another tweet, will it be indexed?") // .containsKey("post_date"); @@ -273,33 +273,44 @@ public void multiGetShouldReturnExistingDocuments() { .verifyComplete(); } - @Test // DATAES-488 + @Test // DATAES-488, #1678 public void multiGetShouldWorkForNonExistingDocuments() { hostProvider.when(HOST) // .receiveJsonFromFile("multi-get-ok-2-hits-1-unavailable"); - client.multiGet(new MultiGetRequest().add("twitter", "_doc", "1").add("twitter", "_doc", "2")) // + client.multiGet(new MultiGetRequest() // + .add("twitter", "1") // + .add("twitter", "2") // + .add("twitter", "3") // + ) // .as(StepVerifier::create) // .consumeNextWith(result -> { - assertThat(result.isExists()).isTrue(); + assertThat(result.isFailed()).isFalse(); assertThat(result.getIndex()).isEqualTo("twitter"); assertThat(result.getId()).isEqualTo("1"); - assertThat(result.getSource()) // + assertThat(result.getResponse().isExists()).isTrue(); + assertThat(result.getResponse().getSource()) // .containsEntry("user", "kimchy") // .containsEntry("message", "Trying out Elasticsearch, so far so good?") // .containsKey("post_date"); }) // .consumeNextWith(result -> { - assertThat(result.isExists()).isTrue(); - assertThat(result.getIndex()).isEqualTo("twitter"); - assertThat(result.getId()).isEqualTo("3"); - assertThat(result.getSource()) // - .containsEntry("user", "elastic") // - .containsEntry("message", "Building the site, should be kewl") // - .containsKey("post_date"); + assertThat(result.isFailed()).isFalse(); + assertThat(result.getResponse().isExists()).isFalse(); + }) // + .consumeNextWith(result -> { + + assertThat(result.isFailed()).isFalse(); + assertThat(result.getIndex()).isEqualTo("twitter"); + assertThat(result.getId()).isEqualTo("3"); + assertThat(result.getResponse().isExists()).isTrue(); + assertThat(result.getResponse().getSource()) // + .containsEntry("user", "elastic") // + .containsEntry("message", "Building the site, should be kewl") // + .containsKey("post_date"); }) // .verifyComplete(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java b/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java index 9815b30e3..a4ea0314b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java @@ -234,17 +234,17 @@ void getWithCoordinatesShouldInvokeAfterConvertCallback() { assertThat(result.firstname).isEqualTo("after-convert"); } - @Test // DATAES-772 + @Test // DATAES-772, #1678 void multiGetShouldInvokeAfterConvertCallback() { template.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback)); - List results = template.multiGet(queryForTwo(), Person.class, index); + List> results = template.multiGet(queryForTwo(), Person.class, index); verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), eq(index)); - assertThat(results.get(0).firstname).isEqualTo("after-convert"); - assertThat(results.get(1).firstname).isEqualTo("after-convert"); + assertThat(results.get(0).getItem().firstname).isEqualTo("after-convert"); + assertThat(results.get(1).getItem().firstname).isEqualTo("after-convert"); } private Query queryForTwo() { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 59b8c1f25..a83142205 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -237,7 +237,7 @@ public void shouldReturnObjectForGivenId() { assertThat(sampleEntity1).isEqualTo(sampleEntity); } - @Test // DATAES-52 + @Test // DATAES-52, #1678 public void shouldReturnObjectsForGivenIdsUsingMultiGet() { // given @@ -258,15 +258,15 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGet() { // when NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)).build(); - List sampleEntities = operations.multiGet(query, SampleEntity.class, index); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, index); // then assertThat(sampleEntities).hasSize(2); - assertThat(sampleEntities.get(0)).isEqualTo(sampleEntity1); - assertThat(sampleEntities.get(1)).isEqualTo(sampleEntity2); + assertThat(sampleEntities.get(0).getItem()).isEqualTo(sampleEntity1); + assertThat(sampleEntities.get(1).getItem()).isEqualTo(sampleEntity2); } - @Test // DATAES-791 + @Test // DATAES-791, #1678 public void shouldReturnNullObjectForNotExistingIdUsingMultiGet() { // given @@ -290,16 +290,30 @@ public void shouldReturnNullObjectForNotExistingIdUsingMultiGet() { assertThat(idsToSearch).hasSize(3); NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(idsToSearch).build(); - List sampleEntities = operations.multiGet(query, SampleEntity.class, index); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, index); // then assertThat(sampleEntities).hasSize(3); - assertThat(sampleEntities.get(0)).isEqualTo(sampleEntity1); - assertThat(sampleEntities.get(1)).isNull(); - assertThat(sampleEntities.get(2)).isEqualTo(sampleEntity2); + assertThat(sampleEntities.get(0).getItem()).isEqualTo(sampleEntity1); + assertThat(sampleEntities.get(1).getItem()).isNull(); + assertThat(sampleEntities.get(2).getItem()).isEqualTo(sampleEntity2); } - @Test // DATAES-52 + @Test // #1678 + @DisplayName("should return failure in multiget result") + void shouldReturnFailureInMultigetResult() { + + NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList("42")).build(); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, + IndexCoordinates.of("not-existing-index")); + + // then + assertThat(sampleEntities).hasSize(1); + assertThat(sampleEntities.get(0).isFailed()).isTrue(); + assertThat(sampleEntities.get(0).getFailure()).isNotNull(); + } + + @Test // DATAES-52, #1678 public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { // given @@ -321,7 +335,7 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { // when NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)) .withFields("message", "type").build(); - List sampleEntities = operations.multiGet(query, SampleEntity.class, index); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, index); // then assertThat(sampleEntities).hasSize(2); @@ -3263,9 +3277,9 @@ void multigetShouldReturnSeqNoPrimaryTerm() { OptimisticEntity saved = operations.save(original); operations.indexOps(OptimisticEntity.class).refresh(); - List retrievedList = operations.multiGet(queryForOne(saved.getId()), OptimisticEntity.class, - operations.getIndexCoordinatesFor(OptimisticEntity.class)); - OptimisticEntity retrieved = retrievedList.get(0); + List> retrievedList = operations.multiGet(queryForOne(saved.getId()), + OptimisticEntity.class, operations.getIndexCoordinatesFor(OptimisticEntity.class)); + OptimisticEntity retrieved = retrievedList.get(0).getItem(); assertThatSeqNoPrimaryTermIsFilled(retrieved); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java index d523fe50c..bfff6f2f6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java @@ -21,6 +21,8 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -85,6 +87,8 @@ public class ReactiveElasticsearchTemplateCallbackTests { @Mock private BulkItemResponse bulkItemResponse; @Mock private DocWriteResponse docWriteResponse; @Mock private GetResult getResult; + @Mock private GetResponse getResponse; + @Mock private MultiGetItemResponse multiGetItemResponse; @Mock private org.elasticsearch.search.SearchHit searchHit; @Mock private org.elasticsearch.action.search.SearchResponse searchResponse; @@ -109,8 +113,6 @@ public void setUp() { doReturn(docWriteResponse).when(bulkItemResponse).getResponse(); doReturn("response-id").when(docWriteResponse).getId(); - when(client.multiGet(any(MultiGetRequest.class))).thenReturn(Flux.just(getResult, getResult)); - doReturn(true).when(getResult).isExists(); doReturn(false).when(getResult).isSourceEmpty(); doReturn(new HashMap() { @@ -120,6 +122,16 @@ public void setUp() { } }).when(getResult).getSource(); + doReturn(true).when(getResponse).isExists(); + doReturn(new HashMap() { + { + put("id", "init"); + put("firstname", "luke"); + } + }).when(getResponse).getSourceAsMap(); + doReturn(getResponse).when(multiGetItemResponse).getResponse(); + when(client.multiGet(any(MultiGetRequest.class))).thenReturn(Flux.just(multiGetItemResponse, multiGetItemResponse)); + doReturn(Mono.just(getResult)).when(client).get(any(GetRequest.class)); when(client.search(any(SearchRequest.class))).thenReturn(Flux.just(searchHit, searchHit)); @@ -224,18 +236,18 @@ void saveFromMonoAllShouldInvokeAfterSaveCallbacks() { assertThat(saved.get(1).firstname).isEqualTo("after-save"); } - @Test // DATAES-772 + @Test // DATAES-772, #1678 void multiGetShouldInvokeAfterConvertCallbacks() { template.setEntityCallbacks(ReactiveEntityCallbacks.create(afterConvertCallback)); - List results = template.multiGet(pagedQueryForTwo(), Person.class, index).timeout(Duration.ofSeconds(1)) + List> results = template.multiGet(pagedQueryForTwo(), Person.class, index).timeout(Duration.ofSeconds(1)) .toStream().collect(Collectors.toList()); verify(afterConvertCallback, times(2)).onAfterConvert(eq(new Person("init", "luke")), eq(lukeDocument()), eq(index)); - assertThat(results.get(0).firstname).isEqualTo("after-convert"); - assertThat(results.get(1).firstname).isEqualTo("after-convert"); + assertThat(results.get(0).getItem().firstname).isEqualTo("after-convert"); + assertThat(results.get(1).getItem().firstname).isEqualTo("after-convert"); } @Test // DATAES-772 diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index 793f40d38..69b09aac9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -772,7 +772,7 @@ void shouldReturnSortFields() { .verifyComplete(); } - @Test // DATAES-623 + @Test // DATAES-623, #1678 public void shouldReturnObjectsForGivenIdsUsingMultiGet() { SampleEntity entity1 = randomEntity("test message 1"); entity1.rate = 1; @@ -786,7 +786,7 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGet() { .build(); template.multiGet(query, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // - .as(StepVerifier::create) // + .map(MultiGetItem::getItem).as(StepVerifier::create) // .expectNext(entity1, entity2) // .verifyComplete(); } @@ -811,7 +811,7 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { .verifyComplete(); } - @Test // DATAES-623 + @Test // DATAES-623. #1678 public void shouldDoBulkUpdate() { SampleEntity entity1 = randomEntity("test message 1"); entity1.rate = 1; @@ -841,6 +841,7 @@ public void shouldDoBulkUpdate() { .withIds(Arrays.asList(entity1.getId(), entity2.getId())) // .build(); template.multiGet(getQuery, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + .map(MultiGetItem::getItem) // .as(StepVerifier::create) // .expectNextMatches(entity -> entity.getMessage().equals("updated 1")) // .expectNextMatches(entity -> entity.getMessage().equals("updated 2")) // @@ -891,7 +892,7 @@ private void assertThatSeqNoPrimaryTermIsFilled(OptimisticEntity retrieved) { assertThat(retrieved.seqNoPrimaryTerm.getPrimaryTerm()).isPositive(); } - @Test // DATAES-799 + @Test // DATAES-799, #1678 void multiGetShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); original.setMessage("It's fine"); @@ -899,8 +900,10 @@ void multiGetShouldReturnSeqNoPrimaryTerm() { template .multiGet(multiGetQueryForOne(saved.getId()), OptimisticEntity.class, - template.getIndexCoordinatesFor(OptimisticEntity.class)) - .as(StepVerifier::create).assertNext(this::assertThatSeqNoPrimaryTermIsFilled).verifyComplete(); + template.getIndexCoordinatesFor(OptimisticEntity.class)) // + .map(MultiGetItem::getItem) // + .as(StepVerifier::create) // + .assertNext(this::assertThatSeqNoPrimaryTermIsFilled).verifyComplete(); } private Query multiGetQueryForOne(String id) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java index 50d62d79d..abcc79998 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java @@ -81,17 +81,17 @@ void shouldOnlyReturnRequestedFieldsOnSearch() { assertThat(entity.getField3()).isNull(); } - @Test // #1659 + @Test // #1659, #1678 @DisplayName("should only return requested fields on multiget") void shouldOnlyReturnRequestedFieldsOnGMultiGet() { Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build(); query.addFields("field2"); - List entities = operations.multiGet(query, Entity.class); + List> entities = operations.multiGet(query, Entity.class); assertThat(entities).hasSize(1); - Entity entity = entities.get(0); + Entity entity = entities.get(0).getItem(); assertThat(entity.getField1()).isNull(); assertThat(entity.getField2()).isEqualTo("two"); assertThat(entity.getField3()).isNull(); @@ -123,7 +123,7 @@ public String[] getExcludes() { assertThat(entity.getField3()).isNotNull(); } - @Test // #1659 + @Test // #1659, #1678 @DisplayName("should not return excluded fields from SourceFilter on multiget") void shouldNotReturnExcludedFieldsFromSourceFilterOnMultiGet() { @@ -140,10 +140,10 @@ public String[] getExcludes() { } }); - List entities = operations.multiGet(query, Entity.class); + List> entities = operations.multiGet(query, Entity.class); assertThat(entities).hasSize(1); - Entity entity = entities.get(0); + Entity entity = entities.get(0).getItem(); assertThat(entity.getField1()).isNotNull(); assertThat(entity.getField2()).isNull(); assertThat(entity.getField3()).isNotNull(); @@ -175,7 +175,7 @@ public String[] getExcludes() { assertThat(entity.getField3()).isNull(); } - @Test // #1659 + @Test // #1659, #1678 @DisplayName("should only return included fields from SourceFilter on multiget") void shouldOnlyReturnIncludedFieldsFromSourceFilterOnMultiGet() { @@ -192,10 +192,10 @@ public String[] getExcludes() { } }); - List entities = operations.multiGet(query, Entity.class); + List> entities = operations.multiGet(query, Entity.class); assertThat(entities).hasSize(1); - Entity entity = entities.get(0); + Entity entity = entities.get(0).getItem(); assertThat(entity.getField1()).isNull(); assertThat(entity.getField2()).isNotNull(); assertThat(entity.getField3()).isNull(); From 6634d0075ace745e17d34d655e15d21abc0fb786 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 3 Mar 2021 06:25:06 +0100 Subject: [PATCH 004/776] DefaultReactiveElasticsearchClient handle 5xx error with empty body Original Pull Request #1713 Closes #1712 --- .../DefaultReactiveElasticsearchClient.java | 4 ++ ...efaultReactiveElasticsearchClientTest.java | 66 ++++++++++++++----- 2 files changed, 53 insertions(+), 17 deletions(-) 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 ce3ab80c7..c0e5aaedd 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 @@ -866,6 +866,10 @@ private Publisher handleServerError(Request request, ClientResp String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType()); return response.body(BodyExtractors.toMono(byte[].class)) // + .switchIfEmpty(Mono + .error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s and no body.", + request.getMethod(), request.getEndpoint(), statusCode), status)) + ) .map(bytes -> new String(bytes, StandardCharsets.UTF_8)) // .flatMap(content -> contentOrError(content, mediaType, status)) .flatMap(unused -> Mono diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClientTest.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClientTest.java index 3fc6d1516..48307af4a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClientTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClientTest.java @@ -22,20 +22,29 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.net.URI; +import java.util.Optional; import java.util.function.Function; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.client.Request; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient.ResponseSpec; +import org.springframework.web.util.UriBuilder; /** * @author Peter-Josef Meisch @@ -46,30 +55,24 @@ class DefaultReactiveElasticsearchClientTest { @Mock private HostProvider hostProvider; @Mock private Function searchRequestConverter; + @Spy private RequestCreator requestCreator; - private DefaultReactiveElasticsearchClient client; - - @BeforeEach - void setUp() { - client = new DefaultReactiveElasticsearchClient(hostProvider, new RequestCreator() { - @Override - public Function search() { - return searchRequestConverter; - } - }) { - @Override - public Mono execute(ReactiveElasticsearchClientCallback callback) { - return Mono.empty(); - } - }; - } + @Mock private WebClient webClient; @Test void shouldSetAppropriateRequestParametersOnCount() { + when(requestCreator.search()).thenReturn(searchRequestConverter); SearchRequest searchRequest = new SearchRequest("someindex") // .source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery())); + ReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator) { + @Override + public Mono execute(ReactiveElasticsearchClientCallback callback) { + return Mono.empty(); + } + }; + client.count(searchRequest).as(StepVerifier::create).verifyComplete(); ArgumentCaptor captor = ArgumentCaptor.forClass(SearchRequest.class); @@ -79,4 +82,33 @@ void shouldSetAppropriateRequestParametersOnCount() { assertThat(source.trackTotalHitsUpTo()).isEqualTo(TRACK_TOTAL_HITS_ACCURATE); assertThat(source.fetchSource()).isEqualTo(FetchSourceContext.DO_NOT_FETCH_SOURCE); } + + @Test // #1712 + @DisplayName("should throw ElasticsearchStatusException on server 5xx with empty body") + void shouldThrowElasticsearchStatusExceptionOnServer5xxWithEmptyBody() { + + when(hostProvider.getActive(any())).thenReturn(Mono.just(webClient)); + WebClient.RequestBodyUriSpec requestBodyUriSpec = mock(WebClient.RequestBodyUriSpec.class); + when(requestBodyUriSpec.uri((Function) any())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.attribute(any(), any())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.headers(any())).thenReturn(requestBodyUriSpec); + when(webClient.method(any())).thenReturn(requestBodyUriSpec); + when(requestBodyUriSpec.exchangeToMono(any())).thenAnswer(invocationOnMock -> { + Function> responseHandler = invocationOnMock.getArgument(0); + ClientResponse clientResponse = mock(ClientResponse.class); + when(clientResponse.statusCode()).thenReturn(HttpStatus.SERVICE_UNAVAILABLE); + ClientResponse.Headers headers = mock(ClientResponse.Headers.class); + when(headers.contentType()).thenReturn(Optional.empty()); + when(clientResponse.headers()).thenReturn(headers); + when(clientResponse.body(any())).thenReturn(Mono.empty()); + return responseHandler.apply(clientResponse); + }); + + ReactiveElasticsearchClient client = new DefaultReactiveElasticsearchClient(hostProvider, requestCreator); + + client.get(new GetRequest("42")) // + .as(StepVerifier::create) // + .expectError(ElasticsearchStatusException.class) // + .verify(); // + } } From e4c7b968e1e07a0c517e35c037cff2e79f7b0f7f Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Thu, 4 Mar 2021 23:56:29 +0100 Subject: [PATCH 005/776] Add the type hint _class attribute to the index mapping. Original Pull Request #1717 Closes #1711 --- .../MappingElasticsearchConverter.java | 33 ++++--- .../core/index/MappingBuilder.java | 12 +++ .../core/ReactiveIndexOperationsTest.java | 9 +- .../core/index/MappingBuilderUnitTests.java | 92 +++++++++++++++---- .../SimpleDynamicTemplatesMappingTests.java | 11 ++- .../SimpleElasticsearchDateMappingTests.java | 7 +- 6 files changed, 114 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index a6145b98b..226779e3a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -23,7 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; @@ -550,13 +549,13 @@ public void write(Object source, Document sink) { } Class entityType = ClassUtils.getUserClass(source.getClass()); - TypeInformation type = ClassTypeInformation.from(entityType); + TypeInformation typeInformation = ClassTypeInformation.from(entityType); if (requiresTypeHint(entityType)) { - typeMapper.writeType(type, sink); + typeMapper.writeType(typeInformation, sink); } - writeInternal(source, sink, type); + writeInternal(source, sink, typeInformation); } /** @@ -564,11 +563,11 @@ public void write(Object source, Document sink) { * * @param source * @param sink - * @param typeHint + * @param typeInformation */ @SuppressWarnings("unchecked") protected void writeInternal(@Nullable Object source, Map sink, - @Nullable TypeInformation typeHint) { + @Nullable TypeInformation typeInformation) { if (null == source) { return; @@ -594,7 +593,7 @@ protected void writeInternal(@Nullable Object source, Map sink, } ElasticsearchPersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityType); - addCustomTypeKeyIfNecessary(typeHint, source, sink); + addCustomTypeKeyIfNecessary(source, sink, typeInformation); writeInternal(source, sink, entity); } @@ -603,7 +602,7 @@ protected void writeInternal(@Nullable Object source, Map sink, * * @param source * @param sink - * @param typeHint + * @param entity */ protected void writeInternal(@Nullable Object source, Map sink, @Nullable ElasticsearchPersistentEntity entity) { @@ -725,7 +724,7 @@ protected void writeProperty(ElasticsearchPersistentProperty property, Object va Map document = existingValue instanceof Map ? (Map) existingValue : Document.create(); - addCustomTypeKeyIfNecessary(ClassTypeInformation.from(property.getRawType()), value, document); + addCustomTypeKeyIfNecessary(value, document, ClassTypeInformation.from(property.getRawType())); writeInternal(value, document, entity); sink.set(property, document); } @@ -923,18 +922,18 @@ protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, // region helper methods /** - * Adds custom type information to the given {@link Map} if necessary. That is if the value is not the same as the one - * given. This is usually the case if you store a subtype of the actual declared type of the property. + * Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the same + * as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of the + * property. * - * @param type - * @param value must not be {@literal null}. + * @param source must not be {@literal null}. * @param sink must not be {@literal null}. + * @param type */ - protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation type, Object value, - Map sink) { + protected void addCustomTypeKeyIfNecessary(Object source, Map sink, @Nullable TypeInformation type) { Class reference = type != null ? type.getActualType().getType() : Object.class; - Class valueType = ClassUtils.getUserClass(value.getClass()); + Class valueType = ClassUtils.getUserClass(source.getClass()); boolean notTheSameClass = !valueType.equals(reference); if (notTheSameClass) { @@ -948,7 +947,7 @@ protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation type, Ob * @param type must not be {@literal null}. * @return {@literal true} if not a simple type, {@link Collection} or type with custom write target. */ - private boolean requiresTypeHint(Class type) { + public boolean requiresTypeHint(Class type) { return !isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type) && !conversions.hasCustomWriteTarget(type, Document.class); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index f2e2b02e1..fa984f1dd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -81,6 +81,8 @@ public class MappingBuilder { private static final String COMPLETION_MAX_INPUT_LENGTH = "max_input_length"; private static final String COMPLETION_CONTEXTS = "contexts"; + private static final String TYPEHINT_PROPERTY = "_class"; + private static final String TYPE_DYNAMIC = "dynamic"; private static final String TYPE_VALUE_KEYWORD = "keyword"; private static final String TYPE_VALUE_GEO_POINT = "geo_point"; @@ -131,6 +133,14 @@ public String buildPropertyMapping(Class clazz) throws MappingException { } } + private void writeTypeHintMapping(XContentBuilder builder) throws IOException { + builder.startObject(TYPEHINT_PROPERTY) // + .field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // + .field(FIELD_PARAM_INDEX, false) // + .field(FIELD_PARAM_DOC_VALUES, false) // + .endObject(); + } + private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException { @@ -162,6 +172,8 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten builder.startObject(FIELD_PROPERTIES); + writeTypeHintMapping(builder); + if (entity != null) { entity.doWithProperties((PropertyHandler) property -> { try { 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 9b034537c..10adb780a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -257,7 +256,7 @@ void shouldCreateMappingForEntityFromProperties() { .as(StepVerifier::create) // .assertNext(document -> { try { - assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE); + assertEquals(expected, document.toJson(), false); } catch (JSONException e) { fail("", e); } @@ -282,7 +281,7 @@ void shouldCreateMappingForEntityFromMappingAnnotation() { .as(StepVerifier::create) // .assertNext(document -> { try { - assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE); + assertEquals(expected, document.toJson(), false); } catch (JSONException e) { fail("", e); } @@ -310,7 +309,7 @@ void shouldCreateMappingBoundEntity() { .as(StepVerifier::create) // .assertNext(document -> { try { - assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE); + assertEquals(expected, document.toJson(), false); } catch (JSONException e) { fail("", e); } @@ -340,7 +339,7 @@ void shouldPutAndGetMapping() { .as(StepVerifier::create) // .assertNext(document -> { try { - assertEquals(expected, document.toJson(), JSONCompareMode.NON_EXTENSIBLE); + assertEquals(expected, document.toJson(), false); } catch (JSONException e) { fail("", e); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 76b11d8aa..aa46971ca 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -37,9 +37,7 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import org.elasticsearch.search.suggest.completion.context.ContextMapping; import org.json.JSONException; @@ -420,7 +418,7 @@ public void shouldSetFieldMappingProperties() throws JSONException { String mapping = getMappingBuilder().buildPropertyMapping(FieldMappingParameters.class); // then - assertEquals(expected, mapping, true); + assertEquals(expected, mapping, false); } @Test @@ -439,7 +437,7 @@ void shouldWriteDynamicMappingSettings() throws JSONException { String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class); - assertEquals(expected, mapping, true); + assertEquals(expected, mapping, false); } @Test // DATAES-784 @@ -454,7 +452,7 @@ void shouldMapPropertyObjectsToFieldDefinition() throws JSONException { String mapping = getMappingBuilder().buildPropertyMapping(ValueDoc.class); - assertEquals(expected, mapping, true); + assertEquals(expected, mapping, false); } @Test // DATAES-788 @@ -568,6 +566,54 @@ void shouldOnlyAllowDisabledPropertiesOnTypeObject() { .isInstanceOf(MappingException.class); } + @Test // #1711 + @DisplayName("should write typeHint entries") + void shouldWriteTypeHintEntries() throws JSONException { + + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"id\": {\n" + // + " \"type\": \"keyword\"\n" + // + " },\n" + // + " \"nestedEntity\": {\n" + // + " \"type\": \"nested\",\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"nestedField\": {\n" + // + " \"type\": \"text\"\n" + // + " }\n" + // + " }\n" + // + " },\n" + // + " \"objectEntity\": {\n" + // + " \"type\": \"object\",\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"objectField\": {\n" + // + " \"type\": \"text\"\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(TypeHintEntity.class); + + assertEquals(expected, mapping, false); + } + @Setter @Getter @NoArgsConstructor @@ -862,21 +908,6 @@ static class GeoEntity { orientation = GeoShapeField.Orientation.clockwise) private String shape2; } - @Document(indexName = "test-index-user-mapping-builder") - static class User { - @Nullable @Id private String id; - - @Field(type = FieldType.Nested, ignoreFields = { "users" }) private Set groups = new HashSet<>(); - } - - @Document(indexName = "test-index-group-mapping-builder") - static class Group { - - @Nullable @Id String id; - - @Field(type = FieldType.Nested, ignoreFields = { "groups" }) private Set users = new HashSet<>(); - } - @Document(indexName = "test-index-field-mapping-parameters") static class FieldMappingParameters { @Nullable @Field private String indexTrue; @@ -1008,4 +1039,25 @@ static class DisabledMappingProperty { @Field(type = Text) private String text; @Mapping(enabled = false) @Field(type = Object) private Object object; } + + @Data + @AllArgsConstructor + @NoArgsConstructor + static class TypeHintEntity { + @Id @Field(type = Keyword) private String id; + + @Field(type = Nested) private NestedEntity nestedEntity; + + @Field(type = Object) private ObjectEntity objectEntity; + + @Data + static class NestedEntity { + @Field(type = Text) private String nestedField; + } + + @Data + static class ObjectEntity { + @Field(type = Text) private String objectField; + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java index aa427ed41..d4a134ea9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java @@ -15,11 +15,12 @@ */ package org.springframework.data.elasticsearch.core.index; -import static org.assertj.core.api.Assertions.*; +import static org.skyscreamer.jsonassert.JSONAssert.*; import java.util.HashMap; import java.util.Map; +import org.json.JSONException; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; @@ -38,7 +39,7 @@ public class SimpleDynamicTemplatesMappingTests extends MappingContextBaseTests { @Test // DATAES-568 - public void testCorrectDynamicTemplatesMappings() { + public void testCorrectDynamicTemplatesMappings() throws JSONException { String mapping = getMappingBuilder().buildPropertyMapping(SampleDynamicTemplatesEntity.class); @@ -46,11 +47,11 @@ public void testCorrectDynamicTemplatesMappings() { + "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," + "\"path_match\":\"names.*\"}}]," + "\"properties\":{\"names\":{\"type\":\"object\"}}}"; - assertThat(mapping).isEqualTo(EXPECTED_MAPPING_ONE); + assertEquals(EXPECTED_MAPPING_ONE, mapping, false); } @Test // DATAES-568 - public void testCorrectDynamicTemplatesMappingsTwo() { + public void testCorrectDynamicTemplatesMappingsTwo() throws JSONException { String mapping = getMappingBuilder().buildPropertyMapping(SampleDynamicTemplatesEntityTwo.class); String EXPECTED_MAPPING_TWO = "{\"dynamic_templates\":" + "[{\"with_custom_analyzer\":{" @@ -59,7 +60,7 @@ public void testCorrectDynamicTemplatesMappingsTwo() { + "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," + "\"path_match\":\"participantA1.*\"}}]," + "\"properties\":{\"names\":{\"type\":\"object\"}}}"; - assertThat(mapping).isEqualTo(EXPECTED_MAPPING_TWO); + assertEquals(EXPECTED_MAPPING_TWO, mapping, false); } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java index e2248c20d..4349b8572 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java @@ -15,13 +15,14 @@ */ package org.springframework.data.elasticsearch.core.index; -import static org.assertj.core.api.Assertions.*; +import static org.skyscreamer.jsonassert.JSONAssert.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; import lombok.Data; import java.time.LocalDateTime; +import org.json.JSONException; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; @@ -43,11 +44,11 @@ public class SimpleElasticsearchDateMappingTests extends MappingContextBaseTests + "\"basicFormatDate\":{\"" + "type\":\"date\",\"format\":\"basic_date\"}}}"; @Test // DATAES-568, DATAES-828 - public void testCorrectDateMappings() { + public void testCorrectDateMappings() throws JSONException { String mapping = getMappingBuilder().buildPropertyMapping(SampleDateMappingEntity.class); - assertThat(mapping).isEqualTo(EXPECTED_MAPPING); + assertEquals(EXPECTED_MAPPING, mapping, false); } /** From 2f5773a5ff4bf058a13f927000c1a889ff9ec9b8 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 6 Mar 2021 18:42:57 +0100 Subject: [PATCH 006/776] Create index with mapping in one step. Original Pull Request #1723 Closes #1718 --- .../core/AbstractDefaultIndexOperations.java | 20 ++++++--- .../core/DefaultIndexOperations.java | 4 +- .../core/DefaultReactiveIndexOperations.java | 31 +++++++++---- .../core/DefaultTransportIndexOperations.java | 4 +- .../elasticsearch/core/IndexOperations.java | 44 +++++++++++++------ .../core/ReactiveIndexOperations.java | 20 +++++++++ .../elasticsearch/core/RequestFactory.java | 36 +++++++++++++++ .../SimpleElasticsearchRepository.java | 3 +- ...SimpleReactiveElasticsearchRepository.java | 3 +- ...ElasticsearchTemplateIntegrationTests.java | 5 +-- .../index/IndexOperationIntegrationTests.java | 14 ++++-- 11 files changed, 141 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java index 7559fc52b..9a3f5e53a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -96,7 +96,7 @@ public boolean create() { settings = createSettings(boundClass); } - return doCreate(getIndexCoordinates(), settings); + return doCreate(getIndexCoordinates(), settings, null); } @Override @@ -119,12 +119,22 @@ public Document createSettings(Class clazz) { return settings; } + @Override + public boolean createWithMapping() { + return doCreate(getIndexCoordinates(), createSettings(), createMapping()); + } + @Override public boolean create(Document settings) { - return doCreate(getIndexCoordinates(), settings); + return doCreate(getIndexCoordinates(), settings, null); } - protected abstract boolean doCreate(IndexCoordinates index, @Nullable Document settings); + @Override + public boolean create(Document settings, Document mapping) { + return doCreate(getIndexCoordinates(), settings, mapping); + } + + protected abstract boolean doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping); @Override public boolean delete() { @@ -242,9 +252,7 @@ protected Document buildMapping(Class clazz) { } // build mapping from field annotations - try - - { + try { String mapping = new MappingBuilder(elasticsearchConverter).buildPropertyMapping(clazz); return Document.parse(mapping); } catch (Exception e) { 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 38501e266..e07e276e1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java @@ -81,8 +81,8 @@ public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoord } @Override - protected boolean doCreate(IndexCoordinates index, @Nullable Document settings) { - CreateIndexRequest request = requestFactory.createIndexRequest(index, settings); + protected boolean doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { + CreateIndexRequest request = requestFactory.createIndexRequest(index, settings, mapping); return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged()); } 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 963c9222c..d3c9eae9c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -26,13 +26,13 @@ import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; -import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; 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.CreateIndexRequest; import org.elasticsearch.client.indices.GetIndexTemplatesRequest; import org.elasticsearch.client.indices.IndexTemplatesExistRequest; import org.elasticsearch.client.indices.PutIndexTemplateRequest; @@ -101,23 +101,36 @@ public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations @Override public Mono create() { - String indexName = getIndexCoordinates().getIndexName(); + IndexCoordinates index = getIndexCoordinates(); if (boundClass != null) { - return createSettings(boundClass).flatMap(settings -> doCreate(indexName, settings)); + return createSettings(boundClass).flatMap(settings -> doCreate(index, settings, null)); } else { - return doCreate(indexName, null); + return doCreate(index, null, null); } } + @Override + public Mono createWithMapping() { + return createSettings() // + .flatMap(settings -> // + createMapping().flatMap(mapping -> // + doCreate(getIndexCoordinates(), settings, mapping))); // + } + @Override public Mono create(Document settings) { - return doCreate(getIndexCoordinates().getIndexName(), settings); + return doCreate(getIndexCoordinates(), settings, null); } - private Mono doCreate(String indexName, @Nullable Document settings) { + @Override + public Mono create(Document settings, Document mapping) { + throw new UnsupportedOperationException("not implemented"); + } - CreateIndexRequest request = requestFactory.createIndexRequestReactive(indexName, settings); + private Mono doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { + + CreateIndexRequest request = requestFactory.createIndexRequest(index, settings, mapping); return Mono.from(operations.executeWithIndicesClient(client -> client.createIndex(request))); } @@ -309,9 +322,9 @@ public IndexCoordinates getIndexCoordinates() { @Override public Flux getInformation(IndexCoordinates index) { - Assert.notNull(index, "index must not be null"); + Assert.notNull(index, "index must not be null"); - org.elasticsearch.client.indices.GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(index); + org.elasticsearch.client.indices.GetIndexRequest getIndexRequest = requestFactory.getIndexRequest(index); return Mono .from(operations.executeWithIndicesClient( client -> client.getIndex(getIndexRequest).map(ResponseConverter::getIndexInformations))) 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 0c76b39a7..1d47d82e0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java @@ -90,9 +90,9 @@ public DefaultTransportIndexOperations(Client client, ElasticsearchConverter ela } @Override - protected boolean doCreate(IndexCoordinates index, @Nullable Document settings) { + protected boolean doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, index, - settings); + settings, mapping); return createIndexRequestBuilder.execute().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 8ee03490b..8d28bb2db 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -55,13 +55,31 @@ public interface IndexOperations { boolean create(); /** - * Create an index for given Settings. + * Create an index for given settings. * * @param settings the index settings * @return {@literal true} if the index was created */ boolean create(Document settings); + /** + * Create an index for given settings and mapping. + * + * @param settings the index settings + * @param mapping the index mapping + * @return {@literal true} if the index was created + * @since 4.2 + */ + boolean create(Document settings, Document mapping); + + /** + * Create an index with the settings and mapping defined for the entity this IndexOperations is bound to. + * + * @return {@literal true} if the index was created + * @since 4.2 + */ + boolean createWithMapping(); + /** * Deletes the index this {@link IndexOperations} is bound to * @@ -82,7 +100,7 @@ public interface IndexOperations { void refresh(); // endregion - // region mappings + // region mapping /** * Creates the index mapping for the entity this IndexOperations is bound to. * @@ -309,16 +327,16 @@ default boolean deleteTemplate(String templateName) { // endregion - //region index information - /** - * Gets the {@link IndexInformation} for the indices defined by {@link #getIndexCoordinates()}. - * - * @return a list of {@link IndexInformation} - * @since 4.2 - */ - default List getInformation() { - return getInformation(getIndexCoordinates()); - } + // region index information + /** + * Gets the {@link IndexInformation} for the indices defined by {@link #getIndexCoordinates()}. + * + * @return a list of {@link IndexInformation} + * @since 4.2 + */ + default List getInformation() { + return getInformation(getIndexCoordinates()); + } /** * Gets the {@link IndexInformation} for the indices defined by #index. @@ -328,7 +346,7 @@ default List getInformation() { * @since 4.2 */ List getInformation(IndexCoordinates index); - //endregion + // endregion // region helper functions /** 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 f56446e7c..684756203 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java @@ -58,6 +58,26 @@ public interface ReactiveIndexOperations { */ Mono create(Document settings); + /** + * Create an index for given settings and mapping. + * + * @param settings the index settings + * @param mapping the index mapping + * @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg. + * the index already exist. + * @since 4.2 + */ + Mono create(Document settings, Document mapping); + + /** + * Create an index with the settings and mapping defined for the entity this IndexOperations is bound to. + * + * @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg. + * the index already exist. + * @since 4.2 + */ + Mono createWithMapping(); + /** * Delete an index. * 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 d399f4e09..0395e3891 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -355,11 +355,29 @@ public BulkRequestBuilder bulkRequestBuilder(Client client, List queries, Bul * @return request */ public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings) { + return createIndexRequest(index, settings, null); + } + + /** + * creates a CreateIndexRequest from the rest-high-level-client library. + * + * @param index name of the index + * @param settings optional settings + * @param mapping optional mapping + * @return request + * @since 4.2 + */ + public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { CreateIndexRequest request = new CreateIndexRequest(index.getIndexName()); if (settings != null && !settings.isEmpty()) { request.settings(settings); } + + if (mapping != null && !mapping.isEmpty()) { + request.mapping(mapping); + } + return request; } @@ -395,6 +413,24 @@ public CreateIndexRequestBuilder createIndexRequestBuilder(Client client, IndexC return createIndexRequestBuilder; } + public CreateIndexRequestBuilder createIndexRequestBuilder(Client client, IndexCoordinates index, + @Nullable Document settings, @Nullable Document mapping) { + + String indexName = index.getIndexName(); + CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(indexName); + + if (settings != null && !settings.isEmpty()) { + + createIndexRequestBuilder.setSettings(settings); + } + + if (mapping != null && !mapping.isEmpty()) { + createIndexRequestBuilder.addMapping(IndexCoordinates.TYPE, mapping); + } + + return createIndexRequestBuilder; + } + /** * creates a GetIndexRequest from the rest-high-level-client library. * diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java index 7e8a4c252..ebff7cd87 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepository.java @@ -91,8 +91,7 @@ public SimpleElasticsearchRepository(ElasticsearchEntityInformation metad this.indexOperations = operations.indexOps(this.entityClass); if (shouldCreateIndexAndMapping() && !indexOperations.exists()) { - indexOperations.create(); - indexOperations.putMapping(entityClass); + indexOperations.createWithMapping(); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java index c863333e4..decdd04b8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepository.java @@ -65,8 +65,7 @@ private void createIndexAndMappingIfNeeded() { if (shouldCreateIndexAndMapping()) { indexOperations.exists() // - .flatMap(exists -> exists ? Mono.empty() : indexOperations.create()) // - .flatMap(success -> success ? indexOperations.putMapping() : Mono.empty()) // + .flatMap(exists -> exists ? Mono.empty() : indexOperations.createWithMapping()) // .block(); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index 69b09aac9..ec8c25153 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -1114,15 +1114,14 @@ void shouldReturnExplanationWhenRequested() { }).verifyComplete(); } - @Test // #1646 + @Test // #1646, #1718 @DisplayName("should return a list of info for specific index") void shouldReturnInformationListOfAllIndices() { String indexName = "test-index-reactive-information-list"; String aliasName = "testindexinformationindex"; ReactiveIndexOperations indexOps = template.indexOps(EntityWithSettingsAndMappingsReactive.class); - indexOps.create().block(); - indexOps.putMapping().block(); + indexOps.createWithMapping().block(); AliasActionParameters parameters = AliasActionParameters.builder().withAliases(aliasName).withIndices(indexName) .withIsHidden(false).withIsWriteIndex(false).withRouting("indexrouting").withSearchRouting("searchrouting") diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java index 5b0bf2797..3d69f9c54 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java @@ -54,15 +54,14 @@ void setUp() { operations.indexOps(EntityWithSettingsAndMappings.class).delete(); } - @Test // #1646 + @Test // #1646, #1718 @DisplayName("should return a list of info for specific index") void shouldReturnInformationList() throws JSONException { IndexOperations indexOps = operations.indexOps(EntityWithSettingsAndMappings.class); String aliasName = "testindexinformationindex"; - indexOps.create(); - indexOps.putMapping(); + indexOps.createWithMapping(); AliasActionParameters parameters = AliasActionParameters.builder().withAliases(aliasName).withIndices(INDEX_NAME) .withIsHidden(false).withIsWriteIndex(false).withRouting("indexrouting").withSearchRouting("searchrouting") @@ -88,7 +87,14 @@ void shouldReturnInformationList() throws JSONException { assertThat(aliasData.getIndexRouting()).isEqualTo("indexrouting"); assertThat(aliasData.getSearchRouting()).isEqualTo("searchrouting"); - String expectedMappings = "{\"properties\":{\"email\":{\"type\":\"text\",\"analyzer\":\"emailAnalyzer\"}}}"; + String expectedMappings = "{\n" + // + " \"properties\": {\n" + // + " \"email\": {\n" + // + " \"type\": \"text\",\n" + // + " \"analyzer\": \"emailAnalyzer\"\n" + // + " }\n" + // + " }\n" + // + "}"; // JSONAssert.assertEquals(expectedMappings, indexInformation.getMapping().toJson(), false); } From 3f39f5d5b7bbc488e0de7bd33c30b4910e7a0170 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 7 Mar 2021 13:28:21 +0100 Subject: [PATCH 007/776] Use correct classes in reactive operations. Original Pull Request #1724 Closes #1721 --- .../DefaultReactiveElasticsearchClient.java | 1 + .../client/util/RequestConverters.java | 1 + .../core/AbstractDefaultIndexOperations.java | 4 + .../core/DefaultIndexOperations.java | 2 + .../core/DefaultReactiveIndexOperations.java | 16 ++-- .../core/DefaultTransportIndexOperations.java | 1 + .../elasticsearch/core/RequestFactory.java | 90 +------------------ .../core/ReactiveIndexOperationsTest.java | 3 +- 8 files changed, 22 insertions(+), 96 deletions(-) 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 c0e5aaedd..af02927b7 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 @@ -685,6 +685,7 @@ public Mono flushIndex(HttpHeaders headers, FlushRequest flushRequest) { } @Override + @Deprecated public Mono getMapping(HttpHeaders headers, org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingsRequest) { return sendRequest(getMappingsRequest, requestCreator.getMapping(), 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 b6dd59b35..cc0e81811 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 @@ -812,6 +812,7 @@ public static Request indexRefresh(RefreshRequest refreshRequest) { return request; } + @Deprecated public static Request putMapping(PutMappingRequest putMappingRequest) { // The concreteIndex is an internal concept, not applicable to requests made over the REST API. if (putMappingRequest.getConcreteIndex() != null) { 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 9a3f5e53a..b2da38a7a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -184,10 +184,12 @@ public void refresh() { protected abstract void doRefresh(IndexCoordinates indexCoordinates); @Override + @Deprecated public boolean addAlias(AliasQuery query) { return doAddAlias(query, getIndexCoordinates()); } + @Deprecated protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index); @Override @@ -198,10 +200,12 @@ public List queryForAlias() { protected abstract List doQueryForAlias(IndexCoordinates index); @Override + @Deprecated public boolean removeAlias(AliasQuery query) { return doRemoveAlias(query, getIndexCoordinates()); } + @Deprecated protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index); @Override 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 e07e276e1..3355aab4a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java @@ -141,6 +141,7 @@ protected Map doGetMapping(IndexCoordinates index) { } @Override + @Deprecated protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) { IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index); @@ -149,6 +150,7 @@ protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) { } @Override + @Deprecated protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) { Assert.notNull(index, "No index defined for Alias"); 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 d3c9eae9c..50cad41f7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -27,13 +27,13 @@ import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; 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.CreateIndexRequest; +import org.elasticsearch.client.indices.GetIndexRequest; import org.elasticsearch.client.indices.GetIndexTemplatesRequest; +import org.elasticsearch.client.indices.GetMappingsRequest; import org.elasticsearch.client.indices.IndexTemplatesExistRequest; import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.slf4j.Logger; @@ -153,7 +153,7 @@ public Mono delete() { @Override public Mono exists() { - GetIndexRequest request = requestFactory.getIndexRequestReactive(getIndexCoordinates().getIndexName()); + GetIndexRequest request = requestFactory.getIndexRequest(getIndexCoordinates()); return Mono.from(operations.executeWithIndicesClient(client -> client.existsIndex(request))); } @@ -185,7 +185,7 @@ public Mono createMapping(Class clazz) { @Override public Mono putMapping(Mono mapping) { - return mapping.map(document -> requestFactory.putMappingRequestReactive(getIndexCoordinates(), document)) // + return mapping.map(document -> requestFactory.putMappingRequest(getIndexCoordinates(), document)) // .flatMap(request -> Mono.from(operations.executeWithIndicesClient(client -> client.putMapping(request)))); } @@ -193,13 +193,13 @@ public Mono putMapping(Mono mapping) { public Mono getMapping() { IndexCoordinates indexCoordinates = getIndexCoordinates(); - GetMappingsRequest request = requestFactory.getMappingRequestReactive(indexCoordinates); + GetMappingsRequest request = requestFactory.getMappingsRequest(indexCoordinates); return Mono.from(operations.executeWithIndicesClient(client -> client.getMapping(request))) .flatMap(getMappingsResponse -> { - Document document = Document.create(); - document.put("properties", - getMappingsResponse.mappings().get(indexCoordinates.getIndexName()).get("properties").getSourceAsMap()); + Map source = getMappingsResponse.mappings().get(indexCoordinates.getIndexName()) + .getSourceAsMap(); + Document document = Document.from(source); return Mono.just(document); }); } 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 1d47d82e0..df49ac0b7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java @@ -153,6 +153,7 @@ protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) { } @Override + @Deprecated protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) { Assert.notNull(index, "No index defined for Alias"); 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 0395e3891..822b6cff7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -133,6 +133,7 @@ public RequestFactory(ElasticsearchConverter elasticsearchConverter) { } // region alias + @Deprecated public IndicesAliasesRequest.AliasActions aliasAction(AliasQuery query, IndexCoordinates index) { Assert.notNull(index, "No index defined for Alias"); @@ -178,6 +179,7 @@ public GetAliasesRequest getAliasesRequest(@Nullable String[] aliasNames, @Nulla return getAliasesRequest; } + @Deprecated public IndicesAliasesRequest indicesAddAliasesRequest(AliasQuery query, IndexCoordinates index) { IndicesAliasesRequest.AliasActions aliasAction = aliasAction(query, index); IndicesAliasesRequest request = new IndicesAliasesRequest(); @@ -253,6 +255,7 @@ public IndicesAliasesRequestBuilder indicesAliasesRequestBuilder(Client client, return requestBuilder; } + @Deprecated public IndicesAliasesRequest indicesRemoveAliasesRequest(AliasQuery query, IndexCoordinates index) { String[] indexNames = index.getIndexNames(); @@ -264,6 +267,7 @@ public IndicesAliasesRequest indicesRemoveAliasesRequest(AliasQuery query, Index .addAliasAction(aliasAction); } + @Deprecated IndicesAliasesRequestBuilder indicesRemoveAliasesRequestBuilder(Client client, AliasQuery query, IndexCoordinates index) { @@ -347,26 +351,7 @@ public BulkRequestBuilder bulkRequestBuilder(Client client, List queries, Bul // endregion // region index management - /** - * creates a CreateIndexRequest from the rest-high-level-client library. - * - * @param index name of the index - * @param settings optional settings - * @return request - */ - public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings) { - return createIndexRequest(index, settings, null); - } - /** - * creates a CreateIndexRequest from the rest-high-level-client library. - * - * @param index name of the index - * @param settings optional settings - * @param mapping optional mapping - * @return request - * @since 4.2 - */ public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { CreateIndexRequest request = new CreateIndexRequest(index.getIndexName()); @@ -381,38 +366,6 @@ public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable D return request; } - /** - * creates a CreateIndexRequest from the elasticsearch library, used by the reactive methods. - * - * @param indexName name of the index - * @param settings optional settings - * @return request - */ - public org.elasticsearch.action.admin.indices.create.CreateIndexRequest createIndexRequestReactive(String indexName, - @Nullable Document settings) { - - org.elasticsearch.action.admin.indices.create.CreateIndexRequest request = new org.elasticsearch.action.admin.indices.create.CreateIndexRequest( - indexName); - request.index(indexName); - - if (settings != null && !settings.isEmpty()) { - request.settings(settings); - } - return request; - } - - public CreateIndexRequestBuilder createIndexRequestBuilder(Client client, IndexCoordinates index, - @Nullable Document settings) { - - String indexName = index.getIndexName(); - CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(indexName); - - if (settings != null) { - createIndexRequestBuilder.setSettings(settings); - } - return createIndexRequestBuilder; - } - public CreateIndexRequestBuilder createIndexRequestBuilder(Client client, IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { @@ -431,29 +384,10 @@ public CreateIndexRequestBuilder createIndexRequestBuilder(Client client, IndexC return createIndexRequestBuilder; } - /** - * creates a GetIndexRequest from the rest-high-level-client library. - * - * @param index name of the index - * @return request - */ public GetIndexRequest getIndexRequest(IndexCoordinates index) { return new GetIndexRequest(index.getIndexNames()); } - /** - * creates a CreateIndexRequest from the elasticsearch library, used by the reactive methods. - * - * @param indexName name of the index - * @return request - */ - public org.elasticsearch.action.admin.indices.get.GetIndexRequest getIndexRequestReactive(String indexName) { - - org.elasticsearch.action.admin.indices.get.GetIndexRequest request = new org.elasticsearch.action.admin.indices.get.GetIndexRequest(); - request.indices(indexName); - return request; - } - public IndicesExistsRequest indicesExistsRequest(IndexCoordinates index) { String[] indexNames = index.getIndexNames(); @@ -485,15 +419,6 @@ public PutMappingRequest putMappingRequest(IndexCoordinates index, Document mapp return request; } - public org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequestReactive( - IndexCoordinates index, Document mapping) { - org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest request = new org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest( - index.getIndexName()); - request.type("not-used-but-must-be-there"); - request.source(mapping); - return request; - } - public PutMappingRequestBuilder putMappingRequestBuilder(Client client, IndexCoordinates index, Document mapping) { String[] indexNames = index.getIndexNames(); @@ -503,13 +428,6 @@ public PutMappingRequestBuilder putMappingRequestBuilder(Client client, IndexCoo return requestBuilder; } - public org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest getMappingRequestReactive( - IndexCoordinates index) { - org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest request = new org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequest(); - request.indices(index.getIndexName()); - return request; - } - public GetSettingsRequest getSettingsRequest(String indexName, boolean includeDefaults) { return new GetSettingsRequest().indices(indexName).includeDefaults(includeDefaults); } 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 10adb780a..01e6e9dd9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java @@ -51,7 +51,6 @@ 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; @@ -67,7 +66,7 @@ public class ReactiveIndexOperationsTest { public static final String TESTINDEX = "reactive-index-operations-testindex"; @Configuration - @Import({ ReactiveElasticsearchRestTemplateConfiguration.class, ElasticsearchRestTemplateConfiguration.class }) + @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) static class Config {} @Autowired private ReactiveElasticsearchOperations operations; From 4dc8b2589aa9865d5d29b55fa1247842d31ffdf8 Mon Sep 17 00:00:00 2001 From: Matt Gilene Date: Thu, 11 Mar 2021 12:32:37 -0500 Subject: [PATCH 008/776] Add matched_queries field to SearchHit. Original Pull Request #1722 Closes #1514 --- .../data/elasticsearch/core/SearchHit.java | 16 ++++++++-- .../elasticsearch/core/SearchHitMapping.java | 3 ++ .../core/document/DocumentAdapters.java | 29 ++++++++++++++----- .../core/document/SearchDocument.java | 7 +++++ .../core/DocumentAdaptersUnitTests.java | 17 ++++++++++- 5 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java index 327066b47..4eebdcd2a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java @@ -33,6 +33,7 @@ * * @param the result data class. * @author Peter-Josef Meisch + * @author Matt Gilene * @since 4.0 */ public class SearchHit { @@ -47,16 +48,18 @@ public class SearchHit { @Nullable private final NestedMetaData nestedMetaData; @Nullable private final String routing; @Nullable private final Explanation explanation; + @Nullable private final List matchedQueries; public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score, @Nullable Object[] sortValues, @Nullable Map> highlightFields, T content) { - this(index, id, routing, score, sortValues, highlightFields, null, null, null, content); + this(index, id, routing, score, sortValues, highlightFields, null, null, null, null, content); } public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score, @Nullable Object[] sortValues, @Nullable Map> highlightFields, @Nullable Map> innerHits, @Nullable NestedMetaData nestedMetaData, - @Nullable Explanation explanation, T content) { + @Nullable Explanation explanation, + @Nullable List matchedQueries, T content) { this.index = index; this.id = id; this.routing = routing; @@ -74,6 +77,7 @@ public SearchHit(@Nullable String index, @Nullable String id, @Nullable String r this.nestedMetaData = nestedMetaData; this.explanation = explanation; this.content = content; + this.matchedQueries = matchedQueries; } /** @@ -188,4 +192,12 @@ public String getRouting() { public Explanation getExplanation() { return explanation; } + + /** + * @return the matched queries for this SearchHit. + */ + @Nullable + public List getMatchedQueries() { + return matchedQueries; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java index 3971dcef6..b89d6ee0e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHitMapping.java @@ -43,6 +43,7 @@ * @author Peter-Josef Meisch * @author Mark Paluch * @author Roman Puchkovskiy + * @author Matt Gilene * @since 4.0 */ class SearchHitMapping { @@ -114,6 +115,7 @@ SearchHit mapHit(SearchDocument searchDocument, T content) { mapInnerHits(searchDocument), // searchDocument.getNestedMetaData(), // searchDocument.getExplanation(), // + searchDocument.getMatchedQueries(), // content); // } @@ -198,6 +200,7 @@ private SearchHits mapInnerDocuments(SearchHits searchHits, C searchHit.getInnerHits(), // persistentEntityWithNestedMetaData.nestedMetaData, // searchHit.getExplanation(), // + searchHit.getMatchedQueries(), // targetObject)); }); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index ae4dc09fa..ee8d0908d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -31,6 +31,10 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetResponse; @@ -47,10 +51,6 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; - /** * Utility class to adapt {@link org.elasticsearch.action.get.GetResponse}, * {@link org.elasticsearch.index.get.GetResult}, {@link org.elasticsearch.action.get.MultiGetResponse} @@ -60,6 +60,7 @@ * @author Mark Paluch * @author Peter-Josef Meisch * @author Roman Puchkovskiy + * @author Matt Gilene * @since 4.0 */ public class DocumentAdapters { @@ -181,6 +182,7 @@ public static SearchDocument from(SearchHit source) { NestedMetaData nestedMetaData = from(source.getNestedIdentity()); Explanation explanation = from(source.getExplanation()); + List matchedQueries = from(source.getMatchedQueries()); BytesReference sourceRef = source.getSourceRef(); @@ -188,7 +190,7 @@ public static SearchDocument from(SearchHit source) { return new SearchDocumentAdapter( source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()), - innerHits, nestedMetaData, explanation); + innerHits, nestedMetaData, explanation, matchedQueries); } Document document = Document.from(source.getSourceAsMap()); @@ -202,7 +204,7 @@ public static SearchDocument from(SearchHit source) { document.setPrimaryTerm(source.getPrimaryTerm()); return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields, - document, innerHits, nestedMetaData, explanation); + document, innerHits, nestedMetaData, explanation, matchedQueries); } @Nullable @@ -231,6 +233,11 @@ private static NestedMetaData from(@Nullable SearchHit.NestedIdentity nestedIden return NestedMetaData.of(nestedIdentity.getField().string(), nestedIdentity.getOffset(), child); } + @Nullable + private static List from(@Nullable String[] matchedQueries) { + return matchedQueries == null ? null : Arrays.asList(matchedQueries); + } + /** * Create an unmodifiable {@link Document} from {@link Iterable} of {@link DocumentField}s. * @@ -484,10 +491,11 @@ static class SearchDocumentAdapter implements SearchDocument { private final Map innerHits = new HashMap<>(); @Nullable private final NestedMetaData nestedMetaData; @Nullable private final Explanation explanation; + @Nullable private final List matchedQueries; SearchDocumentAdapter(float score, Object[] sortValues, Map fields, Map> highlightFields, Document delegate, Map innerHits, - @Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation) { + @Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable List matchedQueries) { this.score = score; this.sortValues = sortValues; @@ -497,6 +505,7 @@ static class SearchDocumentAdapter implements SearchDocument { this.innerHits.putAll(innerHits); this.nestedMetaData = nestedMetaData; this.explanation = explanation; + this.matchedQueries = matchedQueries; } @Override @@ -679,6 +688,12 @@ public Explanation getExplanation() { return explanation; } + @Override + @Nullable + public List getMatchedQueries() { + return matchedQueries; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java index 17daec2df..e3f21f963 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java @@ -25,6 +25,7 @@ * * @author Mark Paluch * @author Peter-Josef Meisch + * @author Matt Gilene * @since 4.0 * @see Document */ @@ -105,4 +106,10 @@ default String getRouting() { */ @Nullable Explanation getExplanation(); + + /** + * @return the matched queries for the SearchHit. + */ + @Nullable + List getMatchedQueries(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java index 34c3b1246..ca9c3366c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java @@ -15,7 +15,7 @@ */ package org.springframework.data.elasticsearch.core; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.util.Arrays; @@ -45,6 +45,7 @@ * @author Mark Paluch * @author Peter-Josef Meisch * @author Roman Puchkovskiy + * @author Matt Gilene */ public class DocumentAdaptersUnitTests { @@ -262,4 +263,18 @@ void shouldAdaptReturnedExplanations() { List details = explanation.getDetails(); assertThat(details).containsExactly(new Explanation(false, 0.0, "explanation noMatch", Collections.emptyList())); } + + @Test // DATAES-979 + @DisplayName("should adapt returned matched queries") + void shouldAdaptReturnedMatchedQueries() { + SearchHit searchHit = new SearchHit(42); + searchHit.matchedQueries(new String[] { "query1", "query2" }); + + SearchDocument searchDocument = DocumentAdapters.from(searchHit); + + List matchedQueries = searchDocument.getMatchedQueries(); + assertThat(matchedQueries).isNotNull(); + assertThat(matchedQueries).hasSize(2); + assertThat(matchedQueries).isEqualTo(Arrays.asList("query1", "query2")); + } } From eb816cce876b250392bbe20885ea2466065cd491 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Thu, 11 Mar 2021 19:13:34 +0100 Subject: [PATCH 009/776] Polishing. --- .../data/elasticsearch/core/SearchHit.java | 23 ++++++++++++------- .../core/document/DocumentAdapters.java | 15 ++++++------ .../core/DocumentAdaptersUnitTests.java | 9 ++++---- .../core/SearchHitSupportTest.java | 12 +++++----- .../elasticsearch/core/StreamQueriesTest.java | 2 +- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java index 4eebdcd2a..f934c7e53 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchHit.java @@ -30,7 +30,7 @@ /** * Encapsulates the found data with additional information from the search. - * + * * @param the result data class. * @author Peter-Josef Meisch * @author Matt Gilene @@ -48,8 +48,13 @@ public class SearchHit { @Nullable private final NestedMetaData nestedMetaData; @Nullable private final String routing; @Nullable private final Explanation explanation; - @Nullable private final List matchedQueries; + private final List matchedQueries = new ArrayList<>(); + /** + * @deprecated since 4.2 use + * {@link #SearchHit(String, String, String, float, Object[], Map, Map, NestedMetaData, Explanation, List, Object)}. + */ + @Deprecated public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score, @Nullable Object[] sortValues, @Nullable Map> highlightFields, T content) { this(index, id, routing, score, sortValues, highlightFields, null, null, null, null, content); @@ -58,8 +63,7 @@ public SearchHit(@Nullable String index, @Nullable String id, @Nullable String r public SearchHit(@Nullable String index, @Nullable String id, @Nullable String routing, float score, @Nullable Object[] sortValues, @Nullable Map> highlightFields, @Nullable Map> innerHits, @Nullable NestedMetaData nestedMetaData, - @Nullable Explanation explanation, - @Nullable List matchedQueries, T content) { + @Nullable Explanation explanation, @Nullable List matchedQueries, T content) { this.index = index; this.id = id; this.routing = routing; @@ -77,7 +81,10 @@ public SearchHit(@Nullable String index, @Nullable String id, @Nullable String r this.nestedMetaData = nestedMetaData; this.explanation = explanation; this.content = content; - this.matchedQueries = matchedQueries; + + if (matchedQueries != null) { + this.matchedQueries.addAll(matchedQueries); + } } /** @@ -125,7 +132,7 @@ public Map> getHighlightFields() { /** * gets the highlight values for a field. - * + * * @param field must not be {@literal null} * @return possibly empty List, never null */ @@ -141,7 +148,7 @@ public List getHighlightField(String field) { * nested entity class, the returned data will be of this type, otherwise * {{@link org.springframework.data.elasticsearch.core.document.SearchDocument}} instances are returned in this * {@link SearchHits} object. - * + * * @param name the inner hits name * @return {@link SearchHits} if available, otherwise {@literal null} */ @@ -160,7 +167,7 @@ public Map> getInnerHits() { /** * If this is a nested inner hit, return the nested metadata information - * + * * @return {{@link NestedMetaData} * @since 4.1 */ diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index ee8d0908d..11b07c774 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -31,10 +31,6 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; - import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetResponse; @@ -51,6 +47,10 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + /** * Utility class to adapt {@link org.elasticsearch.action.get.GetResponse}, * {@link org.elasticsearch.index.get.GetResult}, {@link org.elasticsearch.action.get.MultiGetResponse} @@ -131,7 +131,7 @@ public static Document from(GetResult source) { /** * Creates a List of {@link MultiGetItem}s from {@link MultiGetResponse}. - * + * * @param source the source {@link MultiGetResponse}, not {@literal null}. * @return a list of Documents, contains null values for not found Documents. */ @@ -146,7 +146,7 @@ public static List> from(MultiGetResponse source) { /** * Creates a {@link MultiGetItem} from a {@link MultiGetItemResponse}. - * + * * @param itemResponse the response, must not be {@literal null} * @return the MultiGetItem */ @@ -495,7 +495,8 @@ static class SearchDocumentAdapter implements SearchDocument { SearchDocumentAdapter(float score, Object[] sortValues, Map fields, Map> highlightFields, Document delegate, Map innerHits, - @Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable List matchedQueries) { + @Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, + @Nullable List matchedQueries) { this.score = score; this.sortValues = sortValues; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java index ca9c3366c..befede8d2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java @@ -15,9 +15,8 @@ */ package org.springframework.data.elasticsearch.core; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; @@ -138,13 +137,13 @@ public void shouldAdaptGetResultSource() { } @Test // DATAES-628, DATAES-848 - public void shouldAdaptSearchResponse() throws IOException { + public void shouldAdaptSearchResponse() { Map fields = Collections.singletonMap("field", new DocumentField("field", Collections.singletonList("value"))); SearchShardTarget shard = new SearchShardTarget("node", new ShardId("index", "uuid", 42), null, null); - SearchHit searchHit = new SearchHit(123, "my-id", new Text("type"), fields, null); + SearchHit searchHit = new SearchHit(123, "my-id", new Text("type"), null, fields); searchHit.shard(shard); searchHit.setSeqNo(1); searchHit.setPrimaryTerm(2); @@ -219,7 +218,7 @@ public void shouldAdaptSearchResponseSource() { BytesArray source = new BytesArray("{\"field\":\"value\"}"); SearchShardTarget shard = new SearchShardTarget("node", new ShardId("index", "uuid", 42), null, null); - SearchHit searchHit = new SearchHit(123, "my-id", new Text("type"), Collections.emptyMap(), null); + SearchHit searchHit = new SearchHit(123, "my-id", new Text("type"), null, null); searchHit.shard(shard); searchHit.sourceRef(source).score(42); searchHit.version(22); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SearchHitSupportTest.java b/src/test/java/org/springframework/data/elasticsearch/core/SearchHitSupportTest.java index a985b2cc4..2b4d75103 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SearchHitSupportTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SearchHitSupportTest.java @@ -59,11 +59,11 @@ void unwrapsSearchHitsIteratorToCloseableIteratorOfEntities() { void shouldReturnTheSameListInstanceInSearchHitsAndGetContent() { List> hits = new ArrayList<>(); - hits.add(new SearchHit<>(null, null, null, 0, null, null, "one")); - hits.add(new SearchHit<>(null, null, null, 0, null, null, "two")); - hits.add(new SearchHit<>(null, null, null, 0, null, null, "three")); - hits.add(new SearchHit<>(null, null, null, 0, null, null, "four")); - hits.add(new SearchHit<>(null, null, null, 0, null, null, "five")); + hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "one")); + hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "two")); + hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "three")); + hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "four")); + hits.add(new SearchHit<>(null, null, null, 0, null, null, null, null, null, null, "five")); SearchHits originalSearchHits = new SearchHitsImpl<>(hits.size(), TotalHitsRelation.EQUAL_TO, 0, "scroll", hits, null); @@ -112,7 +112,7 @@ public boolean hasNext() { @Override public SearchHit next() { String nextString = iterator.next(); - return new SearchHit<>("index", "id", null, 1.0f, new Object[0], emptyMap(), nextString); + return new SearchHit<>("index", "id", null, 1.0f, new Object[0], emptyMap(), null, null, null, null, nextString); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java b/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java index 2045fb499..580202780 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java @@ -62,7 +62,7 @@ public void shouldCallClearScrollOnIteratorClose() { } private SearchHit getOneSearchHit() { - return new SearchHit(null, null, null, 0, null, null, "one"); + return new SearchHit(null, null, null, 0, null, null, null, null, null, null, "one"); } @Test // DATAES-766 From b289d5f9747be4c2dc7a2b96bbbf7b12de6acfa0 Mon Sep 17 00:00:00 2001 From: peermuellerxw <43608581+peermuellerxw@users.noreply.github.com> Date: Sat, 13 Mar 2021 15:52:14 +0100 Subject: [PATCH 010/776] Add Rescore functionality. Original Pull Request #1688 Closes #1686 --- .../elasticsearch/core/RequestFactory.java | 37 ++++- .../core/query/AbstractQuery.java | 18 +++ .../core/query/NativeSearchQueryBuilder.java | 20 ++- .../data/elasticsearch/core/query/Query.java | 24 ++- .../core/query/RescorerQuery.java | 94 ++++++++++++ .../core/ElasticsearchTemplateTests.java | 72 ++++++++- .../core/RequestFactoryTests.java | 144 ++++++++++++++++++ 7 files changed, 402 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java 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 822b6cff7..3be0ee0ef 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import org.elasticsearch.action.DocWriteRequest; @@ -37,7 +38,6 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuilder; 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; @@ -65,7 +65,6 @@ import org.elasticsearch.client.indices.PutIndexTemplateRequest; import org.elasticsearch.client.indices.PutMappingRequest; import org.elasticsearch.common.geo.GeoDistance; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.VersionType; @@ -83,6 +82,8 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; +import org.elasticsearch.search.rescore.QueryRescoreMode; +import org.elasticsearch.search.rescore.QueryRescorerBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.GeoDistanceSortBuilder; import org.elasticsearch.search.sort.ScoreSortBuilder; @@ -106,6 +107,7 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; +import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode; import org.springframework.data.mapping.context.MappingContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -119,6 +121,7 @@ * @author Roman Puchkovskiy * @author Subhobrata Dey * @author Farid Faoudi + * @author Peer Mueller * @since 4.0 */ class RequestFactory { @@ -1050,6 +1053,9 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz sourceBuilder.searchAfter(query.getSearchAfter().toArray()); } + query.getRescorerQueries().forEach(rescorer -> sourceBuilder.addRescorer( + getQueryRescorerBuilder(rescorer))); + request.source(sourceBuilder); return request; } @@ -1136,6 +1142,9 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli searchRequestBuilder.searchAfter(query.getSearchAfter().toArray()); } + query.getRescorerQueries().forEach(rescorer -> searchRequestBuilder.addRescorer( + getQueryRescorerBuilder(rescorer))); + return searchRequestBuilder; } @@ -1260,6 +1269,30 @@ private SortBuilder getSortBuilder(Sort.Order order, @Nullable ElasticsearchP } } } + + private QueryRescorerBuilder getQueryRescorerBuilder(RescorerQuery rescorerQuery) { + + QueryRescorerBuilder builder = new QueryRescorerBuilder(Objects.requireNonNull(getQuery(rescorerQuery.getQuery()))); + + if (rescorerQuery.getScoreMode() != ScoreMode.Default) { + builder.setScoreMode(QueryRescoreMode.valueOf(rescorerQuery.getScoreMode().name())); + } + + if (rescorerQuery.getQueryWeight() != null) { + builder.setQueryWeight(rescorerQuery.getQueryWeight()); + } + + if (rescorerQuery.getRescoreQueryWeight() != null) { + builder.setRescoreQueryWeight(rescorerQuery.getRescoreQueryWeight()); + } + + if (rescorerQuery.getWindowSize() != null) { + builder.windowSize(rescorerQuery.getWindowSize()); + } + + return builder; + + } // endregion // region update diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index a00f2dcee..252638ef5 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -41,6 +41,7 @@ * @author Sascha Woo * @author Farid Azaza * @author Peter-Josef Meisch + * @author Peer Mueller */ abstract class AbstractQuery implements Query { @@ -63,6 +64,7 @@ abstract class AbstractQuery implements Query { @Nullable private TimeValue timeout; private boolean explain = false; @Nullable private List searchAfter; + protected List rescorerQueries = new ArrayList<>(); @Override @Nullable @@ -295,4 +297,20 @@ public void setSearchAfter(@Nullable List searchAfter) { public List getSearchAfter() { return searchAfter; } + + @Override + public void addRescorerQuery(RescorerQuery rescorerQuery) { + this.rescorerQueries.add(rescorerQuery); + } + + @Override + public void setRescorerQueries(List rescorerQueryList) { + this.rescorerQueries.clear(); + this.rescorerQueries.addAll(rescorerQueryList); + } + + @Override + public List getRescorerQueries() { + return rescorerQueries; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 45af3f1f5..7a74f41bf 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.IndicesOptions; @@ -28,6 +29,7 @@ import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; +import org.elasticsearch.search.rescore.QueryRescorerBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.springframework.data.domain.Pageable; import org.springframework.lang.Nullable; @@ -45,6 +47,7 @@ * @author Martin Choraine * @author Farid Azaza * @author Peter-Josef Meisch + * @author Peer Mueller */ public class NativeSearchQueryBuilder { @@ -70,6 +73,7 @@ public class NativeSearchQueryBuilder { @Nullable private Integer maxResults; @Nullable private Boolean trackTotalHits; @Nullable private TimeValue timeout; + private final List rescorerQueries = new ArrayList<>(); public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; @@ -183,12 +187,17 @@ public NativeSearchQueryBuilder withTrackTotalHits(Boolean trackTotalHits) { this.trackTotalHits = trackTotalHits; return this; } - - public NativeSearchQueryBuilder withTimeout(TimeValue timeout) { + + public NativeSearchQueryBuilder withTimeout(TimeValue timeout) { this.timeout = timeout; return this; } + public NativeSearchQueryBuilder withRescorerQuery(RescorerQuery rescorerQuery) { + this.rescorerQueries.add(rescorerQuery); + return this; + } + public NativeSearchQuery build() { NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryBuilder, filterBuilder, sortBuilders, @@ -250,11 +259,16 @@ public NativeSearchQuery build() { } nativeSearchQuery.setTrackTotalHits(trackTotalHits); - + if (timeout != null) { nativeSearchQuery.setTimeout(timeout); } + if (!isEmpty(rescorerQueries)) { + nativeSearchQuery.setRescorerQueries( + rescorerQueries); + } + return nativeSearchQuery; } } 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 4e93d0725..ee926d5dd 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 @@ -17,6 +17,7 @@ import java.time.Duration; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -41,6 +42,7 @@ * @author Christoph Strobl * @author Farid Azaza * @author Peter-Josef Meisch + * @author Peer Mueller */ public interface Query { @@ -297,7 +299,7 @@ default boolean getExplain() { /** * Sets the setSearchAfter objects for this query. - * + * * @param searchAfter the setSearchAfter objects. These are obtained with {@link SearchHit#getSortValues()} from a * search result. * @since 4.2 @@ -310,4 +312,24 @@ default boolean getExplain() { */ @Nullable List getSearchAfter(); + + /** + * Sets the {@link RescorerQuery}. + * + * @param rescorerQuery the query to add to the list of rescorer queries + * @since 4.2 + */ + void addRescorerQuery(RescorerQuery rescorerQuery); + + /** + * Sets the {@link RescorerQuery}. + * + * @param rescorerQueryList list of rescorer queries set + * @since 4.2 + */ + void setRescorerQueries(List rescorerQueryList); + + default List getRescorerQueries() { + return Collections.emptyList(); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java new file mode 100644 index 000000000..b8be4b9a6 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.query; + +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.search.rescore.QueryRescorerBuilder; +import org.springframework.lang.Nullable; + +/** + * Implementation of RescorerQuery to be used for rescoring filtered search results. + * + * @author Peer Mueller + * @since 4.2 + */ +public class RescorerQuery { + + private final Query query; + private ScoreMode scoreMode = ScoreMode.Default; + @Nullable private Integer windowSize; + @Nullable private Float queryWeight; + @Nullable private Float rescoreQueryWeight; + + public RescorerQuery(Query query) { + this.query = query; + } + + public Query getQuery() { + return query; + } + + public ScoreMode getScoreMode() { + return scoreMode; + } + + @Nullable + public Integer getWindowSize() { + return windowSize; + } + + @Nullable + public Float getQueryWeight() { + return queryWeight; + } + + @Nullable + public Float getRescoreQueryWeight() { + return rescoreQueryWeight; + } + + public RescorerQuery withScoreMode(ScoreMode scoreMode) { + this.scoreMode = scoreMode; + return this; + } + + public RescorerQuery withWindowSize(int windowSize) { + this.windowSize = windowSize; + return this; + } + + public RescorerQuery withQueryWeight(float queryWeight) { + this.queryWeight = queryWeight; + return this; + } + + public RescorerQuery withRescoreQueryWeight(float rescoreQueryWeight) { + this.rescoreQueryWeight = rescoreQueryWeight; + return this; + } + + + + public enum ScoreMode { + Default, + Avg, + Max, + Min, + Total, + Multiply + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index a83142205..915cab29b 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -53,12 +53,20 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.cluster.metadata.AliasMetadata; +import org.elasticsearch.common.lucene.search.function.CombineFunction; +import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.index.VersionType; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; +import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; import org.elasticsearch.join.query.ParentIdQueryBuilder; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; +import org.elasticsearch.search.rescore.QueryRescoreMode; +import org.elasticsearch.search.rescore.QueryRescorerBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; @@ -92,6 +100,7 @@ import org.springframework.data.elasticsearch.core.join.JoinField; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; +import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.util.StreamUtils; import org.springframework.lang.Nullable; @@ -121,6 +130,7 @@ * @author Roman Puchkovskiy * @author Subhobrata Dey * @author Farid Faoudi + * @author Peer Mueller */ @SpringIntegrationTest public abstract class ElasticsearchTemplateTests { @@ -3122,7 +3132,67 @@ void shouldReturnHighlightFieldsInSearchHit() { assertThat(highlightField.get(1)).contains("message"); } - @Test // DATAES-738 + @Test + void shouldRunRescoreQueryInSearchQuery() { + IndexCoordinates index = IndexCoordinates.of("test-index-rescore-entity-template"); + + // matches main query better + SampleEntity entity = SampleEntity.builder() // + .id("1") // + .message("some message") // + .rate(java.lang.Integer.MAX_VALUE) + .version(System.currentTimeMillis()) // + .build(); + + // high score from rescore query + SampleEntity entity2 = SampleEntity.builder() // + .id("2") // + .message("nothing") // + .rate(1) + .version(System.currentTimeMillis()) // + .build(); + + List indexQueries = getIndexQueries(Arrays.asList(entity, entity2)); + + operations.bulkIndex(indexQueries, index); + indexOperations.refresh(); + + NativeSearchQuery query = new NativeSearchQueryBuilder() // + .withQuery( + boolQuery().filter(existsQuery("rate")).should(termQuery("message", "message"))) // + .withRescorerQuery(new RescorerQuery( + new NativeSearchQueryBuilder().withQuery( + QueryBuilders.functionScoreQuery( + new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ + new FilterFunctionBuilder( + new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5) + .setWeight(1f)), + new FilterFunctionBuilder( + new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5) + .setWeight(100f))} + ) + .scoreMode(FunctionScoreQuery.ScoreMode.SUM) + .maxBoost(80f) + .boostMode(CombineFunction.REPLACE) + ).build() + ) + .withScoreMode(ScoreMode.Max) + .withWindowSize(100)) + .build(); + + SearchHits searchHits = operations.search(query, SampleEntity.class, index); + + assertThat(searchHits).isNotNull(); + assertThat(searchHits.getSearchHits()).hasSize(2); + + SearchHit searchHit = searchHits.getSearchHit(0); + assertThat(searchHit.getContent().getMessage()).isEqualTo("nothing"); + //score capped to 80 + assertThat(searchHit.getScore()).isEqualTo(80f); + } + + @Test + // DATAES-738 void shouldSaveEntityWithIndexCoordinates() { String id = "42"; SampleEntity entity = new SampleEntity(); 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 40a29a362..9aaf7cd31 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java @@ -39,10 +39,18 @@ import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.client.Client; import org.elasticsearch.client.indices.PutIndexTemplateRequest; +import org.elasticsearch.common.lucene.search.function.CombineFunction; +import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.MatchPhraseQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; +import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; +import org.elasticsearch.index.query.functionscore.ScriptScoreQueryBuilder; import org.json.JSONException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; @@ -70,12 +78,15 @@ import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.RescorerQuery; +import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.lang.Nullable; /** * @author Peter-Josef Meisch * @author Roman Puchkovskiy + * @author Peer Mueller */ @ExtendWith(MockitoExtension.class) class RequestFactoryTests { @@ -511,6 +522,139 @@ private String requestToString(ToXContent request) throws IOException { return XContentHelper.toXContent(request, XContentType.JSON, true).utf8ToString(); } + @Test + void shouldBuildSearchWithRescorerQuery() throws JSONException { + CriteriaQuery query = new CriteriaQuery(new Criteria("lastName").is("Smith")); + RescorerQuery rescorerQuery = new RescorerQuery( new NativeSearchQueryBuilder() // + .withQuery( + QueryBuilders.functionScoreQuery(new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ + new FilterFunctionBuilder(QueryBuilders.existsQuery("someField"), + new GaussDecayFunctionBuilder("someField", 0, 100000.0, null, 0.683) + .setWeight(5.022317f)), + new FilterFunctionBuilder(QueryBuilders.existsQuery("anotherField"), + new GaussDecayFunctionBuilder("anotherField", "202102", "31536000s", null, 0.683) + .setWeight(4.170836f))}) + .scoreMode(FunctionScoreQuery.ScoreMode.SUM) + .maxBoost(50.0f) + .boostMode(CombineFunction.AVG) + .boost(1.5f)) + .build() + ) + .withWindowSize(50) + .withQueryWeight(2.0f) + .withRescoreQueryWeight(5.0f) + .withScoreMode(ScoreMode.Multiply); + + RescorerQuery anotherRescorerQuery = new RescorerQuery(new NativeSearchQueryBuilder() // + .withQuery( + QueryBuilders.matchPhraseQuery("message", "the quick brown").slop(2)) + .build() + ) + .withWindowSize(100) + .withQueryWeight(0.7f) + .withRescoreQueryWeight(1.2f); + + query.addRescorerQuery(rescorerQuery); + query.addRescorerQuery(anotherRescorerQuery); + + converter.updateQuery(query, Person.class); + + String expected = '{' + // + " \"query\": {" + // + " \"bool\": {" + // + " \"must\": [" + // + " {" + // + " \"query_string\": {" + // + " \"query\": \"Smith\"," + // + " \"fields\": [" + // + " \"last-name^1.0\"" + // + " ]" + // + " }" + // + " }" + // + " ]" + // + " }" + // + " }," + // + " \"rescore\": [{\n" + + " \"window_size\" : 100,\n" + + " \"query\" : {\n" + + " \"rescore_query\" : {\n" + + " \"match_phrase\" : {\n" + + " \"message\" : {\n" + + " \"query\" : \"the quick brown\",\n" + + " \"slop\" : 2\n" + + " }\n" + + " }\n" + + " },\n" + + " \"query_weight\" : 0.7,\n" + + " \"rescore_query_weight\" : 1.2\n" + + " }\n" + + " }," + + " {\n" + + " \"window_size\": 50,\n" + + " \"query\": {\n" + + " \"rescore_query\": {\n" + + " \"function_score\": {\n" + + " \"query\": {\n" + + " \"match_all\": {\n" + + " \"boost\": 1.0\n" + + " }\n" + + " },\n" + + " \"functions\": [\n" + + " {\n" + + " \"filter\": {\n" + + " \"exists\": {\n" + + " \"field\": \"someField\",\n" + + " \"boost\": 1.0\n" + + " }\n" + + " },\n" + + " \"weight\": 5.022317,\n" + + " \"gauss\": {\n" + + " \"someField\": {\n" + + " \"origin\": 0.0,\n" + + " \"scale\": 100000.0,\n" + + " \"decay\": 0.683\n" + + " },\n" + + " \"multi_value_mode\": \"MIN\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"filter\": {\n" + + " \"exists\": {\n" + + " \"field\": \"anotherField\",\n" + + " \"boost\": 1.0\n" + + " }\n" + + " },\n" + + " \"weight\": 4.170836,\n" + + " \"gauss\": {\n" + + " \"anotherField\": {\n" + + " \"origin\": \"202102\",\n" + + " \"scale\": \"31536000s\",\n" + + " \"decay\": 0.683\n" + + " },\n" + + " \"multi_value_mode\": \"MIN\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"score_mode\": \"sum\",\n" + + " \"boost_mode\": \"avg\",\n" + + " \"max_boost\": 50.0,\n" + + " \"boost\": 1.5\n" + + " }\n" + + " },\n" + + " \"query_weight\": 2.0," + + " \"rescore_query_weight\": 5.0," + + " \"score_mode\": \"multiply\"" + + " }\n" + + " }\n" + + " ]\n" + + '}'; + + String searchRequest = requestFactory.searchRequest(query, Person.class, IndexCoordinates.of("persons")).source() + .toString(); + + assertEquals(expected, searchRequest, false); + } + @Data @Builder @NoArgsConstructor From 120eed02ee648adb2607f33487aa837da204314c Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 13 Mar 2021 17:50:33 +0100 Subject: [PATCH 011/776] Polishing. --- .../elasticsearch/core/RequestFactory.java | 14 +- .../core/query/AbstractQuery.java | 6 + .../core/query/NativeSearchQueryBuilder.java | 5 +- .../data/elasticsearch/core/query/Query.java | 11 +- .../core/query/RescorerQuery.java | 20 ++- .../core/ElasticsearchTemplateTests.java | 2 +- .../core/RequestFactoryTests.java | 130 +++++------------- 7 files changed, 71 insertions(+), 117 deletions(-) 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 3be0ee0ef..f8c3145d0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -355,7 +355,8 @@ public BulkRequestBuilder bulkRequestBuilder(Client client, List queries, Bul // region index management - public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { + public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings, + @Nullable Document mapping) { CreateIndexRequest request = new CreateIndexRequest(index.getIndexName()); if (settings != null && !settings.isEmpty()) { @@ -1053,8 +1054,7 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz sourceBuilder.searchAfter(query.getSearchAfter().toArray()); } - query.getRescorerQueries().forEach(rescorer -> sourceBuilder.addRescorer( - getQueryRescorerBuilder(rescorer))); + query.getRescorerQueries().forEach(rescorer -> sourceBuilder.addRescorer(getQueryRescorerBuilder(rescorer))); request.source(sourceBuilder); return request; @@ -1142,8 +1142,7 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli searchRequestBuilder.searchAfter(query.getSearchAfter().toArray()); } - query.getRescorerQueries().forEach(rescorer -> searchRequestBuilder.addRescorer( - getQueryRescorerBuilder(rescorer))); + query.getRescorerQueries().forEach(rescorer -> searchRequestBuilder.addRescorer(getQueryRescorerBuilder(rescorer))); return searchRequestBuilder; } @@ -1272,7 +1271,10 @@ private SortBuilder getSortBuilder(Sort.Order order, @Nullable ElasticsearchP private QueryRescorerBuilder getQueryRescorerBuilder(RescorerQuery rescorerQuery) { - QueryRescorerBuilder builder = new QueryRescorerBuilder(Objects.requireNonNull(getQuery(rescorerQuery.getQuery()))); + QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery()); + Assert.notNull("queryBuilder", "Could not build query for rescorerQuery"); + + QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder); if (rescorerQuery.getScoreMode() != ScoreMode.Default) { builder.setScoreMode(QueryRescoreMode.valueOf(rescorerQuery.getScoreMode().name())); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index 252638ef5..8384d757a 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -300,11 +300,17 @@ public List getSearchAfter() { @Override public void addRescorerQuery(RescorerQuery rescorerQuery) { + + Assert.notNull(rescorerQuery, "rescorerQuery must not be null"); + this.rescorerQueries.add(rescorerQuery); } @Override public void setRescorerQueries(List rescorerQueryList) { + + Assert.notNull(rescorerQueries, "rescorerQueries must not be null"); + this.rescorerQueries.clear(); this.rescorerQueries.addAll(rescorerQueryList); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 7a74f41bf..6d8852e35 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.stream.Collectors; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.IndicesOptions; @@ -29,7 +28,6 @@ import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.search.rescore.QueryRescorerBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.springframework.data.domain.Pageable; import org.springframework.lang.Nullable; @@ -265,8 +263,7 @@ public NativeSearchQuery build() { } if (!isEmpty(rescorerQueries)) { - nativeSearchQuery.setRescorerQueries( - rescorerQueries); + nativeSearchQuery.setRescorerQueries(rescorerQueries); } return nativeSearchQuery; 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 ee926d5dd..25aeb0bf0 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 @@ -314,9 +314,9 @@ default boolean getExplain() { List getSearchAfter(); /** - * Sets the {@link RescorerQuery}. + * Adds a {@link RescorerQuery}. * - * @param rescorerQuery the query to add to the list of rescorer queries + * @param rescorerQuery the query to add to the list of rescorer queries, must not be {@literal null} * @since 4.2 */ void addRescorerQuery(RescorerQuery rescorerQuery); @@ -324,11 +324,16 @@ default boolean getExplain() { /** * Sets the {@link RescorerQuery}. * - * @param rescorerQueryList list of rescorer queries set + * @param rescorerQueryList list of rescorer queries set, must not be {@literal null}. * @since 4.2 */ void setRescorerQueries(List rescorerQueryList); + /** + * get the list of {@link RescorerQuery}s + * + * @since 4.2 + */ default List getRescorerQueries() { return Collections.emptyList(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java index b8be4b9a6..0d25b17f8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/RescorerQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,8 @@ */ package org.springframework.data.elasticsearch.core.query; -import org.elasticsearch.index.query.QueryBuilder; -import org.elasticsearch.search.rescore.QueryRescorerBuilder; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Implementation of RescorerQuery to be used for rescoring filtered search results. @@ -34,6 +33,9 @@ public class RescorerQuery { @Nullable private Float rescoreQueryWeight; public RescorerQuery(Query query) { + + Assert.notNull(query, "query must not be null"); + this.query = query; } @@ -61,6 +63,9 @@ public Float getRescoreQueryWeight() { } public RescorerQuery withScoreMode(ScoreMode scoreMode) { + + Assert.notNull(scoreMode, "scoreMode must not be null"); + this.scoreMode = scoreMode; return this; } @@ -80,15 +85,8 @@ public RescorerQuery withRescoreQueryWeight(float rescoreQueryWeight) { return this; } - - public enum ScoreMode { - Default, - Avg, - Max, - Min, - Total, - Multiply + Default, Avg, Max, Min, Total, Multiply } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 915cab29b..68fd8085b 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -3132,7 +3132,7 @@ void shouldReturnHighlightFieldsInSearchHit() { assertThat(highlightField.get(1)).contains("message"); } - @Test + @Test // #1686 void shouldRunRescoreQueryInSearchQuery() { IndexCoordinates index = IndexCoordinates.of("test-index-rescore-entity-template"); 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 9aaf7cd31..b06b7edbf 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java @@ -45,12 +45,10 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.query.MatchPhraseQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; import org.elasticsearch.index.query.functionscore.GaussDecayFunctionBuilder; -import org.elasticsearch.index.query.functionscore.ScriptScoreQueryBuilder; import org.json.JSONException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; @@ -522,37 +520,24 @@ private String requestToString(ToXContent request) throws IOException { return XContentHelper.toXContent(request, XContentType.JSON, true).utf8ToString(); } - @Test + @Test // #1686 void shouldBuildSearchWithRescorerQuery() throws JSONException { CriteriaQuery query = new CriteriaQuery(new Criteria("lastName").is("Smith")); - RescorerQuery rescorerQuery = new RescorerQuery( new NativeSearchQueryBuilder() // - .withQuery( - QueryBuilders.functionScoreQuery(new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ + RescorerQuery rescorerQuery = new RescorerQuery(new NativeSearchQueryBuilder() // + .withQuery(QueryBuilders + .functionScoreQuery(new FunctionScoreQueryBuilder.FilterFunctionBuilder[] { new FilterFunctionBuilder(QueryBuilders.existsQuery("someField"), - new GaussDecayFunctionBuilder("someField", 0, 100000.0, null, 0.683) - .setWeight(5.022317f)), + new GaussDecayFunctionBuilder("someField", 0, 100000.0, null, 0.683).setWeight(5.022317f)), new FilterFunctionBuilder(QueryBuilders.existsQuery("anotherField"), new GaussDecayFunctionBuilder("anotherField", "202102", "31536000s", null, 0.683) - .setWeight(4.170836f))}) - .scoreMode(FunctionScoreQuery.ScoreMode.SUM) - .maxBoost(50.0f) - .boostMode(CombineFunction.AVG) - .boost(1.5f)) - .build() - ) - .withWindowSize(50) - .withQueryWeight(2.0f) - .withRescoreQueryWeight(5.0f) - .withScoreMode(ScoreMode.Multiply); + .setWeight(4.170836f)) }) + .scoreMode(FunctionScoreQuery.ScoreMode.SUM).maxBoost(50.0f).boostMode(CombineFunction.AVG).boost(1.5f)) + .build()).withWindowSize(50).withQueryWeight(2.0f).withRescoreQueryWeight(5.0f) + .withScoreMode(ScoreMode.Multiply); RescorerQuery anotherRescorerQuery = new RescorerQuery(new NativeSearchQueryBuilder() // - .withQuery( - QueryBuilders.matchPhraseQuery("message", "the quick brown").slop(2)) - .build() - ) - .withWindowSize(100) - .withQueryWeight(0.7f) - .withRescoreQueryWeight(1.2f); + .withQuery(QueryBuilders.matchPhraseQuery("message", "the quick brown").slop(2)).build()).withWindowSize(100) + .withQueryWeight(0.7f).withRescoreQueryWeight(1.2f); query.addRescorerQuery(rescorerQuery); query.addRescorerQuery(anotherRescorerQuery); @@ -574,79 +559,40 @@ void shouldBuildSearchWithRescorerQuery() throws JSONException { " ]" + // " }" + // " }," + // - " \"rescore\": [{\n" - + " \"window_size\" : 100,\n" - + " \"query\" : {\n" - + " \"rescore_query\" : {\n" - + " \"match_phrase\" : {\n" - + " \"message\" : {\n" - + " \"query\" : \"the quick brown\",\n" - + " \"slop\" : 2\n" - + " }\n" - + " }\n" - + " },\n" - + " \"query_weight\" : 0.7,\n" - + " \"rescore_query_weight\" : 1.2\n" - + " }\n" - + " }," - + " {\n" - + " \"window_size\": 50,\n" - + " \"query\": {\n" - + " \"rescore_query\": {\n" - + " \"function_score\": {\n" - + " \"query\": {\n" - + " \"match_all\": {\n" - + " \"boost\": 1.0\n" - + " }\n" - + " },\n" - + " \"functions\": [\n" - + " {\n" - + " \"filter\": {\n" + " \"rescore\": [{\n" + " \"window_size\" : 100,\n" + " \"query\" : {\n" + + " \"rescore_query\" : {\n" + " \"match_phrase\" : {\n" + " \"message\" : {\n" + + " \"query\" : \"the quick brown\",\n" + " \"slop\" : 2\n" + + " }\n" + " }\n" + " },\n" + " \"query_weight\" : 0.7,\n" + + " \"rescore_query_weight\" : 1.2\n" + " }\n" + " }," + " {\n" + " \"window_size\": 50,\n" + + " \"query\": {\n" + " \"rescore_query\": {\n" + " \"function_score\": {\n" + + " \"query\": {\n" + " \"match_all\": {\n" + + " \"boost\": 1.0\n" + " }\n" + + " },\n" + " \"functions\": [\n" + + " {\n" + " \"filter\": {\n" + " \"exists\": {\n" + " \"field\": \"someField\",\n" - + " \"boost\": 1.0\n" - + " }\n" - + " },\n" - + " \"weight\": 5.022317,\n" - + " \"gauss\": {\n" - + " \"someField\": {\n" + + " \"boost\": 1.0\n" + " }\n" + + " },\n" + " \"weight\": 5.022317,\n" + + " \"gauss\": {\n" + " \"someField\": {\n" + " \"origin\": 0.0,\n" + " \"scale\": 100000.0,\n" - + " \"decay\": 0.683\n" - + " },\n" - + " \"multi_value_mode\": \"MIN\"\n" - + " }\n" - + " },\n" - + " {\n" - + " \"filter\": {\n" - + " \"exists\": {\n" + + " \"decay\": 0.683\n" + " },\n" + + " \"multi_value_mode\": \"MIN\"\n" + " }\n" + + " },\n" + " {\n" + + " \"filter\": {\n" + " \"exists\": {\n" + " \"field\": \"anotherField\",\n" - + " \"boost\": 1.0\n" - + " }\n" - + " },\n" - + " \"weight\": 4.170836,\n" - + " \"gauss\": {\n" - + " \"anotherField\": {\n" + + " \"boost\": 1.0\n" + " }\n" + + " },\n" + " \"weight\": 4.170836,\n" + + " \"gauss\": {\n" + " \"anotherField\": {\n" + " \"origin\": \"202102\",\n" + " \"scale\": \"31536000s\",\n" - + " \"decay\": 0.683\n" - + " },\n" - + " \"multi_value_mode\": \"MIN\"\n" - + " }\n" - + " }\n" - + " ],\n" - + " \"score_mode\": \"sum\",\n" - + " \"boost_mode\": \"avg\",\n" - + " \"max_boost\": 50.0,\n" - + " \"boost\": 1.5\n" - + " }\n" - + " },\n" - + " \"query_weight\": 2.0," - + " \"rescore_query_weight\": 5.0," - + " \"score_mode\": \"multiply\"" - + " }\n" - + " }\n" - + " ]\n" + + " \"decay\": 0.683\n" + " },\n" + + " \"multi_value_mode\": \"MIN\"\n" + " }\n" + + " }\n" + " ],\n" + + " \"score_mode\": \"sum\",\n" + " \"boost_mode\": \"avg\",\n" + + " \"max_boost\": 50.0,\n" + " \"boost\": 1.5\n" + + " }\n" + " },\n" + " \"query_weight\": 2.0," + + " \"rescore_query_weight\": 5.0," + " \"score_mode\": \"multiply\"" + " }\n" + " }\n" + " ]\n" + '}'; String searchRequest = requestFactory.searchRequest(query, Person.class, IndexCoordinates.of("persons")).source() From 005d6a4d6ff1a868d67335d45ee53728ac38206b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Vasek?= Date: Tue, 16 Mar 2021 21:37:55 +0100 Subject: [PATCH 012/776] Added SearchTemplate support for reactive client Original Pull Request #1726 Closes #1725 --- .../DefaultReactiveElasticsearchClient.java | 9 +++ .../reactive/ReactiveElasticsearchClient.java | 38 ++++++++++++ .../client/reactive/RequestCreator.java | 5 ++ .../client/util/RequestConverters.java | 16 +++++ .../elasticsearch/core/SearchOperations.java | 2 +- .../core/query/NativeSearchQuery.java | 10 ++++ .../core/query/NativeSearchQueryBuilder.java | 11 ++++ ...veElasticsearchClientIntegrationTests.java | 59 +++++++++++++++++++ 8 files changed, 149 insertions(+), 1 deletion(-) 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 af02927b7..8e7f6a08f 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 @@ -92,6 +92,8 @@ import org.elasticsearch.index.reindex.UpdateByQueryRequest; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.script.mustache.SearchTemplateRequest; +import org.elasticsearch.script.mustache.SearchTemplateResponse; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.Aggregation; @@ -404,6 +406,13 @@ public Mono count(HttpHeaders headers, SearchRequest searchRequest) { .next(); } + @Override + public Flux searchTemplate(HttpHeaders headers, SearchTemplateRequest searchTemplateRequest) { + return sendRequest(searchTemplateRequest, requestCreator.searchTemplate(), SearchTemplateResponse.class, headers) + .map(r -> r.getResponse().getHits()) + .flatMap(Flux::fromIterable); + } + /* * (non-Javadoc) * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest) 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 dcc430758..9e407c10f 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 @@ -52,6 +52,7 @@ import org.elasticsearch.index.reindex.BulkByScrollResponse; import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequest; +import org.elasticsearch.script.mustache.SearchTemplateRequest; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.suggest.Suggest; @@ -385,6 +386,43 @@ default Mono count(SearchRequest searchRequest) { */ Mono count(HttpHeaders headers, SearchRequest searchRequest); + /** + * Executes a {@link SearchTemplateRequest} against the {@literal search template} API. + * + * @param consumer must not be {@literal null}. + * @see Search Template + * API on elastic.co + * @return the {@link Flux} emitting {@link SearchHit hits} one by one. + */ + default Flux searchTemplate(Consumer consumer) { + SearchTemplateRequest request = new SearchTemplateRequest(); + consumer.accept(request); + return searchTemplate(request); + } + + /** + * Executes a {@link SearchTemplateRequest} against the {@literal search template} API. + * + * @param searchTemplateRequest must not be {@literal null}. + * @see Search Template + * API on elastic.co + * @return the {@link Flux} emitting {@link SearchHit hits} one by one. + */ + default Flux searchTemplate(SearchTemplateRequest searchTemplateRequest) { + return searchTemplate(HttpHeaders.EMPTY, searchTemplateRequest); + } + + /** + * Executes a {@link SearchTemplateRequest} against the {@literal search template} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param searchTemplateRequest must not be {@literal null}. + * @see Search Template + * API on elastic.co + * @return the {@link Flux} emitting {@link SearchHit hits} one by one. + */ + Flux searchTemplate(HttpHeaders headers, SearchTemplateRequest searchTemplateRequest); + /** * Execute a {@link SearchRequest} against the {@literal search} API. * 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 abe82762d..1d5363137 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 @@ -34,6 +34,7 @@ import org.elasticsearch.client.indices.PutMappingRequest; import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequest; +import org.elasticsearch.script.mustache.SearchTemplateRequest; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.client.util.RequestConverters; @@ -49,6 +50,10 @@ default Function search() { return RequestConverters::search; } + default Function searchTemplate() { + return RequestConverters::searchTemplate; + } + default Function scroll() { return RequestConverters::searchScroll; } 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 cc0e81811..dadbd4d79 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 @@ -98,6 +98,7 @@ import org.elasticsearch.index.reindex.ReindexRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequest; import org.elasticsearch.index.seqno.SequenceNumbers; +import org.elasticsearch.script.mustache.SearchTemplateRequest; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.tasks.TaskId; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; @@ -411,6 +412,21 @@ public static Request search(SearchRequest searchRequest) { return request; } + public static Request searchTemplate(SearchTemplateRequest templateRequest) { + SearchRequest searchRequest = templateRequest.getRequest(); + + String endpoint = new EndpointBuilder().addCommaSeparatedPathParts(templateRequest.getRequest().indices()) + .addPathPart("_search").addPathPart("template").build(); + + Request request = new Request(HttpMethod.GET.name(), endpoint); + Params params = new Params(request); + addSearchRequestParams(params, searchRequest); + + request.setEntity(createEntity(templateRequest, REQUEST_BODY_CONTENT_TYPE)); + + return request; + } + /** * Creates a count request. * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java index 04c9b38d3..2de504a38 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java @@ -68,7 +68,7 @@ default long count(Query query, IndexCoordinates index) { * Does a suggest query * * @param suggestion the query - * @param the entity class + * @param clazz the entity class * @return the suggest response * @since 4.1 */ diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java index 8c592650d..5201ea73b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java @@ -20,6 +20,7 @@ import java.util.List; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; @@ -50,6 +51,7 @@ public class NativeSearchQuery extends AbstractQuery { @Nullable private HighlightBuilder highlightBuilder; @Nullable private HighlightBuilder.Field[] highlightFields; @Nullable private List indicesBoost; + @Nullable private SearchTemplateRequestBuilder searchTemplate; public NativeSearchQuery(@Nullable QueryBuilder query) { @@ -163,4 +165,12 @@ public void setIndicesBoost(List indicesBoost) { this.indicesBoost = indicesBoost; } + @Nullable + public SearchTemplateRequestBuilder getSearchTemplate() { + return searchTemplate; + } + + public void setSearchTemplate(@Nullable SearchTemplateRequestBuilder searchTemplate) { + this.searchTemplate = searchTemplate; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 6d8852e35..e9d6ef205 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -25,6 +25,7 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; @@ -61,6 +62,7 @@ public class NativeSearchQueryBuilder { @Nullable private SourceFilter sourceFilter; @Nullable private CollapseBuilder collapseBuilder; @Nullable private List indicesBoost; + @Nullable private SearchTemplateRequestBuilder searchTemplateBuilder; private float minScore; private boolean trackScores; @Nullable private Collection ids; @@ -118,6 +120,11 @@ public NativeSearchQueryBuilder withIndicesBoost(List indicesBoost) return this; } + public NativeSearchQueryBuilder withSearchTemplate(SearchTemplateRequestBuilder searchTemplateBuilder){ + this.searchTemplateBuilder = searchTemplateBuilder; + return this; + } + public NativeSearchQueryBuilder withPageable(Pageable pageable) { this.pageable = pageable; return this; @@ -216,6 +223,10 @@ public NativeSearchQuery build() { nativeSearchQuery.setIndicesBoost(indicesBoost); } + if (searchTemplateBuilder != null) { + nativeSearchQuery.setSearchTemplate(searchTemplateBuilder); + } + if (!isEmpty(scriptFields)) { nativeSearchQuery.setScriptFields(scriptFields); } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java index 1bfa6053a..3e866f275 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java @@ -52,6 +52,7 @@ import org.elasticsearch.rest.RestStatus; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; +import org.elasticsearch.script.mustache.SearchTemplateRequest; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; import org.elasticsearch.search.builder.SearchSourceBuilder; @@ -73,6 +74,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; /** @@ -434,6 +436,63 @@ public void deleteShouldReturnNotFoundForNonExistingDocument() { .verifyComplete(); } + @Test // #1725 + public void inlineSearchTemplateShouldFindMatchingDocuments() { + + addSourceDocument().to(INDEX_I); + addSourceDocument().to(INDEX_I); + + Map testDoc = new LinkedHashMap<>(); + testDoc.put("firstname", "inline"); + testDoc.put("lastname", "template"); + add(testDoc).to(INDEX_I); + + SearchTemplateRequest request = new SearchTemplateRequest(new SearchRequest(INDEX_I)); + request.setScriptType(ScriptType.INLINE); + request.setScript("{\"query\":{\"match\":{\"firstname\":\"{{firstname}}\"}}}"); + Map params = new LinkedHashMap<>(); + params.put("firstname", "inline"); + request.setScriptParams(params); + + client.searchTemplate(request) + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); + } + + @Test // #1725 + public void storedSearchTemplateShouldFindMatchingDocuments() { + + addSourceDocument().to(INDEX_I); + addSourceDocument().to(INDEX_I); + + Map testDoc = new LinkedHashMap<>(); + testDoc.put("firstname", "stored"); + testDoc.put("lastname", "template"); + add(testDoc).to(INDEX_I); + + client.execute(c -> c.post() + .uri(builder -> builder.path("_scripts/searchbyfirstname").build()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue( + "{\"script\":{\"lang\":\"mustache\",\"source\":{\"query\":{\"match\":{\"firstname\":\"{{firstname}}\"}}}}}") + .retrieve() + .bodyToMono(Void.class)) + .block(); + + SearchTemplateRequest request = new SearchTemplateRequest(new SearchRequest(INDEX_I)); + request.setScriptType(ScriptType.STORED); + request.setScript("searchbyfirstname"); + Map params = new LinkedHashMap<>(); + params.put("firstname", "stored"); + request.setScriptParams(params); + + client.searchTemplate(request) + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); + } + @Test // DATAES-488 public void searchShouldFindExistingDocuments() { From 98a8d1a5ac0dcb33ab917de1dfe62b2379f377a1 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Tue, 16 Mar 2021 22:36:57 +0100 Subject: [PATCH 013/776] Polishing --- .../DefaultReactiveElasticsearchClient.java | 12 ++-- .../reactive/ReactiveElasticsearchClient.java | 6 +- .../client/util/RequestConverters.java | 58 +++++++++---------- .../core/query/NativeSearchQueryBuilder.java | 2 +- ...veElasticsearchClientIntegrationTests.java | 28 ++++----- 5 files changed, 50 insertions(+), 56 deletions(-) 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 8e7f6a08f..3c9446d61 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 @@ -22,7 +22,6 @@ import io.netty.handler.ssl.JdkSslContext; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; -import org.elasticsearch.action.get.MultiGetItemResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; @@ -64,6 +63,7 @@ import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.index.IndexRequest; @@ -409,8 +409,7 @@ public Mono count(HttpHeaders headers, SearchRequest searchRequest) { @Override public Flux searchTemplate(HttpHeaders headers, SearchTemplateRequest searchTemplateRequest) { return sendRequest(searchTemplateRequest, requestCreator.searchTemplate(), SearchTemplateResponse.class, headers) - .map(r -> r.getResponse().getHits()) - .flatMap(Flux::fromIterable); + .map(response -> response.getResponse().getHits()).flatMap(Flux::fromIterable); } /* @@ -876,10 +875,9 @@ private Publisher handleServerError(Request request, ClientResp String mediaType = response.headers().contentType().map(MediaType::toString).orElse(XContentType.JSON.mediaType()); return response.body(BodyExtractors.toMono(byte[].class)) // - .switchIfEmpty(Mono - .error(new ElasticsearchStatusException(String.format("%s request to %s returned error code %s and no body.", - request.getMethod(), request.getEndpoint(), statusCode), status)) - ) + .switchIfEmpty(Mono.error( + new ElasticsearchStatusException(String.format("%s request to %s returned error code %s and no body.", + request.getMethod(), request.getEndpoint(), statusCode), status))) .map(bytes -> new String(bytes, StandardCharsets.UTF_8)) // .flatMap(content -> contentOrError(content, mediaType, status)) .flatMap(unused -> Mono 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 9e407c10f..c8f4ae9f2 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 @@ -391,7 +391,7 @@ default Mono count(SearchRequest searchRequest) { * * @param consumer must not be {@literal null}. * @see Search Template - * API on elastic.co + * API on elastic.co * @return the {@link Flux} emitting {@link SearchHit hits} one by one. */ default Flux searchTemplate(Consumer consumer) { @@ -405,7 +405,7 @@ default Flux searchTemplate(Consumer consumer) * * @param searchTemplateRequest must not be {@literal null}. * @see Search Template - * API on elastic.co + * API on elastic.co * @return the {@link Flux} emitting {@link SearchHit hits} one by one. */ default Flux searchTemplate(SearchTemplateRequest searchTemplateRequest) { @@ -418,7 +418,7 @@ default Flux searchTemplate(SearchTemplateRequest searchTemplateReque * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. * @param searchTemplateRequest must not be {@literal null}. * @see Search Template - * API on elastic.co + * API on elastic.co * @return the {@link Flux} emitting {@link SearchHit hits} one by one. */ Flux searchTemplate(HttpHeaders headers, SearchTemplateRequest searchTemplateRequest); 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 dadbd4d79..ea2e5df32 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 @@ -416,7 +416,7 @@ public static Request searchTemplate(SearchTemplateRequest templateRequest) { SearchRequest searchRequest = templateRequest.getRequest(); String endpoint = new EndpointBuilder().addCommaSeparatedPathParts(templateRequest.getRequest().indices()) - .addPathPart("_search").addPathPart("template").build(); + .addPathPart("_search").addPathPart("template").build(); Request request = new Request(HttpMethod.GET.name(), endpoint); Params params = new Params(request); @@ -751,21 +751,21 @@ public static Request indexExists(GetIndexRequest getIndexRequest) { return request; } - public static Request indexExists(org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) { - // this can be called with no indices as argument by transport client, not via REST though - if (getIndexRequest.indices() == null || getIndexRequest.indices().length == 0) { - throw new IllegalArgumentException("indices are mandatory"); - } - String endpoint = endpoint(getIndexRequest.indices(), ""); - Request request = new Request(HttpMethod.HEAD.name(), endpoint); + public static Request indexExists(org.elasticsearch.client.indices.GetIndexRequest getIndexRequest) { + // this can be called with no indices as argument by transport client, not via REST though + if (getIndexRequest.indices() == null || getIndexRequest.indices().length == 0) { + throw new IllegalArgumentException("indices are mandatory"); + } + String endpoint = endpoint(getIndexRequest.indices(), ""); + Request request = new Request(HttpMethod.HEAD.name(), endpoint); - Params params = new Params(request); - params.withLocal(getIndexRequest.local()); - params.withHuman(getIndexRequest.humanReadable()); - params.withIndicesOptions(getIndexRequest.indicesOptions()); - params.withIncludeDefaults(getIndexRequest.includeDefaults()); - return request; - } + Params params = new Params(request); + params.withLocal(getIndexRequest.local()); + params.withHuman(getIndexRequest.humanReadable()); + params.withIndicesOptions(getIndexRequest.indicesOptions()); + params.withIncludeDefaults(getIndexRequest.includeDefaults()); + return request; + } public static Request indexOpen(OpenIndexRequest openIndexRequest) { String endpoint = RequestConverters.endpoint(openIndexRequest.indices(), "_open"); @@ -804,7 +804,7 @@ public static Request indexCreate(CreateIndexRequest createIndexRequest) { } public static Request indexCreate(org.elasticsearch.client.indices.CreateIndexRequest createIndexRequest) { - String endpoint = RequestConverters.endpoint(new String[]{createIndexRequest.index()}); + String endpoint = RequestConverters.endpoint(new String[] { createIndexRequest.index() }); Request request = new Request(HttpMethod.PUT.name(), endpoint); Params parameters = new Params(request); @@ -846,17 +846,17 @@ public static Request putMapping(PutMappingRequest putMappingRequest) { return request; } - public static Request putMapping(org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) { - Request request = new Request(HttpMethod.PUT.name(), - RequestConverters.endpoint(putMappingRequest.indices(), "_mapping")); + public static Request putMapping(org.elasticsearch.client.indices.PutMappingRequest putMappingRequest) { + Request request = new Request(HttpMethod.PUT.name(), + RequestConverters.endpoint(putMappingRequest.indices(), "_mapping")); - new RequestConverters.Params(request) // - .withTimeout(putMappingRequest.timeout()) // - .withMasterTimeout(putMappingRequest.masterNodeTimeout()) // - .withIncludeTypeName(false); - request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE)); - return request; - } + new RequestConverters.Params(request) // + .withTimeout(putMappingRequest.timeout()) // + .withMasterTimeout(putMappingRequest.masterNodeTimeout()) // + .withIncludeTypeName(false); + request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE)); + return request; + } public static Request flushIndex(FlushRequest flushRequest) { String[] indices = flushRequest.indices() == null ? Strings.EMPTY_ARRAY : flushRequest.indices(); @@ -883,10 +883,10 @@ public static Request getMapping(GetMappingsRequest getMappingsRequest) { return request; } - public static Request getMapping(org.elasticsearch.client.indices.GetMappingsRequest getMappingsRequest) { - String[] indices = getMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.indices(); + public static Request getMapping(org.elasticsearch.client.indices.GetMappingsRequest getMappingsRequest) { + String[] indices = getMappingsRequest.indices() == null ? Strings.EMPTY_ARRAY : getMappingsRequest.indices(); - Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_mapping")); + Request request = new Request(HttpMethod.GET.name(), RequestConverters.endpoint(indices, "_mapping")); RequestConverters.Params parameters = new RequestConverters.Params(request); parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index e9d6ef205..33abb43b1 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -120,7 +120,7 @@ public NativeSearchQueryBuilder withIndicesBoost(List indicesBoost) return this; } - public NativeSearchQueryBuilder withSearchTemplate(SearchTemplateRequestBuilder searchTemplateBuilder){ + public NativeSearchQueryBuilder withSearchTemplate(SearchTemplateRequestBuilder searchTemplateBuilder) { this.searchTemplateBuilder = searchTemplateBuilder; return this; } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java index 3e866f275..d4aafe4e6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java @@ -454,10 +454,7 @@ public void inlineSearchTemplateShouldFindMatchingDocuments() { params.put("firstname", "inline"); request.setScriptParams(params); - client.searchTemplate(request) - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); + client.searchTemplate(request).as(StepVerifier::create).expectNextCount(1).verifyComplete(); } @Test // #1725 @@ -471,14 +468,13 @@ public void storedSearchTemplateShouldFindMatchingDocuments() { testDoc.put("lastname", "template"); add(testDoc).to(INDEX_I); - client.execute(c -> c.post() - .uri(builder -> builder.path("_scripts/searchbyfirstname").build()) - .contentType(MediaType.APPLICATION_JSON) - .bodyValue( - "{\"script\":{\"lang\":\"mustache\",\"source\":{\"query\":{\"match\":{\"firstname\":\"{{firstname}}\"}}}}}") - .retrieve() - .bodyToMono(Void.class)) - .block(); + client.execute(c -> c.post().uri(builder -> builder.path("_scripts/searchbyfirstname").build()) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue( + "{\"script\":{\"lang\":\"mustache\",\"source\":{\"query\":{\"match\":{\"firstname\":\"{{firstname}}\"}}}}}") + .retrieve().bodyToMono(Void.class)).block(); + + client.indices().refreshIndex(request -> request.indices(INDEX_I)).block(); SearchTemplateRequest request = new SearchTemplateRequest(new SearchRequest(INDEX_I)); request.setScriptType(ScriptType.STORED); @@ -487,10 +483,10 @@ public void storedSearchTemplateShouldFindMatchingDocuments() { params.put("firstname", "stored"); request.setScriptParams(params); - client.searchTemplate(request) - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); + client.searchTemplate(request) // + .as(StepVerifier::create) // + .expectNextCount(1) // + .verifyComplete(); // } @Test // DATAES-488 From 83314880e4450b04d121e697e47cdd9ac1868ece Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 10:21:07 +0100 Subject: [PATCH 014/776] Updated changelog. See #1697 --- src/main/resources/changelog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 323d3fbde..ab017b06b 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,11 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.0.8.RELEASE (2021-03-17) +--------------------------------------------- +* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. + + Changes in version 4.2.0-M4 (2021-02-18) ---------------------------------------- @@ -1528,5 +1533,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 6e3579d1fed9ec25d495fac49b9afba96b942056 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 10:53:42 +0100 Subject: [PATCH 015/776] Updated changelog. See #1702 --- src/main/resources/changelog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index ab017b06b..d3f31b4ef 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,11 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.1.6 (2021-03-17) +------------------------------------- +* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. + + Changes in version 4.0.8.RELEASE (2021-03-17) --------------------------------------------- * #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. @@ -1534,5 +1539,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 28fe9ee25bdf30223407a728267ad7883cdf41eb Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 11:17:25 +0100 Subject: [PATCH 016/776] Updated changelog. See #1709 --- src/main/resources/changelog.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index d3f31b4ef..dde5281c4 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,23 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.2.0-M5 (2021-03-17) +---------------------------------------- +* #1725 - Add support for SearchTemplate for reactive client. +* #1721 - IndexOps.getMapping raises exception if mapping contains "dynamic_templates". +* #1718 - Create index with mapping in one step. +* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. +* #1711 - Add the type hint _class attribute to the index mapping. +* #1704 - Add SearchFailure field in ByQueryResponse. +* #1700 - Add missing "Document ranking types". +* #1687 - Upgrade to Elasticsearch 7.11. +* #1686 - Add rescore functionality. +* #1678 - Errors are silent in multiGet. +* #1658 - ReactiveElasticsearchClient should use the same request parameters as non-reactive code. +* #1646 - Add function to list all indexes. +* #1514 - Add `matched_queries` field in SearchHit [DATAES-979]. + + Changes in version 4.1.6 (2021-03-17) ------------------------------------- * #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. @@ -1540,5 +1557,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From a89fd89d1254107e5058f62826adf0e8c6ba4d57 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 11:17:27 +0100 Subject: [PATCH 017/776] Prepare 4.2 M5 (2021.0.0). See #1709 --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 191786d37..4ac675bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.5.0-SNAPSHOT + 2.5.0-M5 Spring Data Elasticsearch @@ -22,7 +22,7 @@ 7.11.1 2.13.3 4.1.52.Final - 2.5.0-SNAPSHOT + 2.5.0-M5 1.15.1 spring.data.elasticsearch @@ -432,8 +432,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-milestone + https://repo.spring.io/libs-milestone diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 666635610..0ac44c445 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Elasticsearch 4.2 M4 (2021.0.0) +Spring Data Elasticsearch 4.2 M5 (2021.0.0) Copyright (c) [2013-2021] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -24,3 +24,4 @@ conditions of the subcomponent's license, as noted in the LICENSE file. + From 399820680dbc548ede6199b9733c32579b1da08c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 11:17:50 +0100 Subject: [PATCH 018/776] Release version 4.2 M5 (2021.0.0). See #1709 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4ac675bb5..c4b072115 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.2.0-SNAPSHOT + 4.2.0-M5 org.springframework.data.build From ff5a6043f1504668b177713bbd8669bb720f93fa Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 11:30:29 +0100 Subject: [PATCH 019/776] Prepare next development iteration. See #1709 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c4b072115..4ac675bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.2.0-M5 + 4.2.0-SNAPSHOT org.springframework.data.build From db39b9e27c91fe6a1f0ed7000c25cfff65261966 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 17 Mar 2021 11:30:31 +0100 Subject: [PATCH 020/776] After release cleanups. See #1709 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 4ac675bb5..191786d37 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.5.0-M5 + 2.5.0-SNAPSHOT Spring Data Elasticsearch @@ -22,7 +22,7 @@ 7.11.1 2.13.3 4.1.52.Final - 2.5.0-M5 + 2.5.0-SNAPSHOT 1.15.1 spring.data.elasticsearch @@ -432,8 +432,8 @@ - spring-libs-milestone - https://repo.spring.io/libs-milestone + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From 31b488d08fa93ba268a4406ef86ef5d2129a695d Mon Sep 17 00:00:00 2001 From: Sascha Woo Date: Wed, 17 Mar 2021 22:39:12 +0100 Subject: [PATCH 021/776] Allow multiple date formats for date fields. Original Pull Request #1728 Closes #1727 --- .../elasticsearch-object-mapping.adoc | 12 +-- .../elasticsearch/annotations/DateFormat.java | 12 +++ .../data/elasticsearch/annotations/Field.java | 5 +- .../elasticsearch/annotations/InnerField.java | 4 +- .../core/index/MappingParameters.java | 36 +++++++-- ...SimpleElasticsearchPersistentProperty.java | 80 +++++++++++-------- .../core/CriteriaQueryMappingUnitTests.java | 4 +- .../ElasticsearchDateConverterUnitTests.java | 15 +++- ...appingElasticsearchConverterUnitTests.java | 4 +- .../core/index/MappingBuilderUnitTests.java | 2 +- .../SimpleElasticsearchDateMappingTests.java | 4 +- ...sticsearchPersistentPropertyUnitTests.java | 6 +- 12 files changed, 123 insertions(+), 61 deletions(-) diff --git a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc index e39f15358..f32dd1f97 100644 --- a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc +++ b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc @@ -56,16 +56,18 @@ Default value is _EXTERNAL_. Constructor arguments are mapped by name to the key values in the retrieved Document. * `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference): ** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used. -** `type`: the field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. +** `type`: The field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types] -** `format` and `pattern` definitions for the _Date_ type. +** `format`: One or more built-in formats, default value is _strict_date_optional_time_ and _epoch_millis_. +See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] +** `pattern`: One or more custom date formats. NOTE: If you want to use only custom date formats, you must set the `format` property to empty `{}`. +See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats] ** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_. ** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer. -* `@GeoPoint`: marks a field as _geo_point_ datatype. +* `@GeoPoint`: Marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class. -NOTE: Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` and a -format different from `DateFormat.none` or a custom converter must be registered for this type. + +NOTE: Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date`. If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7]. diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java index 8ee7b7dcb..396dec451 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DateFormat.java @@ -23,9 +23,21 @@ * @author Jakub Vavrik * @author Tim te Beek * @author Peter-Josef Meisch + * @author Sascha Woo */ public enum DateFormat { + /** + * @deprecated since 4.2, will be removed in a future version. Use format = {} to disable built-in date + * formats in the @Field annotation. + */ + @Deprecated none(""), // + /** + * @deprecated since 4.2, will be removed in a future version.It is no longer required for using a custom date format + * pattern. If you want to use only a custom date format pattern, you must set the format + * property to empty {}. + */ + @Deprecated custom(""), // basic_date("uuuuMMdd"), // basic_date_time("uuuuMMdd'T'HHmmss.SSSXXX"), // diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index b97b243ad..be5ae13f5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -36,6 +36,7 @@ * @author Aleksei Arsenev * @author Brian Kimmig * @author Morgan Lutz + * @author Sascha Woo */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @@ -65,9 +66,9 @@ boolean index() default true; - DateFormat format() default DateFormat.none; + DateFormat[] format() default { DateFormat.date_optional_time, DateFormat.epoch_millis }; - String pattern() default ""; + String[] pattern() default {}; boolean store() default false; diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java index c2d8b803f..8ba0dca61 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java @@ -40,9 +40,9 @@ boolean index() default true; - DateFormat format() default DateFormat.none; + DateFormat[] format() default { DateFormat.date_optional_time, DateFormat.epoch_millis }; - String pattern() default ""; + String[] pattern() default {}; boolean store() default false; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java index 6d7d7104a..38bdbe7e7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java @@ -17,6 +17,9 @@ import java.io.IOException; import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -41,6 +44,7 @@ * @author Aleksei Arsenev * @author Brian Kimmig * @author Morgan Lutz + * @author Sascha Woo * @since 4.0 */ public final class MappingParameters { @@ -78,12 +82,12 @@ public final class MappingParameters { private final String analyzer; private final boolean coerce; @Nullable private final String[] copyTo; - private final String datePattern; + private final DateFormat[] dateFormats; + private final String[] dateFormatPatterns; private final boolean docValues; private final boolean eagerGlobalOrdinals; private final boolean enabled; private final boolean fielddata; - private final DateFormat format; @Nullable private final Integer ignoreAbove; private final boolean ignoreMalformed; private final boolean index; @@ -129,8 +133,8 @@ private MappingParameters(Field field) { store = field.store(); fielddata = field.fielddata(); type = field.type(); - format = field.format(); - datePattern = field.pattern(); + dateFormats = field.format(); + dateFormatPatterns = field.pattern(); analyzer = field.analyzer(); searchAnalyzer = field.searchAnalyzer(); normalizer = field.normalizer(); @@ -171,8 +175,8 @@ private MappingParameters(InnerField field) { store = field.store(); fielddata = field.fielddata(); type = field.type(); - format = field.format(); - datePattern = field.pattern(); + dateFormats = field.format(); + dateFormatPatterns = field.pattern(); analyzer = field.analyzer(); searchAnalyzer = field.searchAnalyzer(); normalizer = field.normalizer(); @@ -226,8 +230,24 @@ public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException if (type != FieldType.Auto) { builder.field(FIELD_PARAM_TYPE, type.name().toLowerCase()); - if (type == FieldType.Date && format != DateFormat.none) { - builder.field(FIELD_PARAM_FORMAT, format == DateFormat.custom ? datePattern : format.toString()); + + if (type == FieldType.Date) { + List formats = new ArrayList<>(); + + // built-in formats + for (DateFormat dateFormat : dateFormats) { + if (dateFormat == DateFormat.none || dateFormat == DateFormat.custom) { + continue; + } + formats.add(dateFormat.toString()); + } + + // custom date formats + Collections.addAll(formats, dateFormatPatterns); + + if (!formats.isEmpty()) { + builder.field(FIELD_PARAM_FORMAT, String.join("||", formats)); + } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 681dc05a1..0ece1ed5b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.core.mapping; import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -154,51 +155,74 @@ private void initDateConverter() { if (field != null && (field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos) && (isTemporalAccessor || isDate)) { - DateFormat dateFormat = field.format(); + + DateFormat[] dateFormats = field.format(); + String[] dateFormatPatterns = field.pattern(); String property = getOwner().getType().getSimpleName() + "." + getName(); - if (dateFormat == DateFormat.none) { + if (dateFormats.length == 0 && dateFormatPatterns.length == 0) { LOGGER.warn( - String.format("No DateFormat defined for property %s. Make sure you have a Converter registered for %s", - property, actualType.getSimpleName())); + "Property '{}' has @Field type '{}' but has no built-in format or custom date pattern defined. Make sure you have a converter registered for type {}.", + property, field.type().name(), actualType.getSimpleName()); return; } - ElasticsearchDateConverter converter = null; - - if (dateFormat == DateFormat.custom) { - String pattern = field.pattern(); - - if (!StringUtils.hasLength(pattern)) { - throw new MappingException( - String.format("Property %s is annotated with FieldType.%s and a custom format but has no pattern defined", - property, field.type().name())); - } - - converter = ElasticsearchDateConverter.of(pattern); - } else { + List converters = new ArrayList<>(); + // register converters for built-in formats + for (DateFormat dateFormat : dateFormats) { switch (dateFormat) { + case none: + case custom: + break; case weekyear: case weekyear_week: case weekyear_week_day: - LOGGER.warn("no Converter available for " + actualType.getName() + " and date format " + dateFormat.name() - + ". Use a custom converter instead"); + LOGGER.warn("No default converter available for '{}' and date format '{}'. Use a custom converter instead.", + actualType.getName(), dateFormat.name()); break; default: - converter = ElasticsearchDateConverter.of(dateFormat); + converters.add(ElasticsearchDateConverter.of(dateFormat)); break; } } - if (converter != null) { - ElasticsearchDateConverter finalConverter = converter; + // register converters for custom formats + for (String dateFormatPattern : dateFormatPatterns) { + if (!StringUtils.hasText(dateFormatPattern)) { + throw new MappingException( + String.format("Date pattern of property '%s' must not be empty", property)); + } + converters.add(ElasticsearchDateConverter.of(dateFormatPattern)); + } + + if (!converters.isEmpty()) { propertyConverter = new ElasticsearchPersistentPropertyConverter() { - final ElasticsearchDateConverter dateConverter = finalConverter; + final List dateConverters = converters; + + @SuppressWarnings("unchecked") + @Override + public Object read(String s) { + for (ElasticsearchDateConverter dateConverter : dateConverters) { + try { + if (isTemporalAccessor) { + return dateConverter.parse(s, (Class) actualType); + } else { // must be date + return dateConverter.parse(s); + } + } catch (Exception e) { + LOGGER.trace(e.getMessage(), e); + } + } + + throw new RuntimeException(String + .format("Unable to parse date value '%s' of property '%s' with configured converters", s, property)); + } @Override public String write(Object property) { + ElasticsearchDateConverter dateConverter = dateConverters.get(0); if (isTemporalAccessor && TemporalAccessor.class.isAssignableFrom(property.getClass())) { return dateConverter.format((TemporalAccessor) property); } else if (isDate && Date.class.isAssignableFrom(property.getClass())) { @@ -207,16 +231,6 @@ public String write(Object property) { return property.toString(); } } - - @SuppressWarnings("unchecked") - @Override - public Object read(String s) { - if (isTemporalAccessor) { - return dateConverter.parse(s, (Class) actualType); - } else { // must be date - return dateConverter.parse(s); - } - } }; } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index 46ba47dba..c3e65e627 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -48,6 +48,7 @@ * {@link CriteriaQueryProcessor} as this is needed to get the String representation to assert. * * @author Peter-Josef Meisch + * @author Sascha Woo */ public class CriteriaQueryMappingUnitTests { @@ -346,8 +347,7 @@ static class Person { @Nullable @Field(name = "first-name") String firstName; @Nullable @Field(name = "last-name") String lastName; @Nullable @Field(name = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate; - @Nullable @Field(name = "birth-date", type = FieldType.Date, format = DateFormat.custom, - pattern = "dd.MM.uuuu") LocalDate birthDate; + @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; } static class GeoShapeEntity { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterUnitTests.java index 673e3d22f..839db6cf8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchDateConverterUnitTests.java @@ -23,6 +23,7 @@ /** * @author Peter-Josef Meisch * @author Tim te Beek + * @author Sascha Woo */ class ElasticsearchDateConverterUnitTests { @@ -34,16 +35,28 @@ void shouldCreateConvertersForAllKnownFormats(DateFormat dateFormat) { switch (dateFormat) { case none: + case custom: case weekyear: case weekyear_week: case weekyear_week_day: return; } - String pattern = (dateFormat != DateFormat.custom) ? dateFormat.name() : "dd.MM.uuuu"; + ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(dateFormat.name()); + assertThat(converter).isNotNull(); + } + + @Test // DATAES-716 + void shouldCreateConvertersForDateFormatPattern() { + + // given + String pattern = "dd.MM.uuuu"; + + // when ElasticsearchDateConverter converter = ElasticsearchDateConverter.of(pattern); + // then assertThat(converter).isNotNull(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index ae5822ba9..8d20208a8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -83,6 +83,7 @@ * @author Peter-Josef Meisch * @author Konrad Kurdej * @author Roman Puchkovskiy + * @author Sascha Woo */ public class MappingElasticsearchConverterUnitTests { @@ -1218,8 +1219,7 @@ static class Person { String name; @Field(name = "first-name") String firstName; @Field(name = "last-name") String lastName; - @Field(name = "birth-date", type = FieldType.Date, format = DateFormat.custom, - pattern = "dd.MM.uuuu") LocalDate birthDate; + @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; Gender gender; Address address; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index aa46971ca..78db40292 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -921,7 +921,7 @@ static class FieldMappingParameters { @Nullable @Field(copyTo = { "foo", "bar" }) private String copyTo; @Nullable @Field(ignoreAbove = 42) private String ignoreAbove; @Nullable @Field(type = FieldType.Integer) private String type; - @Nullable @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "YYYYMMDD") private LocalDate date; + @Nullable @Field(type = FieldType.Date, format = {}, pattern = "YYYYMMDD") private LocalDate date; @Nullable @Field(analyzer = "ana", searchAnalyzer = "sana", normalizer = "norma") private String analyzers; @Nullable @Field(type = Keyword) private String docValuesTrue; @Nullable @Field(type = Keyword, docValues = false) private String docValuesFalse; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java index 4349b8572..f8ab2aed0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java @@ -36,6 +36,7 @@ * @author Mohsin Husen * @author Don Wellington * @author Peter-Josef Meisch + * @author Sascha Woo */ public class SimpleElasticsearchDateMappingTests extends MappingContextBaseTests { @@ -62,8 +63,7 @@ static class SampleDateMappingEntity { @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; - @Field(type = Date, format = DateFormat.custom, - pattern = "dd.MM.uuuu hh:mm") private LocalDateTime customFormatDate; + @Field(type = Date, format = {}, pattern = "dd.MM.uuuu hh:mm") private LocalDateTime customFormatDate; @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime basicFormatDate; } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java index 698978da0..46f529aba 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java @@ -50,6 +50,7 @@ * @author Oliver Gierke * @author Peter-Josef Meisch * @author Roman Puchkovskiy + * @author Sascha Woo */ public class SimpleElasticsearchPersistentPropertyUnitTests { @@ -257,11 +258,10 @@ static class MultiFieldProperty { } static class DatesProperty { - @Nullable @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "dd.MM.uuuu") LocalDate localDate; + @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate localDate; @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) LocalDateTime localDateTime; @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time) Date legacyDate; - @Nullable @Field(type = FieldType.Date, format = DateFormat.custom, - pattern = "dd.MM.uuuu") List localDateList; + @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List localDateList; } @Data From ebac4c097c7f5170dd8e4b9a533b758682c7fcbd Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 17 Mar 2021 22:43:09 +0100 Subject: [PATCH 022/776] Polishing. --- .../elasticsearch-object-mapping.adoc | 53 +++++++++++++++--- ...SimpleElasticsearchPersistentProperty.java | 6 +-- .../core/index/MappingBuilderUnitTests.java | 54 ++++++++++++++++++- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc index f32dd1f97..d26e64e26 100644 --- a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc +++ b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc @@ -58,21 +58,58 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu ** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used. ** `type`: The field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types] -** `format`: One or more built-in formats, default value is _strict_date_optional_time_ and _epoch_millis_. -See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] -** `pattern`: One or more custom date formats. NOTE: If you want to use only custom date formats, you must set the `format` property to empty `{}`. -See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats] +** `format`: One or more built-in date formats, see the next section <>. +** `pattern`: One or more custom date formats, see the next section <>. ** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_. ** `analyzer`, `searchAnalyzer`, `normalizer` for specifying custom analyzers and normalizer. * `@GeoPoint`: Marks a field as _geo_point_ datatype. Can be omitted if the field is an instance of the `GeoPoint` class. -NOTE: Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date`. -If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. -This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7]. - The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic. +[[elasticsearch.mapping.meta-model.date-formats]] +==== Date format mapping + +Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation +of type `FieldType.Date` or a custom converter must be registered for this type. This paragraph describes the use of +`FieldType.Date`. + +There are two attributes of the `@Field` annotation that define which date format information is written to the +mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats]) + +The `format` attributes is used to define at least one of the predefined formats. If it is not defined, then a +default value of __date_optional_time_ and _epoch_millis_ is used. + +The `pattern` attribute can be used to add additional custom format strings. If you want to use only custom date formats, you must set the `format` property to empty `{}`. + +The following table shows the different attributes and the mapping created from their values: + + +[cols=2*,options=header] +|=== +| annotation +| format string in Elasticsearch mapping + +| @Field(type=FieldType.Date) +| "date_optional_time\|\|epoch_millis", + +| @Field(type=FieldType.Date, format=DateFormat.basic_date) +| "basic_date" + +| @Field(type=FieldType.Date, format={DateFormat.basic_date, DateFormat.basic_time}) +| "basic_date\|\|basic_time" + +| @Field(type=FieldType.Date, pattern="dd.MM.uuuu") +| "date_optional_time\|\|epoch_millis\|\|dd.MM.uuuu", + +| @Field(type=FieldType.Date, format={}, pattern="dd.MM.uuuu") +| "dd.MM.uuuu" + +|=== + +NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. +This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7]. + ==== Mapped field names Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch. This can be changed for individual field by using the `@Field` annotation on that property. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 0ece1ed5b..1ab2723cf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -31,6 +31,7 @@ import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.annotations.Parent; import org.springframework.data.elasticsearch.core.completion.Completion; +import org.springframework.data.elasticsearch.core.convert.ConversionException; import org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter; import org.springframework.data.elasticsearch.core.geo.GeoJson; import org.springframework.data.elasticsearch.core.geo.GeoPoint; @@ -191,8 +192,7 @@ private void initDateConverter() { // register converters for custom formats for (String dateFormatPattern : dateFormatPatterns) { if (!StringUtils.hasText(dateFormatPattern)) { - throw new MappingException( - String.format("Date pattern of property '%s' must not be empty", property)); + throw new MappingException(String.format("Date pattern of property '%s' must not be empty", property)); } converters.add(ElasticsearchDateConverter.of(dateFormatPattern)); } @@ -216,7 +216,7 @@ public Object read(String s) { } } - throw new RuntimeException(String + throw new ConversionException(String .format("Unable to parse date value '%s' of property '%s' with configured converters", s, property)); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 78db40292..fc92ec0d6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -34,6 +34,7 @@ import java.lang.Object; import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -614,6 +615,40 @@ void shouldWriteTypeHintEntries() throws JSONException { assertEquals(expected, mapping, false); } + @Test // #1727 + @DisplayName("should map according to the annotated properties") + void shouldMapAccordingToTheAnnotatedProperties() throws JSONException { + + String expected = "{\n" + + " \"properties\": {\n" + // + " \"field1\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"date_optional_time||epoch_millis\"\n" + // + " },\n" + // + " \"field2\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"basic_date\"\n" + // + " },\n" + // + " \"field3\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"basic_date||basic_time\"\n" + // + " },\n" + // + " \"field4\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"date_optional_time||epoch_millis||dd.MM.uuuu\"\n" + // + " },\n" + // + " \"field5\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"dd.MM.uuuu\"\n" + // + " }\n" + // + " }\n" + // + "}"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DateFormatsEntity.class); + + assertEquals(expected, mapping, false); + } + @Setter @Getter @NoArgsConstructor @@ -971,7 +1006,7 @@ public void setAuthor(Author author) { } static class ValueObject { - private String value; + private final String value; public ValueObject(String value) { this.value = value; @@ -1060,4 +1095,21 @@ static class ObjectEntity { @Field(type = Text) private String objectField; } } + + @Data + @AllArgsConstructor + @NoArgsConstructor + static class DateFormatsEntity { + @Id private String id; + @Field(type = FieldType.Date) private LocalDateTime field1; + + @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime field2; + + @Field(type = FieldType.Date, + format = { DateFormat.basic_date, DateFormat.basic_time }) private LocalDateTime field3; + + @Field(type = FieldType.Date, pattern = "dd.MM.uuuu") private LocalDateTime field4; + + @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") private LocalDateTime field5; + } } From 843fd4db8534b5a46891607316c317e20e35a7ce Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 21 Mar 2021 18:36:38 +0100 Subject: [PATCH 023/776] Remove lombok. Original Pull Request #1735 Closes #1734 --- pom.xml | 22 + .../data/elasticsearch/NestedObjectTests.java | 269 ++++-- .../ComposableAnnotationsUnitTest.java | 52 +- ...veElasticsearchClientIntegrationTests.java | 2 - ...eNestedElasticsearchRepositoriesTests.java | 25 +- .../EnableElasticsearchRepositoriesTests.java | 196 +++- ...actElasticsearchTemplateCallbackTests.java | 54 +- .../core/ElasticsearchRestTemplateTests.java | 32 +- .../core/ElasticsearchTemplateTests.java | 837 ++++++++++++++--- .../ElasticsearchTransportTemplateTests.java | 50 +- .../core/EntityOperationsTest.java | 83 +- .../elasticsearch/core/InnerHitsTests.java | 114 ++- .../elasticsearch/core/LogEntityTests.java | 59 +- ...iveElasticsearchTemplateCallbackTests.java | 53 +- ...ElasticsearchTemplateIntegrationTests.java | 266 +++++- ...eactiveElasticsearchTemplateUnitTests.java | 105 ++- .../core/ReactiveIndexOperationsTest.java | 59 +- .../core/RequestFactoryTests.java | 56 +- .../core/SearchAsYouTypeTests.java | 92 +- .../core/SourceFilterIntegrationTests.java | 61 +- ...ElasticsearchTemplateAggregationTests.java | 75 +- ...appingElasticsearchConverterUnitTests.java | 851 +++++++++++++++--- ...archOperationsCallbackIntegrationTest.java | 50 +- .../geo/ElasticsearchTemplateGeoTests.java | 132 ++- .../elasticsearch/core/geo/GeoJsonEntity.java | 261 +++++- .../core/geo/GeoJsonIntegrationTest.java | 76 +- .../index/IndexOperationIntegrationTests.java | 15 +- .../index/MappingBuilderIntegrationTests.java | 637 ++++++++++--- .../core/index/MappingBuilderUnitTests.java | 656 +++++++++++--- .../SimpleElasticsearchDateMappingTests.java | 47 +- ...ntityCustomConversionIntegrationTests.java | 67 +- ...NamingStrategyIntegrationReactiveTest.java | 35 +- .../FieldNamingStrategyIntegrationTest.java | 32 +- ...sticsearchPersistentPropertyUnitTests.java | 65 +- .../ReactiveSearchAfterIntegrationTests.java | 61 +- .../SearchAfterIntegrationTests.java | 62 +- .../query/CriteriaQueryIntegrationTests.java | 85 +- .../DefaultRoutingResolverUnitTest.java | 94 +- .../ElasticsearchOperationsRoutingTests.java | 63 +- ...veElasticsearchOperationsRoutingTests.java | 63 +- .../ImmutableElasticsearchRepository.java | 24 - ...ImmutableElasticsearchRepositoryTests.java | 14 +- .../immutable/ImmutableEntity.java | 38 - .../repositories/cdi/CdiRepositoryTests.java | 264 ++++-- .../ComplexCustomMethodRepositoryTests.java | 37 +- ...stomMethodRepositoryManualWiringTests.java | 36 +- .../CustomMethodRepositoryBaseTests.java | 96 +- .../geo/SpringDataGeoRepositoryTests.java | 116 ++- .../nestedobject/InnerObjectTests.java | 73 +- .../synonym/SynonymRepositoryTests.java | 35 +- .../UUIDElasticsearchRepositoryTests.java | 147 ++- ...asticsearchRepositoriesRegistrarTests.java | 42 +- .../ElasticsearchQueryMethodUnitTests.java | 36 +- .../ElasticsearchStringQueryUnitTests.java | 97 +- ...tiveElasticsearchQueryMethodUnitTests.java | 96 +- ...tiveElasticsearchStringQueryUnitTests.java | 96 +- .../query/keywords/QueryKeywordsTests.java | 117 ++- ...asticsearchRepositoryIntegrationTests.java | 121 ++- ...eReactiveElasticsearchRepositoryTests.java | 273 +++--- .../lombok/999999/lombok-999999.jar | Bin 0 -> 425 bytes .../lombok/999999/lombok-999999.jar.md5 | 1 + .../lombok/999999/lombok-999999.jar.sha1 | 1 + .../lombok/999999/lombok-999999.pom | 8 + .../lombok/999999/lombok-999999.pom.md5 | 1 + .../lombok/999999/lombok-999999.pom.sha1 | 1 + .../projectlombok/lombok/maven-metadata.xml | 12 + .../lombok/maven-metadata.xml.md5 | 1 + .../lombok/maven-metadata.xml.sha1 | 1 + 68 files changed, 6005 insertions(+), 1693 deletions(-) delete mode 100644 src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepository.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableEntity.java create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.md5 create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.sha1 create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.md5 create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.sha1 create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.md5 create mode 100644 src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.sha1 diff --git a/pom.xml b/pom.xml index 191786d37..4e95887e5 100644 --- a/pom.xml +++ b/pom.xml @@ -226,6 +226,22 @@ test + + + org.projectlombok + lombok + 999999 + test + + org.apache.openwebbeans.test cditest-owb @@ -435,6 +451,12 @@ spring-libs-snapshot https://repo.spring.io/libs-snapshot + + + local-maven-repo + file:///${project.basedir}/src/test/resources/local-maven-repo + + diff --git a/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java b/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java index 905510aad..6cc24ff90 100644 --- a/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java @@ -19,10 +19,6 @@ import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import lombok.Data; -import lombok.Getter; -import lombok.Setter; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -53,6 +49,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -384,82 +381,238 @@ public void shouldIndexAndSearchMapAsNestedType() { assertThat(books.getSearchHit(0).getContent().getId()).isEqualTo(book2.getId()); } - @Setter - @Getter @Document(indexName = "test-index-book-nested-objects", replicas = 0, refreshInterval = "-1") static class Book { - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - @Data @Document(indexName = "test-index-person", replicas = 0, refreshInterval = "-1") static class Person { - - @Id private String id; - - private String name; - - @Field(type = FieldType.Nested) private List car; - - @Field(type = FieldType.Nested, includeInParent = true) private List books; + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Nested) private List car; + @Nullable @Field(type = FieldType.Nested, includeInParent = true) private List books; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public List getCar() { + return car; + } + + public void setCar(@Nullable List car) { + this.car = car; + } + + @Nullable + public List getBooks() { + return books; + } + + public void setBooks(@Nullable List books) { + this.books = books; + } } - @Data static class Car { - - private String name; - private String model; + @Nullable private String name; + @Nullable private String model; + + @Nullable + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Nullable + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Artur Konczak - */ - @Data @Document(indexName = "test-index-person-multiple-level-nested", replicas = 0, refreshInterval = "-1") static class PersonMultipleLevelNested { - - @Id private String id; - - private String name; - - @Field(type = FieldType.Nested) private List girlFriends; - - @Field(type = FieldType.Nested) private List cars; - - @Field(type = FieldType.Nested, includeInParent = true) private List bestCars; + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Nested) private List girlFriends; + @Nullable @Field(type = FieldType.Nested) private List cars; + @Nullable @Field(type = FieldType.Nested, includeInParent = true) private List bestCars; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public List getGirlFriends() { + return girlFriends; + } + + public void setGirlFriends(@Nullable List girlFriends) { + this.girlFriends = girlFriends; + } + + @Nullable + public List getCars() { + return cars; + } + + public void setCars(@Nullable List cars) { + this.cars = cars; + } + + @Nullable + public List getBestCars() { + return bestCars; + } + + public void setBestCars(@Nullable List bestCars) { + this.bestCars = bestCars; + } } - /** - * @author Mohsin Husen - */ - @Data static class GirlFriend { - - private String name; - - private String type; - - @Field(type = FieldType.Nested) private List cars; + @Nullable private String name; + @Nullable private String type; + @Nullable @Field(type = FieldType.Nested) private List cars; + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public List getCars() { + return cars; + } + + public void setCars(@Nullable List cars) { + this.cars = cars; + } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ - @Data static class Author { - - private String id; - private String name; + @Nullable private String id; + @Nullable private String name; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java b/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java index 645dfdeea..dc468eb90 100644 --- a/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java @@ -18,10 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -41,6 +37,7 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentEntity; +import org.springframework.lang.Nullable; /** * @author Peter-Josef Meisch @@ -144,14 +141,47 @@ void shouldUseComposedFieldAnnotationsInMappingBuilder() throws JSONException { public @interface TextKeywordField { } - @Data - @NoArgsConstructor - @AllArgsConstructor @DocumentNoCreate(indexName = "test-no-create") static class ComposedAnnotationEntity { - @Id private String id; - @NullValueField(name = "null-value") private String nullValue; - @LocalDateField private LocalDate theDate; - @TextKeywordField private String multiField; + @Nullable @Id private String id; + @Nullable @NullValueField(name = "null-value") private String nullValue; + @Nullable @LocalDateField private LocalDate theDate; + @Nullable @TextKeywordField private String multiField; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getNullValue() { + return nullValue; + } + + public void setNullValue(@Nullable String nullValue) { + this.nullValue = nullValue; + } + + @Nullable + public LocalDate getTheDate() { + return theDate; + } + + public void setTheDate(@Nullable LocalDate theDate) { + this.theDate = theDate; + } + + @Nullable + public String getMultiField() { + return multiField; + } + + public void setMultiField(@Nullable String multiField) { + this.multiField = multiField; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java index d4aafe4e6..eea2e77a8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java @@ -17,7 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.SneakyThrows; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -1112,7 +1111,6 @@ private IndexRequest indexRequest() { .create(true); } - @SneakyThrows private String doIndex(Map source, String index) { return operations.save(source, IndexCoordinates.of(index)).block().get("id").toString(); } diff --git a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java index 098e9d0d5..47a7d73a9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java @@ -18,9 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.Builder; -import lombok.Data; - import java.lang.Double; import java.lang.Long; @@ -41,6 +38,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.repository.Repository; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -75,20 +73,17 @@ public void hasNestedRepository() { assertThat(nestedRepository).isNotNull(); } - @Data - @Builder @Document(indexName = "test-index-sample-config-nested", replicas = 0, refreshInterval = "-1") static class SampleEntity { - - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - @ScriptedField private Double scriptedRate; - private boolean available; - private String highlightedMessage; - private GeoPoint location; - @Version private Long version; + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Double scriptedRate; + @Nullable private boolean available; + @Nullable private String highlightedMessage; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; } interface SampleRepository extends Repository {} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java index 8e167a6ac..c089210c7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java @@ -18,8 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.Data; - import java.lang.Double; import java.lang.Long; import java.util.UUID; @@ -122,33 +120,187 @@ public void hasNotNestedRepository() { assertThat(nestedRepository).isNull(); } - @Data @Document(indexName = "test-index-sample-config-not-nested", replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Double scriptedRate; + @Nullable private boolean available; + @Nullable private String highlightedMessage; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public java.lang.Double getScriptedRate() { + return scriptedRate; + } + + public void setScriptedRate(@Nullable java.lang.Double scriptedRate) { + this.scriptedRate = scriptedRate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public String getHighlightedMessage() { + return highlightedMessage; + } + + public void setHighlightedMessage(@Nullable String highlightedMessage) { + this.highlightedMessage = highlightedMessage; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - @ScriptedField private Double scriptedRate; - private boolean available; - private String highlightedMessage; - private GeoPoint location; - @Version private Long version; + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data @Document(indexName = "test-index-uuid-keyed-config-not-nested", replicas = 0, refreshInterval = "-1") static class SampleEntityUUIDKeyed { + @Nullable @Id private UUID id; + @Nullable private String type; + @Nullable @Field(type = FieldType.Text, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Long scriptedRate; + @Nullable private boolean available; + @Nullable private String highlightedMessage; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + @Nullable + public UUID getId() { + return id; + } + + public void setId(@Nullable UUID id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public java.lang.Long getScriptedRate() { + return scriptedRate; + } + + public void setScriptedRate(@Nullable java.lang.Long scriptedRate) { + this.scriptedRate = scriptedRate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public String getHighlightedMessage() { + return highlightedMessage; + } + + public void setHighlightedMessage(@Nullable String highlightedMessage) { + this.highlightedMessage = highlightedMessage; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } - @Id private UUID id; - private String type; - @Field(type = FieldType.Text, fielddata = true) private String message; - private int rate; - @ScriptedField private Long scriptedRate; - private boolean available; - private String highlightedMessage; - private GeoPoint location; - @Version private Long version; + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java b/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java index a4ea0314b..0131fb45d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplateCallbackTests.java @@ -19,10 +19,6 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -443,13 +439,53 @@ void saveAllShouldInvokeBeforeConvertCallbacks() { assertThat(iterator.next().firstname).isEqualTo("before-convert"); } - @Data - @AllArgsConstructor - @NoArgsConstructor static class Person { + @Nullable @Id String id; + @Nullable String firstname; + + public Person(@Nullable String id, @Nullable String firstname) { + this.id = id; + this.firstname = firstname; + } + + public Person() { + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getFirstname() { + return firstname; + } + + public void setFirstname(@Nullable String firstname) { + this.firstname = firstname; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - @Id String id; - String firstname; + Person person = (Person) o; + + if (id != null ? !id.equals(person.id) : person.id != null) return false; + return firstname != null ? firstname.equals(person.firstname) : person.firstname == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (firstname != null ? firstname.hashCode() : 0); + return result; + } } static class ValueCapturingEntityCallback { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java index 513629edc..8e8ae9701 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java @@ -21,10 +21,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import lombok.Builder; -import lombok.Data; -import lombok.val; - import java.lang.Object; import java.time.Duration; import java.util.Collections; @@ -37,6 +33,7 @@ import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.reindex.UpdateByQueryRequest; +import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.json.JSONException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -49,6 +46,7 @@ import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -81,13 +79,29 @@ public void shouldThrowExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() .isInstanceOf(UncategorizedElasticsearchException.class); } - @Data - @Builder @Document(indexName = "test-index-sample-core-rest-template", replicas = 0, refreshInterval = "-1") static class SampleEntity { - - @Id private String id; + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } } @Test // DATAES-768 @@ -122,7 +136,7 @@ void shouldUseAllOptionsFromUpdateQuery() { assertThat(request.retryOnConflict()).isEqualTo(7); assertThat(request.timeout()).isEqualByComparingTo(TimeValue.parseTimeValue("4711s", "test")); assertThat(request.waitForActiveShards()).isEqualTo(ActiveShardCount.ALL); - val fetchSourceContext = request.fetchSource(); + FetchSourceContext fetchSourceContext = request.fetchSource(); assertThat(fetchSourceContext).isNotNull(); assertThat(fetchSourceContext.includes()).containsExactlyInAnyOrder("incl"); assertThat(fetchSourceContext.excludes()).containsExactlyInAnyOrder("excl"); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 68fd8085b..b05df9b71 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -23,12 +23,6 @@ import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import static org.springframework.data.elasticsearch.utils.IndexBuilder.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - import java.lang.Double; import java.lang.Integer; import java.lang.Long; @@ -65,8 +59,6 @@ import org.elasticsearch.script.ScriptType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.search.rescore.QueryRescoreMode; -import org.elasticsearch.search.rescore.QueryRescorerBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; @@ -1989,8 +1981,11 @@ public void shouldIndexGteEntityWithVersionType() { // given String documentId = nextIdAsString(); - GTEVersionEntity entity = GTEVersionEntity.builder().id(documentId).name("FooBar") - .version(System.currentTimeMillis()).build(); + + GTEVersionEntity entity = new GTEVersionEntity(); + entity.setId(documentId); + entity.setName("FooBar"); + entity.setVersion(System.currentTimeMillis()); IndexQueryBuilder indexQueryBuilder = new IndexQueryBuilder().withId(documentId).withVersion(entity.getVersion()) .withObject(entity); @@ -3072,7 +3067,10 @@ public void shouldIncludeDefaultsOnGetIndexSettings() { @Test // DATAES-714 void shouldReturnSortFieldsInSearchHits() { IndexCoordinates index = IndexCoordinates.of("test-index-searchhits-entity-template"); - SearchHitsEntity entity = SearchHitsEntity.builder().id("1").number(1000L).keyword("thousands").build(); + SearchHitsEntity entity = new SearchHitsEntity(); + entity.setId("1"); + entity.setNumber(1000L); + entity.setKeyword("thousands"); IndexQuery indexQuery = new IndexQueryBuilder().withId(entity.getId()).withObject(entity).build(); operations.index(indexQuery, index); operations.indexOps(index).refresh(); @@ -3107,10 +3105,9 @@ void shouldReturnSortFieldsInSearchHits() { @Test // DATAES-715 void shouldReturnHighlightFieldsInSearchHit() { IndexCoordinates index = IndexCoordinates.of("test-index-highlight-entity-template"); - HighlightEntity entity = HighlightEntity.builder().id("1") - .message("This message is a long text which contains the word to search for " - + "in two places, the first being near the beginning and the second near the end of the message") - .build(); + HighlightEntity entity = new HighlightEntity("1", + "This message is a long text which contains the word to search for " + + "in two places, the first being near the beginning and the second near the end of the message"); IndexQuery indexQuery = new IndexQueryBuilder().withId(entity.getId()).withObject(entity).build(); operations.index(indexQuery, index); operations.indexOps(index).refresh(); @@ -3140,16 +3137,14 @@ void shouldRunRescoreQueryInSearchQuery() { SampleEntity entity = SampleEntity.builder() // .id("1") // .message("some message") // - .rate(java.lang.Integer.MAX_VALUE) - .version(System.currentTimeMillis()) // + .rate(java.lang.Integer.MAX_VALUE).version(System.currentTimeMillis()) // .build(); // high score from rescore query SampleEntity entity2 = SampleEntity.builder() // .id("2") // .message("nothing") // - .rate(1) - .version(System.currentTimeMillis()) // + .rate(1).version(System.currentTimeMillis()) // .build(); List indexQueries = getIndexQueries(Arrays.asList(entity, entity2)); @@ -3158,26 +3153,15 @@ void shouldRunRescoreQueryInSearchQuery() { indexOperations.refresh(); NativeSearchQuery query = new NativeSearchQueryBuilder() // - .withQuery( - boolQuery().filter(existsQuery("rate")).should(termQuery("message", "message"))) // - .withRescorerQuery(new RescorerQuery( - new NativeSearchQueryBuilder().withQuery( - QueryBuilders.functionScoreQuery( - new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ - new FilterFunctionBuilder( - new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5) - .setWeight(1f)), - new FilterFunctionBuilder( - new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5) - .setWeight(100f))} - ) - .scoreMode(FunctionScoreQuery.ScoreMode.SUM) - .maxBoost(80f) - .boostMode(CombineFunction.REPLACE) - ).build() - ) - .withScoreMode(ScoreMode.Max) - .withWindowSize(100)) + .withQuery(boolQuery().filter(existsQuery("rate")).should(termQuery("message", "message"))) // + .withRescorerQuery( + new RescorerQuery(new NativeSearchQueryBuilder().withQuery(QueryBuilders + .functionScoreQuery(new FunctionScoreQueryBuilder.FilterFunctionBuilder[] { + new FilterFunctionBuilder(new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5).setWeight(1f)), + new FilterFunctionBuilder( + new GaussDecayFunctionBuilder("rate", 0, 10, null, 0.5).setWeight(100f)) }) + .scoreMode(FunctionScoreQuery.ScoreMode.SUM).maxBoost(80f).boostMode(CombineFunction.REPLACE)).build()) + .withScoreMode(ScoreMode.Max).withWindowSize(100)) .build(); SearchHits searchHits = operations.search(query, SampleEntity.class, index); @@ -3187,12 +3171,12 @@ void shouldRunRescoreQueryInSearchQuery() { SearchHit searchHit = searchHits.getSearchHit(0); assertThat(searchHit.getContent().getMessage()).isEqualTo("nothing"); - //score capped to 80 + // score capped to 80 assertThat(searchHit.getScore()).isEqualTo(80f); } @Test - // DATAES-738 + // DATAES-738 void shouldSaveEntityWithIndexCoordinates() { String id = "42"; SampleEntity entity = new SampleEntity(); @@ -3766,130 +3750,560 @@ void shouldReturnExplanationWhenRequested() { assertThat(explanation).isNotNull(); } - @Data - @NoArgsConstructor - @AllArgsConstructor - @EqualsAndHashCode(exclude = "score") - @Builder @Document(indexName = INDEX_NAME_SAMPLE_ENTITY, replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Double scriptedRate; + @Nullable private boolean available; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + static Builder builder() { + return new Builder(); + } + + static class Builder { + + @Nullable private String id; + @Nullable private String type; + @Nullable private String message; + @Nullable private Long version; + @Nullable private int rate; + @Nullable private GeoPoint location; + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder type(String type) { + this.type = type; + return this; + } + + public Builder message(String message) { + this.message = message; + return this; + } + + public Builder version(Long version) { + this.version = version; + return this; + } + + public Builder rate(int rate) { + this.rate = rate; + return this; + } + + public Builder location(GeoPoint location) { + this.location = location; + return this; + } + + public SampleEntity build() { + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setId(id); + sampleEntity.setType(type); + sampleEntity.setMessage(message); + sampleEntity.setRate(rate); + sampleEntity.setVersion(version); + sampleEntity.setLocation(location); + return sampleEntity; + } + } + + public SampleEntity() {} + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public java.lang.Double getScriptedRate() { + return scriptedRate; + } - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - @ScriptedField private Double scriptedRate; - private boolean available; - private GeoPoint location; - @Version private Long version; + public void setScriptedRate(@Nullable java.lang.Double scriptedRate) { + this.scriptedRate = scriptedRate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + SampleEntity that = (SampleEntity) o; + + if (rate != that.rate) + return false; + if (available != that.available) + return false; + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (type != null ? !type.equals(that.type) : that.type != null) + return false; + if (message != null ? !message.equals(that.message) : that.message != null) + return false; + if (scriptedRate != null ? !scriptedRate.equals(that.scriptedRate) : that.scriptedRate != null) + return false; + if (location != null ? !location.equals(that.location) : that.location != null) + return false; + return version != null ? version.equals(that.version) : that.version == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (message != null ? message.hashCode() : 0); + result = 31 * result + rate; + result = 31 * result + (scriptedRate != null ? scriptedRate.hashCode() : 0); + result = 31 * result + (available ? 1 : 0); + result = 31 * result + (location != null ? location.hashCode() : 0); + result = 31 * result + (version != null ? version.hashCode() : 0); + return result; + } } - /** - * @author Gad Akuka - * @author Rizwan Idrees - * @author Mohsin Husen - */ - @Data - @AllArgsConstructor - @Builder @Document(indexName = "test-index-uuid-keyed-core-template", replicas = 0, refreshInterval = "-1") private static class SampleEntityUUIDKeyed { + @Nullable @Id private UUID id; + @Nullable private String type; + @Nullable @Field(type = FieldType.Text, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Long scriptedRate; + @Nullable private boolean available; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + @Nullable + public UUID getId() { + return id; + } + + public void setId(@Nullable UUID id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } - @Id private UUID id; - private String type; - @Field(type = FieldType.Text, fielddata = true) private String message; - private int rate; - @ScriptedField private Long scriptedRate; - private boolean available; - private GeoPoint location; - @Version private Long version; + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public java.lang.Long getScriptedRate() { + return scriptedRate; + } + public void setScriptedRate(@Nullable java.lang.Long scriptedRate) { + this.scriptedRate = scriptedRate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data - @Builder - @AllArgsConstructor - @NoArgsConstructor @Document(indexName = "test-index-book-core-template", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + public Book(@Nullable String id, @Nullable String name, @Nullable Author author, + @Nullable Map> buckets, @Nullable String description) { + this.id = id; + this.name = name; + this.author = author; + this.buckets = buckets; + this.description = description; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } + + static Builder builder() { + return new Builder(); + } + + static class Builder { + @Nullable private String id; + @Nullable private String name; + @Nullable private Author author; + @Nullable private Map> buckets = new HashMap<>(); + @Nullable private String description; + + public Builder id(@Nullable String id) { + this.id = id; + return this; + } + + public Builder name(@Nullable String name) { + this.name = name; + return this; + } + + public Builder author(@Nullable Author author) { + this.author = author; + return this; + } + + public Builder buckets(@Nullable Map> buckets) { + this.buckets = buckets; + return this; + } + + public Builder description(@Nullable String description) { + this.description = description; + return this; + } + + Book build() { + return new Book(id, name, author, buckets, description); + } + } } - @Data static class Author { - private String id; - private String name; + @Nullable private String id; + @Nullable private String name; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } } - @Data - @Builder - @AllArgsConstructor - @NoArgsConstructor @Document(indexName = "test-index-version-core-template", replicas = 0, refreshInterval = "-1", versionType = VersionType.EXTERNAL_GTE) private static class GTEVersionEntity { + @Nullable @Version private Long version; + @Nullable @Id private String id; + @Nullable private String name; + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } + + @Nullable + public String getId() { + return id; + } - @Version private Long version; + public void setId(@Nullable String id) { + this.id = id; + } - @Id private String id; + @Nullable + public String getName() { + return name; + } - private String name; + public void setName(@Nullable String name) { + this.name = name; + } } - @Data @Document(indexName = "test-index-hetro1-core-template", replicas = 0) static class HetroEntity1 { - - @Id private String id; - private String firstName; - @Version private Long version; + @Nullable @Id private String id; + @Nullable private String firstName; + @Nullable @Version private Long version; HetroEntity1(String id, String firstName) { this.id = id; this.firstName = firstName; this.version = System.currentTimeMillis(); } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getFirstName() { + return firstName; + } + + public void setFirstName(@Nullable String firstName) { + this.firstName = firstName; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data @Document(indexName = "test-index-hetro2-core-template", replicas = 0) static class HetroEntity2 { - @Id private String id; - private String lastName; - @Version private Long version; + @Nullable @Id private String id; + @Nullable private String lastName; + @Nullable @Version private Long version; HetroEntity2(String id, String lastName) { this.id = id; this.lastName = lastName; this.version = System.currentTimeMillis(); } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getLastName() { + return lastName; + } + + public void setLastName(@Nullable String lastName) { + this.lastName = lastName; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data @Document(indexName = "test-index-server-configuration", useServerConfiguration = true, shards = 10, replicas = 10, refreshInterval = "-1") private static class UseServerConfigurationEntity { - @Id private String id; - private String val; + @Nullable @Id private String id; + @Nullable private String val; + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getVal() { + return val; + } + + public void setVal(@Nullable String val) { + this.val = val; + } } - @Data @Document(indexName = "test-index-sample-mapping", replicas = 0, refreshInterval = "-1") static class SampleMappingEntity { - @Id private String id; + @Nullable @Id private String id; + @Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; - @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } static class NestedEntity { @@ -3906,58 +4320,211 @@ public void setSomeField(String someField) { } } - @Data - @AllArgsConstructor - @Builder @Document(indexName = "test-index-searchhits-entity-template") static class SearchHitsEntity { - @Id private String id; - @Field(type = FieldType.Long) Long number; - @Field(type = FieldType.Keyword) String keyword; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Long) Long number; + @Nullable @Field(type = FieldType.Keyword) String keyword; + + public SearchHitsEntity() {} + + public SearchHitsEntity(@Nullable String id, @Nullable java.lang.Long number, @Nullable String keyword) { + this.id = id; + this.number = number; + this.keyword = keyword; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public java.lang.Long getNumber() { + return number; + } + + public void setNumber(@Nullable java.lang.Long number) { + this.number = number; + } + + @Nullable + public String getKeyword() { + return keyword; + } + + public void setKeyword(@Nullable String keyword) { + this.keyword = keyword; + } } - @Data - @AllArgsConstructor - @Builder @Document(indexName = "test-index-highlight-entity-template") static class HighlightEntity { - @Id private String id; - private String message; + @Nullable @Id private String id; + @Nullable private String message; + + public HighlightEntity(@Nullable String id, @Nullable String message) { + this.id = id; + this.message = message; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } } - @Data @Document(indexName = "test-index-optimistic-entity-template") static class OptimisticEntity { - @Id private String id; - private String message; - private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable @Id private String id; + @Nullable private String message; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } } - @Data @Document(indexName = "test-index-optimistic-and-versioned-entity-template") static class OptimisticAndVersionedEntity { - @Id private String id; - private String message; - private SeqNoPrimaryTerm seqNoPrimaryTerm; - @Version private Long version; + @Nullable @Id private String id; + @Nullable private String message; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data @Document(indexName = "test-index-versioned-entity-template") static class VersionedEntity { - @Id private String id; - @Version private Long version; + @Nullable @Id private String id; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = INDEX_NAME_JOIN_SAMPLE_ENTITY) static class SampleJoinEntity { - @Id @Field(type = Keyword) private String uuid; - @JoinTypeRelations(relations = { + @Nullable @Id @Field(type = Keyword) private String uuid; + @Nullable @JoinTypeRelations(relations = { @JoinTypeRelation(parent = "question", children = { "answer" }) }) private JoinField myJoinField; - @Field(type = Text) private String text; + @Nullable @Field(type = Text) private String text; + + @Nullable + public String getUuid() { + return uuid; + } + + public void setUuid(@Nullable String uuid) { + this.uuid = uuid; + } + + @Nullable + public JoinField getMyJoinField() { + return myJoinField; + } + + public void setMyJoinField(@Nullable JoinField myJoinField) { + this.myJoinField = myJoinField; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java index 864d58fe1..485e00575 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java @@ -21,9 +21,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import lombok.Data; -import lombok.val; - import java.lang.Object; import java.time.Duration; import java.util.Collections; @@ -55,6 +52,7 @@ import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -108,18 +106,18 @@ void shouldUseAllOptionsFromUpdateQuery() { doc.put("message", "test"); org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document .from(doc); - UpdateQuery updateQuery = UpdateQuery.builder("1") // - .withDocument(document) // - .withIfSeqNo(42) // - .withIfPrimaryTerm(13) // - .withScript("script")// - .withLang("lang") // - .withRefreshPolicy(RefreshPolicy.WAIT_UNTIL) // - .withRetryOnConflict(7) // - .withTimeout("4711s") // - .withWaitForActiveShards("all").withFetchSourceIncludes(Collections.singletonList("incl")) // - .withFetchSourceExcludes(Collections.singletonList("excl")) // - .build(); + UpdateQuery updateQuery = UpdateQuery.builder("1") // + .withDocument(document) // + .withIfSeqNo(42) // + .withIfPrimaryTerm(13) // + .withScript("script")// + .withLang("lang") // + .withRefreshPolicy(RefreshPolicy.WAIT_UNTIL) // + .withRetryOnConflict(7) // + .withTimeout("4711s") // + .withWaitForActiveShards("all").withFetchSourceIncludes(Collections.singletonList("incl")) // + .withFetchSourceExcludes(Collections.singletonList("excl")) // + .build(); UpdateRequestBuilder request = getRequestFactory().updateRequestBuilderFor(client, updateQuery, IndexCoordinates.of("index")); @@ -133,7 +131,7 @@ void shouldUseAllOptionsFromUpdateQuery() { assertThat(request.request().retryOnConflict()).isEqualTo(7); assertThat(request.request().timeout()).isEqualByComparingTo(TimeValue.parseTimeValue("4711s", "test")); assertThat(request.request().waitForActiveShards()).isEqualTo(ActiveShardCount.ALL); - FetchSourceContext fetchSourceContext = request.request().fetchSource(); + FetchSourceContext fetchSourceContext = request.request().fetchSource(); assertThat(fetchSourceContext).isNotNull(); assertThat(fetchSourceContext.includes()).containsExactlyInAnyOrder("incl"); assertThat(fetchSourceContext.excludes()).containsExactlyInAnyOrder("excl"); @@ -197,11 +195,25 @@ void shouldUseAllOptionsFromUpdateByQuery() throws JSONException { assertThat(request.request().getScript().getType()).isEqualTo(org.elasticsearch.script.ScriptType.STORED); } - @Data @Document(indexName = "test-index-sample-core-transport-template", replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; + public void setType(String type) { + this.type = type; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java b/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java index 0f0fa40d5..cf266ab92 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/EntityOperationsTest.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.Arrays; import java.util.HashSet; @@ -66,7 +61,9 @@ static void setUpAll() { @DisplayName("should return routing from DefaultRoutingAccessor") void shouldReturnRoutingFromDefaultRoutingAccessor() { - EntityWithRouting entity = EntityWithRouting.builder().id("42").routing("theRoute").build(); + EntityWithRouting entity = new EntityWithRouting(); + entity.setId("42"); + entity.setRouting("theRoute"); EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, conversionService, new DefaultRoutingResolver(mappingContext)); @@ -79,8 +76,9 @@ void shouldReturnRoutingFromDefaultRoutingAccessor() { @DisplayName("should return routing from JoinField when routing value is null") void shouldReturnRoutingFromJoinFieldWhenRoutingValueIsNull() { - EntityWithRoutingAndJoinField entity = EntityWithRoutingAndJoinField.builder().id("42") - .joinField(new JoinField<>("foo", "foo-routing")).build(); + EntityWithRoutingAndJoinField entity = new EntityWithRoutingAndJoinField(); + entity.setId("42"); + entity.setJoinField(new JoinField<>("foo", "foo-routing")); EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, conversionService, new DefaultRoutingResolver(mappingContext)); @@ -93,8 +91,10 @@ void shouldReturnRoutingFromJoinFieldWhenRoutingValueIsNull() { @Test // #1218 @DisplayName("should return routing from routing when JoinField is set") void shouldReturnRoutingFromRoutingWhenJoinFieldIsSet() { - EntityWithRoutingAndJoinField entity = EntityWithRoutingAndJoinField.builder().id("42").routing("theRoute") - .joinField(new JoinField<>("foo", "foo-routing")).build(); + EntityWithRoutingAndJoinField entity = new EntityWithRoutingAndJoinField(); + entity.setId("42"); + entity.setRouting("theRoute"); + entity.setJoinField(new JoinField<>("foo", "foo-routing")); EntityOperations.AdaptibleEntity adaptibleEntity = entityOperations.forEntity(entity, conversionService, new DefaultRoutingResolver(mappingContext)); @@ -104,26 +104,63 @@ void shouldReturnRoutingFromRoutingWhenJoinFieldIsSet() { assertThat(routing).isEqualTo("theRoute"); } - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "entity-operations-test") @Routing("routing") static class EntityWithRouting { - @Id private String id; - private String routing; + @Nullable @Id private String id; + @Nullable private String routing; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getRouting() { + return routing; + } + + public void setRouting(@Nullable String routing) { + this.routing = routing; + } } - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "entity-operations-test") @Routing("routing") static class EntityWithRoutingAndJoinField { - @Id private String id; - private String routing; - private JoinField joinField; + @Nullable @Id private String id; + @Nullable private String routing; + @Nullable private JoinField joinField; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getRouting() { + return routing; + } + + public void setRouting(@Nullable String routing) { + this.routing = routing; + } + + @Nullable + public JoinField getJoinField() { + return joinField; + } + + public void setJoinField(@Nullable JoinField joinField) { + this.joinField = joinField; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsTests.java b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsTests.java index e4406b1e5..9bd1a1706 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/InnerHitsTests.java @@ -18,10 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.RequiredArgsConstructor; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -132,39 +128,103 @@ void shouldReturnInnerHits() { softly.assertAll(); } - @Data - @AllArgsConstructor - @RequiredArgsConstructor @Document(indexName = INDEX_NAME) static class City { - - @Id private String name; - + @Nullable @Id private String name; // NOTE: using a custom names here to cover property name matching - @Field(name = "hou-ses", type = FieldType.Nested) private Collection houses = new ArrayList<>(); + @Nullable @Field(name = "hou-ses", type = FieldType.Nested) private Collection houses = new ArrayList<>(); + + public City(@Nullable String name, @Nullable Collection houses) { + this.name = name; + this.houses = houses; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Collection getHouses() { + return houses; + } + + public void setHouses(@Nullable Collection houses) { + this.houses = houses; + } } - @Data - @AllArgsConstructor - @RequiredArgsConstructor static class House { - - @Field(type = FieldType.Text) private String street; - - @Field(type = FieldType.Text) private String streetNumber; - + @Nullable @Field(type = FieldType.Text) private String street; + @Nullable @Field(type = FieldType.Text) private String streetNumber; // NOTE: using a custom names here to cover property name matching - @Field(name = "in-habi-tants", type = FieldType.Nested) private List inhabitants = new ArrayList<>(); + @Nullable @Field(name = "in-habi-tants", + type = FieldType.Nested) private List inhabitants = new ArrayList<>(); + + public House(@Nullable String street, @Nullable String streetNumber, @Nullable List inhabitants) { + this.street = street; + this.streetNumber = streetNumber; + this.inhabitants = inhabitants; + } + + @Nullable + public String getStreet() { + return street; + } + + public void setStreet(@Nullable String street) { + this.street = street; + } + + @Nullable + public String getStreetNumber() { + return streetNumber; + } + + public void setStreetNumber(@Nullable String streetNumber) { + this.streetNumber = streetNumber; + } + + @Nullable + public List getInhabitants() { + return inhabitants; + } + + public void setInhabitants(@Nullable List inhabitants) { + this.inhabitants = inhabitants; + } } - @Data - @AllArgsConstructor - @RequiredArgsConstructor static class Inhabitant { // NOTE: using a custom names here to cover property name matching - - @Field(name = "first-name", type = FieldType.Text) private String firstName; - - @Field(name = "last-name", type = FieldType.Text) private String lastName; + @Nullable @Field(name = "first-name", type = FieldType.Text) private String firstName; + @Nullable @Field(name = "last-name", type = FieldType.Text) private String lastName; + + public Inhabitant(@Nullable String firstName, @Nullable String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Nullable + public String getFirstName() { + return firstName; + } + + public void setFirstName(@Nullable String firstName) { + this.firstName = firstName; + } + + @Nullable + public String getLastName() { + return lastName; + } + + public void setLastName(@Nullable String lastName) { + this.lastName = lastName; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java index fb2d5be77..a54ca91ea 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java @@ -19,8 +19,6 @@ import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.Data; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -44,6 +42,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -133,28 +132,66 @@ public void shouldReturnLogsForGivenIPRanges() { /** * Simple type to test facets */ - @Data @Document(indexName = "test-index-log-core", replicas = 0, refreshInterval = "-1") static class LogEntity { private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); - @Id private String id; - - private String action; + @Nullable @Id private String id; + @Nullable private String action; + @Nullable private long sequenceCode; + @Nullable @Field(type = Ip) private String ip; + @Nullable @Field(type = Date, format = DateFormat.date_time) private java.util.Date date; - private long sequenceCode; + private LogEntity() {} - @Field(type = Ip) private String ip; + public LogEntity(String id) { + this.id = id; + } - @Field(type = Date, format = DateFormat.date_time) private java.util.Date date; + public static SimpleDateFormat getFormat() { + return format; + } - private LogEntity() {} + public String getId() { + return id; + } - public LogEntity(String id) { + public void setId(String id) { this.id = id; } + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public long getSequenceCode() { + return sequenceCode; + } + + public void setSequenceCode(long sequenceCode) { + this.sequenceCode = sequenceCode; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public java.util.Date getDate() { + return date; + } + + public void setDate(java.util.Date date) { + this.date = date; + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java index bfff6f2f6..aaeb3bdd8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateCallbackTests.java @@ -18,9 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.MultiGetItemResponse; import reactor.core.publisher.Flux; @@ -378,13 +375,53 @@ void saveAllShouldInvokeBeforeConvertCallbacks() { assertThat(saved.get(1).firstname).isEqualTo("before-convert"); } - @Data - @AllArgsConstructor - @NoArgsConstructor static class Person { + @Nullable @Id String id; + @Nullable String firstname; - @Id String id; - String firstname; + public Person() { + } + + public Person(@Nullable String id, @Nullable String firstname) { + this.id = id; + this.firstname = firstname; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getFirstname() { + return firstname; + } + + public void setFirstname(@Nullable String firstname) { + this.firstname = firstname; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Person person = (Person) o; + + if (id != null ? !id.equals(person.id) : person.id != null) return false; + return firstname != null ? firstname.equals(person.firstname) : person.firstname == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (firstname != null ? firstname.hashCode() : 0); + return result; + } } static class ValueCapturingEntityCallback { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index ec8c25153..45e580f46 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -20,11 +20,6 @@ import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -81,6 +76,7 @@ import org.springframework.data.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -179,7 +175,8 @@ public void insertWithIdShouldWork() { @Test // DATAES-504 public void insertWithAutogeneratedIdShouldUpdateEntityId() { - SampleEntity sampleEntity = SampleEntity.builder().message("wohoo").build(); + SampleEntity sampleEntity = new SampleEntity(); + sampleEntity.setMessage("wohoo"); template.save(sampleEntity) // .map(SampleEntity::getId) // @@ -1157,10 +1154,11 @@ void shouldReturnInformationListOfAllIndices() { // region Helper functions private SampleEntity randomEntity(String message) { - return SampleEntity.builder() // - .id(UUID.randomUUID().toString()) // - .message(StringUtils.hasText(message) ? message : "test message") // - .version(System.currentTimeMillis()).build(); + SampleEntity entity = new SampleEntity(); + entity.setId(UUID.randomUUID().toString()); + entity.setMessage(StringUtils.hasText(message) ? message : "test message"); + entity.setVersion(System.currentTimeMillis()); + return entity; } private IndexQuery getIndexQuery(SampleEntity sampleEntity) { @@ -1187,75 +1185,259 @@ private void index(SampleEntity... entities) { // endregion // region Entities - @Data @Document(indexName = "marvel") static class Person { - - private @Id String id; - private String name; - private int age; + @Nullable private @Id String id; + @Nullable private String name; + @Nullable private int age; public Person() {} public Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + public void setName(@Nullable String name) { this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { this.age = age; } } - @Data - @AllArgsConstructor - @NoArgsConstructor static class Message { + @Nullable String message; + + public Message(String message) { + this.message = message; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - String message; + Message message1 = (Message) o; + + return message != null ? message.equals(message1.message) : message1.message == null; + } + + @Override + public int hashCode() { + return message != null ? message.hashCode() : 0; + } } - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - @EqualsAndHashCode(exclude = "score") @Document(indexName = DEFAULT_INDEX, replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SampleEntity that = (SampleEntity) o; - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - @Version private Long version; + if (rate != that.rate) return false; + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (message != null ? !message.equals(that.message) : that.message != null) return false; + return version != null ? version.equals(that.version) : that.version == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (message != null ? message.hashCode() : 0); + result = 31 * result + rate; + result = 31 * result + (version != null ? version.hashCode() : 0); + return result; + } } - @Data @Document(indexName = "test-index-reactive-optimistic-entity-template") static class OptimisticEntity { - @Id private String id; - private String message; - private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable @Id private String id; + @Nullable private String message; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } } - @Data @Document(indexName = "test-index-reactive-optimistic-and-versioned-entity-template") static class OptimisticAndVersionedEntity { - @Id private String id; - private String message; - private SeqNoPrimaryTerm seqNoPrimaryTerm; - @Version private Long version; + @Nullable @Id private String id; + @Nullable private String message; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data @Document(indexName = "test-index-reactive-versioned-entity-template") static class VersionedEntity { - @Id private String id; - @Version private Long version; + @Nullable @Id private String id; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } - @Data @Document(indexName = "test-index-reactive-information-list", createIndex = false) @Setting(settingPath = "settings/test-settings.json") @Mapping(mappingPath = "mappings/test-mappings.json") private static class EntityWithSettingsAndMappingsReactive { - @Id String id; + @Nullable @Id String id; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } } // endregion diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java index fb467a144..98eb8dc65 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java @@ -20,10 +20,6 @@ import static org.mockito.Mockito.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -62,6 +58,7 @@ import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.StringQuery; +import org.springframework.lang.Nullable; /** * @author Christoph Strobl @@ -253,27 +250,89 @@ public void deleteByShouldApplyIndicesOptionsIfSet() { assertThat(captor.getValue().indicesOptions()).isEqualTo(IndicesOptions.LENIENT_EXPAND_OPEN); } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Chris White - * @author Sascha Woo - */ - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-sample-core-reactive-template-Unit", replicas = 0, refreshInterval = "-1") static class SampleEntity { - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - @ScriptedField private Double scriptedRate; - private boolean available; - private String highlightedMessage; - private GeoPoint location; - @Version private Long version; + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Double scriptedRate; + @Nullable private boolean available; + @Nullable private String highlightedMessage; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + public java.lang.Double getScriptedRate() { + return scriptedRate; + } + + public void setScriptedRate(java.lang.Double scriptedRate) { + this.scriptedRate = scriptedRate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + public String getHighlightedMessage() { + return highlightedMessage; + } + + public void setHighlightedMessage(String highlightedMessage) { + this.highlightedMessage = highlightedMessage; + } + + public GeoPoint getLocation() { + return location; + } + + public void setLocation(GeoPoint location) { + this.location = location; + } + + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(java.lang.Long version) { + this.version = version; + } } } 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 01e6e9dd9..8348b98ef 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java @@ -18,7 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import lombok.Data; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -506,27 +505,69 @@ void shouldDeleteTemplate() { assertThat(exists).isFalse(); } - @Data @Document(indexName = TESTINDEX, shards = 3, replicas = 2, refreshInterval = "4s") static class Entity { - @Id private String id; - @Field(type = FieldType.Text) private String text; - @Field(name = "publication-date", type = FieldType.Date, + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Text) private String text; + @Nullable @Field(name = "publication-date", type = FieldType.Date, format = DateFormat.basic_date) private LocalDate publicationDate; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + + @Nullable + public LocalDate getPublicationDate() { + return publicationDate; + } + + public void setPublicationDate(@Nullable LocalDate publicationDate) { + this.publicationDate = publicationDate; + } } - @Data @Document(indexName = TESTINDEX, useServerConfiguration = true) static class EntityUseServerConfig { - @Id private String id; + @Nullable @Id private String id; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } } - @Data @Document(indexName = TESTINDEX) @Setting(settingPath = "/settings/test-settings.json") @Mapping(mappingPath = "/mappings/test-mappings.json") static class EntityWithAnnotatedSettingsAndMappings { - @Id private String id; + @Nullable @Id private String id; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } } @Document(indexName = "test-template", shards = 3, replicas = 2, refreshInterval = "5s") 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 b06b7edbf..ffc7ef184 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java @@ -20,11 +20,6 @@ import static org.mockito.Mockito.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.io.IOException; import java.util.Arrays; import java.util.HashSet; @@ -460,8 +455,7 @@ void shouldCreatePutIndexTemplateRequest() throws JSONException, IOException { @DisplayName("should set op_type INDEX if not specified") void shouldSetOpTypeIndexIfNotSpecifiedAndIdIsSet() { - IndexQuery indexQuery = new IndexQueryBuilder().withId("42").withObject(Person.builder().id("42").lastName("Smith")) - .build(); + IndexQuery indexQuery = new IndexQueryBuilder().withId("42").withObject(new Person("42", "Smith")).build(); IndexRequest indexRequest = requestFactory.indexRequest(indexQuery, IndexCoordinates.of("optype")); @@ -473,7 +467,7 @@ void shouldSetOpTypeIndexIfNotSpecifiedAndIdIsSet() { void shouldSetOpTypeCreateIfSpecified() { IndexQuery indexQuery = new IndexQueryBuilder().withOpType(IndexQuery.OpType.CREATE).withId("42") - .withObject(Person.builder().id("42").lastName("Smith")).build(); + .withObject(new Person("42", "Smith")).build(); IndexRequest indexRequest = requestFactory.indexRequest(indexQuery, IndexCoordinates.of("optype")); @@ -485,7 +479,7 @@ void shouldSetOpTypeCreateIfSpecified() { void shouldSetOpTypeIndexIfSpecified() { IndexQuery indexQuery = new IndexQueryBuilder().withOpType(IndexQuery.OpType.INDEX).withId("42") - .withObject(Person.builder().id("42").lastName("Smith")).build(); + .withObject(new Person("42", "Smith")).build(); IndexRequest indexRequest = requestFactory.indexRequest(indexQuery, IndexCoordinates.of("optype")); @@ -601,14 +595,50 @@ void shouldBuildSearchWithRescorerQuery() throws JSONException { assertEquals(expected, searchRequest, false); } - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor static class Person { @Nullable @Id String id; @Nullable @Field(name = "last-name") String lastName; @Nullable @Field(name = "current-location") GeoPoint location; + + public Person() {} + + public Person(@Nullable String id, @Nullable String lastName) { + this.id = id; + this.lastName = lastName; + } + + public Person(@Nullable String id, @Nullable String lastName, @Nullable GeoPoint location) { + this.id = id; + this.lastName = lastName; + this.location = location; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getLastName() { + return lastName; + } + + public void setLastName(@Nullable String lastName) { + this.lastName = lastName; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } } static class EntityWithSeqNoPrimaryTerm { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java index c03c3cd94..c04c30879 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java @@ -17,12 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -76,10 +70,10 @@ void after() { private void loadEntities() { List indexQueries = new ArrayList<>(); - indexQueries.add(SearchAsYouTypeEntity.builder().id("1").name("test 1").suggest("test 1234").build().toIndex()); - indexQueries.add(SearchAsYouTypeEntity.builder().id("2").name("test 2").suggest("test 5678").build().toIndex()); - indexQueries.add(SearchAsYouTypeEntity.builder().id("3").name("test 3").suggest("asd 5678").build().toIndex()); - indexQueries.add(SearchAsYouTypeEntity.builder().id("4").name("test 4").suggest("not match").build().toIndex()); + indexQueries.add(new SearchAsYouTypeEntity("1", "test 1", "test 1234").toIndex()); + indexQueries.add(new SearchAsYouTypeEntity("2", "test 2", "test 5678").toIndex()); + indexQueries.add(new SearchAsYouTypeEntity("3", "test 3", "asd 5678").toIndex()); + indexQueries.add(new SearchAsYouTypeEntity("4", "test 4", "not match").toIndex()); IndexCoordinates index = IndexCoordinates.of("test-index-core-search-as-you-type"); operations.bulkIndex(indexQueries, index); operations.indexOps(SearchAsYouTypeEntity.class).refresh(); @@ -109,9 +103,8 @@ void shouldReturnCorrectResultsForTextString() { .collect(Collectors.toList()); // then - assertThat(result).hasSize(2); - assertThat(result).contains(new SearchAsYouTypeEntity("1")); - assertThat(result).contains(new SearchAsYouTypeEntity("2")); + List ids = result.stream().map(SearchAsYouTypeEntity::getId).collect(Collectors.toList()); + assertThat(ids).containsExactlyInAnyOrder("1", "2"); } @Test // DATAES-773 @@ -131,9 +124,8 @@ void shouldReturnCorrectResultsForNumQuery() { .collect(Collectors.toList()); // then - assertThat(result).hasSize(2); - assertThat(result).contains(new SearchAsYouTypeEntity("2")); - assertThat(result).contains(new SearchAsYouTypeEntity("3")); + List ids = result.stream().map(SearchAsYouTypeEntity::getId).collect(Collectors.toList()); + assertThat(ids).containsExactlyInAnyOrder("2", "3"); } @Test // DATAES-773 @@ -153,18 +145,12 @@ void shouldReturnCorrectResultsForNotMatchQuery() { .collect(Collectors.toList()); // then - assertThat(result).hasSize(1); - assertThat(result).contains(new SearchAsYouTypeEntity("4")); + assertThat(result.get(0).getId()).isEqualTo("4"); } /** * @author Aleksei Arsenev */ - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Document(indexName = "test-index-core-search-as-you-type", replicas = 0, refreshInterval = "-1") static class SearchAsYouTypeEntity { @@ -172,17 +158,71 @@ public SearchAsYouTypeEntity(@Nonnull String id) { this.id = id; } - @NonNull @Id @EqualsAndHashCode.Include private String id; - + @Nullable @Id private String id; @Nullable private String name; - @Nullable @Field(type = FieldType.Search_As_You_Type, maxShingleSize = 4) private String suggest; + public SearchAsYouTypeEntity() { + } + + public SearchAsYouTypeEntity(String id, @Nullable String name, @Nullable String suggest) { + this.id = id; + this.name = name; + this.suggest = suggest; + } + public IndexQuery toIndex() { IndexQuery indexQuery = new IndexQuery(); indexQuery.setId(getId()); indexQuery.setObject(this); return indexQuery; } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getSuggest() { + return suggest; + } + + public void setSuggest(@Nullable String suggest) { + this.suggest = suggest; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SearchAsYouTypeEntity that = (SearchAsYouTypeEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + return suggest != null ? suggest.equals(that.suggest) : that.suggest == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (suggest != null ? suggest.hashCode() : 0); + return result; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java index abcc79998..0bd7f4a18 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.Collections; import java.util.List; @@ -39,6 +34,7 @@ import org.springframework.data.elasticsearch.core.query.SourceFilter; 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; /** @@ -57,7 +53,12 @@ void setUp() { indexOps.create(); indexOps.putMapping(); - operations.save(Entity.builder().id("42").field1("one").field2("two").field3("three").build()); + Entity entity = new Entity(); + entity.setId("42"); + entity.setField1("one"); + entity.setField2("two"); + entity.setField3("three"); + operations.save(entity); } @AfterEach @@ -201,15 +202,47 @@ public String[] getExcludes() { assertThat(entity.getField3()).isNull(); } - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "sourcefilter-tests") public static class Entity { - @Id private String id; - @Field(type = FieldType.Text) private String field1; - @Field(type = FieldType.Text) private String field2; - @Field(type = FieldType.Text) private String field3; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Text) private String field1; + @Nullable @Field(type = FieldType.Text) private String field2; + @Nullable @Field(type = FieldType.Text) private String field3; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getField1() { + return field1; + } + + public void setField1(@Nullable String field1) { + this.field1 = field1; + } + + @Nullable + public String getField2() { + return field2; + } + + public void setField2(@Nullable String field2) { + this.field2 = field2; + } + + @Nullable + public String getField3() { + return field3; + } + + public void setField3(@Nullable String field3) { + this.field3 = field3; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java index 7b88efb8b..c60c23bf9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java @@ -21,8 +21,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Integer; -import lombok.Data; - import java.lang.Integer; import java.util.ArrayList; import java.util.List; @@ -50,6 +48,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -131,33 +130,79 @@ public void shouldReturnAggregatedResponseForGivenSearchQuery() { assertThat(searchHits.hasSearchHits()).isFalse(); } - /** - * Simple type to test facets - * - * @author Artur Konczak - * @author Mohsin Husen - */ - @Data @Document(indexName = "test-index-articles-core-aggregation", replicas = 0, refreshInterval = "-1") static class ArticleEntity { - @Id private String id; - private String title; - @Field(type = Text, fielddata = true) private String subject; + @Nullable @Id private String id; + @Nullable private String title; + @Nullable @Field(type = Text, fielddata = true) private String subject; - @MultiField(mainField = @Field(type = Text), + @Nullable @MultiField(mainField = @Field(type = Text), otherFields = { @InnerField(suffix = "untouched", type = Text, store = true, fielddata = true, analyzer = "keyword"), @InnerField(suffix = "sort", type = Text, store = true, analyzer = "keyword") }) private List authors = new ArrayList<>(); - @Field(type = Integer, store = true) private List publishedYears = new ArrayList<>(); + @Nullable @Field(type = Integer, store = true) private List publishedYears = new ArrayList<>(); - private int score; + @Nullable private int score; public ArticleEntity(String id) { this.id = id; } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getTitle() { + return title; + } + + public void setTitle(@Nullable String title) { + this.title = title; + } + + @Nullable + public String getSubject() { + return subject; + } + + public void setSubject(@Nullable String subject) { + this.subject = subject; + } + + @Nullable + public List getAuthors() { + return authors; + } + + public void setAuthors(@Nullable List authors) { + this.authors = authors; + } + + @Nullable + public List getPublishedYears() { + return publishedYears; + } + + public void setPublishedYears(@Nullable List publishedYears) { + this.publishedYears = publishedYears; + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 8d20208a8..225102876 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -19,15 +19,6 @@ import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.Setter; - import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -156,10 +147,10 @@ public void init() { observatoryRoad.location = new Point(-118.3026284D, 34.118347D); bigBunsCafe = new Place(); - bigBunsCafe.name = "Big Buns Cafe"; - bigBunsCafe.city = "Los Angeles"; - bigBunsCafe.street = "15 South Fremont Avenue"; - bigBunsCafe.location = new Point(-118.1545845D, 34.0945637D); + bigBunsCafe.setName("Big Buns Cafe"); + bigBunsCafe.setCity("Los Angeles"); + bigBunsCafe.setStreet("15 South Fremont Avenue"); + bigBunsCafe.setLocation(new Point(-118.1545845D, 34.0945637D)); sarahAsMap = Document.create(); sarahAsMap.put("id", "sarah"); @@ -258,13 +249,11 @@ public void shouldReturnDefaultConversionService() { @Test // DATAES-530 public void shouldMapObjectToJsonString() { - // Given - - // When - String jsonResult = mappingElasticsearchConverter.mapObject(Car.builder().model(CAR_MODEL).name(CAR_NAME).build()) - .toJson(); + Car car = new Car(); + car.setModel(CAR_MODEL); + car.setName(CAR_NAME); + String jsonResult = mappingElasticsearchConverter.mapObject(car).toJson(); - // Then assertThat(jsonResult).isEqualTo(JSON_STRING); } @@ -307,19 +296,14 @@ public void shouldMapGeoPointElasticsearchNames() throws JSONException { " ]\n" + // "}\n"; // - GeoEntity geoEntity = GeoEntity.builder().pointA(point).pointB(GeoPoint.fromPoint(point)).pointC(pointAsString) - .pointD(pointAsArray).build(); - // when + GeoEntity geoEntity = new GeoEntity(); + geoEntity.setPointA(point); + geoEntity.setPointB(GeoPoint.fromPoint(point)); + geoEntity.setPointC(pointAsString); + geoEntity.setPointD(pointAsArray); String jsonResult = mappingElasticsearchConverter.mapObject(geoEntity).toJson(); - // then - assertEquals(expected, jsonResult, false); - // assertThat(jsonResult).contains(pointTemplate("pointA", point)); - // assertThat(jsonResult).contains(pointTemplate("pointB", point)); - // assertThat(jsonResult).contains(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "pointC", pointAsString)); - // assertThat(jsonResult) - // .contains(String.format(Locale.ENGLISH, "\"%s\":[%.1f,%.1f]", "pointD", pointAsArray[0], pointAsArray[1])); } @Test // DATAES-530 @@ -327,10 +311,10 @@ public void ignoresReadOnlyProperties() { // given Sample sample = new Sample(); - sample.readOnly = "readOnly"; - sample.property = "property"; - sample.javaTransientProperty = "javaTransient"; - sample.annotatedTransientProperty = "transient"; + sample.setReadOnly("readOnly"); + sample.setProperty("property"); + sample.setJavaTransientProperty("javaTransient"); + sample.setAnnotatedTransientProperty("transient"); // when String result = mappingElasticsearchConverter.mapObject(sample).toJson(); @@ -360,8 +344,8 @@ public void writesNestedEntity() { public void writesConcreteList() { Person ginger = new Person(); - ginger.id = "ginger"; - ginger.gender = Gender.MAN; + ginger.setId("ginger"); + ginger.setGender(Gender.MAN); sarahConnor.coWorkers = Arrays.asList(kyleReese, ginger); @@ -630,9 +614,9 @@ public void readSubTypeCorrectly() { @Test // DATAES-716 void shouldWriteLocalDate() throws JSONException { Person person = new Person(); - person.id = "4711"; - person.firstName = "John"; - person.lastName = "Doe"; + person.setId("4711"); + person.setFirstName("John"); + person.setLastName("Doe"); person.birthDate = LocalDate.of(2000, 8, 22); person.gender = Gender.MAN; @@ -703,8 +687,8 @@ void shouldReadListOfLocalDate() { void writeEntityWithMapDataType() { Notification notification = new Notification(); - notification.fromEmail = "from@email.com"; - notification.toEmail = "to@email.com"; + notification.setFromEmail("from@email.com"); + notification.setToEmail("to@email.com"); Map data = new HashMap<>(); data.put("documentType", "abc"); data.put("content", null); @@ -920,23 +904,22 @@ void setup() { .of(Arrays.asList(GeoJsonPoint.of(12, 34), GeoJsonPolygon .of(GeoJsonLineString.of(new Point(12, 34), new Point(56, 78), new Point(90, 12), new Point(12, 34))))); - entity = GeoJsonEntity.builder() // - .id("42") // - .point1(GeoJsonPoint.of(12, 34)) // - .point2(GeoJsonPoint.of(56, 78)) // - .multiPoint1(GeoJsonMultiPoint.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))) // - .multiPoint2(GeoJsonMultiPoint.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))) // - .lineString1(GeoJsonLineString.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))) // - .lineString2(GeoJsonLineString.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))) // - .multiLineString1(multiLineString) // - .multiLineString2(multiLineString) // - .polygon1(geoJsonPolygon) // - .polygon2(geoJsonPolygon) // - .multiPolygon1(geoJsonMultiPolygon) // - .multiPolygon2(geoJsonMultiPolygon) // - .geometryCollection1(geoJsonGeometryCollection) // - .geometryCollection2(geoJsonGeometryCollection) // - .build(); + entity = new GeoJsonEntity(); + entity.setId("42"); + entity.setPoint1(GeoJsonPoint.of(12, 34)); + entity.setPoint2(GeoJsonPoint.of(56, 78)); + entity.setMultiPoint1(GeoJsonMultiPoint.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))); + entity.setMultiPoint2(GeoJsonMultiPoint.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))); + entity.setLineString1(GeoJsonLineString.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))); + entity.setLineString2(GeoJsonLineString.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))); + entity.setMultiLineString1(multiLineString); + entity.setMultiLineString2(multiLineString); + entity.setPolygon1(geoJsonPolygon); + entity.setPolygon2(geoJsonPolygon); + entity.setMultiPolygon1(geoJsonMultiPolygon); + entity.setMultiPolygon2(geoJsonMultiPolygon); + entity.setGeometryCollection1(geoJsonGeometryCollection); + entity.setGeometryCollection2(geoJsonGeometryCollection); } @Test // DATAES-930 @@ -1205,37 +1188,233 @@ private Map writeToMap(Object source) { } public static class Sample { - @Nullable public @ReadOnlyProperty String readOnly; @Nullable public @Transient String annotatedTransientProperty; @Nullable public transient String javaTransientProperty; @Nullable public String property; + + @Nullable + public String getReadOnly() { + return readOnly; + } + + public void setReadOnly(@Nullable String readOnly) { + this.readOnly = readOnly; + } + + @Nullable + public String getAnnotatedTransientProperty() { + return annotatedTransientProperty; + } + + public void setAnnotatedTransientProperty(@Nullable String annotatedTransientProperty) { + this.annotatedTransientProperty = annotatedTransientProperty; + } + + @Nullable + public String getJavaTransientProperty() { + return javaTransientProperty; + } + + public void setJavaTransientProperty(@Nullable String javaTransientProperty) { + this.javaTransientProperty = javaTransientProperty; + } + + @Nullable + public String getProperty() { + return property; + } + + public void setProperty(@Nullable String property) { + this.property = property; + } } - @Data static class Person { + @Nullable @Id String id; + @Nullable String name; + @Nullable @Field(name = "first-name") String firstName; + @Nullable @Field(name = "last-name") String lastName; + @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, + pattern = "dd.MM.uuuu") LocalDate birthDate; + @Nullable Gender gender; + @Nullable Address address; + @Nullable List coWorkers; + @Nullable List inventoryList; + @Nullable Map shippingAddresses; + @Nullable Map inventoryMap; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getFirstName() { + return firstName; + } + + public void setFirstName(@Nullable String firstName) { + this.firstName = firstName; + } + + @Nullable + public String getLastName() { + return lastName; + } + + public void setLastName(@Nullable String lastName) { + this.lastName = lastName; + } + + @Nullable + public LocalDate getBirthDate() { + return birthDate; + } + + public void setBirthDate(@Nullable LocalDate birthDate) { + this.birthDate = birthDate; + } + + @Nullable + public Gender getGender() { + return gender; + } + + public void setGender(@Nullable Gender gender) { + this.gender = gender; + } + + @Nullable + public Address getAddress() { + return address; + } + + public void setAddress(@Nullable Address address) { + this.address = address; + } + + @Nullable + public List getCoWorkers() { + return coWorkers; + } + + public void setCoWorkers(@Nullable List coWorkers) { + this.coWorkers = coWorkers; + } + + @Nullable + public List getInventoryList() { + return inventoryList; + } + + public void setInventoryList(@Nullable List inventoryList) { + this.inventoryList = inventoryList; + } + + @Nullable + public Map getShippingAddresses() { + return shippingAddresses; + } + + public void setShippingAddresses(@Nullable Map shippingAddresses) { + this.shippingAddresses = shippingAddresses; + } - @Id String id; - String name; - @Field(name = "first-name") String firstName; - @Field(name = "last-name") String lastName; - @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; - Gender gender; - Address address; + @Nullable + public Map getInventoryMap() { + return inventoryMap; + } + + public void setInventoryMap(@Nullable Map inventoryMap) { + this.inventoryMap = inventoryMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Person person = (Person) o; + + if (id != null ? !id.equals(person.id) : person.id != null) + return false; + if (name != null ? !name.equals(person.name) : person.name != null) + return false; + if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) + return false; + if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) + return false; + if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null) + return false; + if (gender != person.gender) + return false; + if (address != null ? !address.equals(person.address) : person.address != null) + return false; + if (coWorkers != null ? !coWorkers.equals(person.coWorkers) : person.coWorkers != null) + return false; + if (inventoryList != null ? !inventoryList.equals(person.inventoryList) : person.inventoryList != null) + return false; + if (shippingAddresses != null ? !shippingAddresses.equals(person.shippingAddresses) + : person.shippingAddresses != null) + return false; + return inventoryMap != null ? inventoryMap.equals(person.inventoryMap) : person.inventoryMap == null; + } - List coWorkers; - List inventoryList; - Map shippingAddresses; - Map inventoryMap; + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (firstName != null ? firstName.hashCode() : 0); + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + result = 31 * result + (birthDate != null ? birthDate.hashCode() : 0); + result = 31 * result + (gender != null ? gender.hashCode() : 0); + result = 31 * result + (address != null ? address.hashCode() : 0); + result = 31 * result + (coWorkers != null ? coWorkers.hashCode() : 0); + result = 31 * result + (inventoryList != null ? inventoryList.hashCode() : 0); + result = 31 * result + (shippingAddresses != null ? shippingAddresses.hashCode() : 0); + result = 31 * result + (inventoryMap != null ? inventoryMap.hashCode() : 0); + return result; + } } - @Data - @Getter - @Setter static class LocalDatesEntity { - @Id private String id; - @Field(name = "dates", type = FieldType.Date, format = DateFormat.custom, + @Nullable @Id private String id; + @Nullable @Field(name = "dates", type = FieldType.Date, format = DateFormat.custom, pattern = "dd.MM.uuuu") private List dates; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public List getDates() { + return dates; + } + + public void setDates(@Nullable List dates) { + this.dates = dates; + } } enum Gender { @@ -1258,89 +1437,314 @@ interface Inventory { String getLabel(); } - @Getter - @RequiredArgsConstructor - @EqualsAndHashCode static class Gun implements Inventory { - final String label; final int shotsPerMagazine; + public Gun(@Nullable String label, int shotsPerMagazine) { + this.label = label; + this.shotsPerMagazine = shotsPerMagazine; + } + @Override public String getLabel() { return label; } + + public int getShotsPerMagazine() { + return shotsPerMagazine; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + Gun gun = (Gun) o; + + if (shotsPerMagazine != gun.shotsPerMagazine) + return false; + return label.equals(gun.label); + } + + @Override + public int hashCode() { + int result = label.hashCode(); + result = 31 * result + shotsPerMagazine; + return result; + } } - @RequiredArgsConstructor - @EqualsAndHashCode static class Grenade implements Inventory { - final String label; + public Grenade(String label) { + this.label = label; + } + @Override public String getLabel() { return label; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Grenade)) + return false; + + Grenade grenade = (Grenade) o; + + return label.equals(grenade.label); + } + + @Override + public int hashCode() { + return label.hashCode(); + } } @TypeAlias("rifle") - @EqualsAndHashCode - @RequiredArgsConstructor static class Rifle implements Inventory { final String label; final double weight; final int maxShotsPerMagazine; + public Rifle(String label, double weight, int maxShotsPerMagazine) { + this.label = label; + this.weight = weight; + this.maxShotsPerMagazine = maxShotsPerMagazine; + } + @Override public String getLabel() { return label; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Rifle)) + return false; + + Rifle rifle = (Rifle) o; + + if (Double.compare(rifle.weight, weight) != 0) + return false; + if (maxShotsPerMagazine != rifle.maxShotsPerMagazine) + return false; + return label.equals(rifle.label); + } + + @Override + public int hashCode() { + int result; + long temp; + result = label.hashCode(); + temp = Double.doubleToLongBits(weight); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + result = 31 * result + maxShotsPerMagazine; + return result; + } } - @EqualsAndHashCode - @RequiredArgsConstructor static class ShotGun implements Inventory { - final String label; + private final String label; + + public ShotGun(String label) { + this.label = label; + } @Override public String getLabel() { return label; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ShotGun)) + return false; + + ShotGun shotGun = (ShotGun) o; + + return label.equals(shotGun.label); + } + + @Override + public int hashCode() { + return label.hashCode(); + } } - @Data static class Address { + @Nullable private Point location; + @Nullable private String street; + @Nullable private String city; + + @Nullable + public Point getLocation() { + return location; + } + + public void setLocation(@Nullable Point location) { + this.location = location; + } - Point location; - String street; - String city; + @Nullable + public String getStreet() { + return street; + } + + public void setStreet(@Nullable String street) { + this.street = street; + } + + @Nullable + public String getCity() { + return city; + } + + public void setCity(@Nullable String city) { + this.city = city; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Address)) + return false; + + Address address = (Address) o; + + if (location != null ? !location.equals(address.location) : address.location != null) + return false; + if (street != null ? !street.equals(address.street) : address.street != null) + return false; + return city != null ? city.equals(address.city) : address.city == null; + } + + @Override + public int hashCode() { + int result = location != null ? location.hashCode() : 0; + result = 31 * result + (street != null ? street.hashCode() : 0); + result = 31 * result + (city != null ? city.hashCode() : 0); + return result; + } } - @EqualsAndHashCode(callSuper = true) - @Data static class Place extends Address { + @Nullable private String name; + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } - String name; + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Place)) + return false; + + Place place = (Place) o; + + return name != null ? name.equals(place.name) : place.name == null; + } + + @Override + public int hashCode() { + return name != null ? name.hashCode() : 0; + } } - @Data static class Skynet { + @Nullable private Object object; + @Nullable private List objectList; + @Nullable private Map objectMap; + + @Nullable + public Object getObject() { + return object; + } + + public void setObject(@Nullable Object object) { + this.object = object; + } + + @Nullable + public List getObjectList() { + return objectList; + } + + public void setObjectList(@Nullable List objectList) { + this.objectList = objectList; + } + + @Nullable + public Map getObjectMap() { + return objectMap; + } - Object object; - List objectList; - Map objectMap; + public void setObjectMap(@Nullable Map objectMap) { + this.objectMap = objectMap; + } } - @Data static class Notification { + @Nullable private Long id; + @Nullable private String fromEmail; + @Nullable private String toEmail; + @Nullable private Map params; + + @Nullable + public Long getId() { + return id; + } + + public void setId(@Nullable Long id) { + this.id = id; + } + + @Nullable + public String getFromEmail() { + return fromEmail; + } + + public void setFromEmail(@Nullable String fromEmail) { + this.fromEmail = fromEmail; + } + + @Nullable + public String getToEmail() { + return toEmail; + } + + public void setToEmail(@Nullable String toEmail) { + this.toEmail = toEmail; + } + + @Nullable + public Map getParams() { + return params; + } - Long id; - String fromEmail; - String toEmail; - Map params; + public void setParams(@Nullable Map params) { + this.params = params; + } } @WritingConverter @@ -1365,81 +1769,244 @@ public ShotGun convert(Map source) { } } - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder static class Car { + @Nullable private String name; + @Nullable private String model; - private String name; - private String model; + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getModel() { + return model; + } + + public void setModel(@Nullable String model) { + this.model = model; + } } - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @org.springframework.data.elasticsearch.annotations.Document(indexName = "test-index-geo-core-entity-mapper", replicas = 0, refreshInterval = "-1") static class GeoEntity { + @Nullable @Id private String id; + // geo shape - Spring Data + @Nullable private Box box; + @Nullable private Circle circle; + @Nullable private Polygon polygon; + // geo point - Custom implementation + Spring Data + @Nullable @GeoPointField private Point pointA; + @Nullable private GeoPoint pointB; + @Nullable @GeoPointField private String pointC; + @Nullable @GeoPointField private double[] pointD; + + @Nullable + public String getId() { + return id; + } - @Id private String id; + public void setId(@Nullable String id) { + this.id = id; + } - // geo shape - Spring Data - private Box box; - private Circle circle; - private Polygon polygon; + @Nullable + public Box getBox() { + return box; + } - // geo point - Custom implementation + Spring Data - @GeoPointField private Point pointA; + public void setBox(@Nullable Box box) { + this.box = box; + } - private GeoPoint pointB; + @Nullable + public Circle getCircle() { + return circle; + } - @GeoPointField private String pointC; + public void setCircle(@Nullable Circle circle) { + this.circle = circle; + } + + @Nullable + public Polygon getPolygon() { + return polygon; + } + + public void setPolygon(@Nullable Polygon polygon) { + this.polygon = polygon; + } + + @Nullable + public Point getPointA() { + return pointA; + } + + public void setPointA(@Nullable Point pointA) { + this.pointA = pointA; + } + + @Nullable + public GeoPoint getPointB() { + return pointB; + } + + public void setPointB(@Nullable GeoPoint pointB) { + this.pointB = pointB; + } + + @Nullable + public String getPointC() { + return pointC; + } + + public void setPointC(@Nullable String pointC) { + this.pointC = pointC; + } + + @Nullable + public double[] getPointD() { + return pointD; + } - @GeoPointField private double[] pointD; + public void setPointD(@Nullable double[] pointD) { + this.pointD = pointD; + } } - @Data - @NoArgsConstructor - @AllArgsConstructor static class SchemaLessObjectWrapper { + @Nullable private Map schemaLessObject; - private Map schemaLessObject; + @Nullable + public Map getSchemaLessObject() { + return schemaLessObject; + } + + public void setSchemaLessObject(@Nullable Map schemaLessObject) { + this.schemaLessObject = schemaLessObject; + } } - @Data @org.springframework.data.elasticsearch.annotations.Document( indexName = "test-index-entity-with-seq-no-primary-term-mapper") static class EntityWithSeqNoPrimaryTerm { - @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } } - @Data static class EntityWithListProperty { - @Id private String id; + @Nullable @Id private String id; + @Nullable private List values; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } - private List values; + @Nullable + public List getValues() { + return values; + } + + public void setValues(@Nullable List values) { + this.values = values; + } } - @Data static class GeoPointListEntity { - @Id String id; - List locations; + @Nullable private @Id String id; + @Nullable private List locations; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public List getLocations() { + return locations; + } + + public void setLocations(@Nullable List locations) { + this.locations = locations; + } } - @Data static class EntityWithObject { - @Id private String id; - private Object content; + @Nullable @Id private String id; + @Nullable private Object content; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public Object getContent() { + return content; + } + + public void setContent(@Nullable Object content) { + this.content = content; + } } - @Data static class EntityWithNullField { - @Id private String id; - @Field(type = FieldType.Text) private String notSaved; - @Field(type = FieldType.Text, storeNullValue = true) private String saved; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Text) private String notSaved; + @Nullable @Field(type = FieldType.Text, storeNullValue = true) private String saved; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getNotSaved() { + return notSaved; + } + + public void setNotSaved(@Nullable String notSaved) { + this.notSaved = notSaved; + } + + @Nullable + public String getSaved() { + return saved; + } + + public void setSaved(@Nullable String saved) { + this.saved = saved; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTest.java index 3b0c22eb9..6020d29e9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/event/ElasticsearchOperationsCallbackIntegrationTest.java @@ -18,8 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import lombok.Data; - import java.util.Collections; import java.util.List; @@ -216,20 +214,56 @@ void shouldApplyConversionResultToIndexQueryInBulkIndex() { assertThat(capturedIndexQuery.getPrimaryTerm()).isEqualTo(seqNoPrimaryTerm.getPrimaryTerm()); } - @Data @Document(indexName = INDEX) static class SampleEntity { - @Id private String id; - private String text; + @Nullable @Id private String id; + @Nullable private String text; - @JoinTypeRelations(relations = { @JoinTypeRelation(parent = "question", - children = { "answer" }) }) @Nullable private JoinField joinField; + @Nullable @JoinTypeRelations(relations = { @JoinTypeRelation(parent = "question", + children = { "answer" }) }) + private JoinField joinField; - private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; public SampleEntity(String id, String text) { this.id = id; this.text = text; } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + + @Nullable + public JoinField getJoinField() { + return joinField; + } + + public void setJoinField(@Nullable JoinField joinField) { + this.joinField = joinField; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java index fd59402ff..548bc6847 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java @@ -17,13 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.util.ArrayList; import java.util.List; @@ -50,6 +43,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.utils.IndexInitializer; import org.springframework.data.geo.Point; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -106,23 +100,24 @@ private void loadAnnotationBaseEntities() { String latLonString = "51.000000, 0.100000"; String geohash = "u10j46mkfekr"; Geohash.stringEncode(0.100000, 51.000000); - LocationMarkerEntity location1 = LocationMarkerEntity.builder() // - .id("1").name("Artur Konczak") // - .locationAsString(latLonString) // - .locationAsArray(lonLatArray) // - .locationAsGeoHash(geohash).build(); - LocationMarkerEntity location2 = LocationMarkerEntity.builder() // - .id("2").name("Mohsin Husen") // - .locationAsString(geohash.substring(0, 8)) // - .locationAsArray(lonLatArray) // - .locationAsGeoHash(geohash.substring(0, 8)) // - .build(); - LocationMarkerEntity location3 = LocationMarkerEntity.builder() // - .id("3").name("Rizwan Idrees") // - .locationAsString(geohash) // - .locationAsArray(lonLatArray) // - .locationAsGeoHash(geohash) // - .build(); + LocationMarkerEntity location1 = new LocationMarkerEntity(); + location1.setId("1"); + location1.setName("Artur Konczak"); + location1.setLocationAsString(latLonString); + location1.setLocationAsArray(lonLatArray); + location1.setLocationAsGeoHash(geohash); + LocationMarkerEntity location2 = new LocationMarkerEntity(); + location2.setId("2"); + location2.setName("Mohsin Husen"); + location2.setLocationAsString(geohash.substring(0, 8)); + location2.setLocationAsArray(lonLatArray); + location2.setLocationAsGeoHash(geohash.substring(0, 8)); + LocationMarkerEntity location3 = new LocationMarkerEntity(); + location3.setId("3"); + location3.setName("Rizwan Idrees"); + location3.setLocationAsString(geohash); + location3.setLocationAsArray(lonLatArray); + location3.setLocationAsGeoHash(geohash); indexQueries.add(buildIndex(location1)); indexQueries.add(buildIndex(location2)); indexQueries.add(buildIndex(location3)); @@ -366,20 +361,44 @@ private IndexQuery buildIndex(LocationMarkerEntity result) { * @author Franck Marchand * @author Mohsin Husen */ - @Data @Document(indexName = "test-index-author-marker-core-geo", replicas = 0, refreshInterval = "-1") static class AuthorMarkerEntity { - - @Id private String id; - private String name; - - private GeoPoint location; + @Nullable @Id private String id; + @Nullable private String name; + @Nullable private GeoPoint location; private AuthorMarkerEntity() {} public AuthorMarkerEntity(String id) { this.id = id; } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } } /** @@ -417,25 +436,52 @@ public IndexQuery buildIndex() { } } - /** - * @author Franck Marchand - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-location-marker-core-geo", replicas = 0, refreshInterval = "-1") static class LocationMarkerEntity { + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @GeoPointField private String locationAsString; + @Nullable @GeoPointField private double[] locationAsArray; + @Nullable @GeoPointField private String locationAsGeoHash; + + public String getId() { + return id; + } - @Id private String id; - private String name; + public void setId(String id) { + this.id = id; + } - @GeoPointField private String locationAsString; + public String getName() { + return name; + } - @GeoPointField private double[] locationAsArray; + public void setName(String name) { + this.name = name; + } - @GeoPointField private String locationAsGeoHash; + public String getLocationAsString() { + return locationAsString; + } + public void setLocationAsString(String locationAsString) { + this.locationAsString = locationAsString; + } + + public double[] getLocationAsArray() { + return locationAsArray; + } + + public void setLocationAsArray(double[] locationAsArray) { + this.locationAsArray = locationAsArray; + } + + public String getLocationAsGeoHash() { + return locationAsGeoHash; + } + + public void setLocationAsGeoHash(String locationAsGeoHash) { + this.locationAsGeoHash = locationAsGeoHash; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonEntity.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonEntity.java index 7e780cc98..dde990712 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonEntity.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonEntity.java @@ -15,33 +15,254 @@ */ package org.springframework.data.elasticsearch.core.geo; -import lombok.Builder; -import lombok.Data; - import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.geo.Point; +import org.springframework.lang.Nullable; /** * this class contains each GeoJson type as explicit type and as GeoJson interface. Used by several test classes */ -@Data -@Builder @Document(indexName = "geojson-index") public class GeoJsonEntity { - @Id private String id; - GeoJsonPoint point1; - GeoJson> point2; - GeoJsonMultiPoint multiPoint1; - GeoJson> multiPoint2; - GeoJsonLineString lineString1; - GeoJson> lineString2; - GeoJsonMultiLineString multiLineString1; - GeoJson> multiLineString2; - GeoJsonPolygon polygon1; - GeoJson> polygon2; - GeoJsonMultiPolygon multiPolygon1; - GeoJson> multiPolygon2; - GeoJsonGeometryCollection geometryCollection1; - GeoJson>> geometryCollection2; + @Nullable @Id private String id; + @Nullable private GeoJsonPoint point1; + @Nullable private GeoJson> point2; + @Nullable private GeoJsonMultiPoint multiPoint1; + @Nullable private GeoJson> multiPoint2; + @Nullable private GeoJsonLineString lineString1; + @Nullable private GeoJson> lineString2; + @Nullable private GeoJsonMultiLineString multiLineString1; + @Nullable private GeoJson> multiLineString2; + @Nullable private GeoJsonPolygon polygon1; + @Nullable private GeoJson> polygon2; + @Nullable private GeoJsonMultiPolygon multiPolygon1; + @Nullable private GeoJson> multiPolygon2; + @Nullable private GeoJsonGeometryCollection geometryCollection1; + @Nullable private GeoJson>> geometryCollection2; + + public GeoJsonEntity() {} + + public GeoJsonEntity(@Nullable String id, @Nullable GeoJsonPoint point1, + @Nullable GeoJson> point2, @Nullable GeoJsonMultiPoint multiPoint1, + @Nullable GeoJson> multiPoint2, @Nullable GeoJsonLineString lineString1, + @Nullable GeoJson> lineString2, @Nullable GeoJsonMultiLineString multiLineString1, + @Nullable GeoJson> multiLineString2, @Nullable GeoJsonPolygon polygon1, + @Nullable GeoJson> polygon2, @Nullable GeoJsonMultiPolygon multiPolygon1, + @Nullable GeoJson> multiPolygon2, + @Nullable GeoJsonGeometryCollection geometryCollection1, + @Nullable GeoJson>> geometryCollection2) { + this.id = id; + this.point1 = point1; + this.point2 = point2; + this.multiPoint1 = multiPoint1; + this.multiPoint2 = multiPoint2; + this.lineString1 = lineString1; + this.lineString2 = lineString2; + this.multiLineString1 = multiLineString1; + this.multiLineString2 = multiLineString2; + this.polygon1 = polygon1; + this.polygon2 = polygon2; + this.multiPolygon1 = multiPolygon1; + this.multiPolygon2 = multiPolygon2; + this.geometryCollection1 = geometryCollection1; + this.geometryCollection2 = geometryCollection2; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public GeoJsonPoint getPoint1() { + return point1; + } + + public void setPoint1(@Nullable GeoJsonPoint point1) { + this.point1 = point1; + } + + @Nullable + public GeoJson> getPoint2() { + return point2; + } + + public void setPoint2(@Nullable GeoJson> point2) { + this.point2 = point2; + } + + @Nullable + public GeoJsonMultiPoint getMultiPoint1() { + return multiPoint1; + } + + public void setMultiPoint1(@Nullable GeoJsonMultiPoint multiPoint1) { + this.multiPoint1 = multiPoint1; + } + + @Nullable + public GeoJson> getMultiPoint2() { + return multiPoint2; + } + + public void setMultiPoint2(@Nullable GeoJson> multiPoint2) { + this.multiPoint2 = multiPoint2; + } + + @Nullable + public GeoJsonLineString getLineString1() { + return lineString1; + } + + public void setLineString1(@Nullable GeoJsonLineString lineString1) { + this.lineString1 = lineString1; + } + + @Nullable + public GeoJson> getLineString2() { + return lineString2; + } + + public void setLineString2(@Nullable GeoJson> lineString2) { + this.lineString2 = lineString2; + } + + @Nullable + public GeoJsonMultiLineString getMultiLineString1() { + return multiLineString1; + } + + public void setMultiLineString1(@Nullable GeoJsonMultiLineString multiLineString1) { + this.multiLineString1 = multiLineString1; + } + + @Nullable + public GeoJson> getMultiLineString2() { + return multiLineString2; + } + + public void setMultiLineString2(@Nullable GeoJson> multiLineString2) { + this.multiLineString2 = multiLineString2; + } + + @Nullable + public GeoJsonPolygon getPolygon1() { + return polygon1; + } + + public void setPolygon1(@Nullable GeoJsonPolygon polygon1) { + this.polygon1 = polygon1; + } + + @Nullable + public GeoJson> getPolygon2() { + return polygon2; + } + + public void setPolygon2(@Nullable GeoJson> polygon2) { + this.polygon2 = polygon2; + } + + @Nullable + public GeoJsonMultiPolygon getMultiPolygon1() { + return multiPolygon1; + } + + public void setMultiPolygon1(@Nullable GeoJsonMultiPolygon multiPolygon1) { + this.multiPolygon1 = multiPolygon1; + } + + @Nullable + public GeoJson> getMultiPolygon2() { + return multiPolygon2; + } + + public void setMultiPolygon2(@Nullable GeoJson> multiPolygon2) { + this.multiPolygon2 = multiPolygon2; + } + + @Nullable + public GeoJsonGeometryCollection getGeometryCollection1() { + return geometryCollection1; + } + + public void setGeometryCollection1(@Nullable GeoJsonGeometryCollection geometryCollection1) { + this.geometryCollection1 = geometryCollection1; + } + + @Nullable + public GeoJson>> getGeometryCollection2() { + return geometryCollection2; + } + + public void setGeometryCollection2(@Nullable GeoJson>> geometryCollection2) { + this.geometryCollection2 = geometryCollection2; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof GeoJsonEntity)) + return false; + + GeoJsonEntity that = (GeoJsonEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (point1 != null ? !point1.equals(that.point1) : that.point1 != null) + return false; + if (point2 != null ? !point2.equals(that.point2) : that.point2 != null) + return false; + if (multiPoint1 != null ? !multiPoint1.equals(that.multiPoint1) : that.multiPoint1 != null) + return false; + if (multiPoint2 != null ? !multiPoint2.equals(that.multiPoint2) : that.multiPoint2 != null) + return false; + if (lineString1 != null ? !lineString1.equals(that.lineString1) : that.lineString1 != null) + return false; + if (lineString2 != null ? !lineString2.equals(that.lineString2) : that.lineString2 != null) + return false; + if (multiLineString1 != null ? !multiLineString1.equals(that.multiLineString1) : that.multiLineString1 != null) + return false; + if (multiLineString2 != null ? !multiLineString2.equals(that.multiLineString2) : that.multiLineString2 != null) + return false; + if (polygon1 != null ? !polygon1.equals(that.polygon1) : that.polygon1 != null) + return false; + if (polygon2 != null ? !polygon2.equals(that.polygon2) : that.polygon2 != null) + return false; + if (multiPolygon1 != null ? !multiPolygon1.equals(that.multiPolygon1) : that.multiPolygon1 != null) + return false; + if (multiPolygon2 != null ? !multiPolygon2.equals(that.multiPolygon2) : that.multiPolygon2 != null) + return false; + if (geometryCollection1 != null ? !geometryCollection1.equals(that.geometryCollection1) + : that.geometryCollection1 != null) + return false; + return geometryCollection2 != null ? geometryCollection2.equals(that.geometryCollection2) + : that.geometryCollection2 == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (point1 != null ? point1.hashCode() : 0); + result = 31 * result + (point2 != null ? point2.hashCode() : 0); + result = 31 * result + (multiPoint1 != null ? multiPoint1.hashCode() : 0); + result = 31 * result + (multiPoint2 != null ? multiPoint2.hashCode() : 0); + result = 31 * result + (lineString1 != null ? lineString1.hashCode() : 0); + result = 31 * result + (lineString2 != null ? lineString2.hashCode() : 0); + result = 31 * result + (multiLineString1 != null ? multiLineString1.hashCode() : 0); + result = 31 * result + (multiLineString2 != null ? multiLineString2.hashCode() : 0); + result = 31 * result + (polygon1 != null ? polygon1.hashCode() : 0); + result = 31 * result + (polygon2 != null ? polygon2.hashCode() : 0); + result = 31 * result + (multiPolygon1 != null ? multiPolygon1.hashCode() : 0); + result = 31 * result + (multiPolygon2 != null ? multiPolygon2.hashCode() : 0); + result = 31 * result + (geometryCollection1 != null ? geometryCollection1.hashCode() : 0); + result = 31 * result + (geometryCollection2 != null ? geometryCollection2.hashCode() : 0); + return result; + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonIntegrationTest.java index 19fc54c6a..faf75ed9c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonIntegrationTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/GeoJsonIntegrationTest.java @@ -17,9 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.Builder; -import lombok.Data; - import java.util.Arrays; import org.junit.jupiter.api.AfterEach; @@ -38,6 +35,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.geo.Point; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -60,7 +58,7 @@ class GeoJsonIntegrationTest { new Point(20, 20), // new Point(10, 20), // new Point(10, 10)); - private final Area area10To20 = Area.builder().id("area10To20").area(geoShape10To20) /**/.build(); + private final Area area10To20 = new Area("area10To20", geoShape10To20); private final GeoJsonPolygon geoShape5To35 = GeoJsonPolygon.of( // new Point(5, 5), // @@ -68,7 +66,7 @@ class GeoJsonIntegrationTest { new Point(35, 35), // new Point(5, 35), // new Point(5, 5)); - private final Area area5To35 = Area.builder().id("area5To35").area(geoShape5To35).build(); + private final Area area5To35 = new Area("area5To35", geoShape5To35); private final GeoJsonPolygon geoShape15To25 = GeoJsonPolygon.of( // new Point(15, 15), // @@ -76,7 +74,7 @@ class GeoJsonIntegrationTest { new Point(25, 25), // new Point(15, 25), // new Point(15, 15)); - private final Area area15To25 = Area.builder().id("area15To25").area(geoShape15To25).build(); + private final Area area15To25 = new Area("area15To25", geoShape15To25); private final GeoJsonPolygon geoShape30To40 = GeoJsonPolygon.of( // new Point(30, 30), // @@ -84,7 +82,7 @@ class GeoJsonIntegrationTest { new Point(40, 40), // new Point(30, 40), // new Point(30, 30)); - private final Area area30To40 = Area.builder().id("area30To40").area(geoShape30To40).build(); + private final Area area30To40 = new Area("area30To40",geoShape30To40); private final GeoJsonPolygon geoShape32To37 = GeoJsonPolygon.of( // new Point(32, 32), // @@ -92,7 +90,7 @@ class GeoJsonIntegrationTest { new Point(37, 37), // new Point(32, 37), // new Point(32, 32)); - private final Area area32To37 = Area.builder().id("area32To37").area(geoShape30To40).build(); + private final Area area32To37 = new Area("area32To37",geoShape30To40); // endregion // region setup @@ -137,23 +135,22 @@ void shouldWriteAndReadAnEntityWithGeoJsonProperties() { .of(Arrays.asList(GeoJsonPoint.of(12, 34), GeoJsonPolygon .of(GeoJsonLineString.of(new Point(12, 34), new Point(56, 78), new Point(90, 12), new Point(12, 34))))); - GeoJsonEntity entity = GeoJsonEntity.builder() // - .id("42") // - .point1(GeoJsonPoint.of(12, 34)) // - .point2(GeoJsonPoint.of(56, 78)) // - .multiPoint1(GeoJsonMultiPoint.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))) // - .multiPoint2(GeoJsonMultiPoint.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))) // - .lineString1(GeoJsonLineString.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))) // - .lineString2(GeoJsonLineString.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))) // - .multiLineString1(multiLineString) // - .multiLineString2(multiLineString) // - .polygon1(geoJsonPolygon) // - .polygon2(geoJsonPolygon) // - .multiPolygon1(geoJsonMultiPolygon) // - .multiPolygon2(geoJsonMultiPolygon) // - .geometryCollection1(geoJsonGeometryCollection) // - .geometryCollection2(geoJsonGeometryCollection) // - .build(); // + GeoJsonEntity entity = new GeoJsonEntity(); + entity.setId("42"); + entity.setPoint1(GeoJsonPoint.of(12, 34)); + entity.setPoint2(GeoJsonPoint.of(56, 78)); + entity.setMultiPoint1(GeoJsonMultiPoint.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))); + entity.setMultiPoint2(GeoJsonMultiPoint.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))); + entity.setLineString1(GeoJsonLineString.of(new Point(12, 34), new Point(56, 78), new Point(90, 12))); + entity.setLineString2(GeoJsonLineString.of(new Point(90, 12), new Point(56, 78), new Point(12, 34))); + entity.setMultiLineString1(multiLineString); + entity.setMultiLineString2(multiLineString); + entity.setPolygon1(geoJsonPolygon); + entity.setPolygon2(geoJsonPolygon); + entity.setMultiPolygon1(geoJsonMultiPolygon); + entity.setMultiPolygon2(geoJsonMultiPolygon); + entity.setGeometryCollection1(geoJsonGeometryCollection); + entity.setGeometryCollection2(geoJsonGeometryCollection); operations.save(entity); indexOps.refresh(); @@ -209,12 +206,33 @@ void shouldFindContainsObjectsWithCriteriaQuery() { // endregion // region test classes - @Data - @Builder @Document(indexName = "areas") static class Area { - @Id private String id; - @Field(name = "the_area") private GeoJsonPolygon area; + @Nullable @Id private String id; + @Nullable @Field(name = "the_area") private GeoJsonPolygon area; + + public Area(@Nullable String id, @Nullable GeoJsonPolygon area) { + this.id = id; + this.area = area; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public GeoJsonPolygon getArea() { + return area; + } + + public void setArea(@Nullable GeoJsonPolygon area) { + this.area = area; + } } // endregion diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java index 3d69f9c54..e5e284a3b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/IndexOperationIntegrationTests.java @@ -17,8 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.Data; - import java.util.List; import org.json.JSONException; @@ -36,6 +34,7 @@ import org.springframework.data.elasticsearch.core.IndexOperations; 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; /** @@ -98,11 +97,19 @@ void shouldReturnInformationList() throws JSONException { JSONAssert.assertEquals(expectedMappings, indexInformation.getMapping().toJson(), false); } - @Data @Document(indexName = INDEX_NAME) @Setting(settingPath = "settings/test-settings.json") @Mapping(mappingPath = "mappings/test-mappings.json") protected static class EntityWithSettingsAndMappings { - @Id String id; + @Nullable private @Id String id; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index 9abd53802..9223499ad 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -22,13 +22,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.Object; import static org.springframework.data.elasticsearch.utils.IndexBuilder.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.lang.Integer; import java.lang.Object; import java.math.BigDecimal; @@ -128,11 +121,11 @@ public void shouldAddStockPriceDocumentToIndex() { String id = "abc"; IndexCoordinates index = IndexCoordinates.of("test-index-stock-mapping-builder"); - operations.index(buildIndex(StockPrice.builder() // - .id(id) // - .symbol(symbol) // - .price(BigDecimal.valueOf(price)) // - .build()), index); + StockPrice stockPrice = new StockPrice(); // + stockPrice.setId(id); + stockPrice.setSymbol(symbol); + stockPrice.setPrice(BigDecimal.valueOf(price)); + operations.index(buildIndex(stockPrice), index); operations.indexOps(StockPrice.class).refresh(); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); @@ -303,17 +296,28 @@ void shouldWriteMappingForDisabledProperty() { indexOps.delete(); } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; - @Id private String id; + @Nullable + public String getId() { + return id; + } - @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } } static class FieldNameEntity { @@ -380,64 +384,165 @@ static class MultiFieldEntity { } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book-mapping-builder", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - @Data @Document(indexName = "test-index-simple-recursive-mapping-builder", replicas = 0, refreshInterval = "-1") static class SimpleRecursiveEntity { - @Nullable @Id private String id; @Nullable @Field(type = FieldType.Object, ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public SimpleRecursiveEntity getCircularObject() { + return circularObject; + } + + public void setCircularObject(@Nullable SimpleRecursiveEntity circularObject) { + this.circularObject = circularObject; + } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-copy-to-mapping-builder", replicas = 0, refreshInterval = "-1") static class CopyToEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String firstName; + @Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String lastName; + @Nullable @Field(type = FieldType.Keyword) private String name; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } - @Id private String id; + @Nullable + public String getFirstName() { + return firstName; + } + + public void setFirstName(@Nullable String firstName) { + this.firstName = firstName; + } - @Field(type = FieldType.Keyword, copyTo = "name") private String firstName; + @Nullable + public String getLastName() { + return lastName; + } - @Field(type = FieldType.Keyword, copyTo = "name") private String lastName; + public void setLastName(@Nullable String lastName) { + this.lastName = lastName; + } - @Field(type = FieldType.Keyword) private String name; + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-normalizer-mapping-builder", replicas = 0, refreshInterval = "-1") @Setting(settingPath = "/settings/test-normalizer.json") static class NormalizerEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name; + @Nullable @MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case", + type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description; - @Id private String id; + @Nullable + public String getId() { + return id; + } - @Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name; + public void setId(@Nullable String id) { + this.id = id; + } - @MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case", - type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description; + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } static class Author { @@ -510,71 +615,163 @@ public IndexQuery buildIndex() { } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-stock-mapping-builder", replicas = 0, refreshInterval = "-1") static class StockPrice { + @Nullable @Id private String id; + @Nullable private String symbol; + @Nullable @Field(type = FieldType.Double) private BigDecimal price; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getSymbol() { + return symbol; + } - @Id private String id; + public void setSymbol(@Nullable String symbol) { + this.symbol = symbol; + } - private String symbol; + @Nullable + public BigDecimal getPrice() { + return price; + } - @Field(type = FieldType.Double) private BigDecimal price; + public void setPrice(@Nullable BigDecimal price) { + this.price = price; + } } static class AbstractInheritedEntity { - @Nullable @Id private String id; - @Nullable @Field(type = FieldType.Date, format = DateFormat.date_time, index = false) private Date createdDate; - - @Nullable - public String getId() { + @Nullable public String getId() { return id; } - public void setId(String id) { this.id = id; } - - @Nullable - public Date getCreatedDate() { + @Nullable public Date getCreatedDate() { return createdDate; } - public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-geo-mapping-builder", replicas = 0, refreshInterval = "-1") static class GeoEntity { +@Nullable @Id private String id; +// geo shape - Spring Data +@Nullable private Box box; +@Nullable private Circle circle; +@Nullable private Polygon polygon; +// geo point - Custom implementation + Spring Data +@Nullable @GeoPointField private Point pointA; +@Nullable private GeoPoint pointB; +@Nullable @GeoPointField private String pointC; +@Nullable @GeoPointField private double[] pointD; + // geo shape, until e have the classes for this, us a strng +@Nullable @GeoShapeField private String shape1; +@Nullable @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, + orientation = GeoShapeField.Orientation.clockwise) private String shape2; - @Id private String id; + @Nullable + public String getId() { + return id; + } - // geo shape - Spring Data - private Box box; - private Circle circle; - private Polygon polygon; + public void setId(@Nullable String id) { + this.id = id; + } - // geo point - Custom implementation + Spring Data - @GeoPointField private Point pointA; - private GeoPoint pointB; - @GeoPointField private String pointC; - @GeoPointField private double[] pointD; + @Nullable + public Box getBox() { + return box; + } - // geo shape, until e have the classes for this, us a strng - @GeoShapeField private String shape1; - @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, - orientation = GeoShapeField.Orientation.clockwise) private String shape2; + public void setBox(@Nullable Box box) { + this.box = box; + } + + @Nullable + public Circle getCircle() { + return circle; + } + + public void setCircle(@Nullable Circle circle) { + this.circle = circle; + } + + @Nullable + public Polygon getPolygon() { + return polygon; + } + + public void setPolygon(@Nullable Polygon polygon) { + this.polygon = polygon; + } + + @Nullable + public Point getPointA() { + return pointA; + } + + public void setPointA(@Nullable Point pointA) { + this.pointA = pointA; + } + + @Nullable + public GeoPoint getPointB() { + return pointB; + } + + public void setPointB(@Nullable GeoPoint pointB) { + this.pointB = pointB; + } + + @Nullable + public String getPointC() { + return pointC; + } + + public void setPointC(@Nullable String pointC) { + this.pointC = pointC; + } + + @Nullable + public double[] getPointD() { + return pointD; + } + + public void setPointD(@Nullable double[] pointD) { + this.pointD = pointD; + } + + @Nullable + public String getShape1() { + return shape1; + } + + public void setShape1(@Nullable String shape1) { + this.shape1 = shape1; + } + + @Nullable + public String getShape2() { + return shape2; + } + + public void setShape2(@Nullable String shape2) { + this.shape2 = shape2; + } } @Document(indexName = "test-index-user-mapping-builder") @@ -604,76 +801,278 @@ public String getValue() { } } - @Getter - @Setter @Document(indexName = "completion") static class CompletionDocument { - @Id private String id; - - @CompletionField(contexts = { @CompletionContext(name = "location", type = ContextMapping.Type.GEO, + @Nullable @Id private String id; + @Nullable @CompletionField(contexts = { @CompletionContext(name = "location", type = ContextMapping.Type.GEO, path = "proppath") }) private Completion suggest; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public Completion getSuggest() { + return suggest; + } + + public void setSuggest(@Nullable Completion suggest) { + this.suggest = suggest; + } } - @Data @Document(indexName = "test-index-entity-with-seq-no-primary-term-mapping-builder") static class EntityWithSeqNoPrimaryTerm { + @Nullable @Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm; - @Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } } - @Data static class RankFeatureEntity { - @Id private String id; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Rank_Feature) private Integer pageRank; + @Nullable @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength; + @Nullable @Field(type = FieldType.Rank_Features) private Map topics; - @Field(type = FieldType.Rank_Feature) private Integer pageRank; + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public java.lang.Integer getPageRank() { + return pageRank; + } - @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength; + public void setPageRank(@Nullable java.lang.Integer pageRank) { + this.pageRank = pageRank; + } + + @Nullable + public java.lang.Integer getUrlLength() { + return urlLength; + } + + public void setUrlLength(@Nullable java.lang.Integer urlLength) { + this.urlLength = urlLength; + } - @Field(type = FieldType.Rank_Features) private Map topics; + @Nullable + public Map getTopics() { + return topics; + } + + public void setTopics(@Nullable Map topics) { + this.topics = topics; + } } - @Data @Document(indexName = "termvectors-test") static class TermVectorFieldEntity { - @Id private String id; - @Field(type = FieldType.Text, termVector = TermVector.no) private String no; - @Field(type = FieldType.Text, termVector = TermVector.yes) private String yes; - @Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions; - @Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets; - @Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets) private String with_positions_offsets; - @Field(type = FieldType.Text, + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Text, termVector = TermVector.no) private String no; + @Nullable @Field(type = FieldType.Text, termVector = TermVector.yes) private String yes; + @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions; + @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets; + @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets) private String with_positions_offsets; + @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions_payloads) private String with_positions_payloads; - @Field(type = FieldType.Text, + @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets_payloads) private String with_positions_offsets_payloads; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getNo() { + return no; + } + + public void setNo(@Nullable String no) { + this.no = no; + } + + @Nullable + public String getYes() { + return yes; + } + + public void setYes(@Nullable String yes) { + this.yes = yes; + } + + @Nullable + public String getWith_positions() { + return with_positions; + } + + public void setWith_positions(@Nullable String with_positions) { + this.with_positions = with_positions; + } + + @Nullable + public String getWith_offsets() { + return with_offsets; + } + + public void setWith_offsets(@Nullable String with_offsets) { + this.with_offsets = with_offsets; + } + + @Nullable + public String getWith_positions_offsets() { + return with_positions_offsets; + } + + public void setWith_positions_offsets(@Nullable String with_positions_offsets) { + this.with_positions_offsets = with_positions_offsets; + } + + @Nullable + public String getWith_positions_payloads() { + return with_positions_payloads; + } + + public void setWith_positions_payloads(@Nullable String with_positions_payloads) { + this.with_positions_payloads = with_positions_payloads; + } + + @Nullable + public String getWith_positions_offsets_payloads() { + return with_positions_offsets_payloads; + } + + public void setWith_positions_offsets_payloads(@Nullable String with_positions_offsets_payloads) { + this.with_positions_offsets_payloads = with_positions_offsets_payloads; + } } - @Data @Document(indexName = "wildcard-test") static class WildcardEntity { @Nullable @Field(type = Wildcard) private String wildcardWithoutParams; @Nullable @Field(type = Wildcard, nullValue = "WILD", ignoreAbove = 42) private String wildcardWithParams; + + @Nullable + public String getWildcardWithoutParams() { + return wildcardWithoutParams; + } + + public void setWildcardWithoutParams(@Nullable String wildcardWithoutParams) { + this.wildcardWithoutParams = wildcardWithoutParams; + } + + @Nullable + public String getWildcardWithParams() { + return wildcardWithParams; + } + + public void setWildcardWithParams(@Nullable String wildcardWithParams) { + this.wildcardWithParams = wildcardWithParams; + } } - @Data @Document(indexName = "disabled-entity-mapping") @Mapping(enabled = false) static class DisabledMappingEntity { - @Id private String id; - @Field(type = Text) private String text; + @Nullable @Id private String id; + @Nullable @Field(type = Text) private String text; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } } - @Data @Document(indexName = "disabled-property-mapping") static class DisabledMappingProperty { - @Id private String id; - @Field(type = Text) private String text; - @Mapping(enabled = false) @Field(type = Object) private Object object; + @Nullable @Id private String id; + @Nullable @Field(type = Text) private String text; + @Nullable @Mapping(enabled = false) @Field(type = Object) private Object object; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + + @Nullable + public java.lang.Object getObject() { + return object; + } + + public void setObject(@Nullable java.lang.Object object) { + this.object = object; + } } - @Data @Document(indexName = "densevector-test") static class DenseVectorEntity { - @Id private String id; - @Field(type = Dense_Vector, dims = 3) private float[] dense_vector; + @Nullable @Id private String id; + @Nullable @Field(type = Dense_Vector, dims = 3) private float[] dense_vector; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public float[] getDense_vector() { + return dense_vector; + } + + public void setDense_vector(@Nullable float[] dense_vector) { + this.dense_vector = dense_vector; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index fc92ec0d6..66c1d57a0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -21,13 +21,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Object; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.lang.Boolean; import java.lang.Double; import java.lang.Integer; @@ -649,19 +642,31 @@ void shouldMapAccordingToTheAnnotatedProperties() throws JSONException { assertEquals(expected, mapping, false); } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; - @Id private String id; + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } - @Field(type = FieldType.Keyword, ignoreAbove = 10) private String message; + public void setMessage(@Nullable String message) { + this.message = message; + } } + static class FieldNameEntity { @Document(indexName = "fieldname-index") @@ -726,68 +731,168 @@ static class MultiFieldEntity { } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book-mapping-builder", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - @Data @Document(indexName = "test-index-simple-recursive-mapping-builder", replicas = 0, refreshInterval = "-1") static class SimpleRecursiveEntity { - @Nullable @Id private String id; @Nullable @Field(type = FieldType.Object, ignoreFields = { "circularObject" }) private SimpleRecursiveEntity circularObject; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public SimpleRecursiveEntity getCircularObject() { + return circularObject; + } + + public void setCircularObject(@Nullable SimpleRecursiveEntity circularObject) { + this.circularObject = circularObject; + } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-copy-to-mapping-builder", replicas = 0, refreshInterval = "-1") static class CopyToEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String firstName; + @Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String lastName; + @Nullable @Field(type = FieldType.Keyword) private String name; - @Id private String id; + @Nullable + public String getId() { + return id; + } - @Field(type = FieldType.Keyword, copyTo = "name") private String firstName; + public void setId(@Nullable String id) { + this.id = id; + } - @Field(type = FieldType.Keyword, copyTo = "name") private String lastName; + @Nullable + public String getFirstName() { + return firstName; + } - @Field(type = FieldType.Keyword) private String name; + public void setFirstName(@Nullable String firstName) { + this.firstName = firstName; + } + + @Nullable + public String getLastName() { + return lastName; + } + + public void setLastName(@Nullable String lastName) { + this.lastName = lastName; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-normalizer-mapping-builder", replicas = 0, refreshInterval = "-1") @Setting(settingPath = "/settings/test-normalizer.json") static class NormalizerEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name; + @Nullable @MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case", + type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description; + + @Nullable + public String getId() { + return id; + } - @Id private String id; + public void setId(@Nullable String id) { + this.id = id; + } - @Field(type = FieldType.Keyword, normalizer = "lower_case_normalizer") private String name; + @Nullable + public String getName() { + return name; + } - @MultiField(mainField = @Field(type = FieldType.Text), otherFields = { @InnerField(suffix = "lower_case", - type = FieldType.Keyword, normalizer = "lower_case_normalizer") }) private String description; + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } static class Author { - @Nullable private String id; @Nullable private String name; @@ -825,19 +930,38 @@ public void setMessage(String message) { } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-stock-mapping-builder", replicas = 0, refreshInterval = "-1") static class StockPrice { + @Nullable @Id private String id; + @Nullable private String symbol; + @Nullable @Field(type = FieldType.Double) private BigDecimal price; - @Id private String id; + @Nullable + public String getId() { + return id; + } - private String symbol; + public void setId(@Nullable String id) { + this.id = id; + } - @Field(type = FieldType.Double) private BigDecimal price; + @Nullable + public String getSymbol() { + return symbol; + } + + public void setSymbol(@Nullable String symbol) { + this.symbol = symbol; + } + + @Nullable + public BigDecimal getPrice() { + return price; + } + + public void setPrice(@Nullable BigDecimal price) { + this.price = price; + } } static class AbstractInheritedEntity { @@ -916,31 +1040,112 @@ public void setSomething(Boolean something) { } } - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-geo-mapping-builder", replicas = 0, refreshInterval = "-1") static class GeoEntity { - - @Id private String id; - + @Nullable @Id private String id; // geo shape - Spring Data - private Box box; - private Circle circle; - private Polygon polygon; - + @Nullable private Box box; + @Nullable private Circle circle; + @Nullable private Polygon polygon; // geo point - Custom implementation + Spring Data - @GeoPointField private Point pointA; - private GeoPoint pointB; - @GeoPointField private String pointC; - @GeoPointField private double[] pointD; - + @Nullable @GeoPointField private Point pointA; + @Nullable private GeoPoint pointB; + @Nullable @GeoPointField private String pointC; + @Nullable @GeoPointField private double[] pointD; // geo shape, until e have the classes for this, us a strng - @GeoShapeField private String shape1; - @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, + @Nullable @GeoShapeField private String shape1; + @Nullable @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, orientation = GeoShapeField.Orientation.clockwise) private String shape2; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public Box getBox() { + return box; + } + + public void setBox(@Nullable Box box) { + this.box = box; + } + + @Nullable + public Circle getCircle() { + return circle; + } + + public void setCircle(@Nullable Circle circle) { + this.circle = circle; + } + + @Nullable + public Polygon getPolygon() { + return polygon; + } + + public void setPolygon(@Nullable Polygon polygon) { + this.polygon = polygon; + } + + @Nullable + public Point getPointA() { + return pointA; + } + + public void setPointA(@Nullable Point pointA) { + this.pointA = pointA; + } + + @Nullable + public GeoPoint getPointB() { + return pointB; + } + + public void setPointB(@Nullable GeoPoint pointB) { + this.pointB = pointB; + } + + @Nullable + public String getPointC() { + return pointC; + } + + public void setPointC(@Nullable String pointC) { + this.pointC = pointC; + } + + @Nullable + public double[] getPointD() { + return pointD; + } + + public void setPointD(@Nullable double[] pointD) { + this.pointD = pointD; + } + + @Nullable + public String getShape1() { + return shape1; + } + + public void setShape1(@Nullable String shape1) { + this.shape1 = shape1; + } + + @Nullable + public String getShape2() { + return shape2; + } + + public void setShape2(@Nullable String shape2) { + this.shape2 = shape2; + } } @Document(indexName = "test-index-field-mapping-parameters") @@ -1022,94 +1227,309 @@ static class ValueDoc { @Nullable @Field(type = Text) private ValueObject valueObject; } - @Getter - @Setter @Document(indexName = "completion") static class CompletionDocument { - @Id private String id; - - @CompletionField(contexts = { @CompletionContext(name = "location", type = ContextMapping.Type.GEO, + @Nullable @Id private String id; + @Nullable @CompletionField(contexts = { @CompletionContext(name = "location", type = ContextMapping.Type.GEO, path = "proppath") }) private Completion suggest; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public Completion getSuggest() { + return suggest; + } + + public void setSuggest(@Nullable Completion suggest) { + this.suggest = suggest; + } } - @Data @Document(indexName = "test-index-entity-with-seq-no-primary-term-mapping-builder") static class EntityWithSeqNoPrimaryTerm { - @Field(type = Object) private SeqNoPrimaryTerm seqNoPrimaryTerm; + + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } } - @Data static class RankFeatureEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Rank_Feature) private Integer pageRank; + @Nullable @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength; + @Nullable @Field(type = FieldType.Rank_Features) private Map topics; - @Id private String id; - @Field(type = FieldType.Rank_Feature) private Integer pageRank; - @Field(type = FieldType.Rank_Feature, positiveScoreImpact = false) private Integer urlLength; - @Field(type = FieldType.Rank_Features) private Map topics; + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public java.lang.Integer getPageRank() { + return pageRank; + } + + public void setPageRank(@Nullable java.lang.Integer pageRank) { + this.pageRank = pageRank; + } + + @Nullable + public java.lang.Integer getUrlLength() { + return urlLength; + } + + public void setUrlLength(@Nullable java.lang.Integer urlLength) { + this.urlLength = urlLength; + } + + @Nullable + public Map getTopics() { + return topics; + } + + public void setTopics(@Nullable Map topics) { + this.topics = topics; + } } - @Data static class DenseVectorEntity { + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Dense_Vector, dims = 16) private float[] my_vector; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } - @Id private String id; - @Field(type = FieldType.Dense_Vector, dims = 16) private float[] my_vector; + @Nullable + public float[] getMy_vector() { + return my_vector; + } + + public void setMy_vector(@Nullable float[] my_vector) { + this.my_vector = my_vector; + } } - @Data @Mapping(enabled = false) static class DisabledMappingEntity { - @Id private String id; - @Field(type = Text) private String text; + @Nullable @Id private String id; + @Nullable @Field(type = Text) private String text; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } } - @Data static class InvalidDisabledMappingProperty { - @Id private String id; - @Mapping(enabled = false) @Field(type = Text) private String text; + @Nullable @Id private String id; + @Nullable @Mapping(enabled = false) @Field(type = Text) private String text; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } } - @Data static class DisabledMappingProperty { - @Id private String id; - @Field(type = Text) private String text; - @Mapping(enabled = false) @Field(type = Object) private Object object; + @Nullable @Id private String id; + @Nullable @Field(type = Text) private String text; + @Nullable @Mapping(enabled = false) @Field(type = Object) private Object object; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + + @Nullable + public java.lang.Object getObject() { + return object; + } + + public void setObject(@Nullable java.lang.Object object) { + this.object = object; + } } - @Data - @AllArgsConstructor - @NoArgsConstructor static class TypeHintEntity { - @Id @Field(type = Keyword) private String id; + @Nullable @Id @Field(type = Keyword) private String id; + @Nullable @Field(type = Nested) private NestedEntity nestedEntity; + @Nullable @Field(type = Object) private ObjectEntity objectEntity; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public NestedEntity getNestedEntity() { + return nestedEntity; + } + + public void setNestedEntity(@Nullable NestedEntity nestedEntity) { + this.nestedEntity = nestedEntity; + } - @Field(type = Nested) private NestedEntity nestedEntity; + @Nullable + public ObjectEntity getObjectEntity() { + return objectEntity; + } - @Field(type = Object) private ObjectEntity objectEntity; + public void setObjectEntity(@Nullable ObjectEntity objectEntity) { + this.objectEntity = objectEntity; + } - @Data static class NestedEntity { - @Field(type = Text) private String nestedField; + @Nullable @Field(type = Text) private String nestedField; + + @Nullable + public String getNestedField() { + return nestedField; + } + + public void setNestedField(@Nullable String nestedField) { + this.nestedField = nestedField; + } } - @Data static class ObjectEntity { - @Field(type = Text) private String objectField; + @Nullable @Field(type = Text) private String objectField; + + @Nullable + public String getObjectField() { + return objectField; + } + + public void setObjectField(@Nullable String objectField) { + this.objectField = objectField; + } } } - @Data - @AllArgsConstructor - @NoArgsConstructor static class DateFormatsEntity { - @Id private String id; - @Field(type = FieldType.Date) private LocalDateTime field1; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Date) private LocalDateTime field1; + @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime field2; + @Nullable @Field(type = FieldType.Date, format = { DateFormat.basic_date, DateFormat.basic_time }) private LocalDateTime field3; + @Nullable @Field(type = FieldType.Date, pattern = "dd.MM.uuuu") private LocalDateTime field4; + @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") private LocalDateTime field5; - @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime field2; + @Nullable + public String getId() { + return id; + } - @Field(type = FieldType.Date, - format = { DateFormat.basic_date, DateFormat.basic_time }) private LocalDateTime field3; + public void setId(@Nullable String id) { + this.id = id; + } - @Field(type = FieldType.Date, pattern = "dd.MM.uuuu") private LocalDateTime field4; + @Nullable + public LocalDateTime getField1() { + return field1; + } - @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") private LocalDateTime field5; + public void setField1(@Nullable LocalDateTime field1) { + this.field1 = field1; + } + + @Nullable + public LocalDateTime getField2() { + return field2; + } + + public void setField2(@Nullable LocalDateTime field2) { + this.field2 = field2; + } + + @Nullable + public LocalDateTime getField3() { + return field3; + } + + public void setField3(@Nullable LocalDateTime field3) { + this.field3 = field3; + } + + @Nullable + public LocalDateTime getField4() { + return field4; + } + + public void setField4(@Nullable LocalDateTime field4) { + this.field4 = field4; + } + + @Nullable + public LocalDateTime getField5() { + return field5; + } + + public void setField5(@Nullable LocalDateTime field5) { + this.field5 = field5; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java index f8ab2aed0..e974e76bd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java @@ -18,8 +18,6 @@ import static org.skyscreamer.jsonassert.JSONAssert.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.Data; - import java.time.LocalDateTime; import org.json.JSONException; @@ -30,6 +28,7 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.core.MappingContextBaseTests; +import org.springframework.lang.Nullable; /** * @author Jakub Vavrik @@ -52,19 +51,47 @@ public void testCorrectDateMappings() throws JSONException { assertEquals(EXPECTED_MAPPING, mapping, false); } - /** - * @author Jakub Vavrik - */ - @Data @Document(indexName = "test-index-date-mapping-core", replicas = 0, refreshInterval = "-1") static class SampleDateMappingEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; + @Nullable @Field(type = Date, format = {}, pattern = "dd.MM.uuuu hh:mm") private LocalDateTime customFormatDate; + @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime basicFormatDate; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } - @Id private String id; + @Nullable + public LocalDateTime getCustomFormatDate() { + return customFormatDate; + } - @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; + public void setCustomFormatDate(@Nullable LocalDateTime customFormatDate) { + this.customFormatDate = customFormatDate; + } - @Field(type = Date, format = {}, pattern = "dd.MM.uuuu hh:mm") private LocalDateTime customFormatDate; + @Nullable + public LocalDateTime getBasicFormatDate() { + return basicFormatDate; + } - @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime basicFormatDate; + public void setBasicFormatDate(@Nullable LocalDateTime basicFormatDate) { + this.basicFormatDate = basicFormatDate; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java index 900af4ac4..c64b2d597 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/EntityCustomConversionIntegrationTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -45,6 +40,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -84,10 +80,9 @@ void tearDown() { @DisplayName("should use CustomConversions on entity") void shouldUseCustomConversionsOnEntity() { - Entity entity = Entity.builder() // - .value("hello") // - .location(GeoJsonPoint.of(8.0, 42.7)) // - .build(); + Entity entity = new Entity(); + entity.setValue("hello"); // + entity.setLocation(GeoJsonPoint.of(8.0, 42.7)); org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document .create(); @@ -102,10 +97,9 @@ void shouldUseCustomConversionsOnEntity() { @DisplayName("should store and load entity from Elasticsearch") void shouldStoreAndLoadEntityFromElasticsearch() { - Entity entity = Entity.builder() // - .value("hello") // - .location(GeoJsonPoint.of(8.0, 42.7)) // - .build(); + Entity entity = new Entity(); + entity.setValue("hello"); // + entity.setLocation(GeoJsonPoint.of(8.0, 42.7)); Entity savedEntity = operations.save(entity); @@ -115,14 +109,49 @@ void shouldStoreAndLoadEntityFromElasticsearch() { assertThat(foundEntity).isEqualTo(entity); } - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "entity-with-custom-conversions") static class Entity { - private String value; - private GeoJsonPoint location; + @Nullable private String value; + @Nullable private GeoJsonPoint location; + + @Nullable + public String getValue() { + return value; + } + + public void setValue(@Nullable String value) { + this.value = value; + } + + @Nullable + public GeoJsonPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoJsonPoint location) { + this.location = location; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Entity)) + return false; + + Entity entity = (Entity) o; + + if (value != null ? !value.equals(entity.value) : entity.value != null) + return false; + return location != null ? location.equals(entity.location) : entity.location == null; + } + + @Override + public int hashCode() { + int result = value != null ? value.hashCode() : 0; + result = 31 * result + (location != null ? location.hashCode() : 0); + return result; + } } @WritingConverter diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationReactiveTest.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationReactiveTest.java index 2fc442696..510c7734b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationReactiveTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationReactiveTest.java @@ -15,11 +15,9 @@ */ package org.springframework.data.elasticsearch.core.mapping; -import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; -import lombok.Builder; -import lombok.Data; +import reactor.test.StepVerifier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -32,15 +30,14 @@ import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; -import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; -import reactor.test.StepVerifier; /** * @author Peter-Josef Meisch @@ -73,7 +70,9 @@ void setUp() { @DisplayName("should use configured FieldNameStrategy") void shouldUseConfiguredFieldNameStrategy() { - Entity entity = new Entity.EntityBuilder().id("42").someText("the text to be searched").build(); + Entity entity = new Entity(); + entity.setId("42"); + entity.setSomeText("the text to be searched"); operations.save(entity).block(); // use a native query here to prevent automatic property name matching @@ -84,11 +83,27 @@ void shouldUseConfiguredFieldNameStrategy() { .verifyComplete(); } - @Data - @Builder @Document(indexName = "field-naming-strategy-test") static class Entity { - @Id private String id; - @Field(type = FieldType.Text) private String someText; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Text) private String someText; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getSomeText() { + return someText; + } + + public void setSomeText(@Nullable String someText) { + this.someText = someText; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTest.java index cbb589a78..9690ebb82 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/FieldNamingStrategyIntegrationTest.java @@ -18,9 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; -import lombok.Builder; -import lombok.Data; - import org.elasticsearch.client.RestHighLevelClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -42,6 +39,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -81,7 +79,9 @@ void setUp() { @DisplayName("should use configured FieldNameStrategy") void shouldUseConfiguredFieldNameStrategy() { - Entity entity = new Entity.EntityBuilder().id("42").someText("the text to be searched").build(); + Entity entity = new Entity(); + entity.setId("42"); + entity.setSomeText("the text to be searched"); operations.save(entity); // use a native query here to prevent automatic property name matching @@ -91,11 +91,27 @@ void shouldUseConfiguredFieldNameStrategy() { assertThat(searchHits.getTotalHits()).isEqualTo(1); } - @Data - @Builder @Document(indexName = "field-naming-strategy-test") static class Entity { - @Id private String id; - @Field(type = FieldType.Text) private String someText; + @Nullable @Id private String id; + @Nullable @Field(type = FieldType.Text) private String someText; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getSomeText() { + return someText; + } + + public void setSomeText(@Nullable String someText) { + this.someText = someText; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java index 46f529aba..fd9a76092 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java @@ -17,8 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.Data; - import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -264,30 +262,61 @@ static class DatesProperty { @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") List localDateList; } - @Data static class SeqNoPrimaryTermProperty { - SeqNoPrimaryTerm seqNoPrimaryTerm; - String string; - } - - @Data - static class DateFieldWithNoFormat { - @Field(type = FieldType.Date) LocalDateTime datetime; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable private String string; + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + @Nullable + public String getString() { + return string; + } + + public void setString(@Nullable String string) { + this.string = string; + } } - @Data static class DateFieldWithCustomFormatAndNoPattern { - @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "") LocalDateTime datetime; - } + @Nullable private @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "") LocalDateTime datetime; + + @Nullable + public LocalDateTime getDatetime() { + return datetime; + } - @Data - static class DateNanosFieldWithNoFormat { - @Field(type = FieldType.Date_Nanos) LocalDateTime datetime; + public void setDatetime(@Nullable LocalDateTime datetime) { + this.datetime = datetime; + } } - @Data static class FieldNamingStrategyEntity { - private String withoutCustomFieldName; + @Nullable private String withoutCustomFieldName; @Field(name = "CUStomFIEldnAME") private String withCustomFieldName; + + @Nullable + public String getWithoutCustomFieldName() { + return withoutCustomFieldName; + } + + public void setWithoutCustomFieldName(@Nullable String withoutCustomFieldName) { + this.withoutCustomFieldName = withoutCustomFieldName; + } + + public String getWithCustomFieldName() { + return withCustomFieldName; + } + + public void setWithCustomFieldName(String withCustomFieldName) { + this.withCustomFieldName = withCustomFieldName; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java index 43c2ba1ae..08809d1b9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/ReactiveSearchAfterIntegrationTests.java @@ -17,10 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; import reactor.core.publisher.Mono; import java.util.ArrayList; @@ -42,6 +38,7 @@ import org.springframework.data.elasticsearch.core.query.Query; 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; /** @@ -57,8 +54,8 @@ public class ReactiveSearchAfterIntegrationTests { @DisplayName("should read pages with search_after") void shouldReadPagesWithSearchAfter() { - List entities = IntStream.rangeClosed(1, 10) - .mapToObj(i -> Entity.builder().id((long) i).message("message " + i).build()).collect(Collectors.toList()); + List entities = IntStream.rangeClosed(1, 10).mapToObj(i -> new Entity((long) i, "message " + i)) + .collect(Collectors.toList()); operations.saveAll(Mono.just(entities), Entity.class).blockLast(); Query query = Query.findAll(); @@ -87,14 +84,54 @@ void shouldReadPagesWithSearchAfter() { assertThat(foundEntities).containsExactlyElementsOf(entities); } - @Data - @AllArgsConstructor - @NoArgsConstructor - @Builder @Document(indexName = "test-search-after") private static class Entity { - @Id private Long id; - @Field(type = FieldType.Text) private String message; + @Nullable @Id private Long id; + @Nullable @Field(type = FieldType.Text) private String message; + + public Entity(@Nullable Long id, @Nullable String message) { + this.id = id; + this.message = message; + } + + @Nullable + public Long getId() { + return id; + } + + public void setId(@Nullable Long id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Entity)) + return false; + + Entity entity = (Entity) o; + + if (id != null ? !id.equals(entity.id) : entity.id != null) + return false; + return message != null ? message.equals(entity.message) : entity.message == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (message != null ? message.hashCode() : 0); + return result; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java index 120d2bf54..8894954a2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/paginating/SearchAfterIntegrationTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -41,6 +36,7 @@ import org.springframework.data.elasticsearch.core.query.Query; 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; /** @@ -56,8 +52,8 @@ public class SearchAfterIntegrationTests { @DisplayName("should read pages with search_after") void shouldReadPagesWithSearchAfter() { - List entities = IntStream.rangeClosed(1, 10) - .mapToObj(i -> Entity.builder().id((long) i).message("message " + i).build()).collect(Collectors.toList()); + List entities = IntStream.rangeClosed(1, 10).mapToObj(i -> new Entity((long) i, "message " + i)) + .collect(Collectors.toList()); operations.save(entities); Query query = Query.findAll(); @@ -86,13 +82,53 @@ void shouldReadPagesWithSearchAfter() { assertThat(foundEntities).containsExactlyElementsOf(entities); } - @Data - @AllArgsConstructor - @NoArgsConstructor - @Builder @Document(indexName = "test-search-after") private static class Entity { - @Id private Long id; - @Field(type = FieldType.Text) private String message; + @Nullable @Id private Long id; + @Nullable @Field(type = FieldType.Text) private String message; + + public Entity(@Nullable Long id, @Nullable String message) { + this.id = id; + this.message = message; + } + + @Nullable + public Long getId() { + return id; + } + + public void setId(@Nullable Long id) { + this.id = id; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Entity)) + return false; + + Entity entity = (Entity) o; + + if (id != null ? !id.equals(entity.id) : entity.id != null) + return false; + return message != null ? message.equals(entity.message) : entity.message == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (message != null ? message.hashCode() : 0); + return result; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java index 19fdf6cb0..67c0199a2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java @@ -20,12 +20,6 @@ import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import static org.springframework.data.elasticsearch.utils.IndexBuilder.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.lang.Long; import java.util.ArrayList; import java.util.List; @@ -47,6 +41,7 @@ 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; /** @@ -761,23 +756,18 @@ public void shouldPerformBoostOperation() { @Test public void shouldReturnDocumentAboveMinimalScoreGivenCriteria() { - // given List indexQueries = new ArrayList<>(); - - indexQueries.add(buildIndex(SampleEntity.builder().id("1").message("ab").build())); - indexQueries.add(buildIndex(SampleEntity.builder().id("2").message("bc").build())); - indexQueries.add(buildIndex(SampleEntity.builder().id("3").message("ac").build())); - + indexQueries.add(buildIndex(new SampleEntity("1", "ab"))); + indexQueries.add(buildIndex(new SampleEntity("2", "bc"))); + indexQueries.add(buildIndex(new SampleEntity("3", "ac"))); operations.bulkIndex(indexQueries, index); indexOperations.refresh(); - // when CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("a").or(new Criteria("message").contains("b"))); criteriaQuery.setMinScore(2.0F); SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); - // then assertThat(searchHits.getTotalHits()).isEqualTo(1); assertThat(searchHits.getSearchHit(0).getContent().getMessage()).isEqualTo("ab"); } @@ -807,18 +797,65 @@ public void shouldEscapeValue() { assertThat(sampleEntity1).isNotNull(); } - @Builder - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "test-index-sample-core-query", replicas = 0, refreshInterval = "-1") static class SampleEntity { - + @Nullable @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - @Version private Long version; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @Version private Long version; + + public SampleEntity() { + } + + public SampleEntity(@Nullable String id, @Nullable String message) { + this.id = id; + this.message = message; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/DefaultRoutingResolverUnitTest.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/DefaultRoutingResolverUnitTest.java index e34f6367c..707da7b3d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/DefaultRoutingResolverUnitTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/DefaultRoutingResolverUnitTest.java @@ -17,10 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -94,34 +90,94 @@ void shouldReturnRoutingFromSpElExpression() { assertThat(routing).isEqualTo("route 42"); } - @Data - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "routing-resolver-test") @Routing("theRouting") static class ValidRoutingEntity { - @Id private String id; - private String theRouting; + @Nullable @Id private String id; + @Nullable private String theRouting; + + public ValidRoutingEntity(@Nullable String id, @Nullable String theRouting) { + this.id = id; + this.theRouting = theRouting; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getTheRouting() { + return theRouting; + } + + public void setTheRouting(@Nullable String theRouting) { + this.theRouting = theRouting; + } } - @Data - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "routing-resolver-test") @Routing(value = "@spelRouting.getRouting(#entity)") static class ValidSpelRoutingEntity { - @Id private String id; - private String theRouting; + @Nullable @Id private String id; + @Nullable private String theRouting; + + public ValidSpelRoutingEntity(@Nullable String id, @Nullable String theRouting) { + this.id = id; + this.theRouting = theRouting; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getTheRouting() { + return theRouting; + } + + public void setTheRouting(@Nullable String theRouting) { + this.theRouting = theRouting; + } } - @Data - @NoArgsConstructor - @AllArgsConstructor @Document(indexName = "routing-resolver-test") @Routing("unknownProperty") static class InvalidRoutingEntity { - @Id private String id; - private String theRouting; + @Nullable @Id private String id; + @Nullable private String theRouting; + + public InvalidRoutingEntity(@Nullable String id, @Nullable String theRouting) { + this.id = id; + this.theRouting = theRouting; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getTheRouting() { + return theRouting; + } + + public void setTheRouting(@Nullable String theRouting) { + this.theRouting = theRouting; + } } static class SpelRouting { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java index 06b16ae2e..db6f99a1c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.function.Function; import org.elasticsearch.cluster.routing.Murmur3HashFunction; @@ -86,7 +81,7 @@ void setUp() { @DisplayName("should store data with different routing and be able to get it") void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() { - RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build(); + RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity); indexOps.refresh(); @@ -99,7 +94,7 @@ void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() { @DisplayName("should store data with different routing and be able to delete it") void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() { - RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build(); + RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity); indexOps.refresh(); @@ -112,7 +107,7 @@ void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() { @DisplayName("should store data with different routing and get the routing in the search result") void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { - RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build(); + RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity); indexOps.refresh(); @@ -122,14 +117,54 @@ void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { assertThat(searchHits.getSearchHit(0).getRouting()).isEqualTo(ID_2); } - @Data - @Builder - @AllArgsConstructor - @NoArgsConstructor @Document(indexName = INDEX, shards = 5) @Routing("routing") static class RoutingEntity { - @Id private String id; - private String routing; + @Nullable @Id private String id; + @Nullable private String routing; + + public RoutingEntity(@Nullable String id, @Nullable String routing) { + this.id = id; + this.routing = routing; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getRouting() { + return routing; + } + + public void setRouting(@Nullable String routing) { + this.routing = routing; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof RoutingEntity)) + return false; + + RoutingEntity that = (RoutingEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + return routing != null ? routing.equals(that.routing) : that.routing == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (routing != null ? routing.hashCode() : 0); + return result; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java index 6ed02cb8c..84ae0b713 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.List; import java.util.function.Function; @@ -84,7 +79,7 @@ void setUp() { @DisplayName("should store data with different routing and be able to get it") void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() { - RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build(); + RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity).then(indexOps.refresh()).block(); RoutingEntity savedEntity = operations.withRouting(RoutingResolver.just(ID_2)).get(entity.id, RoutingEntity.class) @@ -97,7 +92,7 @@ void shouldStoreDataWithDifferentRoutingAndBeAbleToGetIt() { @DisplayName("should store data with different routing and be able to delete it") void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() { - RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build(); + RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity).then(indexOps.refresh()).block(); String deletedId = operations.withRouting(RoutingResolver.just(ID_2)).delete(entity.id, IndexCoordinates.of(INDEX)) @@ -110,7 +105,7 @@ void shouldStoreDataWithDifferentRoutingAndBeAbleToDeleteIt() { @DisplayName("should store data with different routing and get the routing in the search result") void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { - RoutingEntity entity = RoutingEntity.builder().id(ID_1).routing(ID_2).build(); + RoutingEntity entity = new RoutingEntity(ID_1, ID_2); operations.save(entity).then(indexOps.refresh()).block(); List> searchHits = operations.search(Query.findAll(), RoutingEntity.class).collectList() @@ -120,14 +115,54 @@ void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { assertThat(searchHits.get(0).getRouting()).isEqualTo(ID_2); } - @Data - @Builder - @AllArgsConstructor - @NoArgsConstructor @Document(indexName = INDEX, shards = 5) @Routing("routing") static class RoutingEntity { - @Id private String id; - private String routing; + @Nullable @Id private String id; + @Nullable private String routing; + + public RoutingEntity(@Nullable String id, @Nullable String routing) { + this.id = id; + this.routing = routing; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getRouting() { + return routing; + } + + public void setRouting(@Nullable String routing) { + this.routing = routing; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof RoutingEntity)) + return false; + + RoutingEntity that = (RoutingEntity) o; + + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + return routing != null ? routing.equals(that.routing) : that.routing == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (routing != null ? routing.hashCode() : 0); + return result; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepository.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepository.java deleted file mode 100644 index 1655a4f50..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2016-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.immutable; - -import org.springframework.data.repository.CrudRepository; - -/** - * @author Young Gu - * @author Oliver Gierke - */ -public interface ImmutableElasticsearchRepository extends CrudRepository {} diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java index 07fae2322..1f1b15666 100644 --- a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java @@ -17,9 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.Getter; -import lombok.NoArgsConstructor; - import java.util.Optional; import org.junit.jupiter.api.AfterEach; @@ -95,16 +92,21 @@ public void shouldSaveAndFindImmutableDocument() { * @author Oliver Gierke */ @Document(indexName = "test-index-immutable") - @NoArgsConstructor(force = true) - @Getter static class ImmutableEntity { private final String id, name; public ImmutableEntity(String name) { - this.id = null; this.name = name; } + + public String getId() { + return id; + } + + public String getName() { + return name; + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableEntity.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableEntity.java deleted file mode 100644 index 3746397fd..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableEntity.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.immutable; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -import org.springframework.data.elasticsearch.annotations.Document; - -/** - * @author Young Gu - * @author Oliver Gierke - */ -@Document(indexName = "test-index-immutable") -@NoArgsConstructor(force = true) -@Getter -public class ImmutableEntity { - private final String id, name; - - public ImmutableEntity(String name) { - - this.id = null; - this.name = name; - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java index 53ec4aa9d..f23ac8158 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.Collection; import java.util.Date; import java.util.HashMap; @@ -157,39 +152,129 @@ public void returnOneFromCustomImpl() { * @author Mohsin Husen * @author Artur Konczak */ - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-product-cdi-repository", replicas = 0, refreshInterval = "-1") static class Product { - - @Id private String id; - - private List title; - - private String name; - - private String description; - - private String text; - - private List categories; - - private Float weight; - - @Field(type = FieldType.Float) private Float price; - - private Integer popularity; - - private boolean available; - - private String location; - - private Date lastModified; + @Nullable @Id private String id; + @Nullable private List title; + @Nullable private String name; + @Nullable private String description; + @Nullable private String text; + @Nullable private List categories; + @Nullable private Float weight; + @Nullable @Field(type = FieldType.Float) private Float price; + @Nullable private Integer popularity; + @Nullable private boolean available; + @Nullable private String location; + @Nullable private Date lastModified; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public List getTitle() { + return title; + } + + public void setTitle(@Nullable List title) { + this.title = title; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + + @Nullable + public List getCategories() { + return categories; + } + + public void setCategories(@Nullable List categories) { + this.categories = categories; + } + + @Nullable + public Float getWeight() { + return weight; + } + + public void setWeight(@Nullable Float weight) { + this.weight = weight; + } + + @Nullable + public Float getPrice() { + return price; + } + + public void setPrice(@Nullable Float price) { + this.price = price; + } + + @Nullable + public Integer getPopularity() { + return popularity; + } + + public void setPopularity(@Nullable Integer popularity) { + this.popularity = popularity; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public String getLocation() { + return location; + } + + public void setLocation(@Nullable String location) { + this.location = location; + } + + @Nullable + public Date getLastModified() { + return lastModified; + } + + public void setLastModified(@Nullable Date lastModified) { + this.lastModified = lastModified; + } } - @Data @Document(indexName = "test-index-person-cdi-repository", replicas = 0, refreshInterval = "-1") static class Person { @@ -203,37 +288,106 @@ static class Person { } - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book-cdi-repository", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder static class Car { - - private String name; - private String model; + @Nullable private String name; + @Nullable private String model; + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getModel() { + return model; + } + + public void setModel(@Nullable String model) { + this.model = model; + } } - @Data static class Author { - - private String id; - private String name; + @Nullable private String id; + @Nullable private String name; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java index 4126a2c83..b5b0015b5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java @@ -18,8 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.Data; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,6 +33,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -80,14 +79,40 @@ public void shouldExecuteComplexCustomMethod() { assertThat(result).isEqualTo("2+2=4"); } - @Data @Document(indexName = "test-index-sample-repositories-complex-custommethod-autowiring", replicas = 0, refreshInterval = "-1") static class SampleEntity { - + @Nullable @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java index 75c46a4e6..9a68942f0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java @@ -18,8 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.Data; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,6 +33,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -78,12 +77,37 @@ public void shouldExecuteComplexCustomMethod() { assertThat(result).isEqualTo("3+3=6"); } - @Data @Document(indexName = "test-index-sample-repository-manual-wiring", replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; + public void setMessage(@Nullable String message) { + this.message = message; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java index 3eecaf2b5..e8fe1d60c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java @@ -19,11 +19,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.lang.Long; import java.util.ArrayList; import java.util.Arrays; @@ -66,6 +61,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; +import org.springframework.lang.Nullable; /** * @author Rizwan Idrees @@ -1635,21 +1631,87 @@ void shouldStreamSearchHitsWithQueryAnnotatedMethod() { assertThat(count).isEqualTo(20); } - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-sample-repositories-custom-method", replicas = 0, refreshInterval = "-1") static class SampleEntity { - + @Nullable @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - @Field(type = Keyword) private String keyword; - private int rate; - private boolean available; - private GeoPoint location; - @Version private Long version; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable @Field(type = Keyword) private String keyword; + @Nullable private int rate; + @Nullable private boolean available; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + @Nullable + public String getKeyword() { + return keyword; + } + + public void setKeyword(@Nullable String keyword) { + this.keyword = keyword; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java index fe395861f..6bda75c78 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java @@ -17,12 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.util.Locale; import java.util.Optional; @@ -47,6 +41,7 @@ import org.springframework.data.geo.Circle; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -84,8 +79,11 @@ public void shouldSaveAndLoadGeoPoints() { // given Point point = new Point(15, 25); - GeoEntity entity = GeoEntity.builder().pointA(point).pointB(new GeoPoint(point.getX(), point.getY())) - .pointC(toGeoString(point)).pointD(toGeoArray(point)).build(); + GeoEntity entity = new GeoEntity(); + entity.setPointA(point); + entity.setPointB(new GeoPoint(point.getX(), point.getY())); + entity.setPointC(toGeoString(point)); + entity.setPointD(toGeoArray(point)); // when GeoEntity saved = repository.save(entity); @@ -112,32 +110,90 @@ private double[] toGeoArray(Point point) { return new double[] { point.getX(), point.getY() }; } - /** - * @author Artur Konczak - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-geo-repository", replicas = 0, refreshInterval = "-1") static class GeoEntity { - - @Id private String id; - + @Nullable @Id private String id; // geo shape - Spring Data - private Box box; - private Circle circle; - private Polygon polygon; - + @Nullable private Box box; + @Nullable private Circle circle; + @Nullable private Polygon polygon; // geo point - Custom implementation + Spring Data - @GeoPointField private Point pointA; - - private GeoPoint pointB; - - @GeoPointField private String pointC; - - @GeoPointField private double[] pointD; + @Nullable @GeoPointField private Point pointA; + @Nullable private GeoPoint pointB; + @Nullable @GeoPointField private String pointC; + @Nullable @GeoPointField private double[] pointD; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public Box getBox() { + return box; + } + + public void setBox(@Nullable Box box) { + this.box = box; + } + + @Nullable + public Circle getCircle() { + return circle; + } + + public void setCircle(@Nullable Circle circle) { + this.circle = circle; + } + + @Nullable + public Polygon getPolygon() { + return polygon; + } + + public void setPolygon(@Nullable Polygon polygon) { + this.polygon = polygon; + } + + @Nullable + public Point getPointA() { + return pointA; + } + + public void setPointA(@Nullable Point pointA) { + this.pointA = pointA; + } + + @Nullable + public GeoPoint getPointB() { + return pointB; + } + + public void setPointB(@Nullable GeoPoint pointB) { + this.pointB = pointB; + } + + @Nullable + public String getPointC() { + return pointC; + } + + public void setPointC(@Nullable String pointC) { + this.pointC = pointC; + } + + @Nullable + public double[] getPointD() { + return pointD; + } + + public void setPointD(@Nullable double[] pointD) { + this.pointD = pointD; + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java index 2ae1e9d1b..6d826c3a3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java @@ -19,12 +19,6 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -48,6 +42,7 @@ import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -101,26 +96,60 @@ public void shouldIndexInnerObject() { assertThat(bookRepository.findById(id)).isNotNull(); } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Nordine Bittich - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } public interface SampleElasticSearchBookRepository extends ElasticsearchRepository {} diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java index af44a9c1c..91e7a6219 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/synonym/SynonymRepositoryTests.java @@ -17,8 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.Data; - import org.elasticsearch.index.query.QueryBuilders; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -40,6 +38,7 @@ import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -95,23 +94,31 @@ public void shouldDo() { assertThat(synonymEntities).hasSize(1); } - /** - * @author Mohsin Husen - */ - @Data @Document(indexName = "test-index-synonym") @Setting(settingPath = "/synonyms/settings.json") @Mapping(mappingPath = "/synonyms/mappings.json") static class SynonymEntity { - - @Id private String id; - private String text; + @Nullable @Id private String id; + @Nullable private String text; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } } - /** - * SynonymRepository - * - * @author Artur Konczak - */ interface SynonymRepository extends ElasticsearchRepository {} } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java index 365203a6f..916ea2af6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java @@ -18,11 +18,6 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -55,6 +50,7 @@ import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -549,24 +545,139 @@ private static List createSampleEntitiesWithMessage(Strin return sampleEntities; } - @NoArgsConstructor - @AllArgsConstructor - @Builder - @Data @Document(indexName = "test-index-uuid-keyed", replicas = 0, refreshInterval = "-1") static class SampleEntityUUIDKeyed { + @Nullable @Id private UUID id; + @Nullable private String type; + @Nullable @Field(type = FieldType.Text, fielddata = true) private String message; + @Nullable private int rate; + @Nullable @ScriptedField private Long scriptedRate; + @Nullable private boolean available; + @Nullable private String highlightedMessage; + @Nullable private GeoPoint location; + @Nullable @Version private Long version; + + @Nullable + public UUID getId() { + return id; + } + + public void setId(@Nullable UUID id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } - @Id private UUID id; - private String type; - @Field(type = FieldType.Text, fielddata = true) private String message; - private int rate; - @ScriptedField private Long scriptedRate; - private boolean available; - private String highlightedMessage; + public void setType(@Nullable String type) { + this.type = type; + } - private GeoPoint location; + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } - @Version private Long version; + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Nullable + public Long getScriptedRate() { + return scriptedRate; + } + + public void setScriptedRate(@Nullable Long scriptedRate) { + this.scriptedRate = scriptedRate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public String getHighlightedMessage() { + return highlightedMessage; + } + + public void setHighlightedMessage(@Nullable String highlightedMessage) { + this.highlightedMessage = highlightedMessage; + } + + @Nullable + public GeoPoint getLocation() { + return location; + } + + public void setLocation(@Nullable GeoPoint location) { + this.location = location; + } + + @Nullable + public Long getVersion() { + return version; + } + + public void setVersion(@Nullable Long version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof SampleEntityUUIDKeyed)) + return false; + + SampleEntityUUIDKeyed that = (SampleEntityUUIDKeyed) o; + + if (rate != that.rate) + return false; + if (available != that.available) + return false; + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (type != null ? !type.equals(that.type) : that.type != null) + return false; + if (message != null ? !message.equals(that.message) : that.message != null) + return false; + if (scriptedRate != null ? !scriptedRate.equals(that.scriptedRate) : that.scriptedRate != null) + return false; + if (highlightedMessage != null ? !highlightedMessage.equals(that.highlightedMessage) + : that.highlightedMessage != null) + return false; + if (location != null ? !location.equals(that.location) : that.location != null) + return false; + return version != null ? version.equals(that.version) : that.version == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (message != null ? message.hashCode() : 0); + result = 31 * result + rate; + result = 31 * result + (scriptedRate != null ? scriptedRate.hashCode() : 0); + result = 31 * result + (available ? 1 : 0); + result = 31 * result + (highlightedMessage != null ? highlightedMessage.hashCode() : 0); + result = 31 * result + (location != null ? location.hashCode() : 0); + result = 31 * result + (version != null ? version.hashCode() : 0); + return result; + } } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java index 7f03144f8..30a0d51ac 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java @@ -17,11 +17,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +29,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -62,15 +58,37 @@ public void testConfiguration() { interface ReactiveSampleEntityRepository extends ReactiveElasticsearchRepository {} - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-sample-reactive-repositories-registrar", replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; + public void setMessage(@Nullable String message) { + this.message = message; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java index 3325cd54c..725e84645 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethodUnitTests.java @@ -17,10 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.lang.reflect.Method; import java.util.List; @@ -80,13 +76,37 @@ interface PersonRepository extends Repository books) { } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Nordine Bittich - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book-query-unittest", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Artur Konczak - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder static class Car { + @Nullable private String name; + @Nullable private String model; - private String name; - private String model; - + @Nullable public String getName() { return name; } - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } + @Nullable public String getModel() { return model; } - public void setModel(String model) { + public void setModel(@Nullable String model) { this.model = model; } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ static class Author { @Nullable private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java index b7c3ab193..bc549926c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -220,64 +215,85 @@ public void setBooks(List books) { } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Nordine Bittich - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book-reactive-repository-query", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Artur Konczak - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder static class Car { + @Nullable private String name; + @Nullable private String model; - private String name; - private String model; - + @Nullable public String getName() { return name; } - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } + @Nullable public String getModel() { return model; } - public void setModel(String model) { + public void setModel(@Nullable String model) { this.model = model; } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ static class Author { @Nullable private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java index c6ff792f1..cb45584c7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java @@ -17,11 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -224,64 +219,85 @@ public void setBooks(List books) { } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Nordine Bittich - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-book-reactive-repository-string-query", replicas = 0, refreshInterval = "-1") static class Book { - - @Id private String id; - private String name; - @Field(type = FieldType.Object) private Author author; - @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); - @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Object) private Author author; + @Nullable @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @Nullable @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), otherFields = { @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") }) private String description; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(@Nullable Author author) { + this.author = author; + } + + @Nullable + public Map> getBuckets() { + return buckets; + } + + public void setBuckets(@Nullable Map> buckets) { + this.buckets = buckets; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Artur Konczak - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder static class Car { + @Nullable private String name; + @Nullable private String model; - private String name; - private String model; - + @Nullable public String getName() { return name; } - public void setName(String name) { + public void setName(@Nullable String name) { this.name = name; } + @Nullable public String getModel() { return model; } - public void setModel(String model) { + public void setModel(@Nullable String model) { this.model = model; } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ static class Author { @Nullable private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java index 91ed60ad6..5033a2ff7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java @@ -17,12 +17,6 @@ import static org.assertj.core.api.Assertions.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -76,18 +70,12 @@ public void before() { indexOperations = operations.indexOps(Product.class); IndexInitializer.init(indexOperations); - Product product1 = Product.builder().id("1").name("Sugar").text("Cane sugar").price(1.0f).available(false) - .sortName("sort5").build(); - Product product2 = Product.builder().id("2").name("Sugar").text("Cane sugar").price(1.2f).available(true) - .sortName("sort4").build(); - Product product3 = Product.builder().id("3").name("Sugar").text("Beet sugar").price(1.1f).available(true) - .sortName("sort3").build(); - Product product4 = Product.builder().id("4").name("Salt").text("Rock salt").price(1.9f).available(true) - .sortName("sort2").build(); - Product product5 = Product.builder().id("5").name("Salt").text("Sea salt").price(2.1f).available(false) - .sortName("sort1").build(); - Product product6 = Product.builder().id("6").name(null).text("no name").price(3.4f).available(false) - .sortName("sort0").build(); + Product product1 = new Product("1", "Sugar", "Cane sugar", 1.0f, false, "sort5"); + Product product2 = new Product("2", "Sugar", "Cane sugar", 1.2f, true, "sort4"); + Product product3 = new Product("3", "Sugar", "Beet sugar", 1.1f, true, "sort3"); + Product product4 = new Product("4", "Salt", "Rock salt", 1.9f, true, "sort2"); + Product product5 = new Product("5", "Salt", "Sea salt", 2.1f, false, "sort1"); + Product product6 = new Product("6", null, "no name", 3.4f, false, "sort0"); repository.saveAll(Arrays.asList(product1, product2, product3, product4, product5, product6)); } @@ -285,34 +273,79 @@ void shouldReturnEmptyListOnDerivedMethodWithEmptyInputList() { assertThat(products).isEmpty(); } - /** - * @author Mohsin Husen - * @author Artur Konczak - */ - @Setter - @Getter - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-product-query-keywords", replicas = 0, refreshInterval = "-1") static class Product { - - @Id private String id; - - private String name; - - @Field(type = FieldType.Keyword) private String text; - - @Field(type = FieldType.Float) private Float price; - - private boolean available; - - @Field(name = "sort-name", type = FieldType.Keyword) private String sortName; + @Nullable @Id private String id; + @Nullable private String name; + @Nullable @Field(type = FieldType.Keyword) private String text; + @Nullable @Field(type = FieldType.Float) private Float price; + @Nullable private boolean available; + @Nullable @Field(name = "sort-name", type = FieldType.Keyword) private String sortName; + + public Product(@Nullable String id, @Nullable String name, @Nullable String text, @Nullable Float price, + boolean available, @Nullable String sortName) { + this.id = id; + this.name = name; + this.text = text; + this.price = price; + this.available = available; + this.sortName = sortName; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getText() { + return text; + } + + public void setText(@Nullable String text) { + this.text = text; + } + + @Nullable + public Float getPrice() { + return price; + } + + public void setPrice(@Nullable Float price) { + this.price = price; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public String getSortName() { + return sortName; + } + + public void setSortName(@Nullable String sortName) { + this.sortName = sortName; + } } - /** - * Created by akonczak on 04/09/15. - */ interface ProductRepository extends ElasticsearchRepository { List findByName(@Nullable String name); diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java index 103b590fb..72bb7ff7c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java @@ -20,13 +20,9 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - import java.io.IOException; import java.lang.Long; +import java.lang.Object; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -59,6 +55,7 @@ import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; import org.springframework.data.elasticsearch.utils.IndexInitializer; import org.springframework.data.util.StreamUtils; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -76,8 +73,7 @@ class SimpleElasticsearchRepositoryIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) - @EnableElasticsearchRepositories( - basePackages = { "org.springframework.data.elasticsearch.repository.support" }, + @EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.repository.support" }, considerNestedRepositories = true) static class Config {} @@ -681,32 +677,101 @@ private static List createSampleEntitiesWithMessage(String message return sampleEntities; } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Chris White - * @author Sascha Woo - */ - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = "test-index-sample-simple-repository", replicas = 0, refreshInterval = "-1") static class SampleEntity { + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable private boolean available; + @Nullable @Version private Long version; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - private boolean available; - @Version private Long version; + public void setRate(int rate) { + this.rate = rate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + SampleEntity that = (SampleEntity) o; + + if (rate != that.rate) + return false; + if (available != that.available) + return false; + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (type != null ? !type.equals(that.type) : that.type != null) + return false; + if (message != null ? !message.equals(that.message) : that.message != null) + return false; + return version != null ? version.equals(that.version) : that.version == null; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (message != null ? message.hashCode() : 0); + result = 31 * result + rate; + result = 31 * result + (available ? 1 : 0); + result = 31 * result + (version != null ? version.hashCode() : 0); + return result; + } } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Christoph Strobl - */ interface SampleElasticsearchRepository extends ElasticsearchRepository { long deleteSampleEntityById(String id); diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java index b71e4cbd0..7076c0829 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java @@ -19,11 +19,6 @@ import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.core.query.Query.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.data.elasticsearch.core.query.ByQueryResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -63,6 +58,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; /** @@ -97,7 +93,7 @@ void after() { @Test // DATAES-519 void saveShouldSaveSingleEntity() { - repository.save(SampleEntity.builder().build()) // + repository.save(new SampleEntity()) // .map(SampleEntity::getId) // .flatMap(this::documentWithIdExistsInIndex) // .as(StepVerifier::create) // @@ -111,10 +107,8 @@ private Mono documentWithIdExistsInIndex(String id) { @Test // DATAES-519 void saveShouldComputeMultipleEntities() { - repository - .saveAll(Arrays.asList(SampleEntity.builder().build(), SampleEntity.builder().build(), - SampleEntity.builder().build())) // - .map(SampleEntity::getId) // + repository.saveAll(Arrays.asList(new SampleEntity(), new SampleEntity(), new SampleEntity())) + /**/.map(SampleEntity::getId) // .flatMap(this::documentWithIdExistsInIndex) // .as(StepVerifier::create) // .expectNext(true) // @@ -133,9 +127,9 @@ void findByIdShouldErrorIfIndexDoesNotExist() { @Test // DATAES-519 void findShouldRetrieveSingleEntityById() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build(), // - SampleEntity.builder().id("id-three").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two"), // + new SampleEntity("id-three")) // .block(); repository.findById("id-two").as(StepVerifier::create)// @@ -146,9 +140,9 @@ void findShouldRetrieveSingleEntityById() { @Test // DATAES-519 void findByIdShouldCompleteIfNothingFound() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build(), // - SampleEntity.builder().id("id-three").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two"), // + new SampleEntity("id-three")) // .block(); repository.findById("does-not-exist").as(StepVerifier::create) // @@ -160,7 +154,7 @@ void findAllShouldReturnAllElements() { // make sure to be above the default page size of the Query interface int count = DEFAULT_PAGE_SIZE * 2; bulkIndex(IntStream.range(1, count + 1) // - .mapToObj(it -> SampleEntity.builder().id(String.valueOf(it)).build()) // + .mapToObj(it -> new SampleEntity(String.valueOf(it))) // .toArray(SampleEntity[]::new)) // .block(); @@ -178,9 +172,9 @@ void findAllByIdByIdShouldCompleteIfIndexDoesNotExist() { @Test // DATAES-519 void findAllByIdShouldRetrieveMatchingDocuments() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build(), // - SampleEntity.builder().id("id-three").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two"), // + new SampleEntity("id-three")) // .block(); repository.findAllById(Arrays.asList("id-one", "id-two")) // @@ -193,9 +187,9 @@ void findAllByIdShouldRetrieveMatchingDocuments() { @Test // DATAES-519 void findAllByIdShouldCompleteWhenNothingFound() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build(), // - SampleEntity.builder().id("id-three").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two"), // + new SampleEntity("id-three")) // .block(); repository.findAllById(Arrays.asList("can't", "touch", "this")) // @@ -206,9 +200,9 @@ void findAllByIdShouldCompleteWhenNothingFound() { @Test // DATAES-717 void shouldReturnFluxOfSearchHit() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("message").build(), // - SampleEntity.builder().id("id-three").message("message").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "message"), // + new SampleEntity("id-three", "message")) // .block(); repository.queryAllByMessage("message") // @@ -221,9 +215,9 @@ void shouldReturnFluxOfSearchHit() { @Test // DATAES-717 void shouldReturnFluxOfSearchHitForStringQuery() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("message").build(), // - SampleEntity.builder().id("id-three").message("message").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "message"), // + new SampleEntity("id-three", "message")) // .block(); repository.queryByMessageWithString("message") // @@ -236,9 +230,9 @@ void shouldReturnFluxOfSearchHitForStringQuery() { @Test // DATAES-372 void shouldReturnHighlightsOnAnnotatedMethod() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("message").build(), // - SampleEntity.builder().id("id-three").message("message").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "message"), // + new SampleEntity("id-three", "message")) // .block(); repository.queryAllByMessage("message") // @@ -254,9 +248,9 @@ void shouldReturnHighlightsOnAnnotatedMethod() { @Test // DATAES-372 void shouldReturnHighlightsOnAnnotatedStringQueryMethod() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("message").build(), // - SampleEntity.builder().id("id-three").message("message").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "message"), // + new SampleEntity("id-three", "message")) // .block(); repository.queryByMessageWithString("message") // @@ -279,8 +273,8 @@ void countShouldErrorWhenIndexDoesNotExist() { @Test // DATAES-519 void countShouldCountDocuments() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two")) // .block(); repository.count().as(StepVerifier::create).expectNext(2L).verifyComplete(); @@ -289,9 +283,9 @@ void countShouldCountDocuments() { @Test // DATAES-519 void existsByIdShouldReturnTrueIfExists() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.existsById("id-two") // @@ -303,9 +297,9 @@ void existsByIdShouldReturnTrueIfExists() { @Test // DATAES-519 void existsByIdShouldReturnFalseIfNotExists() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.existsById("wrecking ball") // @@ -317,9 +311,9 @@ void existsByIdShouldReturnFalseIfNotExists() { @Test // DATAES-519 void countShouldCountMatchingDocuments() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()).block(); + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")).block(); repository.countAllByMessage("test") // .as(StepVerifier::create) // @@ -331,9 +325,9 @@ void countShouldCountMatchingDocuments() { @DisplayName("should count with string query") void shouldCountWithStringQuery() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()).block(); + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")).block(); repository.retrieveCountByText("test") // .as(StepVerifier::create) // @@ -344,9 +338,9 @@ void shouldCountWithStringQuery() { @Test // DATAES-519 void existsShouldReturnTrueIfExists() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.existsAllByMessage("message") // @@ -358,9 +352,9 @@ void existsShouldReturnTrueIfExists() { @Test // DATAES-519 void existsShouldReturnFalseIfNotExists() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.existsAllByMessage("these days") // @@ -372,8 +366,8 @@ void existsShouldReturnFalseIfNotExists() { @Test // DATAES-519 void deleteByIdShouldCompleteIfNothingDeleted() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two")) // .block(); repository.deleteById("does-not-exist").as(StepVerifier::create).verifyComplete(); @@ -389,8 +383,8 @@ void deleteByIdShouldCompleteWhenIndexDoesNotExist() { @Test // DATAES-519 void deleteByIdShouldDeleteEntry() { - SampleEntity toBeDeleted = SampleEntity.builder().id("id-two").build(); - bulkIndex(SampleEntity.builder().id("id-one").build(), toBeDeleted) // + SampleEntity toBeDeleted = new SampleEntity("id-two"); + bulkIndex(new SampleEntity("id-one"), toBeDeleted) // .block(); repository.deleteById(toBeDeleted.getId()).as(StepVerifier::create).verifyComplete(); @@ -401,8 +395,8 @@ void deleteByIdShouldDeleteEntry() { @Test // DATAES-976 void deleteAllByIdShouldDeleteEntry() { - SampleEntity toBeDeleted = SampleEntity.builder().id("id-two").build(); - bulkIndex(SampleEntity.builder().id("id-one").build(), toBeDeleted) // + SampleEntity toBeDeleted = new SampleEntity("id-two"); + bulkIndex(new SampleEntity("id-one"), toBeDeleted) // .block(); repository.deleteAllById(Collections.singletonList(toBeDeleted.getId())).as(StepVerifier::create).verifyComplete(); @@ -413,8 +407,8 @@ void deleteAllByIdShouldDeleteEntry() { @Test // DATAES-519 void deleteShouldDeleteEntry() { - SampleEntity toBeDeleted = SampleEntity.builder().id("id-two").build(); - bulkIndex(SampleEntity.builder().id("id-one").build(), toBeDeleted) // + SampleEntity toBeDeleted = new SampleEntity("id-two"); + bulkIndex(new SampleEntity("id-one"), toBeDeleted) // .block(); repository.delete(toBeDeleted).as(StepVerifier::create).verifyComplete(); @@ -425,9 +419,9 @@ void deleteShouldDeleteEntry() { @Test // DATAES-519 void deleteAllShouldDeleteGivenEntries() { - SampleEntity toBeDeleted = SampleEntity.builder().id("id-one").build(); - SampleEntity hangInThere = SampleEntity.builder().id("id-two").build(); - SampleEntity toBeDeleted2 = SampleEntity.builder().id("id-three").build(); + SampleEntity toBeDeleted = new SampleEntity("id-one"); + SampleEntity hangInThere = new SampleEntity("id-two"); + SampleEntity toBeDeleted2 = new SampleEntity("id-three"); bulkIndex(toBeDeleted, hangInThere, toBeDeleted2) // .block(); @@ -442,9 +436,9 @@ void deleteAllShouldDeleteGivenEntries() { @Test // DATAES-519 void deleteAllShouldDeleteAllEntries() { - bulkIndex(SampleEntity.builder().id("id-one").build(), // - SampleEntity.builder().id("id-two").build(), // - SampleEntity.builder().id("id-three").build()) // + bulkIndex(new SampleEntity("id-one"), // + new SampleEntity("id-two"), // + new SampleEntity("id-three")) // .block(); repository.deleteAll().as(StepVerifier::create).verifyComplete(); @@ -458,9 +452,9 @@ void deleteAllShouldDeleteAllEntries() { @Test // DATAES-519 void derivedFinderMethodShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.findAllByMessageLike("test") // @@ -472,9 +466,9 @@ void derivedFinderMethodShouldBeExecutedCorrectly() { @Test // DATAES-519 void derivedFinderMethodShouldBeExecutedCorrectlyWhenGivenPublisher() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.findAllByMessage(Mono.just("test")) // @@ -486,9 +480,9 @@ void derivedFinderMethodShouldBeExecutedCorrectlyWhenGivenPublisher() { @Test // DATAES-519 void derivedFinderWithDerivedSortMethodShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("test").rate(3).build(), // - SampleEntity.builder().id("id-two").message("test test").rate(1).build(), // - SampleEntity.builder().id("id-three").message("test test").rate(2).build()) // + bulkIndex(new SampleEntity("id-one", "test", 3), // + new SampleEntity("id-two", "test test", 1), // + new SampleEntity("id-three", "test test", 2)) // .block(); repository.findAllByMessageLikeOrderByRate("test") // @@ -502,9 +496,9 @@ void derivedFinderWithDerivedSortMethodShouldBeExecutedCorrectly() { @Test // DATAES-519 void derivedFinderMethodWithSortParameterShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("test").rate(3).build(), // - SampleEntity.builder().id("id-two").message("test test").rate(1).build(), // - SampleEntity.builder().id("id-three").message("test test").rate(2).build()) // + bulkIndex(new SampleEntity("id-one", "test", 3), // + new SampleEntity("id-two", "test test", 1), // + new SampleEntity("id-three", "test test", 2)) // .block(); repository.findAllByMessage("test", Sort.by(Order.asc("rate"))) // @@ -518,9 +512,9 @@ void derivedFinderMethodWithSortParameterShouldBeExecutedCorrectly() { @Test // DATAES-519 void derivedFinderMethodWithPageableParameterShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("test").rate(3).build(), // - SampleEntity.builder().id("id-two").message("test test").rate(1).build(), // - SampleEntity.builder().id("id-three").message("test test").rate(2).build()) // + bulkIndex(new SampleEntity("id-one", "test", 3), // + new SampleEntity("id-two", "test test", 1), // + new SampleEntity("id-three", "test test", 2)) // .block(); repository.findAllByMessage("test", PageRequest.of(0, 2, Sort.by(Order.asc("rate")))) // @@ -533,9 +527,9 @@ void derivedFinderMethodWithPageableParameterShouldBeExecutedCorrectly() { @Test // DATAES-519 void derivedFinderMethodReturningMonoShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.findFirstByMessageLike("test") // @@ -547,9 +541,9 @@ void derivedFinderMethodReturningMonoShouldBeExecutedCorrectly() { @Test // DATAES-519 void annotatedFinderMethodShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.findAllViaAnnotatedQueryByMessageLike("test") // @@ -561,9 +555,9 @@ void annotatedFinderMethodShouldBeExecutedCorrectly() { @Test // DATAES-519 void derivedDeleteMethodShouldBeExecutedCorrectly() { - bulkIndex(SampleEntity.builder().id("id-one").message("message").build(), // - SampleEntity.builder().id("id-two").message("test message").build(), // - SampleEntity.builder().id("id-three").message("test test").build()) // + bulkIndex(new SampleEntity("id-one", "message"), // + new SampleEntity("id-two", "test message"), // + new SampleEntity("id-three", "test test")) // .block(); repository.deleteAllByMessage("message") // @@ -614,25 +608,82 @@ interface ReactiveSampleEntityRepository extends ReactiveCrudRepository retrieveCountByText(String message); } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Chris White - * @author Sascha Woo - */ - @Data - @NoArgsConstructor - @AllArgsConstructor - @Builder @Document(indexName = INDEX, replicas = 0, refreshInterval = "-1") static class SampleEntity { - - @Id private String id; - @Field(type = Text, store = true, fielddata = true) private String type; - @Field(type = Text, store = true, fielddata = true) private String message; - private int rate; - private boolean available; - @Version private Long version; - + @Nullable @Id private String id; + @Nullable @Field(type = Text, store = true, fielddata = true) private String type; + @Nullable @Field(type = Text, store = true, fielddata = true) private String message; + @Nullable private int rate; + @Nullable private boolean available; + @Nullable @Version private Long version; + + public SampleEntity() {} + + public SampleEntity(@Nullable String id) { + this.id = id; + } + + public SampleEntity(@Nullable String id, @Nullable String message) { + this.id = id; + this.message = message; + } + + public SampleEntity(@Nullable String id, @Nullable String message, int rate) { + this.id = id; + this.message = message; + this.rate = rate; + } + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getType() { + return type; + } + + public void setType(@Nullable String type) { + this.type = type; + } + + @Nullable + public String getMessage() { + return message; + } + + public void setMessage(@Nullable String message) { + this.message = message; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + public boolean isAvailable() { + return available; + } + + public void setAvailable(boolean available) { + this.available = available; + } + + @Nullable + public java.lang.Long getVersion() { + return version; + } + + public void setVersion(@Nullable java.lang.Long version) { + this.version = version; + } } } diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar new file mode 100644 index 0000000000000000000000000000000000000000..f11c867c5059f6179c26a131960899f58de3d64c GIT binary patch literal 425 zcmWIWW@Zs#;Nak3_*7FG#DD}i8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gDtA_7e=7P|#0a)pXWGOjOQ69PKn${)i-7^?oTnJ>W?)FoEhwqf zE2$_6@MdHZVL7XxlrB23r;WFonl Rl?^1t41`;N^nDPA0RX@NP|5%R literal 0 HcmV?d00001 diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.md5 b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.md5 new file mode 100644 index 000000000..48ea84335 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.md5 @@ -0,0 +1 @@ +f6bbf833798e7af0055b94865a46440e \ No newline at end of file diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.sha1 b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.sha1 new file mode 100644 index 000000000..f6cee39f7 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.jar.sha1 @@ -0,0 +1 @@ +59ddfc2b714be7918808eacecc5739b8d277e60e \ No newline at end of file diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom new file mode 100644 index 000000000..3f9fa7a19 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + org.projectlombok + lombok + 999999 + diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.md5 b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.md5 new file mode 100644 index 000000000..788bc4133 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.md5 @@ -0,0 +1 @@ +63317ccd46b6663ff35cb142e05dce10 \ No newline at end of file diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.sha1 b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.sha1 new file mode 100644 index 000000000..f1cddaef2 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/999999/lombok-999999.pom.sha1 @@ -0,0 +1 @@ +db353983c68ade94ae7e08acfacc0fc21ed8b64b \ No newline at end of file diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml b/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml new file mode 100644 index 000000000..8ddcc39a8 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml @@ -0,0 +1,12 @@ + + + org.projectlombok + lombok + + 999999 + + 999999 + + 20210321155422 + + diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.md5 b/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.md5 new file mode 100644 index 000000000..8c58c1d52 --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.md5 @@ -0,0 +1 @@ +998d3b8876980a3ef5a90adc982cc727 \ No newline at end of file diff --git a/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.sha1 b/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.sha1 new file mode 100644 index 000000000..ec41ca43b --- /dev/null +++ b/src/test/resources/local-maven-repo/org/projectlombok/lombok/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +f4090a49c6eec680c075130b68bf8ee48aac4e94 \ No newline at end of file From a3e87a852589dfc7b271af2962b25487c664de8f Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 21 Mar 2021 21:31:14 +0100 Subject: [PATCH 024/776] Upgrade to OpenWebBeans 2. Original Pull Request #1737 Closes #1736 --- pom.xml | 32 +++++++++++++++++++ .../repositories/cdi/CdiRepositoryTests.java | 23 +++++++------ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 4e95887e5..165816319 100644 --- a/pom.xml +++ b/pom.xml @@ -173,6 +173,7 @@ + org.slf4j log4j-over-slf4j @@ -198,6 +199,21 @@ + + + org.apache.geronimo.specs + geronimo-jcdi_2.0_spec + 1.0.1 + test + + + + javax.interceptor + javax.interceptor-api + 1.2.1 + test + + javax.enterprise cdi-api @@ -206,6 +222,20 @@ true + + javax.annotation + javax.annotation-api + ${javax-annotation-api} + test + + + + org.apache.openwebbeans + openwebbeans-se + ${webbeans} + test + + org.springframework @@ -242,6 +272,7 @@ test + org.skyscreamer diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java index f23ac8158..a2fed6d3b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java @@ -24,8 +24,9 @@ import java.util.Map; import java.util.Optional; -import org.apache.webbeans.cditest.CdiTestContainer; -import org.apache.webbeans.cditest.CdiTestContainerLoader; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -48,7 +49,9 @@ @IntegrationTest public class CdiRepositoryTests { - @Nullable private static CdiTestContainer cdiContainer; + @Nullable private static SeContainer container; + + // @Nullable private static CdiTestContainer cdiContainer; private CdiProductRepository repository; private SamplePersonRepository personRepository; private QualifiedProductRepository qualifiedProductRepository; @@ -56,22 +59,24 @@ public class CdiRepositoryTests { @BeforeAll public static void init() throws Exception { - cdiContainer = CdiTestContainerLoader.getCdiContainer(); - cdiContainer.startApplicationScope(); - cdiContainer.bootContainer(); + container = SeContainerInitializer.newInstance() // + .disableDiscovery()// + .addPackages(CdiRepositoryTests.class) // + .initialize(); } @AfterAll public static void shutdown() throws Exception { - cdiContainer.stopContexts(); - cdiContainer.shutdownContainer(); + if (container != null) { + container.close(); + } } @BeforeEach public void setUp() { - CdiRepositoryClient client = cdiContainer.getInstance(CdiRepositoryClient.class); + CdiRepositoryClient client = container.select(CdiRepositoryClient.class).get(); repository = client.getRepository(); personRepository = client.getSamplePersonRepository(); repository.deleteAll(); From 2e5d2e0fd0fea6448d18bdf819ef5eed5164acc0 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 22 Mar 2021 21:57:10 +0100 Subject: [PATCH 025/776] Update CI to Java 16 Original Pull Request #1739 Closes #1733 --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 63fb2aebd..00f58f40f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -72,7 +72,7 @@ pipeline { } } - stage("test: baseline (jdk15)") { + stage("test: baseline (jdk16)") { agent { label 'data' } @@ -85,7 +85,7 @@ pipeline { steps { script { docker.withRegistry('', 'hub.docker.com-springbuildmaster') { - docker.image('adoptopenjdk/openjdk15:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { + docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" sh 'PROFILE=java11 ci/verify.sh' sh "ci/clean.sh" From 3500dad2bc53bbec5801cf747922adb713f20ce8 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Tue, 23 Mar 2021 07:15:18 +0100 Subject: [PATCH 026/776] Readme lists artifacts with .RELEASE and .BUILD-SNAPSHOT suffixes (#1740) Original Pull Request #1740 Closes #1738 --- README.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index f3a1e2674..30942c9c8 100644 --- a/README.adoc +++ b/README.adoc @@ -114,7 +114,7 @@ Add the Maven dependency: org.springframework.data spring-data-elasticsearch - ${version}.RELEASE + ${version} ---- @@ -149,7 +149,7 @@ If you'd rather like the latest snapshots of the upcoming major version, use our org.springframework.data spring-data-elasticsearch - ${version}.BUILD-SNAPSHOT + ${version}-SNAPSHOT From 13ab2b9e9529c4965280cf846c887247da2107f6 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 27 Mar 2021 15:44:46 +0100 Subject: [PATCH 027/776] Automatically close scroll context when returning streamed results. Original Pull Request #1746 Closes #1745 --- .../elasticsearch/core/StreamQueries.java | 28 +++++++++++++------ .../elasticsearch/core/StreamQueriesTest.java | 28 +++++++++++++++++-- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/StreamQueries.java b/src/main/java/org/springframework/data/elasticsearch/core/StreamQueries.java index da1d94a6c..3640ca700 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/StreamQueries.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/StreamQueries.java @@ -66,10 +66,14 @@ static SearchHitsIterator streamResults(int maxCount, SearchScrollHits private volatile Iterator> currentScrollHits = searchHits.iterator(); private volatile boolean continueScroll = currentScrollHits.hasNext(); private volatile ScrollState scrollState = new ScrollState(searchHits.getScrollId()); + private volatile boolean isClosed = false; @Override public void close() { - clearScrollConsumer.accept(scrollState.getScrollIds()); + if (!isClosed) { + clearScrollConsumer.accept(scrollState.getScrollIds()); + isClosed = true; + } } @Override @@ -96,18 +100,24 @@ public TotalHitsRelation getTotalHitsRelation() { @Override public boolean hasNext() { - if (!continueScroll || (maxCount > 0 && currentCount.get() >= maxCount)) { - return false; + boolean hasNext = false; + + if (!isClosed && continueScroll && (maxCount <= 0 || currentCount.get() < maxCount)) { + + if (!currentScrollHits.hasNext()) { + SearchScrollHits nextPage = continueScrollFunction.apply(scrollState.getScrollId()); + currentScrollHits = nextPage.iterator(); + scrollState.updateScrollId(nextPage.getScrollId()); + continueScroll = currentScrollHits.hasNext(); + } + hasNext = currentScrollHits.hasNext(); } - if (!currentScrollHits.hasNext()) { - SearchScrollHits nextPage = continueScrollFunction.apply(scrollState.getScrollId()); - currentScrollHits = nextPage.iterator(); - scrollState.updateScrollId(nextPage.getScrollId()); - continueScroll = currentScrollHits.hasNext(); + if (!hasNext) { + close(); } - return currentScrollHits.hasNext(); + return hasNext; } @Override diff --git a/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java b/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java index 580202780..518d0c61f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/StreamQueriesTest.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.util.StreamUtils; @@ -39,6 +40,8 @@ public void shouldCallClearScrollOnIteratorClose() { // given List> hits = new ArrayList<>(); hits.add(getOneSearchHit()); + hits.add(getOneSearchHit()); + hits.add(getOneSearchHit()); SearchScrollHits searchHits = newSearchScrollHits(hits, "1234"); @@ -51,9 +54,7 @@ public void shouldCallClearScrollOnIteratorClose() { scrollId -> newSearchScrollHits(Collections.emptyList(), scrollId), // scrollIds -> clearScrollCalled.set(true)); - while (iterator.hasNext()) { - iterator.next(); - } + iterator.next(); iterator.close(); // then @@ -61,6 +62,27 @@ public void shouldCallClearScrollOnIteratorClose() { } + @Test // #1745 + @DisplayName("should call clearScroll when no more data is available") + void shouldCallClearScrollWhenNoMoreDataIsAvailable() { + + List> hits = new ArrayList<>(); + hits.add(getOneSearchHit()); + SearchScrollHits searchHits = newSearchScrollHits(hits, "1234"); + AtomicBoolean clearScrollCalled = new AtomicBoolean(false); + + SearchHitsIterator iterator = StreamQueries.streamResults( // + 0, // + searchHits, // + scrollId -> newSearchScrollHits(Collections.emptyList(), scrollId), // + scrollIds -> clearScrollCalled.set(true)); + + while (iterator.hasNext()) { + iterator.next(); + } + + assertThat(clearScrollCalled).isTrue(); + } private SearchHit getOneSearchHit() { return new SearchHit(null, null, null, 0, null, null, null, null, null, null, "one"); } From 2e9bef0edb36fa722cef00349e764d672cf8bb69 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 28 Mar 2021 13:24:52 +0200 Subject: [PATCH 028/776] Configure index settings with @Setting annotation. Original Pull Request #1748 Closes #1719 --- .../reference/elasticsearch-clients.adoc | 2 +- ...elasticsearch-migration-guide-4.1-4.2.adoc | 4 + .../reference/elasticsearch-misc.adoc | 52 +++- .../elasticsearch-object-mapping.adoc | 14 +- .../reference/elasticsearch-operations.adoc | 3 +- .../reference/elasticsearch-repositories.adoc | 4 +- .../ResourceFailureException.java | 31 ++ .../elasticsearch/annotations/Document.java | 16 + .../elasticsearch/annotations/Setting.java | 78 ++++- .../core/AbstractDefaultIndexOperations.java | 69 ++--- .../core/DefaultIndexOperations.java | 5 +- .../core/DefaultReactiveIndexOperations.java | 38 ++- .../core/DefaultTransportIndexOperations.java | 10 +- .../elasticsearch/core/IndexInformation.java | 9 +- .../elasticsearch/core/IndexOperations.java | 13 +- .../core/ReactiveIndexOperations.java | 13 +- .../core/ReactiveResourceUtil.java | 11 +- .../elasticsearch/core/RequestFactory.java | 17 +- .../data/elasticsearch/core/ResourceUtil.java | 20 +- .../elasticsearch/core/ResponseConverter.java | 41 +-- .../elasticsearch/core/document/Document.java | 246 +--------------- .../core/document/DocumentAdapters.java | 1 + .../core/document/MapDocument.java | 8 +- .../core/index/PutTemplateRequest.java | 14 +- .../elasticsearch/core/index/Settings.java | 50 ++++ .../core/index/TemplateData.java | 12 +- .../ElasticsearchPersistentEntity.java | 7 +- .../SimpleElasticsearchPersistentEntity.java | 214 +++++++++++--- .../support/DefaultStringObjectMap.java | 153 ++++++++++ .../support/StringObjectMap.java | 278 ++++++++++++++++++ .../data/elasticsearch/NestedObjectTests.java | 6 +- .../ComposableAnnotationsUnitTest.java | 5 +- ...eNestedElasticsearchRepositoriesTests.java | 2 +- .../EnableElasticsearchRepositoriesTests.java | 4 +- .../core/ElasticsearchRestTemplateTests.java | 2 +- .../core/ElasticsearchTemplateTests.java | 23 +- .../ElasticsearchTransportTemplateTests.java | 2 +- .../elasticsearch/core/LogEntityTests.java | 2 +- ...ElasticsearchTemplateIntegrationTests.java | 2 +- ...eactiveElasticsearchTemplateUnitTests.java | 2 +- .../core/ReactiveIndexOperationsTest.java | 16 +- .../core/ReactiveResourceUtilTest.java | 4 +- .../core/SearchAsYouTypeTests.java | 2 +- ...ElasticsearchTemplateAggregationTests.java | 2 +- .../ElasticsearchTemplateCompletionTests.java | 4 +- ...chTemplateCompletionWithContextsTests.java | 2 +- ...appingElasticsearchConverterUnitTests.java | 3 +- .../geo/ElasticsearchTemplateGeoTests.java | 4 +- .../index/MappingBuilderIntegrationTests.java | 59 ++-- .../core/index/MappingBuilderUnitTests.java | 16 +- .../SimpleDynamicTemplatesMappingTests.java | 7 +- .../SimpleElasticsearchDateMappingTests.java | 2 +- .../core/index/TemplateTests.java | 10 +- ...pleElasticsearchPersistentEntityTests.java | 249 ++++++++++------ .../query/CriteriaQueryIntegrationTests.java | 8 +- .../ElasticsearchOperationsRoutingTests.java | 4 +- ...veElasticsearchOperationsRoutingTests.java | 4 +- .../repositories/cdi/CdiRepositoryTests.java | 6 +- .../ComplexCustomMethodRepositoryTests.java | 6 +- ...stomMethodRepositoryManualWiringTests.java | 2 +- .../CustomMethodRepositoryBaseTests.java | 2 +- .../doubleid/DoubleIDRepositoryTests.java | 2 +- .../geo/SpringDataGeoRepositoryTests.java | 2 +- .../integer/IntegerIDRepositoryTests.java | 2 +- .../nestedobject/InnerObjectTests.java | 2 +- .../repositories/spel/SpELEntityTests.java | 2 +- .../UUIDElasticsearchRepositoryTests.java | 2 +- ...asticsearchRepositoriesRegistrarTests.java | 2 +- .../ElasticsearchStringQueryUnitTests.java | 4 +- ...tiveElasticsearchQueryMethodUnitTests.java | 4 +- ...tiveElasticsearchStringQueryUnitTests.java | 4 +- .../query/keywords/QueryKeywordsTests.java | 2 +- ...asticsearchRepositoryIntegrationTests.java | 2 +- ...eReactiveElasticsearchRepositoryTests.java | 2 +- 74 files changed, 1292 insertions(+), 635 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/ResourceFailureException.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/support/StringObjectMap.java diff --git a/src/main/asciidoc/reference/elasticsearch-clients.adoc b/src/main/asciidoc/reference/elasticsearch-clients.adoc index 78695f489..62f5a23f8 100644 --- a/src/main/asciidoc/reference/elasticsearch-clients.adoc +++ b/src/main/asciidoc/reference/elasticsearch-clients.adoc @@ -8,7 +8,7 @@ Spring Data Elasticsearch operates upon an Elasticsearch client that is connecte [[elasticsearch.clients.transport]] == Transport Client -WARNING: The well known `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used +WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used Elasticsearch <> but has deprecated the classes using it since version 4.0. We strongly recommend to use the <> instead of the `TransportClient`. diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc index 58bb48f10..860e09226 100644 --- a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.1-4.2.adoc @@ -6,6 +6,10 @@ This section describes breaking changes from version 4.1.x to 4.2.x and how remo [[elasticsearch-migration-guide-4.1-4.2.deprecations]] == Deprecations +=== @Document parameters + +The parameters of the `@Document` annotation that are relevant for the index settings (`useServerConfiguration`, `shards`. `replicas`, `refreshIntervall` and `indexStoretype`) have been moved to the `@Setting` annotation. Use in `@Document` is still possible but deprecated. + [[elasticsearch-migration-guide-4.1-4.2.removal]] == Removals diff --git a/src/main/asciidoc/reference/elasticsearch-misc.adoc b/src/main/asciidoc/reference/elasticsearch-misc.adoc index b92cf1adf..042759e7c 100644 --- a/src/main/asciidoc/reference/elasticsearch-misc.adoc +++ b/src/main/asciidoc/reference/elasticsearch-misc.adoc @@ -4,6 +4,48 @@ This chapter covers additional support for Elasticsearch operations that cannot be directly accessed via the repository interface. It is recommended to add those operations as custom implementation as described in <> . +[[elasticsearc.misc.index.settings]] +== Index settings + +When creating Elasticsearch indices with Spring Data Elasticsearch different index settings can be defined by using the `@Setting` annotation. The following arguments are available: + +* `useServerConfiguration` does not send any settings parameters, so the Elasticsearch server configuration determines them. +* `settingPath` refers to a JSON file defining the settings that must be resolvable in the classpath +* `shards` the number of shards to use, defaults to _1_ +* `replicas` the number of replicas, defaults to _1_ +* `refreshIntervall`, defaults to _"1s"_ +* `indexStoreType`, defaults to _"fs"_ + + +It is as well possible to define https://www.elastic.co/guide/en/elasticsearch/reference/7.11/index-modules-index-sorting.html[index sorting] (check the linked Elasticsearch documentation for the possible field types and values): + +==== +[source,java] +---- +@Document(indexName = "entities") +@Setting( + sortFields = { "secondField", "firstField" }, <.> + sortModes = { Setting.SortMode.max, Setting.SortMode.min }, <.> + sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc }, + sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first }) +class Entity { + @Nullable + @Id private String id; + + @Nullable + @Field(name = "first_field", type = FieldType.Keyword) + private String firstField; + + @Nullable @Field(name = "second_field", type = FieldType.Keyword) + private String secondField; + + // getter and setter... +} +---- +<.> when defining sort fields, use the name of the Java property (_firstField_), not the name that might be defined for Elasticsearch (_first_field_) +<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements +==== + [[elasticsearch.misc.filter]] == Filter Builder @@ -20,7 +62,7 @@ SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchAllQuery()) .withFilter(boolFilter().must(termFilter("id", documentId))) .build(); - + Page sampleEntities = operations.searchForPage(searchQuery, SampleEntity.class, index); ---- ==== @@ -31,6 +73,7 @@ Page sampleEntities = operations.searchForPage(searchQuery, Sample Elasticsearch has a scroll API for getting big result set in chunks. This is internally used by Spring Data Elasticsearch to provide the implementations of the ` SearchHitsIterator SearchOperations.searchForStream(Query query, Class clazz, IndexCoordinates index)` method. +==== [source,java] ---- IndexCoordinates index = IndexCoordinates.of("sample-index"); @@ -50,9 +93,11 @@ while (stream.hasNext()) { stream.close(); ---- +==== There are no methods in the `SearchOperations` API to access the scroll id, if it should be necessary to access this, the following methods of the `ElasticsearchRestTemplate` can be used: +==== [source,java] ---- @@ -77,10 +122,12 @@ while (scroll.hasSearchHits()) { } template.searchScrollClear(scrollId); ---- +==== To use the Scroll API with repository methods, the return type must defined as `Stream` in the Elasticsearch Repository. The implementation of the method will then use the scroll methods from the ElasticsearchTemplate. +==== [source,java] ---- interface SampleEntityRepository extends Repository { @@ -89,6 +136,7 @@ interface SampleEntityRepository extends Repository { } ---- +==== [[elasticsearch.misc.sorts]] == Sort options @@ -97,7 +145,9 @@ In addition to the default sort options described <> @@ -71,13 +63,13 @@ The mapping metadata infrastructure is defined in a separate spring-data-commons ==== Date format mapping Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation -of type `FieldType.Date` or a custom converter must be registered for this type. This paragraph describes the use of +of type `FieldType.Date` or a custom converter must be registered for this type. This paragraph describes the use of `FieldType.Date`. -There are two attributes of the `@Field` annotation that define which date format information is written to the +There are two attributes of the `@Field` annotation that define which date format information is written to the mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats]) -The `format` attributes is used to define at least one of the predefined formats. If it is not defined, then a +The `format` attributes is used to define at least one of the predefined formats. If it is not defined, then a default value of __date_optional_time_ and _epoch_millis_ is used. The `pattern` attribute can be used to add additional custom format strings. If you want to use only custom date formats, you must set the `format` property to empty `{}`. diff --git a/src/main/asciidoc/reference/elasticsearch-operations.adoc b/src/main/asciidoc/reference/elasticsearch-operations.adoc index b1f55eaf1..1d213604c 100644 --- a/src/main/asciidoc/reference/elasticsearch-operations.adoc +++ b/src/main/asciidoc/reference/elasticsearch-operations.adoc @@ -21,7 +21,8 @@ The default implementations of the interfaces offer: ==== .Index management and automatic creation of indices and mappings. -The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster. +The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster. Details of the index that will be created +can be set by using the `@Setting` annotation, refer to <> for further information. **None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`. It is the user's responsibility to call the methods. diff --git a/src/main/asciidoc/reference/elasticsearch-repositories.adoc b/src/main/asciidoc/reference/elasticsearch-repositories.adoc index 6fba3fd3a..c0b770538 100644 --- a/src/main/asciidoc/reference/elasticsearch-repositories.adoc +++ b/src/main/asciidoc/reference/elasticsearch-repositories.adoc @@ -31,7 +31,7 @@ class Book { The `@Document` annotation has an argument `createIndex`. If this argument is set to true - which is the default value - Spring Data Elasticsearch will during bootstrapping the repository support on application startup check if the index defined by the `@Document` annotation exists. -If it does not exist, the index will be created and the mappings derived from the entity's annotations (see <>) will be written to the newly created index. +If it does not exist, the index will be created and the mappings derived from the entity's annotations (see <>) will be written to the newly created index. Details of the index that will be created can be set by using the `@Setting` annotation, refer to <> for further information. include::elasticsearch-repository-queries.adoc[leveloffset=+1] @@ -131,7 +131,7 @@ class ProductService { public void setRepository(ProductRepository repository) { this.repository = repository; } -} +} ---- <1> Create a component by using the same calls as are used in the <> chapter. <2> Let the CDI framework inject the Repository into your class. diff --git a/src/main/java/org/springframework/data/elasticsearch/ResourceFailureException.java b/src/main/java/org/springframework/data/elasticsearch/ResourceFailureException.java new file mode 100644 index 000000000..cd23381ba --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/ResourceFailureException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch; + +import org.springframework.core.NestedRuntimeException; + +/** + * @author Peter-Josef Meisch + */ +public class ResourceFailureException extends NestedRuntimeException { + public ResourceFailureException(String msg) { + super(msg); + } + + public ResourceFailureException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index 59458b11d..ae53f190d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -55,29 +55,45 @@ /** * Use server-side settings when creating the index. + * + * @deprecated since 4.2, use the {@link Setting} annotation to configure settings */ + @Deprecated boolean useServerConfiguration() default false; /** * Number of shards for the index {@link #indexName()}. Used for index creation.
* With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of * Elasticsearch which changed to 1 as well in Elasticsearch 7.0. + * ComposableAnnotationsUnitTest.documentAnnotationShouldBeComposable:60 + * + * @deprecated since 4.2, use the {@link Setting} annotation to configure settings */ + @Deprecated short shards() default 1; /** * Number of replicas for the index {@link #indexName()}. Used for index creation. + * + * @deprecated since 4.2, use the {@link Setting} annotation to configure settings */ + @Deprecated short replicas() default 1; /** * Refresh interval for the index {@link #indexName()}. Used for index creation. + * + * @deprecated since 4.2, use the {@link Setting} annotation to configure settings */ + @Deprecated String refreshInterval() default "1s"; /** * Index storage type for the index {@link #indexName()}. Used for index creation. + * + * @deprecated since 4.2, use the {@link Setting} annotation to configure settings */ + @Deprecated String indexStoreType() default "fs"; /** diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java index 864637550..c2b836d34 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Setting.java @@ -15,7 +15,11 @@ */ package org.springframework.data.elasticsearch.annotations; -import java.lang.annotation.*; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.springframework.data.annotation.Persistent; @@ -23,14 +27,84 @@ * Elasticsearch Setting * * @author Mohsin Husen + * @author Peter-Josef Meisch */ @Persistent @Inherited @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ ElementType.TYPE }) public @interface Setting { + /** + * Resource path for a settings configuration + */ String settingPath() default ""; + /** + * Use server-side settings when creating the index. + */ + boolean useServerConfiguration() default false; + + /** + * Number of shards for the index. Used for index creation.
+ * With version 4.0, the default value is changed from 5 to 1 to reflect the change in the default settings of + * Elasticsearch which changed to 1 as well in Elasticsearch 7.0. + */ + short shards() default 1; + + /** + * Number of replicas for the index. Used for index creation. + */ + short replicas() default 1; + + /** + * Refresh interval for the index. Used for index creation. + */ + String refreshInterval() default "1s"; + + /** + * Index storage type for the index. Used for index creation. + */ + String indexStoreType() default "fs"; + + /** + * fields to define an index sorting + * + * @since 4.2 + */ + String[] sortFields() default {}; + + /** + * defines the order for {@link #sortFields()}. If present, it must have the same number of elements + * + * @since 4.2 + */ + SortOrder[] sortOrders() default {}; + + /** + * defines the mode for {@link #sortFields()}. If present, it must have the same number of elements + * + * @since 4.2 + */ + SortMode[] sortModes() default {}; + + /** + * defines the missing value for {@link #sortFields()}. If present, it must have the same number of elements + * + * @since 4.2 + */ + SortMissing[] sortMissingValues() default {}; + + enum SortOrder { + asc, desc + } + + enum SortMode { + min, max + } + + enum SortMissing { + _last, _first + } } 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 b2da38a7a..ee5e2af3a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -29,17 +29,16 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; 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; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasData; import org.springframework.data.elasticsearch.core.index.MappingBuilder; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.AliasQuery; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Base implementation of {@link IndexOperations} common to Transport and Rest based Implementations of IndexOperations. @@ -90,33 +89,20 @@ protected Class checkForBoundClass() { @Override public boolean create() { - Document settings = null; - - if (boundClass != null) { - settings = createSettings(boundClass); - } - + Settings settings = boundClass != null ? createSettings(boundClass) : new Settings(); return doCreate(getIndexCoordinates(), settings, null); } @Override - public Document createSettings(Class clazz) { + public Settings createSettings(Class clazz) { Assert.notNull(clazz, "clazz must not be null"); - Document settings = null; - - Setting setting = AnnotatedElementUtils.findMergedAnnotation(clazz, Setting.class); - - if (setting != null) { - settings = loadSettings(setting.settingPath()); - } - - if (settings == null) { - settings = getRequiredPersistentEntity(clazz).getDefaultSettings(); - } - - return settings; + ElasticsearchPersistentEntity persistentEntity = getRequiredPersistentEntity(clazz); + String settingPath = persistentEntity.settingPath(); + return hasText(settingPath) // + ? Settings.parse(ResourceUtil.readFileFromClasspath(settingPath)) // + : persistentEntity.getDefaultSettings(); } @Override @@ -125,16 +111,23 @@ public boolean createWithMapping() { } @Override - public boolean create(Document settings) { + public boolean create(Map settings) { + + Assert.notNull(settings, "settings must not be null"); + return doCreate(getIndexCoordinates(), settings, null); } @Override - public boolean create(Document settings, Document mapping) { + public boolean create(Map settings, Document mapping) { + + Assert.notNull(settings, "settings must not be null"); + Assert.notNull(mapping, "mapping must not be null"); + return doCreate(getIndexCoordinates(), settings, mapping); } - protected abstract boolean doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping); + protected abstract boolean doCreate(IndexCoordinates index, Map settings, @Nullable Document mapping); @Override public boolean delete() { @@ -165,16 +158,16 @@ public Map getMapping() { abstract protected Map doGetMapping(IndexCoordinates index); @Override - public Map getSettings() { + public Settings getSettings() { return getSettings(false); } @Override - public Map getSettings(boolean includeDefaults) { + public Settings getSettings(boolean includeDefaults) { return doGetSettings(getIndexCoordinates(), includeDefaults); } - protected abstract Map doGetSettings(IndexCoordinates index, boolean includeDefaults); + protected abstract Settings doGetSettings(IndexCoordinates index, boolean includeDefaults); @Override public void refresh() { @@ -244,10 +237,10 @@ protected Document buildMapping(Class clazz) { if (mappingAnnotation != null) { String mappingPath = mappingAnnotation.mappingPath(); - if (StringUtils.hasText(mappingPath)) { + if (hasText(mappingPath)) { String mappings = ResourceUtil.readFileFromClasspath(mappingPath); - if (StringUtils.hasText(mappings)) { + if (hasText(mappings)) { return Document.parse(mappings); } } else { @@ -265,7 +258,7 @@ protected Document buildMapping(Class clazz) { } @Override - public Document createSettings() { + public Settings createSettings() { return createSettings(checkForBoundClass()); } @@ -284,19 +277,5 @@ public IndexCoordinates getIndexCoordinates() { public IndexCoordinates getIndexCoordinatesFor(Class clazz) { return getRequiredPersistentEntity(clazz).getIndexCoordinates(); } - - @Nullable - private Document loadSettings(String settingPath) { - if (hasText(settingPath)) { - String settingsFile = ResourceUtil.readFileFromClasspath(settingPath); - - if (hasText(settingsFile)) { - return Document.parse(settingsFile); - } - } else { - LOGGER.info("settingPath in @Setting has to be defined. Using default instead."); - } - return null; - } // 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 3355aab4a..ae0121243 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java @@ -50,6 +50,7 @@ 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.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.AliasQuery; @@ -81,7 +82,7 @@ public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoord } @Override - protected boolean doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { + protected boolean doCreate(IndexCoordinates index, Map settings, @Nullable Document mapping) { CreateIndexRequest request = requestFactory.createIndexRequest(index, settings, mapping); return restTemplate.execute(client -> client.indices().create(request, RequestOptions.DEFAULT).isAcknowledged()); } @@ -192,7 +193,7 @@ public boolean alias(AliasActions aliasActions) { } @Override - protected Map doGetSettings(IndexCoordinates index, boolean includeDefaults) { + protected Settings doGetSettings(IndexCoordinates index, boolean includeDefaults) { Assert.notNull(index, "index must not be null"); 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 50cad41f7..a1aae3f59 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -42,7 +42,6 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.NoSuchIndexException; import org.springframework.data.elasticsearch.annotations.Mapping; -import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasActions; @@ -52,6 +51,7 @@ 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.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -106,7 +106,7 @@ public Mono create() { if (boundClass != null) { return createSettings(boundClass).flatMap(settings -> doCreate(index, settings, null)); } else { - return doCreate(index, null, null); + return doCreate(index, new Settings(), null); } } @@ -119,16 +119,23 @@ public Mono createWithMapping() { } @Override - public Mono create(Document settings) { + public Mono create(Map settings) { + + Assert.notNull(settings, "settings must not be null"); + return doCreate(getIndexCoordinates(), settings, null); } @Override - public Mono create(Document settings, Document mapping) { - throw new UnsupportedOperationException("not implemented"); + public Mono create(Map settings, Document mapping) { + + Assert.notNull(settings, "settings must not be null"); + Assert.notNull(mapping, "mapping must not be null"); + + return doCreate(getIndexCoordinates(), settings, mapping); } - private Mono doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { + private Mono doCreate(IndexCoordinates index, Map settings, @Nullable Document mapping) { CreateIndexRequest request = requestFactory.createIndexRequest(index, settings, mapping); return Mono.from(operations.executeWithIndicesClient(client -> client.createIndex(request))); @@ -208,24 +215,25 @@ public Mono getMapping() { // region settings @Override - public Mono createSettings() { + public Mono createSettings() { return createSettings(checkForBoundClass()); } @Override - public Mono createSettings(Class clazz) { + public Mono createSettings(Class clazz) { - Setting setting = AnnotatedElementUtils.findMergedAnnotation(clazz, Setting.class); - - if (setting != null) { - return loadDocument(setting.settingPath(), "@Setting"); - } + Assert.notNull(clazz, "clazz must not be null"); - return Mono.just(getRequiredPersistentEntity(clazz).getDefaultSettings()); + ElasticsearchPersistentEntity persistentEntity = getRequiredPersistentEntity(clazz); + String settingPath = persistentEntity.settingPath(); + return hasText(settingPath) // + ? loadDocument(settingPath, "@Setting") // + .map(Settings::new) // + : Mono.just(persistentEntity.getDefaultSettings()); } @Override - public Mono getSettings(boolean includeDefaults) { + public Mono getSettings(boolean includeDefaults) { String indexName = getIndexCoordinates().getIndexName(); GetSettingsRequest request = requestFactory.getSettingsRequest(indexName, includeDefaults); 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 df49ac0b7..a13924666 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java @@ -46,7 +46,6 @@ import org.elasticsearch.cluster.metadata.MappingMetadata; 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; @@ -57,6 +56,7 @@ 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.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.AliasQuery; @@ -90,7 +90,7 @@ public DefaultTransportIndexOperations(Client client, ElasticsearchConverter ela } @Override - protected boolean doCreate(IndexCoordinates index, @Nullable Document settings, @Nullable Document mapping) { + protected boolean doCreate(IndexCoordinates index, Map settings, @Nullable Document mapping) { CreateIndexRequestBuilder createIndexRequestBuilder = requestFactory.createIndexRequestBuilder(client, index, settings, mapping); return createIndexRequestBuilder.execute().actionGet().isAcknowledged(); @@ -193,7 +193,7 @@ public boolean alias(AliasActions aliasActions) { } @Override - protected Map doGetSettings(IndexCoordinates index, boolean includeDefaults) { + protected Settings doGetSettings(IndexCoordinates index, boolean includeDefaults) { Assert.notNull(index, "index must not be null"); @@ -238,8 +238,8 @@ public TemplateData getTemplate(GetTemplateRequest getTemplateRequest) { if (indexTemplateMetadata.getName().equals(getTemplateRequest.getTemplateName())) { - Document settings = Document.create(); - Settings templateSettings = indexTemplateMetadata.settings(); + Settings settings = new Settings(); + org.elasticsearch.common.settings.Settings templateSettings = indexTemplateMetadata.settings(); templateSettings.keySet().forEach(key -> settings.put(key, templateSettings.get(key))); Map aliases = new LinkedHashMap<>(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexInformation.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexInformation.java index f10c824bc..1dca1d323 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexInformation.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexInformation.java @@ -20,6 +20,7 @@ import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.lang.Nullable; /** @@ -31,16 +32,16 @@ */ public class IndexInformation { private final String name; - @Nullable private final Document settings; + @Nullable private final Settings settings; @Nullable private final Document mapping; @Nullable private final List aliases; - public static IndexInformation of(String name, @Nullable Document settings, @Nullable Document mapping, + public static IndexInformation of(String name, @Nullable Settings settings, @Nullable Document mapping, @Nullable List aliases) { return new IndexInformation(name, settings, mapping, aliases); } - private IndexInformation(String name, @Nullable Document settings, @Nullable Document mapping, + private IndexInformation(String name, @Nullable Settings settings, @Nullable Document mapping, @Nullable List aliases) { this.name = name; this.settings = settings; @@ -58,7 +59,7 @@ public Document getMapping() { } @Nullable - public Document getSettings() { + public Settings getSettings() { return settings; } 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 8d28bb2db..cb1f39f19 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -27,6 +27,7 @@ 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.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.AliasQuery; @@ -60,7 +61,7 @@ public interface IndexOperations { * @param settings the index settings * @return {@literal true} if the index was created */ - boolean create(Document settings); + boolean create(Map settings); /** * Create an index for given settings and mapping. @@ -70,7 +71,7 @@ public interface IndexOperations { * @return {@literal true} if the index was created * @since 4.2 */ - boolean create(Document settings, Document mapping); + boolean create(Map settings, Document mapping); /** * Create an index with the settings and mapping defined for the entity this IndexOperations is bound to. @@ -161,7 +162,7 @@ default boolean putMapping(Class clazz) { * @return a settings document. * @since 4.1 */ - Document createSettings(); + Settings createSettings(); /** * Creates the index settings from the annotations on the given class @@ -170,14 +171,14 @@ default boolean putMapping(Class clazz) { * @return a settings document. * @since 4.1 */ - Document createSettings(Class clazz); + Settings createSettings(Class clazz); /** * Get the index settings. * * @return the settings */ - Map getSettings(); + Settings getSettings(); /** * Get the index settings. @@ -185,7 +186,7 @@ default boolean putMapping(Class clazz) { * @param includeDefaults whether or not to include all the default settings * @return the settings */ - Map getSettings(boolean includeDefaults); + Settings getSettings(boolean includeDefaults); // endregion // region aliases 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 684756203..7b4afbc67 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperations.java @@ -28,6 +28,7 @@ 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.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -56,7 +57,7 @@ public interface ReactiveIndexOperations { * @return a {@link Mono} signalling successful operation completion or an {@link Mono#error(Throwable) error} if eg. * the index already exist. */ - Mono create(Document settings); + Mono create(Map settings); /** * Create an index for given settings and mapping. @@ -67,7 +68,7 @@ public interface ReactiveIndexOperations { * the index already exist. * @since 4.2 */ - Mono create(Document settings, Document mapping); + Mono create(Map settings, Document mapping); /** * Create an index with the settings and mapping defined for the entity this IndexOperations is bound to. @@ -159,7 +160,7 @@ default Mono putMapping(Class clazz) { * @return a settings document. * @since 4.1 */ - Mono createSettings(); + Mono createSettings(); /** * Creates the index settings from the annotations on the given class @@ -168,14 +169,14 @@ default Mono putMapping(Class clazz) { * @return a settings document. * @since 4.1 */ - Mono createSettings(Class clazz); + Mono createSettings(Class clazz); /** * get the settings for the index * * @return a {@link Mono} with a {@link Document} containing the index settings */ - default Mono getSettings() { + default Mono getSettings() { return getSettings(false); } @@ -185,7 +186,7 @@ default Mono getSettings() { * @param includeDefaults whether or not to include all the default settings * @return a {@link Mono} with a {@link Document} containing the index settings */ - Mono getSettings(boolean includeDefaults); + Mono getSettings(boolean includeDefaults); // endregion // region aliases diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java index 9a373e101..1598b17fe 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java @@ -27,6 +27,8 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.data.elasticsearch.ResourceFailureException; +import org.springframework.util.Assert; /** * Utility to reactively read {@link org.springframework.core.io.Resource}s. @@ -47,6 +49,8 @@ public abstract class ReactiveResourceUtil { */ public static Mono readFileFromClasspath(String url) { + Assert.notNull(url, "url must not be null"); + return DataBufferUtils .join(DataBufferUtils.read(new ClassPathResource(url), new DefaultDataBufferFactory(), BUFFER_SIZE)) . handle((it, sink) -> { @@ -65,15 +69,12 @@ public static Mono readFileFromClasspath(String url) { sink.next(sb.toString()); sink.complete(); } catch (Exception e) { - LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, e.getMessage())); sink.complete(); } finally { DataBufferUtils.release(it); } - }).onErrorResume(throwable -> { - LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, throwable.getMessage())); - return Mono.empty(); - }); + }).onErrorResume( + throwable -> Mono.error(new ResourceFailureException("Could not load resource from " + url, throwable))); } // Utility constructor 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 f8c3145d0..ca2ca2314 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -355,11 +355,14 @@ public BulkRequestBuilder bulkRequestBuilder(Client client, List queries, Bul // region index management - public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable Document settings, - @Nullable Document mapping) { + public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map settings, @Nullable Document mapping) { + + Assert.notNull(index, "index must not be null"); + Assert.notNull(settings, "settings must not be null"); + CreateIndexRequest request = new CreateIndexRequest(index.getIndexName()); - if (settings != null && !settings.isEmpty()) { + if (!settings.isEmpty()) { request.settings(settings); } @@ -371,13 +374,15 @@ public CreateIndexRequest createIndexRequest(IndexCoordinates index, @Nullable D } public CreateIndexRequestBuilder createIndexRequestBuilder(Client client, IndexCoordinates index, - @Nullable Document settings, @Nullable Document mapping) { + Map settings, @Nullable Document mapping) { + + Assert.notNull(index, "index must not be null"); + Assert.notNull(settings, "settings must not be null"); String indexName = index.getIndexName(); CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(indexName); - if (settings != null && !settings.isEmpty()) { - + if (!settings.isEmpty()) { createIndexRequestBuilder.setSettings(settings); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResourceUtil.java b/src/main/java/org/springframework/data/elasticsearch/core/ResourceUtil.java index 92eeb27e8..9b92ed5bb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResourceUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResourceUtil.java @@ -18,10 +18,9 @@ import java.io.InputStream; import java.nio.charset.Charset; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; -import org.springframework.lang.Nullable; +import org.springframework.data.elasticsearch.ResourceFailureException; +import org.springframework.util.Assert; import org.springframework.util.StreamUtils; /** @@ -33,23 +32,20 @@ */ public abstract class ResourceUtil { - private static final Logger LOGGER = LoggerFactory.getLogger(ResourceUtil.class); - /** * Read a {@link ClassPathResource} into a {@link String}. * - * @param url url the file url - * @return the contents of the file or null if it could not be read + * @param url url the resource + * @return the contents of the resource */ - @Nullable public static String readFileFromClasspath(String url) { - ClassPathResource classPathResource = new ClassPathResource(url); - try (InputStream is = classPathResource.getInputStream()) { + Assert.notNull(url, "url must not be null"); + + try (InputStream is = new ClassPathResource(url).getInputStream()) { return StreamUtils.copyToString(is, Charset.defaultCharset()); } catch (Exception e) { - LOGGER.warn(String.format("Failed to load file from url: %s: %s", url, e.getMessage())); - return null; + throw new ResourceFailureException("Could not load resource from " + url, e); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java index c2328aad2..546109602 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java @@ -36,9 +36,9 @@ import org.elasticsearch.cluster.metadata.MappingMetadata; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.compress.CompressedXContent; -import org.elasticsearch.common.settings.Settings; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasData; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -91,7 +91,7 @@ public static List getIndexInformations(GetIndexResponse getIn List indexInformationList = new ArrayList<>(); for (String indexName : getIndexResponse.getIndices()) { - Document settings = settingsFromGetIndexResponse(getIndexResponse, indexName); + Settings settings = settingsFromGetIndexResponse(getIndexResponse, indexName); Document mappings = mappingsFromGetIndexResponse(getIndexResponse, indexName); List aliases = aliasDataFromIndexResponse(getIndexResponse, indexName); @@ -108,18 +108,18 @@ public static List getIndexInformations(GetIndexResponse getIn * @param indexName the index name * @return a document that represents {@link Settings} */ - private static Document settingsFromGetIndexResponse(GetIndexResponse getIndexResponse, String indexName) { - Document document = Document.create(); + private static Settings settingsFromGetIndexResponse(GetIndexResponse getIndexResponse, String indexName) { + Settings settings= new Settings(); - Settings indexSettings = getIndexResponse.getSettings().get(indexName); + org.elasticsearch.common.settings.Settings indexSettings = getIndexResponse.getSettings().get(indexName); if (!indexSettings.isEmpty()) { for (String key : indexSettings.keySet()) { - document.put(key, indexSettings.get(key)); + settings.put(key, indexSettings.get(key)); } } - return document; + return settings; } /** @@ -162,7 +162,7 @@ public static List getIndexInformations( List indexInformationList = new ArrayList<>(); for (String indexName : getIndexResponse.getIndices()) { - Document settings = settingsFromGetIndexResponse(getIndexResponse, indexName); + Settings settings = settingsFromGetIndexResponse(getIndexResponse, indexName); Document mappings = mappingsFromGetIndexResponse(getIndexResponse, indexName); List aliases = aliasDataFromIndexResponse(getIndexResponse, indexName); @@ -172,19 +172,20 @@ public static List getIndexInformations( return indexInformationList; } - private static Document settingsFromGetIndexResponse( + private static Settings settingsFromGetIndexResponse( org.elasticsearch.action.admin.indices.get.GetIndexResponse getIndexResponse, String indexName) { - Document document = Document.create(); + + Settings settings = new Settings(); if (getIndexResponse.getSettings().containsKey(indexName)) { - Settings indexSettings = getIndexResponse.getSettings().get(indexName); + org.elasticsearch.common.settings.Settings indexSettings = getIndexResponse.getSettings().get(indexName); for (String key : indexSettings.keySet()) { - document.put(key, indexSettings.get(key)); + settings.put(key, indexSettings.get(key)); } } - return document; + return settings; } private static Document mappingsFromGetIndexResponse( @@ -222,8 +223,8 @@ public static TemplateData getTemplateData(GetIndexTemplatesResponse getIndexTem if (indexTemplateMetadata.name().equals(templateName)) { - Document settings = Document.create(); - Settings templateSettings = indexTemplateMetadata.settings(); + Settings settings = new Settings(); + org.elasticsearch.common.settings.Settings templateSettings = indexTemplateMetadata.settings(); templateSettings.keySet().forEach(key -> settings.put(key, templateSettings.get(key))); Map aliases = new LinkedHashMap<>(); @@ -253,21 +254,21 @@ public static TemplateData getTemplateData(GetIndexTemplatesResponse getIndexTem * * @param response the Elasticsearch response * @param indexName the index name - * @return settings as {@link Document} + * @return settings */ - public static Document fromSettingsResponse(GetSettingsResponse response, String indexName) { + public static Settings fromSettingsResponse(GetSettingsResponse response, String indexName) { - Document settings = Document.create(); + Settings settings = new Settings(); if (!response.getIndexToDefaultSettings().isEmpty()) { - Settings defaultSettings = response.getIndexToDefaultSettings().get(indexName); + org.elasticsearch.common.settings.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); + org.elasticsearch.common.settings.Settings customSettings = response.getIndexToSettings().get(indexName); for (String key : customSettings.keySet()) { settings.put(key, customSettings.get(key)); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java index 08dccaf6a..25af888b6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java @@ -25,11 +25,12 @@ import java.util.function.Supplier; import org.springframework.data.elasticsearch.core.convert.ConversionException; +import org.springframework.data.elasticsearch.support.StringObjectMap; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * A representation of a Elasticsearch document as extended {@link Map} with {@link String} keys. All iterators preserve + * A representation of a Elasticsearch document as extended {@link StringObjectMap Map}. All iterators preserve * original insertion order. *

* Document does not allow {@code null} keys. It allows {@literal null} values. @@ -42,7 +43,7 @@ * @author Roman Puchkovskiy * @since 4.0 */ -public interface Document extends Map { +public interface Document extends StringObjectMap { /** * Create a new mutable {@link Document}. @@ -80,25 +81,19 @@ static Document parse(String json) { Assert.notNull(json, "JSON must not be null"); + return new MapDocument().fromJson(json); + } + + @Override + default Document fromJson(String json) { + Assert.notNull(json, "JSON must not be null"); + + clear(); try { - return new MapDocument(MapDocument.OBJECT_MAPPER.readerFor(Map.class).readValue(json)); + putAll(MapDocument.OBJECT_MAPPER.readerFor(Map.class).readValue(json)); } catch (IOException e) { throw new ConversionException("Cannot parse JSON", e); } - } - - /** - * {@link #put(Object, Object)} the {@code key}/{@code value} tuple and return {@code this} {@link Document}. - * - * @param key key with which the specified value is to be associated. must not be {@literal null}. - * @param value value to be associated with the specified key. - * @return {@code this} {@link Document}. - */ - default Document append(String key, Object value) { - - Assert.notNull(key, "Key must not be null"); - - put(key, value); return this; } @@ -122,7 +117,7 @@ default String getIndex() { /** * Sets the index name for this document - * + * * @param index index name *

* The default implementation throws {@link UnsupportedOperationException}. @@ -251,212 +246,6 @@ default void setPrimaryTerm(long primaryTerm) { throw new UnsupportedOperationException(); } - /** - * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no - * mapping for the key. The value is casted within the method which makes it useful for calling code as it does not - * require casting on the calling side. If the value type is not assignable to {@code type}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @param type the expected return value type. - * @param expected return type. - * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for - * the key. - * @throws ClassCastException if the value of the given key is not of {@code type T}. - */ - @Nullable - default T get(Object key, Class type) { - - Assert.notNull(key, "Key must not be null"); - Assert.notNull(type, "Type must not be null"); - - return type.cast(get(key)); - } - - /** - * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no - * mapping for the key. If the value type is not a {@link Boolean}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for - * the key. - * @throws ClassCastException if the value of the given key is not a {@link Boolean}. - */ - @Nullable - default Boolean getBoolean(String key) { - return get(key, Boolean.class); - } - - /** - * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no - * mapping for the key. If the value type is not a {@link Boolean}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping - * for the key. - * @throws ClassCastException if the value of the given key is not a {@link Boolean}. - */ - default boolean getBooleanOrDefault(String key, boolean defaultValue) { - return getBooleanOrDefault(key, () -> defaultValue); - } - - /** - * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this - * document contains no mapping for the key. If the value type is not a {@link Boolean}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document - * contains no mapping for the key. - * @throws ClassCastException if the value of the given key is not a {@link Boolean}. - * @see BooleanSupplier - */ - default boolean getBooleanOrDefault(String key, BooleanSupplier defaultValue) { - - Boolean value = getBoolean(key); - - return value == null ? defaultValue.getAsBoolean() : value; - } - - /** - * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no - * mapping for the key. If the value type is not a {@link Integer}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for - * the key. - * @throws ClassCastException if the value of the given key is not a {@link Integer}. - */ - @Nullable - default Integer getInt(String key) { - return get(key, Integer.class); - } - - /** - * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no - * mapping for the key. If the value type is not a {@link Integer}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping - * for the key. - * @throws ClassCastException if the value of the given key is not a {@link Integer}. - */ - default int getIntOrDefault(String key, int defaultValue) { - return getIntOrDefault(key, () -> defaultValue); - } - - /** - * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this - * document contains no mapping for the key. If the value type is not a {@link Integer}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document - * contains no mapping for the key. - * @throws ClassCastException if the value of the given key is not a {@link Integer}. - * @see IntSupplier - */ - default int getIntOrDefault(String key, IntSupplier defaultValue) { - - Integer value = getInt(key); - - return value == null ? defaultValue.getAsInt() : value; - } - - /** - * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no - * mapping for the key. If the value type is not a {@link Long}, then this method throws {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for - * the key. - * @throws ClassCastException if the value of the given key is not a {@link Long}. - */ - @Nullable - default Long getLong(String key) { - return get(key, Long.class); - } - - /** - * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no - * mapping for the key. If the value type is not a {@link Long}, then this method throws {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping - * for the key. - * @throws ClassCastException if the value of the given key is not a {@link Long}. - */ - default long getLongOrDefault(String key, long defaultValue) { - return getLongOrDefault(key, () -> defaultValue); - } - - /** - * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this - * document contains no mapping for the key. If the value type is not a {@link Long}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document - * contains no mapping for the key. - * @throws ClassCastException if the value of the given key is not a {@link Long}. - * @see LongSupplier - */ - default long getLongOrDefault(String key, LongSupplier defaultValue) { - - Long value = getLong(key); - - return value == null ? defaultValue.getAsLong() : value; - } - - /** - * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no - * mapping for the key. If the value type is not a {@link String}, then this method throws {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for - * the key. - * @throws ClassCastException if the value of the given key is not a {@link String}. - */ - @Nullable - default String getString(String key) { - return get(key, String.class); - } - - /** - * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no - * mapping for the key. If the value type is not a {@link String}, then this method throws {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping - * for the key. - * @throws ClassCastException if the value of the given key is not a {@link String}. - */ - default String getStringOrDefault(String key, String defaultValue) { - return getStringOrDefault(key, () -> defaultValue); - } - - /** - * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this - * document contains no mapping for the key. If the value type is not a {@link String}, then this method throws - * {@link ClassCastException}. - * - * @param key the key whose associated value is to be returned - * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document - * contains no mapping for the key. - * @throws ClassCastException if the value of the given key is not a {@link String}. - * @see Supplier - */ - default String getStringOrDefault(String key, Supplier defaultValue) { - - String value = getString(key); - - return value == null ? defaultValue.get() : value; - } - /** * This method allows the application of a function to {@code this} {@link Document}. The function should expect a * single {@link Document} argument and produce an {@code R} result. @@ -474,13 +263,4 @@ default R transform(Function transformer) { return transformer.apply(this); } - - /** - * Render this {@link Document} to JSON. Auxiliary values such as Id and version are not considered within the JSON - * representation. - * - * @return a JSON representation of this document. - */ - String toJson(); - } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index 11b07c774..a60be02a0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -458,6 +458,7 @@ public String toJson() { } } + @Override public String toString() { return getClass().getSimpleName() + '@' + this.id + '#' + this.version + ' ' + toJson(); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java b/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java index 12597eb39..847066717 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java @@ -21,6 +21,8 @@ import java.util.Set; import java.util.function.BiConsumer; +import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; +import org.springframework.data.elasticsearch.support.StringObjectMap; import org.springframework.data.mapping.MappingException; import org.springframework.lang.Nullable; @@ -38,7 +40,7 @@ class MapDocument implements Document { static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private final LinkedHashMap documentAsMap; + private final DefaultStringObjectMap documentAsMap; private @Nullable String index; private @Nullable String id; @@ -50,8 +52,8 @@ class MapDocument implements Document { this(new LinkedHashMap<>()); } - MapDocument(Map documentAsMap) { - this.documentAsMap = new LinkedHashMap<>(documentAsMap); + MapDocument(Map documentAsMap) { + this.documentAsMap = new DefaultStringObjectMap<>(documentAsMap); } @Override 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 index 36a6a736d..c3c14aaeb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/PutTemplateRequest.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/PutTemplateRequest.java @@ -19,6 +19,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import java.util.Map; + /** * 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) @@ -29,13 +31,13 @@ public class PutTemplateRequest { private final String name; private final String[] indexPatterns; - @Nullable final private Document settings; + @Nullable final private Settings 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, + private PutTemplateRequest(String name, String[] indexPatterns, @Nullable Settings settings, @Nullable Document mappings, @Nullable AliasActions aliasActions, int order, @Nullable Integer version) { this.name = name; this.indexPatterns = indexPatterns; @@ -55,7 +57,7 @@ public String[] getIndexPatterns() { } @Nullable - public Document getSettings() { + public Settings getSettings() { return settings; } @@ -85,7 +87,7 @@ public static TemplateRequestBuilder builder(String name, String... indexPattern public static final class TemplateRequestBuilder { private final String name; private final String[] indexPatterns; - @Nullable private Document settings; + @Nullable private Settings settings; @Nullable private Document mappings; @Nullable AliasActions aliasActions; private int order = 0; @@ -100,8 +102,8 @@ private TemplateRequestBuilder(String name, String... indexPatterns) { this.indexPatterns = indexPatterns; } - public TemplateRequestBuilder withSettings(Document settings) { - this.settings = settings; + public TemplateRequestBuilder withSettings(Map settings) { + this.settings = new Settings(settings); return this; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java b/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java new file mode 100644 index 000000000..77b005175 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/Settings.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.index; + +import org.springframework.data.elasticsearch.support.DefaultStringObjectMap; + +import java.util.Map; + +/** + * class defining the settings for an index. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +public class Settings extends DefaultStringObjectMap { + + public Settings() { + } + + public Settings(Map map) { + super(map); + } + + /** + * Creates a {@link Settings} object from the given JSON String + * @param json must not be {@literal null} + * @return Settings object + */ + public static Settings parse(String json) { + return new Settings().fromJson(json); + } + + @Override + public String toString() { + return "Settings: " + toJson(); + } +} 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 index 12809e2b1..98bfadcdc 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/TemplateData.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/TemplateData.java @@ -27,13 +27,13 @@ */ public class TemplateData { @Nullable private final String[] indexPatterns; - @Nullable Document settings; + @Nullable Settings 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, + private TemplateData(@Nullable String[] indexPatterns, @Nullable Settings settings, @Nullable Document mapping, @Nullable Map aliases, int order, @Nullable Integer version) { this.indexPatterns = indexPatterns; this.settings = settings; @@ -53,7 +53,7 @@ public String[] getIndexPatterns() { } @Nullable - public Document getSettings() { + public Settings getSettings() { return settings; } @@ -77,7 +77,7 @@ public Integer getVersion() { } public static final class TemplateDataBuilder { - @Nullable Document settings; + @Nullable Settings settings; @Nullable Document mapping; int order; @Nullable Integer version; @@ -91,8 +91,8 @@ public TemplateDataBuilder withIndexPatterns(String[] indexPatterns) { return this; } - public TemplateDataBuilder withSettings(Document settings) { - this.settings = settings; + public TemplateDataBuilder withSettings(Map settings) { + this.settings = new Settings(settings); return this; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 6dc85e57c..28550d508 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -18,6 +18,7 @@ import org.elasticsearch.index.VersionType; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.join.JoinField; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.mapping.PersistentEntity; @@ -148,14 +149,14 @@ default ElasticsearchPersistentProperty getRequiredSeqNoPrimaryTermProperty() { /** * returns the default settings for an index. * - * @return settings as {@link Document} + * @return settings * @since 4.1 */ - Document getDefaultSettings(); + Settings getDefaultSettings(); /** * Resolves the routing for a bean. - * + * * @param bean the bean to resolve the routing for * @return routing value, may be {@literal null} */ diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index bf277e6a9..345d83819 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -19,16 +19,18 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; -import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.index.VersionType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; +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.annotations.Parent; import org.springframework.data.elasticsearch.annotations.Routing; import org.springframework.data.elasticsearch.annotations.Setting; -import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.join.JoinField; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PropertyHandler; @@ -66,16 +68,11 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit private static final SpelExpressionParser PARSER = new SpelExpressionParser(); private @Nullable String indexName; - private boolean useServerConfiguration; - private short shards; - private short replicas; - private @Nullable String refreshInterval; - private @Nullable String indexStoreType; + private final Lazy settingsParameter; @Deprecated private @Nullable String parentType; @Deprecated private @Nullable ElasticsearchPersistentProperty parentIdProperty; private @Nullable ElasticsearchPersistentProperty seqNoPrimaryTermProperty; private @Nullable ElasticsearchPersistentProperty joinFieldProperty; - private @Nullable String settingPath; private @Nullable VersionType versionType; private boolean createIndexAndMapping; private final Map fieldNamePropertyCache = new ConcurrentHashMap<>(); @@ -90,27 +87,20 @@ public SimpleElasticsearchPersistentEntity(TypeInformation typeInformation) { super(typeInformation); Class clazz = typeInformation.getType(); + org.springframework.data.elasticsearch.annotations.Document document = AnnotatedElementUtils .findMergedAnnotation(clazz, org.springframework.data.elasticsearch.annotations.Document.class); + // need a Lazy here, because we need the persistent properties available + this.settingsParameter = Lazy.of(() -> buildSettingsParameter(clazz)); + if (document != null) { Assert.hasText(document.indexName(), " Unknown indexName. Make sure the indexName is defined. e.g @Document(indexName=\"foo\")"); this.indexName = document.indexName(); - this.useServerConfiguration = document.useServerConfiguration(); - this.shards = document.shards(); - this.replicas = document.replicas(); - this.refreshInterval = document.refreshInterval(); - this.indexStoreType = document.indexStoreType(); this.versionType = document.versionType(); this.createIndexAndMapping = document.createIndex(); - - Setting setting = AnnotatedElementUtils.getMergedAnnotation(clazz, Setting.class); - - if (setting != null) { - this.settingPath = setting.settingPath(); - } } Routing routingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Routing.class); @@ -135,28 +125,28 @@ public IndexCoordinates getIndexCoordinates() { @Nullable @Override public String getIndexStoreType() { - return indexStoreType; + return settingsParameter.get().indexStoreType; } @Override public short getShards() { - return shards; + return settingsParameter.get().shards; } @Override public short getReplicas() { - return replicas; + return settingsParameter.get().replicas; } @Override public boolean isUseServerConfiguration() { - return useServerConfiguration; + return settingsParameter.get().useServerConfiguration; } @Nullable @Override public String getRefreshInterval() { - return refreshInterval; + return settingsParameter.get().refreshIntervall; } @Nullable @@ -179,11 +169,6 @@ public VersionType getVersionType() { return versionType; } - @Override - public String settingPath() { - return settingPath; - } - @Override public boolean isCreateIndexAndMapping() { return createIndexAndMapping; @@ -207,7 +192,10 @@ public void addPersistentProperty(ElasticsearchPersistentProperty property) { Parent parentAnnotation = property.findAnnotation(Parent.class); this.parentIdProperty = property; - this.parentType = parentAnnotation.type(); + + if (parentAnnotation != null) { + this.parentType = parentAnnotation.type(); + } } if (property.isSeqNoPrimaryTermProperty()) { @@ -407,17 +395,167 @@ public String resolveRouting(T bean) { // endregion + // region index settings + @Override + public String settingPath() { + return settingsParameter.get().settingPath; + } + @Override - public Document getDefaultSettings() { + public Settings getDefaultSettings() { + return settingsParameter.get().toSettings(); // + } + + private SettingsParameter buildSettingsParameter(Class clazz) { + + SettingsParameter settingsParameter = new SettingsParameter(); + Document documentAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Document.class); + Setting settingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Setting.class); + + if (documentAnnotation != null) { + settingsParameter.useServerConfiguration = documentAnnotation.useServerConfiguration(); + settingsParameter.shards = documentAnnotation.shards(); + settingsParameter.replicas = documentAnnotation.replicas(); + settingsParameter.refreshIntervall = documentAnnotation.refreshInterval(); + settingsParameter.indexStoreType = documentAnnotation.indexStoreType(); + } + + if (settingAnnotation != null) { + processSettingAnnotation(settingAnnotation, settingsParameter); + } + + return settingsParameter; + } + + private void processSettingAnnotation(Setting settingAnnotation, SettingsParameter settingsParameter) { + settingsParameter.useServerConfiguration = settingAnnotation.useServerConfiguration(); + settingsParameter.settingPath = settingAnnotation.settingPath(); + settingsParameter.shards = settingAnnotation.shards(); + settingsParameter.replicas = settingAnnotation.replicas(); + settingsParameter.refreshIntervall = settingAnnotation.refreshInterval(); + settingsParameter.indexStoreType = settingAnnotation.indexStoreType(); + + String[] sortFields = settingAnnotation.sortFields(); + + if (sortFields.length > 0) { + String[] fieldNames = new String[sortFields.length]; + int index = 0; + for (String propertyName : sortFields) { + ElasticsearchPersistentProperty property = getPersistentProperty(propertyName); + + if (property == null) { + throw new IllegalArgumentException("sortField property " + propertyName + " not found"); + } + Field fieldAnnotation = property.getRequiredAnnotation(Field.class); + + FieldType fieldType = fieldAnnotation.type(); + switch (fieldType) { + case Boolean: + case Long: + case Integer: + case Short: + case Byte: + case Float: + case Half_Float: + case Scaled_Float: + case Date: + case Date_Nanos: + case Keyword: + break; + default: + throw new IllegalArgumentException("field type " + fieldType + " not allowed for sortField"); + } + + if (!fieldAnnotation.docValues()) { + throw new IllegalArgumentException("doc_values must be set to true for sortField"); + } + fieldNames[index++] = property.getFieldName(); + } + settingsParameter.sortFields = fieldNames; - if (isUseServerConfiguration()) { - return Document.create(); + Setting.SortOrder[] sortOrders = settingAnnotation.sortOrders(); + if (sortOrders.length > 0) { + + if (sortOrders.length != sortFields.length) { + throw new IllegalArgumentException("@Settings parameter sortFields and sortOrders must have the same size"); + } + settingsParameter.sortOrders = sortOrders; + } + + Setting.SortMode[] sortModes = settingAnnotation.sortModes(); + if (sortModes.length > 0) { + + if (sortModes.length != sortFields.length) { + throw new IllegalArgumentException("@Settings parameter sortFields and sortModes must have the same size"); + } + settingsParameter.sortModes = sortModes; + } + + Setting.SortMissing[] sortMissingValues = settingAnnotation.sortMissingValues(); + if (sortMissingValues.length > 0) { + + if (sortMissingValues.length != sortFields.length) { + throw new IllegalArgumentException( + "@Settings parameter sortFields and sortMissingValues must have the same size"); + } + settingsParameter.sortMissingValues = sortMissingValues; + } } + } + + /** + * internal class to collect settings values from the {@link Document} and {@link Setting} annotations- + */ + private static class SettingsParameter { + boolean useServerConfiguration = false; + @Nullable String settingPath; + short shards; + short replicas; + @Nullable String refreshIntervall; + @Nullable String indexStoreType; + @Nullable private String[] sortFields; + @Nullable private Setting.SortOrder[] sortOrders; + @Nullable private Setting.SortMode[] sortModes; + @Nullable private Setting.SortMissing[] sortMissingValues; + + Settings toSettings() { + + if (useServerConfiguration) { + return new Settings(); + } + + Settings settings = new Settings() // + .append("index.number_of_shards", String.valueOf(shards)) + .append("index.number_of_replicas", String.valueOf(replicas)); - Map map = new MapBuilder() - .put("index.number_of_shards", String.valueOf(getShards())) - .put("index.number_of_replicas", String.valueOf(getReplicas())) - .put("index.refresh_interval", getRefreshInterval()).put("index.store.type", getIndexStoreType()).map(); - return Document.from(map); + if (refreshIntervall != null) { + settings.append("index.refresh_interval", refreshIntervall); + } + + if (indexStoreType != null) { + settings.append("index.store.type", indexStoreType); + } + + if (sortFields != null && sortFields.length > 0) { + settings.append("index.sort.field", sortFields); + + if (sortOrders != null && sortOrders.length > 0) { + settings.append("index.sort.order", sortOrders); + } + + if (sortModes != null && sortModes.length > 0) { + settings.append("index.sort.mode", sortModes); + } + + if (sortMissingValues != null && sortMissingValues.length > 0) { + settings.append("index.sort.missing", sortMissingValues); + } + } + + return settings; // + + } } + + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java b/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java new file mode 100644 index 000000000..f467d4763 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/support/DefaultStringObjectMap.java @@ -0,0 +1,153 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.support; + +import java.io.IOException; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.springframework.util.Assert; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * @author Peter-Josef Meisch + */ +public class DefaultStringObjectMap> implements StringObjectMap { + + static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final LinkedHashMap delegate; + + public DefaultStringObjectMap() { + this(new LinkedHashMap<>()); + } + + public DefaultStringObjectMap(Map map) { + this.delegate = new LinkedHashMap<>(map); + } + + @Override + public String toJson() { + try { + return OBJECT_MAPPER.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Cannot render document to JSON", e); + } + } + + @Override + public T fromJson(String json) { + + Assert.notNull(json, "JSON must not be null"); + + delegate.clear(); + try { + delegate.putAll(OBJECT_MAPPER.readerFor(Map.class).readValue(json)); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot parse JSON", e); + } + return (T) this; + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + @Override + public Object get(Object key) { + return delegate.get(key); + } + + @Override + public Object getOrDefault(Object key, Object defaultValue) { + return delegate.getOrDefault(key, defaultValue); + } + + @Override + public Object put(String key, Object value) { + return delegate.put(key, value); + } + + @Override + public Object remove(Object key) { + return delegate.remove(key); + } + + @Override + public void putAll(Map m) { + delegate.putAll(m); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Set keySet() { + return delegate.keySet(); + } + + @Override + public Collection values() { + return delegate.values(); + } + + @Override + public Set> entrySet() { + return delegate.entrySet(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + @Override + public void forEach(BiConsumer action) { + delegate.forEach(action); + } + + @Override + public String toString() { + return "DefaultStringObjectMap: " + toJson(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/support/StringObjectMap.java b/src/main/java/org/springframework/data/elasticsearch/support/StringObjectMap.java new file mode 100644 index 000000000..0636369d4 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/support/StringObjectMap.java @@ -0,0 +1,278 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.support; + +import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.IntSupplier; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * Defines an interface for a Map<String, Object> with additional convenience methods. All iterators must preserve + * original insertion order. + *

+ * StringObjectMap does not allow {@code null} keys. It allows {@literal null} values. + *

+ * Implementing classes can bei either mutable or immutable. In case a subclass is immutable, its methods may throw + * {@link UnsupportedOperationException} when calling modifying methods. * + * + * @param the type extending this interface + * @author Peter-Josef Meisch + * @since 4.2 + */ +public interface StringObjectMap> extends Map { + /** + * {@link #put(Object, Object)} the {@code key}/{@code value} tuple and return {@code this} object. + * + * @param key key with which the specified value is to be associated. must not be {@literal null}. + * @param value value to be associated with the specified key. + * @return {@code this} object. + */ + default M append(String key, Object value) { + + Assert.notNull(key, "Key must not be null"); + + put(key, value); + // noinspection unchecked + return (M) this; + } + + /** + * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no + * mapping for the key. The value is casted within the method which makes it useful for calling code as it does not + * require casting on the calling side. If the value type is not assignable to {@code type}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @param type the expected return value type. + * @param expected return type. + * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for + * the key. + * @throws ClassCastException if the value of the given key is not of {@code type T}. + */ + @Nullable + default T get(Object key, Class type) { + + Assert.notNull(key, "Key must not be null"); + Assert.notNull(type, "Type must not be null"); + + return type.cast(get(key)); + } + + /** + * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no + * mapping for the key. If the value type is not a {@link Boolean}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for + * the key. + * @throws ClassCastException if the value of the given key is not a {@link Boolean}. + */ + @Nullable + default Boolean getBoolean(String key) { + return get(key, Boolean.class); + } + + /** + * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no + * mapping for the key. If the value type is not a {@link Boolean}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping + * for the key. + * @throws ClassCastException if the value of the given key is not a {@link Boolean}. + */ + default boolean getBooleanOrDefault(String key, boolean defaultValue) { + return getBooleanOrDefault(key, () -> defaultValue); + } + + /** + * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this + * document contains no mapping for the key. If the value type is not a {@link Boolean}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document + * contains no mapping for the key. + * @throws ClassCastException if the value of the given key is not a {@link Boolean}. + * @see BooleanSupplier + */ + default boolean getBooleanOrDefault(String key, BooleanSupplier defaultValue) { + + Boolean value = getBoolean(key); + + return value == null ? defaultValue.getAsBoolean() : value; + } + + /** + * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no + * mapping for the key. If the value type is not a {@link Integer}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for + * the key. + * @throws ClassCastException if the value of the given key is not a {@link Integer}. + */ + @Nullable + default Integer getInt(String key) { + return get(key, Integer.class); + } + + /** + * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no + * mapping for the key. If the value type is not a {@link Integer}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping + * for the key. + * @throws ClassCastException if the value of the given key is not a {@link Integer}. + */ + default int getIntOrDefault(String key, int defaultValue) { + return getIntOrDefault(key, () -> defaultValue); + } + + /** + * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this + * document contains no mapping for the key. If the value type is not a {@link Integer}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document + * contains no mapping for the key. + * @throws ClassCastException if the value of the given key is not a {@link Integer}. + * @see IntSupplier + */ + default int getIntOrDefault(String key, IntSupplier defaultValue) { + + Integer value = getInt(key); + + return value == null ? defaultValue.getAsInt() : value; + } + + /** + * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no + * mapping for the key. If the value type is not a {@link Long}, then this method throws {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for + * the key. + * @throws ClassCastException if the value of the given key is not a {@link Long}. + */ + @Nullable + default Long getLong(String key) { + return get(key, Long.class); + } + + /** + * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no + * mapping for the key. If the value type is not a {@link Long}, then this method throws {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping + * for the key. + * @throws ClassCastException if the value of the given key is not a {@link Long}. + */ + default long getLongOrDefault(String key, long defaultValue) { + return getLongOrDefault(key, () -> defaultValue); + } + + /** + * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this + * document contains no mapping for the key. If the value type is not a {@link Long}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document + * contains no mapping for the key. + * @throws ClassCastException if the value of the given key is not a {@link Long}. + * @see LongSupplier + */ + default long getLongOrDefault(String key, LongSupplier defaultValue) { + + Long value = getLong(key); + + return value == null ? defaultValue.getAsLong() : value; + } + + /** + * Returns the value to which the specified {@code key} is mapped, or {@literal null} if this document contains no + * mapping for the key. If the value type is not a {@link String}, then this method throws {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@literal null} if this document contains no mapping for + * the key. + * @throws ClassCastException if the value of the given key is not a {@link String}. + */ + @Nullable + default String getString(String key) { + return get(key, String.class); + } + + /** + * Returns the value to which the specified {@code key} is mapped or {@code defaultValue} if this document contains no + * mapping for the key. If the value type is not a {@link String}, then this method throws {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or {@code defaultValue} if this document contains no mapping + * for the key. + * @throws ClassCastException if the value of the given key is not a {@link String}. + */ + default String getStringOrDefault(String key, String defaultValue) { + return getStringOrDefault(key, () -> defaultValue); + } + + /** + * Returns the value to which the specified {@code key} is mapped or the value from {@code defaultValue} if this + * document contains no mapping for the key. If the value type is not a {@link String}, then this method throws + * {@link ClassCastException}. + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped or the value from {@code defaultValue} if this document + * contains no mapping for the key. + * @throws ClassCastException if the value of the given key is not a {@link String}. + * @see Supplier + */ + default String getStringOrDefault(String key, Supplier defaultValue) { + + String value = getString(key); + + return value == null ? defaultValue.get() : value; + } + + /** + * Render this {@link Document} to JSON. Auxiliary values such as Id and version are not considered within the JSON + * representation. + * + * @return a JSON representation of this document. + */ + String toJson(); + + /** + * initializes this object from the given JSON String. + * + * @param json must not be {@literal null} + */ + M fromJson(String json); +} diff --git a/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java b/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java index 6cc24ff90..4b5ca6f07 100644 --- a/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/NestedObjectTests.java @@ -381,7 +381,7 @@ public void shouldIndexAndSearchMapAsNestedType() { assertThat(books.getSearchHit(0).getContent().getId()).isEqualTo(book2.getId()); } - @Document(indexName = "test-index-book-nested-objects", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-nested-objects") static class Book { @Nullable @Id private String id; @@ -438,7 +438,7 @@ public void setDescription(@Nullable String description) { } } - @Document(indexName = "test-index-person", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-person") static class Person { @Nullable @Id private String id; @Nullable private String name; @@ -505,7 +505,7 @@ public void setModel(String model) { } } - @Document(indexName = "test-index-person-multiple-level-nested", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-person-multiple-level-nested") static class PersonMultipleLevelNested { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java b/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java index dc468eb90..7ce4e0f1c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java @@ -26,6 +26,7 @@ import java.lang.annotation.Target; import java.time.LocalDate; +import org.elasticsearch.index.VersionType; import org.json.JSONException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -57,7 +58,7 @@ void documentAnnotationShouldBeComposable() { assertThat(entity.getIndexCoordinates()).isEqualTo(IndexCoordinates.of("test-no-create")); assertThat(entity.isCreateIndexAndMapping()).isFalse(); - assertThat(entity.getShards()).isEqualTo((short) 42); + assertThat(entity.getVersionType()).isEqualTo(VersionType.INTERNAL); } @Test // DATAES-362 @@ -105,7 +106,7 @@ void shouldUseComposedFieldAnnotationsInMappingBuilder() throws JSONException { @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) - @Document(indexName = "", createIndex = false, shards = 42) + @Document(indexName = "", createIndex = false, versionType = VersionType.INTERNAL) public @interface DocumentNoCreate { @AliasFor(value = "indexName", annotation = Document.class) diff --git a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java index 47a7d73a9..aee463e31 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/nested/EnableNestedElasticsearchRepositoriesTests.java @@ -73,7 +73,7 @@ public void hasNestedRepository() { assertThat(nestedRepository).isNotNull(); } - @Document(indexName = "test-index-sample-config-nested", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-config-nested") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java index c089210c7..35b2c799f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/notnested/EnableElasticsearchRepositoriesTests.java @@ -120,7 +120,7 @@ public void hasNotNestedRepository() { assertThat(nestedRepository).isNull(); } - @Document(indexName = "test-index-sample-config-not-nested", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-config-not-nested") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; @@ -212,7 +212,7 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = "test-index-uuid-keyed-config-not-nested", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-uuid-keyed-config-not-nested") static class SampleEntityUUIDKeyed { @Nullable @Id private UUID id; @Nullable private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java index 8e8ae9701..b767fc4b0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java @@ -79,7 +79,7 @@ public void shouldThrowExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() .isInstanceOf(UncategorizedElasticsearchException.class); } - @Document(indexName = "test-index-sample-core-rest-template", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-core-rest-template") static class SampleEntity { @Nullable @Id private String id; @Nullable diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index b05df9b71..c8ec98352 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -83,6 +83,7 @@ import org.springframework.data.elasticsearch.annotations.JoinTypeRelations; import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.annotations.ScriptedField; +import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.document.Explanation; import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.index.AliasAction; @@ -3006,7 +3007,7 @@ public void shouldRemoveAlias() { assertThat(aliases).isEmpty(); } - @Document(indexName = INDEX_2_NAME, replicas = 0) + @Document(indexName = INDEX_2_NAME) class ResultAggregator { private String id; @@ -3750,7 +3751,8 @@ void shouldReturnExplanationWhenRequested() { assertThat(explanation).isNotNull(); } - @Document(indexName = INDEX_NAME_SAMPLE_ENTITY, replicas = 0, refreshInterval = "-1") + @Document(indexName = INDEX_NAME_SAMPLE_ENTITY) + @Setting(shards = 1, replicas = 0, refreshInterval = "-1") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; @@ -3928,7 +3930,7 @@ public int hashCode() { } } - @Document(indexName = "test-index-uuid-keyed-core-template", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-uuid-keyed-core-template") private static class SampleEntityUUIDKeyed { @Nullable @Id private UUID id; @Nullable private String type; @@ -4010,7 +4012,7 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = "test-index-book-core-template", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-core-template") static class Book { @Nullable @Id private String id; @Nullable private String name; @@ -4139,8 +4141,7 @@ public void setName(@Nullable String name) { } } - @Document(indexName = "test-index-version-core-template", replicas = 0, refreshInterval = "-1", - versionType = VersionType.EXTERNAL_GTE) + @Document(indexName = "test-index-version-core-template", versionType = VersionType.EXTERNAL_GTE) private static class GTEVersionEntity { @Nullable @Version private Long version; @Nullable @Id private String id; @@ -4174,7 +4175,7 @@ public void setName(@Nullable String name) { } } - @Document(indexName = "test-index-hetro1-core-template", replicas = 0) + @Document(indexName = "test-index-hetro1-core-template") static class HetroEntity1 { @Nullable @Id private String id; @Nullable private String firstName; @@ -4214,7 +4215,7 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = "test-index-hetro2-core-template", replicas = 0) + @Document(indexName = "test-index-hetro2-core-template") static class HetroEntity2 { @Nullable @Id private String id; @@ -4255,8 +4256,8 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = "test-index-server-configuration", useServerConfiguration = true, shards = 10, replicas = 10, - refreshInterval = "-1") + @Document(indexName = "test-index-server-configuration") + @Setting(useServerConfiguration = true, shards = 10, replicas = 10, refreshInterval = "-1") private static class UseServerConfigurationEntity { @Nullable @Id private String id; @@ -4281,7 +4282,7 @@ public void setVal(@Nullable String val) { } } - @Document(indexName = "test-index-sample-mapping", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-mapping") static class SampleMappingEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java index 485e00575..d95faed41 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java @@ -195,7 +195,7 @@ void shouldUseAllOptionsFromUpdateByQuery() throws JSONException { assertThat(request.request().getScript().getType()).isEqualTo(org.elasticsearch.script.ScriptType.STORED); } - @Document(indexName = "test-index-sample-core-transport-template", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-core-transport-template") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java index a54ca91ea..745db975f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/LogEntityTests.java @@ -132,7 +132,7 @@ public void shouldReturnLogsForGivenIPRanges() { /** * Simple type to test facets */ - @Document(indexName = "test-index-log-core", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-log-core") static class LogEntity { private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index 45e580f46..90755add8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -1257,7 +1257,7 @@ public int hashCode() { } } - @Document(indexName = DEFAULT_INDEX, replicas = 0, refreshInterval = "-1") + @Document(indexName = DEFAULT_INDEX) static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String message; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java index 98eb8dc65..ddf2c984a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java @@ -250,7 +250,7 @@ public void deleteByShouldApplyIndicesOptionsIfSet() { assertThat(captor.getValue().indicesOptions()).isEqualTo(IndicesOptions.LENIENT_EXPAND_OPEN); } - @Document(indexName = "test-index-sample-core-reactive-template-Unit", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-core-reactive-template-Unit") static class SampleEntity { @Nullable @Id private String id; 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 8348b98ef..87923872a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveIndexOperationsTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; +import org.springframework.data.elasticsearch.core.index.Settings; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -390,8 +391,7 @@ void shouldPutTemplate() { 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(); + Settings settings = indexOps .createSettings(TemplateClass.class).block(); AliasActions aliasActions = new AliasActions( new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); @@ -423,8 +423,7 @@ void shouldGetTemplate() throws JSONException { 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(); + Settings settings = indexOps .createSettings(TemplateClass.class).block(); AliasActions aliasActions = new AliasActions( new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); @@ -505,7 +504,8 @@ void shouldDeleteTemplate() { assertThat(exists).isFalse(); } - @Document(indexName = TESTINDEX, shards = 3, replicas = 2, refreshInterval = "4s") + @Document(indexName = TESTINDEX) + @Setting(shards = 3, replicas = 2, refreshInterval = "4s") static class Entity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Text) private String text; @@ -540,7 +540,8 @@ public void setPublicationDate(@Nullable LocalDate publicationDate) { } } - @Document(indexName = TESTINDEX, useServerConfiguration = true) + @Document(indexName = TESTINDEX) + @Setting(useServerConfiguration = true) static class EntityUseServerConfig { @Nullable @Id private String id; @@ -570,7 +571,8 @@ public void setId(@Nullable String id) { } } - @Document(indexName = "test-template", shards = 3, replicas = 2, refreshInterval = "5s") + @Document(indexName = "test-template") + @Setting(refreshInterval = "5s") static class TemplateClass { @Id @Nullable private String id; @Field(type = FieldType.Text) @Nullable private String message; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtilTest.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtilTest.java index d82e3dce9..39f62e32f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtilTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtilTest.java @@ -60,9 +60,9 @@ void shouldReadFromClasspath() { } @Test - void shouldReturnEmptyMonoOnNonExistingResource() { + void shouldErrorOnNonExistingResource() { ReactiveResourceUtil.readFileFromClasspath("/this/should/really/not/exist") // .as(StepVerifier::create) // - .verifyComplete(); + .verifyError(); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java index c04c30879..2c67efa6e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SearchAsYouTypeTests.java @@ -151,7 +151,7 @@ void shouldReturnCorrectResultsForNotMatchQuery() { /** * @author Aleksei Arsenev */ - @Document(indexName = "test-index-core-search-as-you-type", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-core-search-as-you-type") static class SearchAsYouTypeEntity { public SearchAsYouTypeEntity(@Nonnull String id) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java index c60c23bf9..86bef45bc 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java @@ -130,7 +130,7 @@ public void shouldReturnAggregatedResponseForGivenSearchQuery() { assertThat(searchHits.hasSearchHits()).isFalse(); } - @Document(indexName = "test-index-articles-core-aggregation", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-articles-core-aggregation") static class ArticleEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionTests.java b/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionTests.java index d683c2ddf..24275198e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionTests.java @@ -248,7 +248,7 @@ public void setSomeField2(String someField2) { /** * @author Mewes Kochheim */ - @Document(indexName = "test-index-core-completion", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-core-completion") static class CompletionEntity { @Nullable @Id private String id; @@ -334,7 +334,7 @@ public IndexQuery buildIndex() { /** * @author Mewes Kochheim */ - @Document(indexName = "test-index-annotated-completion", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-annotated-completion") static class AnnotatedCompletionEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionWithContextsTests.java b/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionWithContextsTests.java index 5626661a2..d25db21e5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionWithContextsTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/completion/ElasticsearchTemplateCompletionWithContextsTests.java @@ -244,7 +244,7 @@ public void setSomeField2(String someField2) { * @author Mewes Kochheim * @author Robert Gruendler */ - @Document(indexName = "test-index-context-completion", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-context-completion") static class ContextCompletionEntity { public static final String LANGUAGE_CATEGORY = "language"; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 225102876..413ade735 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -1792,8 +1792,7 @@ public void setModel(@Nullable String model) { } } - @org.springframework.data.elasticsearch.annotations.Document(indexName = "test-index-geo-core-entity-mapper", - replicas = 0, refreshInterval = "-1") + @org.springframework.data.elasticsearch.annotations.Document(indexName = "test-index-geo-core-entity-mapper") static class GeoEntity { @Nullable @Id private String id; // geo shape - Spring Data diff --git a/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java b/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java index 548bc6847..d8ee328fd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/geo/ElasticsearchTemplateGeoTests.java @@ -361,7 +361,7 @@ private IndexQuery buildIndex(LocationMarkerEntity result) { * @author Franck Marchand * @author Mohsin Husen */ - @Document(indexName = "test-index-author-marker-core-geo", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-author-marker-core-geo") static class AuthorMarkerEntity { @Nullable @Id private String id; @Nullable private String name; @@ -436,7 +436,7 @@ public IndexQuery buildIndex() { } } - @Document(indexName = "test-index-location-marker-core-geo", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-location-marker-core-geo") static class LocationMarkerEntity { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index 9223499ad..31b8727df 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -122,9 +122,9 @@ public void shouldAddStockPriceDocumentToIndex() { IndexCoordinates index = IndexCoordinates.of("test-index-stock-mapping-builder"); StockPrice stockPrice = new StockPrice(); // - stockPrice.setId(id); - stockPrice.setSymbol(symbol); - stockPrice.setPrice(BigDecimal.valueOf(price)); + stockPrice.setId(id); + stockPrice.setSymbol(symbol); + stockPrice.setPrice(BigDecimal.valueOf(price)); operations.index(buildIndex(stockPrice), index); operations.indexOps(StockPrice.class).refresh(); @@ -384,7 +384,7 @@ static class MultiFieldEntity { } } - @Document(indexName = "test-index-book-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-mapping-builder") static class Book { @Nullable @Id private String id; @Nullable private String name; @@ -440,7 +440,7 @@ public void setDescription(@Nullable String description) { } } - @Document(indexName = "test-index-simple-recursive-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-simple-recursive-mapping-builder") static class SimpleRecursiveEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Object, @@ -465,7 +465,7 @@ public void setCircularObject(@Nullable SimpleRecursiveEntity circularObject) { } } - @Document(indexName = "test-copy-to-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-copy-to-mapping-builder") static class CopyToEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String firstName; @@ -509,7 +509,7 @@ public void setName(@Nullable String name) { } } - @Document(indexName = "test-index-normalizer-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-normalizer-mapping-builder") @Setting(settingPath = "/settings/test-normalizer.json") static class NormalizerEntity { @Nullable @Id private String id; @@ -569,7 +569,7 @@ public void setName(String name) { } } - @Document(indexName = "test-index-sample-inherited-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-inherited-mapping-builder") static class SampleInheritedEntity extends AbstractInheritedEntity { @Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; @@ -615,7 +615,7 @@ public IndexQuery buildIndex() { } } - @Document(indexName = "test-index-stock-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-stock-mapping-builder") static class StockPrice { @Nullable @Id private String id; @Nullable private String symbol; @@ -652,35 +652,41 @@ public void setPrice(@Nullable BigDecimal price) { static class AbstractInheritedEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Date, format = DateFormat.date_time, index = false) private Date createdDate; - @Nullable public String getId() { + + @Nullable + public String getId() { return id; } + public void setId(String id) { this.id = id; } - @Nullable public Date getCreatedDate() { + + @Nullable + public Date getCreatedDate() { return createdDate; } + public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } } - @Document(indexName = "test-index-geo-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-geo-mapping-builder") static class GeoEntity { -@Nullable @Id private String id; -// geo shape - Spring Data -@Nullable private Box box; -@Nullable private Circle circle; -@Nullable private Polygon polygon; -// geo point - Custom implementation + Spring Data -@Nullable @GeoPointField private Point pointA; -@Nullable private GeoPoint pointB; -@Nullable @GeoPointField private String pointC; -@Nullable @GeoPointField private double[] pointD; - // geo shape, until e have the classes for this, us a strng -@Nullable @GeoShapeField private String shape1; -@Nullable @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, + @Nullable @Id private String id; + // geo shape - Spring Data + @Nullable private Box box; + @Nullable private Circle circle; + @Nullable private Polygon polygon; + // geo point - Custom implementation + Spring Data + @Nullable @GeoPointField private Point pointA; + @Nullable private GeoPoint pointB; + @Nullable @GeoPointField private String pointC; + @Nullable @GeoPointField private double[] pointD; + // geo shape, until e have the classes for this, us a strng + @Nullable @GeoShapeField private String shape1; + @Nullable @GeoShapeField(coerce = true, ignoreMalformed = true, ignoreZValue = false, orientation = GeoShapeField.Orientation.clockwise) private String shape2; @Nullable @@ -890,7 +896,8 @@ static class TermVectorFieldEntity { @Nullable @Field(type = FieldType.Text, termVector = TermVector.yes) private String yes; @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions) private String with_positions; @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_offsets) private String with_offsets; - @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions_offsets) private String with_positions_offsets; + @Nullable @Field(type = FieldType.Text, + termVector = TermVector.with_positions_offsets) private String with_positions_offsets; @Nullable @Field(type = FieldType.Text, termVector = TermVector.with_positions_payloads) private String with_positions_payloads; @Nullable @Field(type = FieldType.Text, diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 66c1d57a0..29a58e7c9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -731,7 +731,7 @@ static class MultiFieldEntity { } } - @Document(indexName = "test-index-book-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-mapping-builder") static class Book { @Nullable @Id private String id; @Nullable private String name; @@ -787,7 +787,7 @@ public void setDescription(@Nullable String description) { } } - @Document(indexName = "test-index-simple-recursive-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-simple-recursive-mapping-builder") static class SimpleRecursiveEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Object, @@ -812,7 +812,7 @@ public void setCircularObject(@Nullable SimpleRecursiveEntity circularObject) { } } - @Document(indexName = "test-copy-to-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-copy-to-mapping-builder") static class CopyToEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Keyword, copyTo = "name") private String firstName; @@ -856,7 +856,7 @@ public void setName(@Nullable String name) { } } - @Document(indexName = "test-index-normalizer-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-normalizer-mapping-builder") @Setting(settingPath = "/settings/test-normalizer.json") static class NormalizerEntity { @Nullable @Id private String id; @@ -915,7 +915,7 @@ public void setName(String name) { } } - @Document(indexName = "test-index-sample-inherited-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-inherited-mapping-builder") static class SampleInheritedEntity extends AbstractInheritedEntity { @Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; @@ -930,7 +930,7 @@ public void setMessage(String message) { } } - @Document(indexName = "test-index-stock-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-stock-mapping-builder") static class StockPrice { @Nullable @Id private String id; @Nullable private String symbol; @@ -989,7 +989,7 @@ public void setCreatedDate(Date createdDate) { } } - @Document(indexName = "test-index-recursive-mapping-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-recursive-mapping-mapping-builder") static class SampleTransientEntity { @Nullable @Id private String id; @@ -1040,7 +1040,7 @@ public void setSomething(Boolean something) { } } - @Document(indexName = "test-index-geo-mapping-builder", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-geo-mapping-builder") static class GeoEntity { @Nullable @Id private String id; // geo shape - Spring Data diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java index d4a134ea9..c959dc7d1 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleDynamicTemplatesMappingTests.java @@ -27,6 +27,7 @@ import org.springframework.data.elasticsearch.annotations.DynamicTemplates; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.MappingContextBaseTests; import org.springframework.lang.Nullable; @@ -66,7 +67,8 @@ public void testCorrectDynamicTemplatesMappingsTwo() throws JSONException { /** * @author Petr Kukral */ - @Document(indexName = "test-dynamictemplates", indexStoreType = "memory", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-dynamictemplates") + @Setting(indexStoreType = "memory") @DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings.json") static class SampleDynamicTemplatesEntity { @@ -78,7 +80,8 @@ static class SampleDynamicTemplatesEntity { /** * @author Petr Kukral */ - @Document(indexName = "test-dynamictemplates", indexStoreType = "memory", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-dynamictemplates") + @Setting(indexStoreType = "memory") @DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings_two.json") static class SampleDynamicTemplatesEntityTwo { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java index e974e76bd..470dfa0a4 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/SimpleElasticsearchDateMappingTests.java @@ -51,7 +51,7 @@ public void testCorrectDateMappings() throws JSONException { assertEquals(EXPECTED_MAPPING, mapping, false); } - @Document(indexName = "test-index-date-mapping-core", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-date-mapping-core") static class SampleDateMappingEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, index = false, store = true, analyzer = "standard") private String message; 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 index bb89b05a6..1cec28730 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/TemplateTests.java @@ -28,6 +28,7 @@ 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.annotations.Setting; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -51,8 +52,7 @@ 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); + Settings settings = indexOps.createSettings(TemplateClass.class); AliasActions aliasActions = new AliasActions( new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); @@ -84,8 +84,7 @@ 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); + Settings settings = indexOps.createSettings(TemplateClass.class); AliasActions aliasActions = new AliasActions( new AliasAction.Add(AliasActionParameters.builderForTemplate().withAliases("alias1", "alias2").build())); @@ -166,7 +165,8 @@ void shouldDeleteTemplate() { } - @Document(indexName = "test-template", shards = 3, replicas = 2, refreshInterval = "5s") + @Document(indexName = "test-template") + @Setting(shards = 3) static class TemplateClass { @Id @Nullable private String id; @Field(type = FieldType.Text) @Nullable private String message; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java index a5d920c43..bae5f44cc 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java @@ -16,14 +16,18 @@ package org.springframework.data.elasticsearch.core.mapping; import static org.assertj.core.api.Assertions.*; +import static org.skyscreamer.jsonassert.JSONAssert.*; +import org.json.JSONException; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; 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.annotations.Setting; import org.springframework.data.elasticsearch.core.MappingContextBaseTests; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.mapping.MappingException; @@ -44,116 +48,162 @@ */ public class SimpleElasticsearchPersistentEntityTests extends MappingContextBaseTests { - @Test - public void shouldThrowExceptionGivenVersionPropertyIsNotLong() { - // given - TypeInformation typeInformation = ClassTypeInformation.from(EntityWithWrongVersionType.class); - SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); - // when - assertThatThrownBy(() -> { - SimpleElasticsearchPersistentProperty persistentProperty = createProperty(entity, "version"); - }).isInstanceOf(MappingException.class); - } + @Nested + @DisplayName("properties setup") + class PropertiesTests { - @Test - public void shouldThrowExceptionGivenMultipleVersionPropertiesArePresent() { - // given - TypeInformation typeInformation = ClassTypeInformation.from(EntityWithMultipleVersionField.class); - SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + @Test + public void shouldThrowExceptionGivenVersionPropertyIsNotLong() { - SimpleElasticsearchPersistentProperty persistentProperty1 = createProperty(entity, "version1"); + TypeInformation typeInformation = ClassTypeInformation + .from(EntityWithWrongVersionType.class); + SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( + typeInformation); - SimpleElasticsearchPersistentProperty persistentProperty2 = createProperty(entity, "version2"); + assertThatThrownBy(() -> createProperty(entity, "version")).isInstanceOf(MappingException.class); + } - entity.addPersistentProperty(persistentProperty1); - // when - assertThatThrownBy(() -> { - entity.addPersistentProperty(persistentProperty2); - }).isInstanceOf(MappingException.class); - } + @Test + public void shouldThrowExceptionGivenMultipleVersionPropertiesArePresent() { - @Test - void shouldFindPropertiesByMappedName() { + TypeInformation typeInformation = ClassTypeInformation + .from(EntityWithMultipleVersionField.class); + SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( + typeInformation); + SimpleElasticsearchPersistentProperty persistentProperty1 = createProperty(entity, "version1"); + SimpleElasticsearchPersistentProperty persistentProperty2 = createProperty(entity, "version2"); + entity.addPersistentProperty(persistentProperty1); - SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); - SimpleElasticsearchPersistentEntity persistentEntity = context - .getRequiredPersistentEntity(FieldNameEntity.class); + assertThatThrownBy(() -> entity.addPersistentProperty(persistentProperty2)).isInstanceOf(MappingException.class); + } - ElasticsearchPersistentProperty persistentProperty = persistentEntity - .getPersistentPropertyWithFieldName("renamed-field"); + @Test + void shouldFindPropertiesByMappedName() { - assertThat(persistentProperty).isNotNull(); - assertThat(persistentProperty.getName()).isEqualTo("renamedField"); - assertThat(persistentProperty.getFieldName()).isEqualTo("renamed-field"); - } + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + SimpleElasticsearchPersistentEntity persistentEntity = context + .getRequiredPersistentEntity(FieldNameEntity.class); - @Test // DATAES-799 - void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty() { - TypeInformation typeInformation = ClassTypeInformation - .from(EntityWithoutSeqNoPrimaryTerm.class); - SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + ElasticsearchPersistentProperty persistentProperty = persistentEntity + .getPersistentPropertyWithFieldName("renamed-field"); - assertThat(entity.hasSeqNoPrimaryTermProperty()).isFalse(); - } + assertThat(persistentProperty).isNotNull(); + assertThat(persistentProperty.getName()).isEqualTo("renamedField"); + assertThat(persistentProperty.getFieldName()).isEqualTo("renamed-field"); + } - @Test // DATAES-799 - void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() { - TypeInformation typeInformation = ClassTypeInformation - .from(EntityWithSeqNoPrimaryTerm.class); - SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + @Test + // DATAES-799 + void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty() { + TypeInformation typeInformation = ClassTypeInformation + .from(EntityWithoutSeqNoPrimaryTerm.class); + SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( + typeInformation); - entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); + assertThat(entity.hasSeqNoPrimaryTermProperty()).isFalse(); + } - assertThat(entity.hasSeqNoPrimaryTermProperty()).isTrue(); - } + @Test + // DATAES-799 + void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() { + TypeInformation typeInformation = ClassTypeInformation + .from(EntityWithSeqNoPrimaryTerm.class); + SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( + typeInformation); - @Test // DATAES-799 - void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() { - TypeInformation typeInformation = ClassTypeInformation - .from(EntityWithSeqNoPrimaryTerm.class); - SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); - entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); - EntityWithSeqNoPrimaryTerm instance = new EntityWithSeqNoPrimaryTerm(); - SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(1, 2); + entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); - ElasticsearchPersistentProperty property = entity.getSeqNoPrimaryTermProperty(); - entity.getPropertyAccessor(instance).setProperty(property, seqNoPrimaryTerm); + assertThat(entity.hasSeqNoPrimaryTermProperty()).isTrue(); + } - assertThat(instance.seqNoPrimaryTerm).isSameAs(seqNoPrimaryTerm); - } + @Test + // DATAES-799 + void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() { + + TypeInformation typeInformation = ClassTypeInformation + .from(EntityWithSeqNoPrimaryTerm.class); + SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( + typeInformation); + entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); + EntityWithSeqNoPrimaryTerm instance = new EntityWithSeqNoPrimaryTerm(); + SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(1, 2); + + ElasticsearchPersistentProperty property = entity.getSeqNoPrimaryTermProperty(); + assertThat(property).isNotNull(); - @Test // DATAES-799 - void shouldNotAllowMoreThanOneSeqNoPrimaryTermProperties() { - TypeInformation typeInformation = ClassTypeInformation - .from(EntityWithSeqNoPrimaryTerm.class); - SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); - entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); + entity.getPropertyAccessor(instance).setProperty(property, seqNoPrimaryTerm); + + assertThat(instance.seqNoPrimaryTerm).isSameAs(seqNoPrimaryTerm); + } + + @Test + // DATAES-799 + void shouldNotAllowMoreThanOneSeqNoPrimaryTermProperties() { + TypeInformation typeInformation = ClassTypeInformation + .from(EntityWithSeqNoPrimaryTerm.class); + SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( + typeInformation); + entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); + + assertThatThrownBy(() -> entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm2"))) + .isInstanceOf(MappingException.class); + } + + @Test // #1680 + @DisplayName("should allow fields with id property names") + void shouldAllowFieldsWithIdPropertyNames() { + elasticsearchConverter.get().getMappingContext().getRequiredPersistentEntity(EntityWithIdNameFields.class); + } - assertThatThrownBy(() -> entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm2"))) - .isInstanceOf(MappingException.class); } - @Test // #1680 - @DisplayName("should allow fields with id property names") - void shouldAllowFieldsWithIdPropertyNames() { - elasticsearchConverter.get().getMappingContext().getRequiredPersistentEntity(EntityWithIdNameFields.class); + @Nested + @DisplayName("index settings") + class SettingsTests { + + @Test // #1719 + @DisplayName("should error if index sorting parameters do not have the same number of arguments") + void shouldErrorIfIndexSortingParametersDoNotHaveTheSameNumberOfArguments() { + + assertThatThrownBy(() -> { + elasticsearchConverter.get().getMappingContext() + .getRequiredPersistentEntity(SettingsInvalidSortParameterSizes.class).getDefaultSettings(); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test // #1719 + @DisplayName("should write sort parameters to Settings object") + void shouldWriteSortParametersToSettingsObject() throws JSONException { + + String expected = "{\n" + // + " \"index.sort.field\": [\"second_field\", \"first_field\"],\n" + // + " \"index.sort.mode\": [\"max\", \"min\"],\n" + // + " \"index.sort.order\": [\"desc\",\"asc\"],\n" + // + " \"index.sort.missing\": [\"_last\",\"_first\"]\n" + // + "}\n"; // + + ElasticsearchPersistentEntity entity = elasticsearchConverter.get().getMappingContext() + .getRequiredPersistentEntity(SettingsValidSortParameterSizes.class); + + String json = entity.getDefaultSettings().toJson(); + assertEquals(expected, json, false); + } } + // region helper functions private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasticsearchPersistentEntity entity, - String field) { + String fieldName) { TypeInformation type = entity.getTypeInformation(); - Property property = Property.of(type, ReflectionUtils.findField(entity.getType(), field)); + java.lang.reflect.Field field = ReflectionUtils.findField(entity.getType(), fieldName); + assertThat(field).isNotNull(); + Property property = Property.of(type, field); return new SimpleElasticsearchPersistentProperty(property, entity, SimpleTypeHolder.DEFAULT, null); } + // endregion + // region entities private static class EntityWithWrongVersionType { @Nullable @Version private String version; @@ -168,6 +218,7 @@ public void setVersion(String version) { } } + @SuppressWarnings("unused") private static class EntityWithMultipleVersionField { @Nullable @Version private Long version1; @@ -192,6 +243,7 @@ public void setVersion2(Long version2) { } } + @SuppressWarnings("unused") private static class FieldNameEntity { @Nullable @Id private String id; @Nullable @Field(name = "renamed-field") private String renamedField; @@ -199,15 +251,40 @@ private static class FieldNameEntity { private static class EntityWithoutSeqNoPrimaryTerm {} + @SuppressWarnings("unused") private static class EntityWithSeqNoPrimaryTerm { - private SeqNoPrimaryTerm seqNoPrimaryTerm; - private SeqNoPrimaryTerm seqNoPrimaryTerm2; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; + @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm2; } + @SuppressWarnings("unused") @Document(indexName = "fieldnames") private static class EntityWithIdNameFields { - @Id private String theRealId; - @Field(type = FieldType.Text, name = "document") private String document; - @Field(name = "id") private String renamedId; + @Nullable @Id private String theRealId; + @Nullable @Field(type = FieldType.Text, name = "document") private String document; + @Nullable @Field(name = "id") private String renamedId; + } + + @Document(indexName = "dontcare") + @Setting(sortFields = { "first-field", "second-field" }, sortModes = { Setting.SortMode.max }, + sortOrders = { Setting.SortOrder.asc }, + sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._last, Setting.SortMissing._first }) + private static class SettingsInvalidSortParameterSizes { + @Nullable @Id private String id; + @Nullable @Field(name = "first-field", type = FieldType.Keyword) private String firstField; + @Nullable @Field(name = "second-field", type = FieldType.Keyword) private String secondField; } + +@Document(indexName = "dontcare") +// property names here, not field names +@Setting(sortFields = { "secondField", "firstField" }, sortModes = { Setting.SortMode.max, Setting.SortMode.min }, + sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc }, + sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first }) +private static class SettingsValidSortParameterSizes { + @Nullable @Id private String id; + @Nullable @Field(name = "first_field", type = FieldType.Keyword) private String firstField; + @Nullable @Field(name = "second_field", type = FieldType.Keyword) private String secondField; +} + + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java index 67c0199a2..18e2527a3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java @@ -797,17 +797,15 @@ public void shouldEscapeValue() { assertThat(sampleEntity1).isNotNull(); } - @Document(indexName = "test-index-sample-core-query", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-core-query") static class SampleEntity { - @Nullable - @Id private String id; + @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; @Nullable @Field(type = Text, store = true, fielddata = true) private String message; @Nullable private int rate; @Nullable @Version private Long version; - public SampleEntity() { - } + public SampleEntity() {} public SampleEntity(@Nullable String id, @Nullable String message) { this.id = id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java index db6f99a1c..ed096ca0b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ElasticsearchOperationsRoutingTests.java @@ -28,6 +28,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Routing; +import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; @@ -117,7 +118,8 @@ void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { assertThat(searchHits.getSearchHit(0).getRouting()).isEqualTo(ID_2); } - @Document(indexName = INDEX, shards = 5) + @Document(indexName = INDEX) + @Setting(shards = 5) @Routing("routing") static class RoutingEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java index 84ae0b713..c57ebf92a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/routing/ReactiveElasticsearchOperationsRoutingTests.java @@ -29,6 +29,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Routing; +import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; import org.springframework.data.elasticsearch.core.SearchHit; @@ -115,7 +116,8 @@ void shouldStoreDataWithDifferentRoutingAndGetTheRoutingInTheSearchResult() { assertThat(searchHits.get(0).getRouting()).isEqualTo(ID_2); } - @Document(indexName = INDEX, shards = 5) + @Document(indexName = INDEX) + @Setting(shards = 5) @Routing("routing") static class RoutingEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java index a2fed6d3b..321e32f1e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java @@ -157,7 +157,7 @@ public void returnOneFromCustomImpl() { * @author Mohsin Husen * @author Artur Konczak */ - @Document(indexName = "test-index-product-cdi-repository", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-product-cdi-repository") static class Product { @Nullable @Id private String id; @Nullable private List title; @@ -280,7 +280,7 @@ public void setLastModified(@Nullable Date lastModified) { } } - @Document(indexName = "test-index-person-cdi-repository", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-person-cdi-repository") static class Person { @Id private String id; @@ -293,7 +293,7 @@ static class Person { } - @Document(indexName = "test-index-book-cdi-repository", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-cdi-repository") static class Book { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java index b5b0015b5..df74cf0dd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java @@ -79,11 +79,9 @@ public void shouldExecuteComplexCustomMethod() { assertThat(result).isEqualTo("2+2=4"); } - @Document(indexName = "test-index-sample-repositories-complex-custommethod-autowiring", replicas = 0, - refreshInterval = "-1") + @Document(indexName = "test-index-sample-repositories-complex-custommethod-autowiring") static class SampleEntity { - @Nullable - @Id private String id; + @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; @Nullable @Field(type = Text, store = true, fielddata = true) private String message; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java index 9a68942f0..4a535a291 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java @@ -77,7 +77,7 @@ public void shouldExecuteComplexCustomMethod() { assertThat(result).isEqualTo("3+3=6"); } - @Document(indexName = "test-index-sample-repository-manual-wiring", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-repository-manual-wiring") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java index e8fe1d60c..4284f03ab 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java @@ -1631,7 +1631,7 @@ void shouldStreamSearchHitsWithQueryAnnotatedMethod() { assertThat(count).isEqualTo(20); } - @Document(indexName = "test-index-sample-repositories-custom-method", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-repositories-custom-method") static class SampleEntity { @Nullable @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java index 2ccc7ba53..dc8d526f8 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java @@ -121,7 +121,7 @@ public void shouldSaveDocument() { * @author Mohsin Husen */ - @Document(indexName = "test-index-double-keyed-entity", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-double-keyed-entity") static class DoubleIDEntity { @Id private Double id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java index 6bda75c78..5f7fcd1a2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/geo/SpringDataGeoRepositoryTests.java @@ -110,7 +110,7 @@ private double[] toGeoArray(Point point) { return new double[] { point.getX(), point.getY() }; } - @Document(indexName = "test-index-geo-repository", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-geo-repository") static class GeoEntity { @Nullable @Id private String id; // geo shape - Spring Data diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryTests.java index 3f1fb8bc4..ff1a0a923 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/integer/IntegerIDRepositoryTests.java @@ -122,7 +122,7 @@ public void shouldSaveDocument() { * @author Mohsin Husen */ - @Document(indexName = "test-index-integer-keyed-entity", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-integer-keyed-entity") static class IntegerIDEntity { @Id private Integer id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java index 6d826c3a3..2740f99e5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/nestedobject/InnerObjectTests.java @@ -96,7 +96,7 @@ public void shouldIndexInnerObject() { assertThat(bookRepository.findById(id)).isNotNull(); } - @Document(indexName = "test-index-book", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book") static class Book { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityTests.java index 4412f62fb..f9b7f0dfc 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/spel/SpELEntityTests.java @@ -103,7 +103,7 @@ public void shouldSupportSpelInType() { * * @author Artur Konczak */ - @Document(indexName = "#{'test-index-abz'+'-'+'entity'}", replicas = 0, refreshInterval = "-1") + @Document(indexName = "#{'test-index-abz'+'-'+'entity'}") static class SpELEntity { @Id private String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java index 916ea2af6..f9ff406b0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/uuidkeyed/UUIDElasticsearchRepositoryTests.java @@ -545,7 +545,7 @@ private static List createSampleEntitiesWithMessage(Strin return sampleEntities; } - @Document(indexName = "test-index-uuid-keyed", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-uuid-keyed") static class SampleEntityUUIDKeyed { @Nullable @Id private UUID id; @Nullable private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java index 30a0d51ac..dd025bb3d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/config/ReactiveElasticsearchRepositoriesRegistrarTests.java @@ -58,7 +58,7 @@ public void testConfiguration() { interface ReactiveSampleEntityRepository extends ReactiveElasticsearchRepository {} - @Document(indexName = "test-index-sample-reactive-repositories-registrar", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-reactive-repositories-registrar") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java index f12f998cb..6e63773a1 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java @@ -118,7 +118,7 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String * @author Artur Konczak */ - @Document(indexName = "test-index-person-query-unittest", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-person-query-unittest") static class Person { @Nullable @Id private String id; @@ -163,7 +163,7 @@ public void setBooks(List books) { } } - @Document(indexName = "test-index-book-query-unittest", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-query-unittest") static class Book { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java index bc549926c..e6aa4c298 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchQueryMethodUnitTests.java @@ -166,7 +166,7 @@ interface NonReactiveRepository extends Repository { * @author Artur Konczak */ - @Document(indexName = INDEX_NAME, replicas = 0, refreshInterval = "-1") + @Document(indexName = INDEX_NAME) static class Person { @Nullable @Id private String id; @@ -215,7 +215,7 @@ public void setBooks(List books) { } } - @Document(indexName = "test-index-book-reactive-repository-query", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-reactive-repository-query") static class Book { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java index cb45584c7..bf17c3c39 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java @@ -171,7 +171,7 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String * @author Artur Konczak */ - @Document(indexName = "test-index-person-reactive-repository-string-query", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-person-reactive-repository-string-query") public class Person { @Nullable @Id private String id; @@ -219,7 +219,7 @@ public void setBooks(List books) { } } - @Document(indexName = "test-index-book-reactive-repository-string-query", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-book-reactive-repository-string-query") static class Book { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java index 5033a2ff7..67c1c5b1e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsTests.java @@ -273,7 +273,7 @@ void shouldReturnEmptyListOnDerivedMethodWithEmptyInputList() { assertThat(products).isEmpty(); } - @Document(indexName = "test-index-product-query-keywords", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-product-query-keywords") static class Product { @Nullable @Id private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java index 72bb7ff7c..6d4ae3c5e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleElasticsearchRepositoryIntegrationTests.java @@ -677,7 +677,7 @@ private static List createSampleEntitiesWithMessage(String message return sampleEntities; } - @Document(indexName = "test-index-sample-simple-repository", replicas = 0, refreshInterval = "-1") + @Document(indexName = "test-index-sample-simple-repository") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java index 7076c0829..a233be31e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/SimpleReactiveElasticsearchRepositoryTests.java @@ -608,7 +608,7 @@ interface ReactiveSampleEntityRepository extends ReactiveCrudRepository retrieveCountByText(String message); } - @Document(indexName = INDEX, replicas = 0, refreshInterval = "-1") + @Document(indexName = INDEX) static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; From 19ecf89455fb1d933638cf13c18b6b817f2ed22b Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 28 Mar 2021 16:16:41 +0200 Subject: [PATCH 029/776] Upgrade to Elasticsearch 7.12.0. Original Pull Request #1749 Closes #1741 --- pom.xml | 2 +- src/main/asciidoc/preface.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 165816319..0ca891119 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 2.6 - 7.11.1 + 7.12.0 2.13.3 4.1.52.Final 2.5.0-SNAPSHOT diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index 028c72bd4..49b75f3ce 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -34,7 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat [cols="^,^,^,^,^",options="header"] |=== | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot -| 2021.0 (Pascal)footnote:cdv[Currently in development] | 4.2.xfootnote:cdv[] | 7.10.2 | 5.3.xfootnote:cdv[] | 2.4.xfootnote:cdv[] +| 2021.0 (Pascal)footnote:cdv[Currently in development] | 4.2.xfootnote:cdv[] | 7.12.0 | 5.3.xfootnote:cdv[] | 2.4.xfootnote:cdv[] | 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x | Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x | Moore | 3.2.x |6.8.12 | 5.2.12| 2.2.x From 0c4f0d8af999a085714450c39065b63e474e6597 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 17:04:31 +0200 Subject: [PATCH 030/776] Updated changelog. See #1699 --- src/main/resources/changelog.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index dde5281c4..e2a98d48a 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,18 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.2.0-RC1 (2021-03-31) +----------------------------------------- +* #1745 - Automatically close scroll context when returning streamed results. +* #1741 - Upgrade to Elasticsearch 7.12. +* #1738 - Readme lists artifacts with .RELEASE and .BUILD-SNAPSHOT suffixes. +* #1736 - Upgrade to OpenWebBeans 2.0. +* #1734 - Remove lombok. +* #1733 - Update CI to Java 16. +* #1727 - Allow multiple date formats for date fields. +* #1719 - Configure index settings with @Setting annotation. + + Changes in version 4.2.0-M5 (2021-03-17) ---------------------------------------- * #1725 - Add support for SearchTemplate for reactive client. @@ -1558,5 +1570,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 76d979cbbae8cf0e4bca716f4f89ef4cb28845a1 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 17:04:35 +0200 Subject: [PATCH 031/776] Prepare 4.2 RC1 (2021.0.0). See #1699 --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0ca891119..92b5b99ef 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.5.0-SNAPSHOT + 2.5.0-RC1 Spring Data Elasticsearch @@ -22,7 +22,7 @@ 7.12.0 2.13.3 4.1.52.Final - 2.5.0-SNAPSHOT + 2.5.0-RC1 1.15.1 spring.data.elasticsearch @@ -480,8 +480,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-milestone + https://repo.spring.io/libs-milestone diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 0ac44c445..aa294cced 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Elasticsearch 4.2 M5 (2021.0.0) +Spring Data Elasticsearch 4.2 RC1 (2021.0.0) Copyright (c) [2013-2021] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -25,3 +25,4 @@ conditions of the subcomponent's license, as noted in the LICENSE file. + From f9e4ac4d4288ae8342afc2a6603cc4619f5b4715 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 17:05:09 +0200 Subject: [PATCH 032/776] Release version 4.2 RC1 (2021.0.0). See #1699 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 92b5b99ef..2cc5c3ebd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.2.0-SNAPSHOT + 4.2.0-RC1 org.springframework.data.build From 2a016f1aeaf26318befd7454f97cd1329819ac55 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 17:24:04 +0200 Subject: [PATCH 033/776] Prepare next development iteration. See #1699 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2cc5c3ebd..92b5b99ef 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.2.0-RC1 + 4.2.0-SNAPSHOT org.springframework.data.build From cb08adc0c9cc661f17f2a3566ad49be20d4701db Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 17:24:06 +0200 Subject: [PATCH 034/776] After release cleanups. See #1699 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 92b5b99ef..0ca891119 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.5.0-RC1 + 2.5.0-SNAPSHOT Spring Data Elasticsearch @@ -22,7 +22,7 @@ 7.12.0 2.13.3 4.1.52.Final - 2.5.0-RC1 + 2.5.0-SNAPSHOT 1.15.1 spring.data.elasticsearch @@ -480,8 +480,8 @@ - spring-libs-milestone - https://repo.spring.io/libs-milestone + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From dfc68cd33bac858be9ad3c47937a0d3aab65cac6 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 31 Mar 2021 18:19:17 +0200 Subject: [PATCH 035/776] Updated changelog. See #1731 --- src/main/resources/changelog.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index e2a98d48a..f9a4a9cda 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,10 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.1.7 (2021-03-31) +------------------------------------- + + Changes in version 4.2.0-RC1 (2021-03-31) ----------------------------------------- * #1745 - Automatically close scroll context when returning streamed results. @@ -1571,5 +1575,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From d66f8a0650ea8dd9551499c57fbf70f7f25e641d Mon Sep 17 00:00:00 2001 From: Rahul Lokurte <81438015+rahulmlokurte@users.noreply.github.com> Date: Sat, 3 Apr 2021 11:55:54 +0530 Subject: [PATCH 036/776] Documentation fix: Types are in the process of being removed. Original Pull Request: #1754 --- src/main/asciidoc/reference/elasticsearch-clients.adoc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/asciidoc/reference/elasticsearch-clients.adoc b/src/main/asciidoc/reference/elasticsearch-clients.adoc index 62f5a23f8..974bbf3cd 100644 --- a/src/main/asciidoc/reference/elasticsearch-clients.adoc +++ b/src/main/asciidoc/reference/elasticsearch-clients.adoc @@ -85,11 +85,12 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration { // ... -IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID()) +IndexRequest request = new IndexRequest("spring-data") + .id(randomID()) .source(singletonMap("feature", "high-level-rest-client")) .setRefreshPolicy(IMMEDIATE); -IndexResponse response = highLevelClient.index(request); +IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT); ---- <1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL. <2> Create the RestHighLevelClient. From 4ad002746e95e1f4a675a42d19eef7cc7a9da0b3 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 3 Apr 2021 08:53:40 +0200 Subject: [PATCH 037/776] #1755-Documentation-fix-to-not-show-deprecated-calls. Original Pull Request #1756 Closes #1755 --- src/main/asciidoc/reference/elasticsearch-clients.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/asciidoc/reference/elasticsearch-clients.adoc b/src/main/asciidoc/reference/elasticsearch-clients.adoc index 974bbf3cd..935c601bb 100644 --- a/src/main/asciidoc/reference/elasticsearch-clients.adoc +++ b/src/main/asciidoc/reference/elasticsearch-clients.adoc @@ -40,7 +40,8 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport { // ... -IndexRequest request = new IndexRequest("spring-data", "elasticsearch", randomID()) +IndexRequest request = new IndexRequest("spring-data") + .id(randomID()) .source(someObject); IndexResponse response = client.index(request); From 2bd4ef75cf10fa5e4bf36005b6151b32b6f4bc59 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 3 Apr 2021 15:31:04 +0200 Subject: [PATCH 038/776] CriteriaQuery must support nested queries. Original Pull Request: #1757 Closes #1753 --- .../core/CriteriaQueryProcessor.java | 11 +- .../MappingElasticsearchConverter.java | 118 ++++++++++++------ .../core/CriteriaQueryMappingUnitTests.java | 59 +++++++-- .../core/CriteriaQueryProcessorUnitTests.java | 32 +++++ 4 files changed, 172 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java index e252e6783..b6ed81279 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil; +import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -136,7 +137,7 @@ private QueryBuilder queryForEntries(Criteria criteria) { return null; String fieldName = field.getName(); - Assert.notNull(fieldName, "Unknown field"); + Assert.notNull(fieldName, "Unknown field " + fieldName); Iterator it = criteria.getQueryCriteriaEntries().iterator(); QueryBuilder query; @@ -152,6 +153,14 @@ private QueryBuilder queryForEntries(Criteria criteria) { } addBoost(query, criteria.getBoost()); + + int dotPosition = fieldName.lastIndexOf('.'); + + if (dotPosition > 0) { + String nestedPath = fieldName.substring(0, dotPosition); + query = nestedQuery(nestedPath, query, ScoreMode.Avg); + } + return query; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 226779e3a..b544021d1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -168,8 +168,15 @@ public void afterPropertiesSet() { @SuppressWarnings("unchecked") @Override public R read(Class type, Document source) { + TypeInformation typeHint = ClassTypeInformation.from((Class) ClassUtils.getUserClass(type)); - return read(typeHint, source); + R r = read(typeHint, source); + + if (r == null) { + throw new ConversionException("could not convert into object of class " + type); + } + + return r; } protected R readEntity(ElasticsearchPersistentEntity entity, Map source) { @@ -188,7 +195,7 @@ protected R readEntity(ElasticsearchPersistentEntity entity, Map getParameterProv ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator); // TODO: Support for non-static inner classes via ObjectPath + // noinspection ConstantConditions PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider<>( entity, provider, null); @@ -281,7 +289,6 @@ protected R readProperties(ElasticsearchPersistentEntity entity, R instan return accessor.getBean(); } - @SuppressWarnings("unchecked") @Nullable protected R readValue(@Nullable Object value, ElasticsearchPersistentProperty property, TypeInformation type) { @@ -349,7 +356,7 @@ private R read(TypeInformation type, Map source) { } if (typeToUse.isMap()) { - return (R) readMap(typeToUse, source); + return readMap(typeToUse, source); } if (typeToUse.equals(ClassTypeInformation.OBJECT)) { @@ -549,7 +556,7 @@ public void write(Object source, Document sink) { } Class entityType = ClassUtils.getUserClass(source.getClass()); - TypeInformation typeInformation = ClassTypeInformation.from(entityType); + TypeInformation typeInformation = ClassTypeInformation.from(entityType); if (requiresTypeHint(entityType)) { typeMapper.writeType(typeInformation, sink); @@ -561,9 +568,9 @@ public void write(Object source, Document sink) { /** * Internal write conversion method which should be used for nested invocations. * - * @param source - * @param sink - * @param typeInformation + * @param source the object to write + * @param sink the write destination + * @param typeInformation type information for the source */ @SuppressWarnings("unchecked") protected void writeInternal(@Nullable Object source, Map sink, @@ -578,7 +585,10 @@ protected void writeInternal(@Nullable Object source, Map sink, if (customTarget.isPresent()) { Map result = conversionService.convert(source, Map.class); - sink.putAll(result); + + if (result != null) { + sink.putAll(result); + } return; } @@ -600,9 +610,9 @@ protected void writeInternal(@Nullable Object source, Map sink, /** * Internal write conversion method which should be used for nested invocations. * - * @param source - * @param sink - * @param entity + * @param source the object to write + * @param sink the write destination + * @param entity entity for the source */ protected void writeInternal(@Nullable Object source, Map sink, @Nullable ElasticsearchPersistentEntity entity) { @@ -734,7 +744,6 @@ protected void writeProperty(ElasticsearchPersistentProperty property, Object va * * @param collection must not be {@literal null}. * @param property must not be {@literal null}. - * @return */ protected List createCollection(Collection collection, ElasticsearchPersistentProperty property) { return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>(collection.size())); @@ -745,7 +754,6 @@ protected List createCollection(Collection collection, ElasticsearchP * * @param map must not {@literal null}. * @param property must not be {@literal null}. - * @return */ protected Map createMap(Map map, ElasticsearchPersistentProperty property) { @@ -761,7 +769,6 @@ protected Map createMap(Map map, ElasticsearchPersistentPr * @param source must not be {@literal null}. * @param sink must not be {@literal null}. * @param propertyType must not be {@literal null}. - * @return */ protected Map writeMapInternal(Map source, Map sink, TypeInformation propertyType) { @@ -801,7 +808,6 @@ protected Map writeMapInternal(Map source, Map writeCollectionInternal(Collection source, @Nullable TypeInformation type, @@ -837,8 +843,7 @@ private List writeCollectionInternal(Collection source, @Nullable Typ /** * Returns a {@link String} representation of the given {@link Map} key * - * @param key - * @return + * @param key the key to convert */ private String potentiallyConvertMapKey(Object key) { @@ -846,17 +851,22 @@ private String potentiallyConvertMapKey(Object key) { return (String) key; } - return conversions.hasCustomWriteTarget(key.getClass(), String.class) - ? (String) getPotentiallyConvertedSimpleWrite(key, Object.class) - : key.toString(); + if (conversions.hasCustomWriteTarget(key.getClass(), String.class)) { + Object potentiallyConvertedSimpleWrite = getPotentiallyConvertedSimpleWrite(key, Object.class); + + if (potentiallyConvertedSimpleWrite == null) { + return key.toString(); + } + return (String) potentiallyConvertedSimpleWrite; + } + return key.toString(); } /** * Checks whether we have a custom conversion registered for the given value into an arbitrary simple Elasticsearch * type. Returns the converted value if so. If not, we perform special enum handling or simply return the value as is. * - * @param value - * @return + * @param value value to convert */ @Nullable private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nullable Class typeHint) { @@ -869,6 +879,10 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nulla if (conversionService.canConvert(value.getClass(), typeHint)) { value = conversionService.convert(value, typeHint); + + if (value == null) { + return null; + } } } @@ -908,8 +922,8 @@ protected Object getWriteSimpleValue(Object value) { * @deprecated since 4.2, use {@link #writeInternal(Object, Map, TypeInformation)} instead. */ @Deprecated - protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation typeHint, - Object value) { + protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, + @SuppressWarnings("unused") TypeInformation typeHint, Object value) { Document document = Document.create(); writeInternal(value, document, property.getTypeInformation()); @@ -928,11 +942,19 @@ protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, * * @param source must not be {@literal null}. * @param sink must not be {@literal null}. - * @param type + * @param type type to compare to */ - protected void addCustomTypeKeyIfNecessary(Object source, Map sink, @Nullable TypeInformation type) { + protected void addCustomTypeKeyIfNecessary(Object source, Map sink, + @Nullable TypeInformation type) { - Class reference = type != null ? type.getActualType().getType() : Object.class; + Class reference; + + if (type == null) { + reference = Object.class; + } else { + TypeInformation actualType = type.getActualType(); + reference = actualType == null ? Object.class : actualType.getType(); + } Class valueType = ClassUtils.getUserClass(source.getClass()); boolean notTheSameClass = !valueType.equals(reference); @@ -987,8 +1009,7 @@ private boolean isSimpleType(Class type) { * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element * collection for everything else. * - * @param source - * @return + * @param source object to convert */ private static Collection asCollection(Object source) { @@ -1019,21 +1040,42 @@ public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClas } private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity persistentEntity) { + Field field = criteria.getField(); if (field == null) { return; } - String name = field.getName(); - ElasticsearchPersistentProperty property = persistentEntity.getPersistentProperty(name); + String[] fieldNames = field.getName().split("\\."); + ElasticsearchPersistentEntity currentEntity = persistentEntity; + ElasticsearchPersistentProperty persistentProperty = null; + for (int i = 0; i < fieldNames.length; i++) { + persistentProperty = currentEntity.getPersistentProperty(fieldNames[i]); + + if (persistentProperty != null) { + fieldNames[i] = persistentProperty.getFieldName(); + try { + currentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType()); + } catch (Exception e) { + // using system types like UUIDs will lead to java.lang.reflect.InaccessibleObjectException in JDK 16 + // so if we cannot get an entity here, bail out. + currentEntity = null; + } + } + + if (currentEntity == null) { + break; + } + } + + field.setName(String.join(".", fieldNames)); - if (property != null && property.getName().equals(name)) { - field.setName(property.getFieldName()); + if (persistentProperty != null) { - if (property.hasPropertyConverter()) { + if (persistentProperty.hasPropertyConverter()) { ElasticsearchPersistentPropertyConverter propertyConverter = Objects - .requireNonNull(property.getPropertyConverter()); + .requireNonNull(persistentProperty.getPropertyConverter()); criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> { Object value = criteriaEntry.getValue(); if (value.getClass().isArray()) { @@ -1047,7 +1089,7 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity }); } - org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property + org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = persistentProperty .findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class); if (fieldAnnotation != null) { @@ -1055,6 +1097,7 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity } } } + // endregion static class MapValueAccessor { @@ -1148,7 +1191,6 @@ class ElasticsearchPropertyValueProvider implements PropertyValueProvider T getPropertyValue(ElasticsearchPersistentProperty property) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index c3e65e627..4cd01d1ef 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -15,12 +15,14 @@ */ package org.springframework.data.elasticsearch.core; +import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; import java.time.LocalDate; import java.util.Base64; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Objects; import org.json.JSONException; @@ -68,8 +70,7 @@ void setUp() { // endregion // region tests - @Test - // DATAES-716 + @Test // DATAES-716 void shouldMapNamesAndConvertValuesInCriteriaQuery() throws JSONException { // use POJO properties and types in the query building @@ -111,8 +112,7 @@ void shouldMapNamesAndConvertValuesInCriteriaQuery() throws JSONException { assertEquals(expected, queryString, false); } - @Test - // #1668 + @Test // #1668 void shouldMapNamesAndConvertValuesInCriteriaQueryForSubCriteria() throws JSONException { // use POJO properties and types in the query building @@ -185,8 +185,7 @@ void shouldMapNamesAndConvertValuesInCriteriaQueryForSubCriteria() throws JSONEx assertEquals(expected, queryString, false); } - @Test - // #1668 + @Test // #1668 void shouldMapNamesAndConvertValuesInCriteriaQueryForSubCriteriaWithDate() throws JSONException { // use POJO properties and types in the query building CriteriaQuery criteriaQuery = new CriteriaQuery( // @@ -258,8 +257,7 @@ void shouldMapNamesAndConvertValuesInCriteriaQueryForSubCriteriaWithDate() throw assertEquals(expected, queryString, false); } - @Test - // DATAES-706 + @Test // DATAES-706 void shouldMapNamesAndValuesInSubCriteriaQuery() throws JSONException { CriteriaQuery criteriaQuery = new CriteriaQuery( // @@ -332,6 +330,41 @@ void shouldMapNamesInGeoJsonQuery() throws JSONException { assertEquals(expected, queryString, false); } + @Test // #1753 + @DisplayName("should map names and value in nested entities") + void shouldMapNamesAndValueInNestedEntities() throws JSONException { + + String expected = "{\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"nested\": {\n" + // + " \"query\": {\n" + // + " \"query_string\": {\n" + // + " \"query\": \"03.10.1999\",\n" + // + " \"fields\": [\n" + // + " \"per-sons.birth-date^1.0\"\n" + // + " ]\n" + // + " }\n" + // + " },\n" + // + " \"path\": \"per-sons\"\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + "}\n"; // + + CriteriaQuery criteriaQuery = new CriteriaQuery( + new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)) + ); + mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class); + String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); + + assertEquals(expected, queryString, false); + } + // endregion + + // region helper functions private String getBase64EncodedGeoShapeQuery(GeoJson geoJson, String elasticFieldName, String relation) { return Base64.getEncoder() .encodeToString(("{\"geo_shape\": {\"" @@ -347,7 +380,15 @@ static class Person { @Nullable @Field(name = "first-name") String firstName; @Nullable @Field(name = "last-name") String lastName; @Nullable @Field(name = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate; - @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; + @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, + pattern = "dd.MM.uuuu") LocalDate birthDate; + } + + static class House { + @Nullable @Id String id; + @Nullable + @Field(name = "per-sons", type = FieldType.Nested) + List persons; } static class GeoShapeEntity { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java index 5708b762f..fdaa1b520 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java @@ -18,6 +18,7 @@ import static org.skyscreamer.jsonassert.JSONAssert.*; import org.json.JSONException; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.elasticsearch.core.query.Criteria; @@ -338,4 +339,35 @@ void shouldBuildMatchAllQuery() throws JSONException { assertEquals(expected, query, false); } + + @Test // #1753 + @DisplayName("should build nested query") + void shouldBuildNestedQuery() throws JSONException { + + String expected = "{\n" + // + " \"bool\" : {\n" + // + " \"must\" : [\n" + // + " {\n" + // + " \"nested\" : {\n" + // + " \"query\" : {\n" + // + " \"query_string\" : {\n" + // + " \"query\" : \"murphy\",\n" + // + " \"fields\" : [\n" + // + " \"houses.inhabitants.lastName^1.0\"\n" + // + " ]\n" + // + " }\n" + // + " },\n" + // + " \"path\" : \"houses.inhabitants\"\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + "}"; // + + Criteria criteria = new Criteria("houses.inhabitants.lastName").is("murphy"); + + String query = queryProcessor.createQuery(criteria).toString(); + + assertEquals(expected, query, false); + } } From ab73c68ca982e981cf810e7befefb10e37ff86c1 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 5 Apr 2021 15:42:02 +0200 Subject: [PATCH 039/776] Nested Criteria queries must consider sub-fields. Original Pull Request #1760 Closes #1758 --- .../core/CriteriaQueryProcessor.java | 8 ++--- .../core/convert/ElasticsearchConverter.java | 19 +++++----- .../MappingElasticsearchConverter.java | 11 ++++++ .../data/elasticsearch/core/query/Field.java | 13 +++++++ .../elasticsearch/core/query/SimpleField.java | 12 +++++++ .../core/CriteriaQueryMappingUnitTests.java | 36 +++++++++++++++++++ .../core/CriteriaQueryProcessorUnitTests.java | 1 + 7 files changed, 84 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java index b6ed81279..02988ced4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java @@ -18,6 +18,7 @@ import static org.elasticsearch.index.query.Operator.*; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.core.query.Criteria.*; +import static org.springframework.util.StringUtils.*; import java.util.ArrayList; import java.util.Iterator; @@ -154,11 +155,8 @@ private QueryBuilder queryForEntries(Criteria criteria) { addBoost(query, criteria.getBoost()); - int dotPosition = fieldName.lastIndexOf('.'); - - if (dotPosition > 0) { - String nestedPath = fieldName.substring(0, dotPosition); - query = nestedQuery(nestedPath, query, ScoreMode.Avg); + if (hasText(field.getPath())) { + query = nestedQuery(field.getPath(), query, ScoreMode.Avg); } return query; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java index ebdd2bfa4..a4a40a15e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java @@ -86,21 +86,18 @@ default Document mapObject(@Nullable Object source) { // region query /** - * Updates a query by renaming the property names in the query to the correct mapped field names and the values to the - * converted values if the {@link ElasticsearchPersistentProperty} for a property has a + * Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and + * the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a * {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If - * domainClass is null, it's a noop; handling null here eliminates null checks in the caller. - * + * domainClass is null or query is not a {@link CriteriaQuery}, it's a noop. + * * @param query the query that is internally updated * @param domainClass the class of the object that is searched with the query */ default void updateQuery(Query query, @Nullable Class domainClass) { - if (domainClass != null) { - - if (query instanceof CriteriaQuery) { - updateCriteriaQuery((CriteriaQuery) query, domainClass); - } + if (domainClass != null && query instanceof CriteriaQuery) { + updateCriteriaQuery((CriteriaQuery) query, domainClass); } } @@ -109,8 +106,8 @@ default void updateQuery(Query query, @Nullable Class domainClass) { * the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a * {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. * - * @param criteriaQuery the query that is internally updated - * @param domainClass the class of the object that is searched with the query + * @param criteriaQuery the query that is internally updated, must not be {@literal null} + * @param domainClass the class of the object that is searched with the query, must not be {@literal null} */ void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass); // endregion diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index b544021d1..1f3dc30d1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -1025,6 +1025,9 @@ private static Collection asCollection(Object source) { @Override public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass) { + Assert.notNull(criteriaQuery, "criteriaQuery must not be null"); + Assert.notNull(domainClass, "domainClass must not be null"); + ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(domainClass); if (persistentEntity != null) { @@ -1048,12 +1051,15 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity } String[] fieldNames = field.getName().split("\\."); + ElasticsearchPersistentEntity currentEntity = persistentEntity; ElasticsearchPersistentProperty persistentProperty = null; + int propertyCount = 0; for (int i = 0; i < fieldNames.length; i++) { persistentProperty = currentEntity.getPersistentProperty(fieldNames[i]); if (persistentProperty != null) { + propertyCount++; fieldNames[i] = persistentProperty.getFieldName(); try { currentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType()); @@ -1071,6 +1077,11 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity field.setName(String.join(".", fieldNames)); + if (propertyCount > 1) { + List propertyNames = Arrays.asList(fieldNames); + field.setPath(String.join(".", propertyNames.subList(0, propertyCount - 1))); + } + if (persistentProperty != null) { if (persistentProperty.hasPropertyConverter()) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Field.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Field.java index 760664e0e..a2567fea5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Field.java @@ -41,4 +41,17 @@ public interface Field { */ @Nullable FieldType getFieldType(); + + /** + * Sets the path if this field has a multi-part name that should be used in a nested query. + * @param path the value to set + * @since 4.2 + */ + void setPath(@Nullable String path); + + /** + * @return the path if this is a field for a nested query + * @since 4.2 + */ + @Nullable String getPath(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/SimpleField.java b/src/main/java/org/springframework/data/elasticsearch/core/query/SimpleField.java index ca1d77342..67866728d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/SimpleField.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/SimpleField.java @@ -31,6 +31,7 @@ public class SimpleField implements Field { private String name; @Nullable private FieldType fieldType; + @Nullable private String path; public SimpleField(String name) { @@ -63,6 +64,17 @@ public FieldType getFieldType() { return fieldType; } + @Override + public void setPath(@Nullable String path) { + this.path = path; + } + + @Override + @Nullable + public String getPath() { + return path; + } + @Override public String toString() { return getName(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index 4cd01d1ef..10f2344b6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -34,6 +34,8 @@ import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.InnerField; +import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.core.convert.GeoConverters; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; @@ -362,6 +364,39 @@ void shouldMapNamesAndValueInNestedEntities() throws JSONException { assertEquals(expected, queryString, false); } + + @Test // #1753 + @DisplayName("should map names and value in nested entities with sub-fields") + void shouldMapNamesAndValueInNestedEntitiesWithSubfields() throws JSONException { + + String expected = "{\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"nested\": {\n" + // + " \"query\": {\n" + // + " \"query_string\": {\n" + // + " \"query\": \"Foobar\",\n" + // + " \"fields\": [\n" + // + " \"per-sons.nick-name.keyword^1.0\"\n" + // + " ]\n" + // + " }\n" + // + " },\n" + // + " \"path\": \"per-sons\"\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + "}\n"; // + + CriteriaQuery criteriaQuery = new CriteriaQuery( + new Criteria("persons.nickName.keyword").is("Foobar") + ); + mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class); + String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); + + assertEquals(expected, queryString, false); + } // endregion // region helper functions @@ -379,6 +414,7 @@ static class Person { @Nullable @Id String id; @Nullable @Field(name = "first-name") String firstName; @Nullable @Field(name = "last-name") String lastName; + @Nullable @MultiField(mainField = @Field(name="nick-name"), otherFields = {@InnerField(suffix = "keyword", type = FieldType.Keyword)}) String nickName; @Nullable @Field(name = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate; @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java index fdaa1b520..222b18376 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessorUnitTests.java @@ -365,6 +365,7 @@ void shouldBuildNestedQuery() throws JSONException { "}"; // Criteria criteria = new Criteria("houses.inhabitants.lastName").is("murphy"); + criteria.getField().setPath("houses.inhabitants"); String query = queryProcessor.createQuery(criteria).toString(); From 47824145969b3c7ecc62ada8ae3cfc98f409f84c Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Tue, 6 Apr 2021 20:50:24 +0200 Subject: [PATCH 040/776] CriteriaQuery must use nested query only with properties of type nested. Original Pull Request #1763 Closes #1761 --- .../MappingElasticsearchConverter.java | 13 ++++++- .../core/CriteriaQueryMappingUnitTests.java | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 1f3dc30d1..18286bb1c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -33,6 +33,7 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.CustomConversions; +import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.ScriptedField; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.document.SearchDocument; @@ -1055,12 +1056,22 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity ElasticsearchPersistentEntity currentEntity = persistentEntity; ElasticsearchPersistentProperty persistentProperty = null; int propertyCount = 0; + boolean isNested = false; + for (int i = 0; i < fieldNames.length; i++) { persistentProperty = currentEntity.getPersistentProperty(fieldNames[i]); if (persistentProperty != null) { propertyCount++; fieldNames[i] = persistentProperty.getFieldName(); + + org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = persistentProperty + .findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class); + + if (fieldAnnotation != null && fieldAnnotation.type() == FieldType.Nested) { + isNested = true; + } + try { currentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType()); } catch (Exception e) { @@ -1077,7 +1088,7 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity field.setName(String.join(".", fieldNames)); - if (propertyCount > 1) { + if (propertyCount > 1 && isNested) { List propertyNames = Arrays.asList(fieldNames); field.setPath(String.join(".", propertyNames.subList(0, propertyCount - 1))); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index 10f2344b6..a54ae7b63 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -397,6 +397,34 @@ void shouldMapNamesAndValueInNestedEntitiesWithSubfields() throws JSONException assertEquals(expected, queryString, false); } + + @Test // #1761 + @DisplayName("should map names and value in object entities") + void shouldMapNamesAndValueInObjectEntities() throws JSONException { + + String expected = "{\n" + // + " \"bool\": {\n" + // + " \"must\": [\n" + // + " {\n" + // + " \"query_string\": {\n" + // + " \"query\": \"03.10.1999\",\n" + // + " \"fields\": [\n" + // + " \"per-sons.birth-date^1.0\"\n" + // + " ]\n" + // + " }\n" + // + " }\n" + // + " ]\n" + // + " }\n" + // + "}\n"; // + + CriteriaQuery criteriaQuery = new CriteriaQuery( + new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)) + ); + mappingElasticsearchConverter.updateQuery(criteriaQuery, ObjectWithPerson.class); + String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); + + assertEquals(expected, queryString, false); + } // endregion // region helper functions @@ -427,6 +455,13 @@ static class House { List persons; } + static class ObjectWithPerson { + @Nullable @Id String id; + @Nullable + @Field(name = "per-sons", type = FieldType.Object) + List persons; + } + static class GeoShapeEntity { @Nullable @Field(name = "geo-shape-field") GeoJson geoShapeField; } From 58bca88386d9de7ea3946f7691c63bf31ce4ece2 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Thu, 8 Apr 2021 22:00:46 +0200 Subject: [PATCH 041/776] Fix reactive connection handling. Original Pull Request #1766 Closes #1759 --- .../DefaultReactiveElasticsearchClient.java | 24 +++++++- .../reactive/MultiNodeHostProvider.java | 56 ++++++++++++++----- 2 files changed, 65 insertions(+), 15 deletions(-) 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 3c9446d61..6203997aa 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 @@ -541,8 +541,7 @@ public Mono execute(ReactiveElasticsearchClientCallback callback) { .flatMap(callback::doWithClient) // .onErrorResume(throwable -> { - if (throwable instanceof ConnectException) { - + if (isCausedByConnectionException(throwable)) { return hostProvider.getActive(Verification.ACTIVE) // .flatMap(callback::doWithClient); } @@ -551,6 +550,27 @@ public Mono execute(ReactiveElasticsearchClientCallback callback) { }); } + /** + * checks if the given throwable is a {@link ConnectException} or has one in it's cause chain + * + * @param throwable the throwable to check + * @return true if throwable is caused by a {@link ConnectException} + */ + private boolean isCausedByConnectionException(Throwable throwable) { + + Throwable t = throwable; + do { + + if (t instanceof ConnectException) { + return true; + } + + t = t.getCause(); + } while (t != null); + + return false; + } + @Override public Mono status() { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/MultiNodeHostProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/MultiNodeHostProvider.java index 549944edd..4250c6bdb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/MultiNodeHostProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/MultiNodeHostProvider.java @@ -20,6 +20,7 @@ import reactor.util.function.Tuple2; import java.net.InetSocketAddress; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,6 +30,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.elasticsearch.client.ElasticsearchHost; import org.springframework.data.elasticsearch.client.ElasticsearchHost.State; import org.springframework.data.elasticsearch.client.NoReachableHostException; @@ -47,6 +50,8 @@ */ class MultiNodeHostProvider implements HostProvider { + private final static Logger LOG = LoggerFactory.getLogger(MultiNodeHostProvider.class); + private final WebClientProvider clientProvider; private final Supplier headersSupplier; private final Map hosts; @@ -60,6 +65,8 @@ class MultiNodeHostProvider implements HostProvider { for (InetSocketAddress endpoint : endpoints) { this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN)); } + + LOG.debug("initialized with " + hosts); } /* @@ -68,7 +75,7 @@ class MultiNodeHostProvider implements HostProvider { */ @Override public Mono clusterInfo() { - return nodes(null).map(this::updateNodeState).buffer(hosts.size()) + return checkNodes(null).map(this::updateNodeState).buffer(hosts.size()) .then(Mono.just(new ClusterInformation(new LinkedHashSet<>(this.hosts.values())))); } @@ -88,14 +95,19 @@ public WebClient createWebClient(InetSocketAddress endpoint) { @Override public Mono lookupActiveHost(Verification verification) { + LOG.trace("lookupActiveHost " + verification + " from " + hosts()); + if (Verification.LAZY.equals(verification)) { for (ElasticsearchHost entry : hosts()) { if (entry.isOnline()) { + LOG.trace("lookupActiveHost returning " + entry); return Mono.just(entry.getEndpoint()); } } + LOG.trace("no online host found with LAZY"); } + LOG.trace("searching for active host"); return findActiveHostInKnownActives() // .switchIfEmpty(findActiveHostInUnresolved()) // .switchIfEmpty(findActiveHostInDead()) // @@ -107,20 +119,30 @@ Collection getCachedHostState() { } private Mono findActiveHostInKnownActives() { - return findActiveForSate(State.ONLINE); + return findActiveForState(State.ONLINE); } private Mono findActiveHostInUnresolved() { - return findActiveForSate(State.UNKNOWN); + return findActiveForState(State.UNKNOWN); } private Mono findActiveHostInDead() { - return findActiveForSate(State.OFFLINE); + return findActiveForState(State.OFFLINE); } - private Mono findActiveForSate(State state) { - return nodes(state).map(this::updateNodeState).filter(ElasticsearchHost::isOnline) - .map(ElasticsearchHost::getEndpoint).next(); + private Mono findActiveForState(State state) { + + LOG.trace("findActiveForState state " + state + ", current hosts: " + hosts); + + return checkNodes(state) // + .map(this::updateNodeState) // + .filter(ElasticsearchHost::isOnline) // + .map(elasticsearchHost -> { + LOG.trace("findActiveForState returning host " + elasticsearchHost); + return elasticsearchHost; + }).map(ElasticsearchHost::getEndpoint) // + .takeLast(1) // + .next(); } private ElasticsearchHost updateNodeState(Tuple2 tuple2) { @@ -131,28 +153,36 @@ private ElasticsearchHost updateNodeState(Tuple2 tuple return elasticsearchHost; } - private Flux> nodes(@Nullable State state) { + private Flux> checkNodes(@Nullable State state) { + + LOG.trace("checkNodes() with state " + state); return Flux.fromIterable(hosts()) // .filter(entry -> state == null || entry.getState().equals(state)) // .map(ElasticsearchHost::getEndpoint) // - .flatMap(host -> { + .concatMap(host -> { + + LOG.trace("checking host " + host); Mono clientResponseMono = createWebClient(host) // .head().uri("/") // .headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) // .exchangeToMono(Mono::just) // + .timeout(Duration.ofSeconds(1)) // .doOnError(throwable -> { + LOG.trace("error checking host " + host + ", " + throwable.getMessage()); hosts.put(host, new ElasticsearchHost(host, State.OFFLINE)); clientProvider.getErrorListener().accept(throwable); }); return Mono.just(host) // - .zipWith( // - clientResponseMono.flatMap(it -> it.releaseBody() // - .thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE))); + .zipWith(clientResponseMono.flatMap(it -> it.releaseBody() // + .thenReturn(it.statusCode().isError() ? State.OFFLINE : State.ONLINE))); }) // - .onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable)); + .map(tuple -> { + LOG.trace("check result " + tuple); + return tuple; + }).onErrorContinue((throwable, o) -> clientProvider.getErrorListener().accept(throwable)); } private List hosts() { From d561c91678a3dfa686faa9a92eb7777f3b611afc Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 11 Apr 2021 11:05:37 +0200 Subject: [PATCH 042/776] Introduce cluster operations. Original Pull Request: #1768 Closes #1390 --- .../DefaultReactiveElasticsearchClient.java | 76 ++---- .../reactive/ReactiveElasticsearchClient.java | 51 ++++ .../client/reactive/RequestCreator.java | 8 + .../client/util/RequestConverters.java | 25 +- .../core/ElasticsearchOperations.java | 18 +- .../core/ElasticsearchRestTemplate.java | 8 + .../core/ElasticsearchTemplate.java | 8 + .../core/ReactiveElasticsearchOperations.java | 41 ++- .../core/ReactiveElasticsearchTemplate.java | 26 +- .../elasticsearch/core/ResponseConverter.java | 26 +- .../core/cluster/ClusterHealth.java | 248 ++++++++++++++++++ .../core/cluster/ClusterOperations.java | 62 +++++ .../cluster/DefaultClusterOperations.java | 45 ++++ .../DefaultReactiveClusterOperations.java | 42 +++ .../DefaultTransportClusterOperations.java | 45 ++++ .../cluster/ReactiveClusterOperations.java | 34 +++ .../core/cluster/package-info.java | 6 + .../ClusterOperationsIntegrationTests.java | 56 ++++ ...erOperationsTransportIntegrationTests.java | 25 ++ ...tiveClusterOperationsIntegrationTests.java | 62 +++++ 20 files changed, 842 insertions(+), 70 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperations.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultClusterOperations.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultReactiveClusterOperations.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultTransportClusterOperations.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperations.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/cluster/package-info.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsTransportIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperationsIntegrationTests.java diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultReactiveElasticsearchClient.java index 6203997aa..a7e69670f 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 @@ -45,6 +45,8 @@ import org.apache.http.util.EntityUtils; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.close.CloseIndexRequest; @@ -105,6 +107,7 @@ import org.springframework.data.elasticsearch.client.ElasticsearchHost; import org.springframework.data.elasticsearch.client.NoReachableHostException; import org.springframework.data.elasticsearch.client.reactive.HostProvider.Verification; +import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Cluster; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices; import org.springframework.data.elasticsearch.client.util.NamedXContents; import org.springframework.data.elasticsearch.client.util.ScrollState; @@ -142,7 +145,7 @@ * @see ClientConfiguration * @see ReactiveRestClients */ -public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices { +public class DefaultReactiveElasticsearchClient implements ReactiveElasticsearchClient, Indices, Cluster { private final HostProvider hostProvider; private final RequestCreator requestCreator; @@ -297,10 +300,6 @@ public void setHeadersSupplier(Supplier headersSupplier) { this.headersSupplier = headersSupplier; } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders) - */ @Override public Mono ping(HttpHeaders headers) { @@ -309,10 +308,6 @@ public Mono ping(HttpHeaders headers) { .onErrorResume(NoReachableHostException.class, error -> Mono.just(false)).next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#info(org.springframework.http.HttpHeaders) - */ @Override public Mono info(HttpHeaders headers) { @@ -320,10 +315,6 @@ public Mono info(HttpHeaders headers) { .next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#get(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.GetRequest) - */ @Override public Mono get(HttpHeaders headers, GetRequest getRequest) { @@ -341,10 +332,6 @@ public Flux multiGet(HttpHeaders headers, MultiGetRequest .flatMap(Flux::fromArray); // } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#exists(org.springframework.http.HttpHeaders, org.elasticsearch.action.get.GetRequest) - */ @Override public Mono exists(HttpHeaders headers, GetRequest getRequest) { @@ -353,37 +340,26 @@ public Mono exists(HttpHeaders headers, GetRequest getRequest) { .next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.index.IndexRequest) - */ @Override public Mono index(HttpHeaders headers, IndexRequest indexRequest) { return sendRequest(indexRequest, requestCreator.index(), IndexResponse.class, headers).next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#indices() - */ @Override public Indices indices() { return this; } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.update.UpdateRequest) - */ + @Override + public Cluster cluster() { + return this; + } + @Override public Mono update(HttpHeaders headers, UpdateRequest updateRequest) { return sendRequest(updateRequest, requestCreator.update(), UpdateResponse.class, headers).next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.delete.DeleteRequest) - */ @Override public Mono delete(HttpHeaders headers, DeleteRequest deleteRequest) { @@ -391,10 +367,6 @@ public Mono delete(HttpHeaders headers, DeleteRequest deleteRequ .next(); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#count(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest) - */ @Override public Mono count(HttpHeaders headers, SearchRequest searchRequest) { searchRequest.source().trackTotalHits(true); @@ -412,10 +384,6 @@ public Flux searchTemplate(HttpHeaders headers, SearchTemplateRequest .map(response -> response.getResponse().getHits()).flatMap(Flux::fromIterable); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest) - */ @Override public Flux search(HttpHeaders headers, SearchRequest searchRequest) { @@ -435,10 +403,6 @@ public Flux suggest(HttpHeaders headers, SearchRequest searchRequest) { .map(SearchResponse::getSuggest); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#aggregate(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest) - */ @Override public Flux aggregate(HttpHeaders headers, SearchRequest searchRequest) { @@ -453,10 +417,6 @@ public Flux aggregate(HttpHeaders headers, SearchRequest searchRequ .flatMap(Flux::fromIterable); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#scroll(org.springframework.http.HttpHeaders, org.elasticsearch.action.search.SearchRequest) - */ @Override public Flux scroll(HttpHeaders headers, SearchRequest searchRequest) { @@ -506,10 +466,6 @@ private Publisher cleanupScroll(HttpHeaders headers, ScrollState state) { return sendRequest(clearScrollRequest, requestCreator.clearScroll(), ClearScrollResponse.class, headers); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#ping(org.springframework.http.HttpHeaders, org.elasticsearch.index.reindex.DeleteByQueryRequest) - */ @Override public Mono deleteBy(HttpHeaders headers, DeleteByQueryRequest deleteRequest) { @@ -524,10 +480,6 @@ public Mono updateBy(HttpHeaders headers, UpdateByQueryRequest .map(ByQueryResponse::of); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient#bulk(org.springframework.http.HttpHeaders, org.elasticsearch.action.bulk.BulkRequest) - */ @Override public Mono bulk(HttpHeaders headers, BulkRequest bulkRequest) { return sendRequest(bulkRequest, requestCreator.bulk(), BulkResponse.class, headers) // @@ -812,6 +764,14 @@ public Mono getIndex(HttpHeaders headers, GetIndexRequest getI // endregion + // region cluster operations + @Override + public Mono health(HttpHeaders headers, ClusterHealthRequest clusterHealthRequest) { + return sendRequest(clusterHealthRequest, requestCreator.clusterHealth(), ClusterHealthResponse.class, headers) + .next(); + } + // endregion + // region helper functions private Publisher readResponseBody(String logId, Request request, ClientResponse response, Class responseType) { @@ -965,7 +925,7 @@ private static ElasticsearchException getElasticsearchException(String content, } while (token == XContentParser.Token.FIELD_NAME); return null; - } catch (IOException e) { + } catch (Exception e) { return new ElasticsearchStatusException(content, status); } } 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 c8f4ae9f2..201513ef0 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 @@ -22,6 +22,8 @@ import java.util.Collection; import java.util.function.Consumer; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.close.CloseIndexRequest; @@ -269,6 +271,14 @@ default Mono index(IndexRequest indexRequest) { */ Indices indices(); + /** + * Gain Access to cluster related commands. + * + * @return Cluster implementations + * @since 4.2 + */ + Cluster cluster(); + /** * Execute an {@link UpdateRequest} against the {@literal update} API to alter a document. * @@ -1678,4 +1688,45 @@ default Mono getIndex(GetIndexRequest getIndexRequest) { */ Mono getIndex(HttpHeaders headers, GetIndexRequest getIndexRequest); } + + /** + * Encapsulation of methods for accessing the Cluster API. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ + interface Cluster { + + /** + * Execute the given {{@link ClusterHealthRequest}} against the {@literal cluster} API. + * + * @param consumer never {@literal null}. + * @return Mono emitting the {@link ClusterHealthResponse}. + */ + default Mono health(Consumer consumer) { + + ClusterHealthRequest clusterHealthRequest = new ClusterHealthRequest(); + consumer.accept(clusterHealthRequest); + return health(clusterHealthRequest); + } + + /** + * Execute the given {{@link ClusterHealthRequest}} against the {@literal cluster} API. + * + * @param clusterHealthRequest must not be {@literal null} // * @return Mono emitting the + * {@link ClusterHealthResponse}. + */ + default Mono health(ClusterHealthRequest clusterHealthRequest) { + return health(HttpHeaders.EMPTY, clusterHealthRequest); + } + + /** + * Execute the given {{@link ClusterHealthRequest}} against the {@literal cluster} API. + * + * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. + * @param clusterHealthRequest must not be {@literal null} // * @return Mono emitting the + * {@link ClusterHealthResponse}. + */ + Mono health(HttpHeaders headers, ClusterHealthRequest clusterHealthRequest); + } } 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 1d5363137..61f5ac25e 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 @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.function.Function; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.close.CloseIndexRequest; @@ -266,4 +267,11 @@ default Function getFieldMapping() { default Function getIndex() { return RequestConverters::getIndex; } + + /** + * @since 4.2 + */ + default Function clusterHealth() { + return RequestConverters::clusterHealth; + } } 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 ea2e5df32..f77b77c82 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 @@ -107,8 +107,9 @@ /** *

- * Original implementation source {@link org.elasticsearch.client.RequestConverters} and - * {@link org.elasticsearch.client.IndicesRequestConverters} by {@literal Elasticsearch} + * Original implementation source {@link org.elasticsearch.client.RequestConverters}, + * {@link org.elasticsearch.client.IndicesRequestConverters} and + * {@link org.elasticsearch.client.ClusterRequestConverters} by {@literal Elasticsearch} * (https://www.elastic.co) licensed under the Apache License, Version 2.0. *

* Modified for usage with {@link ReactiveElasticsearchClient}. @@ -1003,6 +1004,26 @@ public static Request getFieldMapping(GetFieldMappingsRequest getFieldMappingsRe return request; } + public static Request clusterHealth(ClusterHealthRequest healthRequest) { + String[] indices = healthRequest.indices() == null ? Strings.EMPTY_ARRAY : healthRequest.indices(); + String endpoint = new EndpointBuilder().addPathPartAsIs(new String[] { "_cluster/health" }) + .addCommaSeparatedPathParts(indices).build(); + + Request request = new Request("GET", endpoint); + + RequestConverters.Params parameters = new Params(request); + parameters.withWaitForStatus(healthRequest.waitForStatus()); + parameters.withWaitForNoRelocatingShards(healthRequest.waitForNoRelocatingShards()); + parameters.withWaitForNoInitializingShards(healthRequest.waitForNoInitializingShards()); + parameters.withWaitForActiveShards(healthRequest.waitForActiveShards(), ActiveShardCount.NONE); + parameters.withWaitForNodes(healthRequest.waitForNodes()); + parameters.withWaitForEvents(healthRequest.waitForEvents()); + parameters.withTimeout(healthRequest.timeout()); + parameters.withMasterTimeout(healthRequest.masterNodeTimeout()); + parameters.withLocal(healthRequest.local()).withLevel(healthRequest.level()); + return request; + } + static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) { try { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java index 29a26270e..557dc1fbc 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java @@ -17,6 +17,7 @@ import java.util.Objects; +import org.springframework.data.elasticsearch.core.cluster.ClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.routing.RoutingResolver; @@ -39,25 +40,34 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera /** * get an {@link IndexOperations} that is bound to the given class - * + * * @return IndexOperations */ IndexOperations indexOps(Class clazz); /** * get an {@link IndexOperations} that is bound to the given index - * + * * @return IndexOperations */ IndexOperations indexOps(IndexCoordinates index); + /** + * return a {@link ClusterOperations} instance that uses the same client communication setup as this + * ElasticsearchOperations instance. + * + * @return ClusterOperations implementation + * @since 4.2 + */ + ClusterOperations cluster(); + ElasticsearchConverter getElasticsearchConverter(); IndexCoordinates getIndexCoordinatesFor(Class clazz); /** * gets the routing for an entity which might be defined by a join-type relation - * + * * @param entity the entity * @return the routing, may be null if not set. * @since 4.1 @@ -68,7 +78,7 @@ public interface ElasticsearchOperations extends DocumentOperations, SearchOpera // region helper /** * gets the String representation for an id. - * + * * @param id * @return * @since 4.0 diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 18360dfc6..f4826db23 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -44,6 +44,7 @@ import org.elasticsearch.search.suggest.SuggestBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.elasticsearch.core.cluster.ClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; @@ -142,6 +143,13 @@ public IndexOperations indexOps(IndexCoordinates index) { } // endregion + // region ClusterOperations + @Override + public ClusterOperations cluster() { + return ClusterOperations.forTemplate(this); + } + // endregion + // region DocumentOperations public String doIndex(IndexQuery query, IndexCoordinates index) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index d0d4852a5..70911a12e 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -41,6 +41,7 @@ import org.elasticsearch.search.suggest.SuggestBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.elasticsearch.core.cluster.ClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; @@ -142,6 +143,13 @@ public IndexOperations indexOps(IndexCoordinates index) { } // endregion + // region ClusterOperations + @Override + public ClusterOperations cluster() { + return ClusterOperations.forTemplate(this); + } + // endregion + // region getter/setter @Nullable public String getSearchTimeout() { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java index 37862b212..11bea3f97 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java @@ -17,6 +17,7 @@ import org.reactivestreams.Publisher; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; +import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; @@ -56,6 +57,16 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati */ Publisher executeWithIndicesClient(IndicesClientCallback> callback); + /** + * Execute within a {@link ClusterClientCallback} managing resources and translating errors. + * + * @param callback must not be {@literal null}. + * @param the type the Publisher emits + * @return the {@link Publisher} emitting results. + * @since 4.1 + */ + Publisher executeWithClusterClient(ClusterClientCallback> callback); + /** * Get the {@link ElasticsearchConverter} used. * @@ -75,6 +86,7 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati /** * Creates a {@link ReactiveIndexOperations} that is bound to the given index + * * @param index IndexCoordinates specifying the index * @return ReactiveIndexOperations implementation * @since 4.1 @@ -83,13 +95,23 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati /** * Creates a {@link ReactiveIndexOperations} that is bound to the given class + * * @param clazz the entity clazz specifiying the index information * @return ReactiveIndexOperations implementation * @since 4.1 */ ReactiveIndexOperations indexOps(Class clazz); - //region routing + /** + * return a {@link ReactiveClusterOperations} instance that uses the same client communication setup as this + * ElasticsearchOperations instance. + * + * @return ClusterOperations implementation + * @since 4.2 + */ + ReactiveClusterOperations cluster(); + + // region routing /** * Returns a copy of this instance with the same configuration, but that uses a different {@link RoutingResolver} to * obtain routing information. @@ -98,7 +120,7 @@ public interface ReactiveElasticsearchOperations extends ReactiveDocumentOperati * @return DocumentOperations instance */ ReactiveElasticsearchOperations withRouting(RoutingResolver routingResolver); - //endregion + // endregion /** * Callback interface to be used with {@link #execute(ClientCallback)} for operating directly on @@ -114,8 +136,8 @@ interface ClientCallback> { } /** - * Callback interface to be used with {@link #executeWithIndicesClient(IndicesClientCallback)} for operating directly on - * {@link ReactiveElasticsearchClient.Indices}. + * Callback interface to be used with {@link #executeWithIndicesClient(IndicesClientCallback)} for operating directly + * on {@link ReactiveElasticsearchClient.Indices}. * * @param the return type * @since 4.1 @@ -123,4 +145,15 @@ interface ClientCallback> { interface IndicesClientCallback> { T doWithClient(ReactiveElasticsearchClient.Indices client); } + + /** + * Callback interface to be used with {@link #executeWithClusterClient(ClusterClientCallback)} for operating directly + * on {@link ReactiveElasticsearchClient.Cluster}. + * + * @param the return type + * @since 4.2 + */ + interface ClusterClientCallback> { + T doWithClient(ReactiveElasticsearchClient.Cluster client); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 925f6c948..47d129f6c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -57,6 +57,8 @@ import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; import org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity; +import org.springframework.data.elasticsearch.core.cluster.DefaultReactiveClusterOperations; +import org.springframework.data.elasticsearch.core.cluster.ReactiveClusterOperations; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; @@ -921,6 +923,11 @@ public Publisher executeWithIndicesClient(IndicesClientCallback callback.doWithClient(getIndicesClient())).onErrorMap(this::translateException); } + @Override + public Publisher executeWithClusterClient(ClusterClientCallback> callback) { + return Flux.defer(() -> callback.doWithClient(getClusterClient())).onErrorMap(this::translateException); + } + @Override public ElasticsearchConverter getElasticsearchConverter() { return converter; @@ -936,6 +943,11 @@ public ReactiveIndexOperations indexOps(Class clazz) { return new DefaultReactiveIndexOperations(this, clazz); } + @Override + public ReactiveClusterOperations cluster() { + return new DefaultReactiveClusterOperations(this); + } + @Override public IndexCoordinates getIndexCoordinatesFor(Class clazz) { return getPersistentEntityFor(clazz).getIndexCoordinates(); @@ -970,7 +982,19 @@ protected ReactiveElasticsearchClient.Indices getIndicesClient() { throw new UncategorizedElasticsearchException("No ReactiveElasticsearchClient.Indices implementation available"); } - // endregion + /** + * Obtain the {@link ReactiveElasticsearchClient.Cluster} to operate upon. + * + * @return never {@literal null}. + */ + protected ReactiveElasticsearchClient.Cluster getClusterClient() { + + if (client instanceof ReactiveElasticsearchClient.Cluster) { + return (ReactiveElasticsearchClient.Cluster) client; + } + + throw new UncategorizedElasticsearchException("No ReactiveElasticsearchClient.Cluster implementation available"); + } /** * translates an Exception if possible. Exceptions that are no {@link RuntimeException}s are wrapped in a diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java index 546109602..1b8dfb73b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.stream.Collectors; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetResponse; @@ -36,6 +37,7 @@ import org.elasticsearch.cluster.metadata.MappingMetadata; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.compress.CompressedXContent; +import org.springframework.data.elasticsearch.core.cluster.ClusterHealth; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasData; import org.springframework.data.elasticsearch.core.index.Settings; @@ -109,7 +111,7 @@ public static List getIndexInformations(GetIndexResponse getIn * @return a document that represents {@link Settings} */ private static Settings settingsFromGetIndexResponse(GetIndexResponse getIndexResponse, String indexName) { - Settings settings= new Settings(); + Settings settings = new Settings(); org.elasticsearch.common.settings.Settings indexSettings = getIndexResponse.getSettings().get(indexName); @@ -289,4 +291,26 @@ public static MultiGetItem.Failure getFailure(MultiGetItemResponse itemResponse) } // endregion + // region cluster operations + public static ClusterHealth clusterHealth(ClusterHealthResponse clusterHealthResponse) { + return ClusterHealth.builder() // + .withActivePrimaryShards(clusterHealthResponse.getActivePrimaryShards()) // + .withActiveShards(clusterHealthResponse.getActiveShards()) // + .withActiveShardsPercent(clusterHealthResponse.getActiveShardsPercent()) // + .withClusterName(clusterHealthResponse.getClusterName()) // + .withDelayedUnassignedShards(clusterHealthResponse.getDelayedUnassignedShards()) // + .withInitializingShards(clusterHealthResponse.getInitializingShards()) // + .withNumberOfDataNodes(clusterHealthResponse.getNumberOfDataNodes()) // + .withNumberOfInFlightFetch(clusterHealthResponse.getNumberOfInFlightFetch()) // + .withNumberOfNodes(clusterHealthResponse.getNumberOfNodes()) // + .withNumberOfPendingTasks(clusterHealthResponse.getNumberOfPendingTasks()) // + .withRelocatingShards(clusterHealthResponse.getRelocatingShards()) // + .withStatus(clusterHealthResponse.getStatus().toString()) // + .withTaskMaxWaitingTimeMillis(clusterHealthResponse.getTaskMaxWaitingTime().millis()) // + .withTimedOut(clusterHealthResponse.isTimedOut()) // + .withUnassignedShards(clusterHealthResponse.getUnassignedShards()) // + .build(); // + + } + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java new file mode 100644 index 000000000..a2acc29af --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterHealth.java @@ -0,0 +1,248 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +/** + * Information about the cluster health. Contains currently only the top level elements returned from Elasticsearch. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +public class ClusterHealth { + + private final String clusterName; + private final String status; + private final int numberOfNodes; + private final int numberOfDataNodes; + private final int activeShards; + private final int relocatingShards; + private final int activePrimaryShards; + private final int initializingShards; + private final int unassignedShards; + private final double activeShardsPercent; + private final int numberOfPendingTasks; + private final boolean timedOut; + private final int numberOfInFlightFetch; + private final int delayedUnassignedShards; + private final long taskMaxWaitingTimeMillis; + + private ClusterHealth(String clusterName, String status, int numberOfNodes, int numberOfDataNodes, int activeShards, + int relocatingShards, int activePrimaryShards, int initializingShards, int unassignedShards, + double activeShardsPercent, int numberOfPendingTasks, boolean timedOut, int numberOfInFlightFetch, + int delayedUnassignedShards, long taskMaxWaitingTimeMillis) { + this.clusterName = clusterName; + this.status = status; + this.numberOfNodes = numberOfNodes; + this.numberOfDataNodes = numberOfDataNodes; + this.activeShards = activeShards; + this.relocatingShards = relocatingShards; + this.activePrimaryShards = activePrimaryShards; + this.initializingShards = initializingShards; + this.unassignedShards = unassignedShards; + this.activeShardsPercent = activeShardsPercent; + this.numberOfPendingTasks = numberOfPendingTasks; + this.timedOut = timedOut; + this.numberOfInFlightFetch = numberOfInFlightFetch; + this.delayedUnassignedShards = delayedUnassignedShards; + this.taskMaxWaitingTimeMillis = taskMaxWaitingTimeMillis; + } + + public String getClusterName() { + return clusterName; + } + + public String getStatus() { + return status; + } + + public int getNumberOfNodes() { + return numberOfNodes; + } + + public int getNumberOfDataNodes() { + return numberOfDataNodes; + } + + public int getActiveShards() { + return activeShards; + } + + public int getRelocatingShards() { + return relocatingShards; + } + + public int getActivePrimaryShards() { + return activePrimaryShards; + } + + public int getInitializingShards() { + return initializingShards; + } + + public int getUnassignedShards() { + return unassignedShards; + } + + public double getActiveShardsPercent() { + return activeShardsPercent; + } + + public int getNumberOfPendingTasks() { + return numberOfPendingTasks; + } + + public boolean isTimedOut() { + return timedOut; + } + + public int getNumberOfInFlightFetch() { + return numberOfInFlightFetch; + } + + public int getDelayedUnassignedShards() { + return delayedUnassignedShards; + } + + public long getTaskMaxWaitingTimeMillis() { + return taskMaxWaitingTimeMillis; + } + + @Override + public String toString() { + return "ClusterHealth{" + + "clusterName='" + clusterName + '\'' + + ", status='" + status + '\'' + + ", numberOfNodes=" + numberOfNodes + + ", numberOfDataNodes=" + numberOfDataNodes + + ", activeShards=" + activeShards + + ", relocatingShards=" + relocatingShards + + ", activePrimaryShards=" + activePrimaryShards + + ", initializingShards=" + initializingShards + + ", unassignedShards=" + unassignedShards + + ", activeShardsPercent=" + activeShardsPercent + + ", numberOfPendingTasks=" + numberOfPendingTasks + + ", timedOut=" + timedOut + + ", numberOfInFlightFetch=" + numberOfInFlightFetch + + ", delayedUnassignedShards=" + delayedUnassignedShards + + ", taskMaxWaitingTimeMillis=" + taskMaxWaitingTimeMillis + + '}'; + } + + public static ClusterHealthBuilder builder() { + return new ClusterHealthBuilder(); + } + + public static final class ClusterHealthBuilder { + private String clusterName = ""; + private String status = ""; + private int numberOfNodes; + private int numberOfDataNodes; + private int activeShards; + private int relocatingShards; + private int activePrimaryShards; + private int initializingShards; + private int unassignedShards; + private double activeShardsPercent; + private int numberOfPendingTasks; + private boolean timedOut; + private int numberOfInFlightFetch; + private int delayedUnassignedShards; + private long taskMaxWaitingTimeMillis; + + private ClusterHealthBuilder() {} + + public ClusterHealthBuilder withClusterName(String clusterName) { + this.clusterName = clusterName; + return this; + } + + public ClusterHealthBuilder withStatus(String status) { + this.status = status; + return this; + } + + public ClusterHealthBuilder withNumberOfNodes(int numberOfNodes) { + this.numberOfNodes = numberOfNodes; + return this; + } + + public ClusterHealthBuilder withNumberOfDataNodes(int numberOfDataNodes) { + this.numberOfDataNodes = numberOfDataNodes; + return this; + } + + public ClusterHealthBuilder withActiveShards(int activeShards) { + this.activeShards = activeShards; + return this; + } + + public ClusterHealthBuilder withRelocatingShards(int relocatingShards) { + this.relocatingShards = relocatingShards; + return this; + } + + public ClusterHealthBuilder withActivePrimaryShards(int activePrimaryShards) { + this.activePrimaryShards = activePrimaryShards; + return this; + } + + public ClusterHealthBuilder withInitializingShards(int initializingShards) { + this.initializingShards = initializingShards; + return this; + } + + public ClusterHealthBuilder withUnassignedShards(int unassignedShards) { + this.unassignedShards = unassignedShards; + return this; + } + + public ClusterHealthBuilder withActiveShardsPercent(double activeShardsPercent) { + this.activeShardsPercent = activeShardsPercent; + return this; + } + + public ClusterHealthBuilder withNumberOfPendingTasks(int numberOfPendingTasks) { + this.numberOfPendingTasks = numberOfPendingTasks; + return this; + } + + public ClusterHealthBuilder withTimedOut(boolean timedOut) { + this.timedOut = timedOut; + return this; + } + + public ClusterHealthBuilder withNumberOfInFlightFetch(int numberOfInFlightFetch) { + this.numberOfInFlightFetch = numberOfInFlightFetch; + return this; + } + + public ClusterHealthBuilder withDelayedUnassignedShards(int delayedUnassignedShards) { + this.delayedUnassignedShards = delayedUnassignedShards; + return this; + } + + public ClusterHealthBuilder withTaskMaxWaitingTimeMillis(long taskMaxWaitingTimeMillis) { + this.taskMaxWaitingTimeMillis = taskMaxWaitingTimeMillis; + return this; + } + + public ClusterHealth build() { + return new ClusterHealth(clusterName, status, numberOfNodes, numberOfDataNodes, activeShards, relocatingShards, + activePrimaryShards, initializingShards, unassignedShards, activeShardsPercent, numberOfPendingTasks, + timedOut, numberOfInFlightFetch, delayedUnassignedShards, taskMaxWaitingTimeMillis); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperations.java new file mode 100644 index 000000000..5a6919a20 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperations.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.util.Assert; + +/** + * Elasticsearch operations on cluster level. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +public interface ClusterOperations { + + /** + * Creates a ClusterOperations for a {@link ElasticsearchRestTemplate}. + * + * @param template the template, must not be {@literal null} + * @return ClusterOperations + */ + static ClusterOperations forTemplate(ElasticsearchRestTemplate template) { + + Assert.notNull(template, "template must not be null"); + + return new DefaultClusterOperations(template); + } + + /** + * Creates a ClusterOperations for a {@link ElasticsearchTemplate}. + * + * @param template the template, must not be {@literal null} + * @return ClusterOperations + */ + static ClusterOperations forTemplate(ElasticsearchTemplate template) { + + Assert.notNull(template, "template must not be null"); + + return new DefaultTransportClusterOperations(template); + } + + /** + * get the cluster's health status. + * + * @return health information for the cluster. + */ + ClusterHealth health(); +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultClusterOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultClusterOperations.java new file mode 100644 index 000000000..0c0590a27 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultClusterOperations.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.client.RequestOptions; +import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; +import org.springframework.data.elasticsearch.core.ResponseConverter; + +/** + * Default implementation of {@link ClusterOperations} using the {@link ElasticsearchRestTemplate}. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +class DefaultClusterOperations implements ClusterOperations { + + private final ElasticsearchRestTemplate template; + + DefaultClusterOperations(ElasticsearchRestTemplate template) { + this.template = template; + } + + @Override + public ClusterHealth health() { + + ClusterHealthResponse clusterHealthResponse = template + .execute(client -> client.cluster().health(new ClusterHealthRequest(), RequestOptions.DEFAULT)); + return ResponseConverter.clusterHealth(clusterHealthResponse); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultReactiveClusterOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultReactiveClusterOperations.java new file mode 100644 index 000000000..689b18545 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultReactiveClusterOperations.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import reactor.core.publisher.Mono; + +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.ResponseConverter; + +/** + * Default implementation of {@link ReactiveClusterOperations} using the {@link ReactiveElasticsearchOperations}. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +public class DefaultReactiveClusterOperations implements ReactiveClusterOperations { + private final ReactiveElasticsearchOperations operations; + + public DefaultReactiveClusterOperations(ReactiveElasticsearchOperations operations) { + this.operations = operations; + } + + @Override + public Mono health() { + return Mono.from(operations.executeWithClusterClient( + client -> client.health(new ClusterHealthRequest()).map(ResponseConverter::clusterHealth))); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultTransportClusterOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultTransportClusterOperations.java new file mode 100644 index 000000000..9edb916e2 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/DefaultTransportClusterOperations.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; +import org.springframework.data.elasticsearch.core.ResponseConverter; + +/** + * Default implementation of {@link ClusterOperations} using the + * {@link org.elasticsearch.client.transport.TransportClient}. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +public class DefaultTransportClusterOperations implements ClusterOperations { + + private final ElasticsearchTemplate template; + + public DefaultTransportClusterOperations(ElasticsearchTemplate template) { + this.template = template; + } + + @Override + public ClusterHealth health() { + + ClusterHealthResponse clusterHealthResponse = template.getClient().admin().cluster() + .health(new ClusterHealthRequest()).actionGet(); + return ResponseConverter.clusterHealth(clusterHealthResponse); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperations.java new file mode 100644 index 000000000..85d561d5d --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperations.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import reactor.core.publisher.Mono; + +/** + * Reactive Elasticsearch operations on cluster level. + * + * @author Peter-Josef Meisch + * @since 4.2 + */ +public interface ReactiveClusterOperations { + + /** + * get the cluster's health status. + * + * @return a Mono emitting the health information for the cluster. + */ + Mono health(); +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/cluster/package-info.java b/src/main/java/org/springframework/data/elasticsearch/core/cluster/package-info.java new file mode 100644 index 000000000..6d49233c5 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/cluster/package-info.java @@ -0,0 +1,6 @@ +/** + * Interfaces and classes related to Elasticsearch cluster information and management. + */ +@org.springframework.lang.NonNullApi +@org.springframework.lang.NonNullFields +package org.springframework.data.elasticsearch.core.cluster; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsIntegrationTests.java new file mode 100644 index 000000000..673109223 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsIntegrationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) +public class ClusterOperationsIntegrationTests { + + @Autowired private ElasticsearchOperations operations; + private ClusterOperations clusterOperations; + + @BeforeEach + void setUp() { + clusterOperations = operations.cluster(); + } + + @Test // #1390 + @DisplayName("should return cluster health information") + void shouldReturnClusterHealthInformation() { + + ClusterHealth clusterHealth = clusterOperations.health(); + + List allowedStates = Arrays.asList("GREEN", "YELLOW"); + assertThat(allowedStates).contains(clusterHealth.getStatus()); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsTransportIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsTransportIntegrationTests.java new file mode 100644 index 000000000..7219eeaa5 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ClusterOperationsTransportIntegrationTests.java @@ -0,0 +1,25 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +public class ClusterOperationsTransportIntegrationTests extends ClusterOperationsIntegrationTests {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperationsIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperationsIntegrationTests.java new file mode 100644 index 000000000..4ff7b89fc --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/cluster/ReactiveClusterOperationsIntegrationTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.cluster; + +import static org.assertj.core.api.Assertions.*; + +import reactor.test.StepVerifier; + +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.test.context.ContextConfiguration; + +/** + * @author Peter-Josef Meisch + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) +public class ReactiveClusterOperationsIntegrationTests { + + @Autowired private ReactiveElasticsearchOperations operations; + private ReactiveClusterOperations clusterOperations; + + @BeforeEach + void setUp() { + clusterOperations = operations.cluster(); + } + + @Test // #1390 + @DisplayName("should return cluster health information") + void shouldReturnClusterHealthInformation() { + + List allowedStates = Arrays.asList("GREEN", "YELLOW"); + + clusterOperations.health() // + .as(StepVerifier::create) // + .consumeNextWith(clusterHealth -> { // + assertThat(allowedStates).contains(clusterHealth.getStatus()); // + }) // + .verifyComplete(); + } +} From 010c0cb6ad97e9bcc1667c6fa37e8f33846ef761 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Tue, 13 Apr 2021 20:53:39 +0200 Subject: [PATCH 043/776] Remove @Persistent from entity-scan include filters (#1772) Original PR: #1772 Closes: #1771 --- .../config/ElasticsearchConfigurationSupport.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java index 5697a5a33..bf7ca3dbf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java @@ -120,8 +120,7 @@ protected Set> getInitialEntitySet() { } /** - * Scans the given base package for entities, i.e. Elasticsearch specific types annotated with {@link Document} and - * {@link Persistent}. + * Scans the given base package for entities, i.e. Elasticsearch specific types annotated with {@link Document}. * * @param basePackage must not be {@literal null}. * @return never {@literal null}. @@ -137,7 +136,6 @@ protected Set> scanForEntities(String basePackage) { ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider( false); componentProvider.addIncludeFilter(new AnnotationTypeFilter(Document.class)); - componentProvider.addIncludeFilter(new AnnotationTypeFilter(Persistent.class)); for (BeanDefinition candidate : componentProvider.findCandidateComponents(basePackage)) { From 84391ae62a9f8bdf501581bb85b243e26a9cb97c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 11:04:05 +0200 Subject: [PATCH 044/776] Updated changelog. See #1730 --- src/main/resources/changelog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index f9a4a9cda..740013a42 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,11 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.0.9.RELEASE (2021-04-14) +--------------------------------------------- +* #1759 - health check with DefaultReactiveElasticsearchClient. + + Changes in version 4.1.7 (2021-03-31) ------------------------------------- @@ -1576,5 +1581,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 58f10128746f6818a29dbe8351bf712673fb4810 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 11:32:46 +0200 Subject: [PATCH 045/776] Updated changelog. See #1751 --- src/main/resources/changelog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 740013a42..621b73670 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,11 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.1.8 (2021-04-14) +------------------------------------- +* #1759 - health check with DefaultReactiveElasticsearchClient. + + Changes in version 4.0.9.RELEASE (2021-04-14) --------------------------------------------- * #1759 - health check with DefaultReactiveElasticsearchClient. @@ -1582,5 +1587,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 4e6df37e2a3978b322ccfb5bd4dbc5248efb47f7 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 14:18:19 +0200 Subject: [PATCH 046/776] Updated changelog. See #1750 --- src/main/resources/changelog.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 621b73670..a3615b6e1 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,18 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.2.0 (2021-04-14) +------------------------------------- +* #1771 - Remove `@Persistent` from entity-scan include filters. +* #1761 - CriteriaQuery must use nested query only with properties of type nested. +* #1759 - health check with DefaultReactiveElasticsearchClient. +* #1758 - Nested Criteria queries must consider sub-fields. +* #1755 - Documentation fix to not show deprecated calls. +* #1754 - Types are in the process of being removed. +* #1753 - CriteriaQuery must support nested queries. +* #1390 - Introduce ClusterOperations [DATAES-818]. + + Changes in version 4.1.8 (2021-04-14) ------------------------------------- * #1759 - health check with DefaultReactiveElasticsearchClient. @@ -1588,5 +1600,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 03da6535cdc7b58b053cf27b5921536b85c5f37b Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 14:18:22 +0200 Subject: [PATCH 047/776] Prepare 4.2 GA (2021.0.0). See #1750 --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0ca891119..21d27ce14 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.5.0-SNAPSHOT + 2.5.0 Spring Data Elasticsearch @@ -22,7 +22,7 @@ 7.12.0 2.13.3 4.1.52.Final - 2.5.0-SNAPSHOT + 2.5.0 1.15.1 spring.data.elasticsearch @@ -480,8 +480,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-release + https://repo.spring.io/libs-release diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index aa294cced..2be9fae82 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Elasticsearch 4.2 RC1 (2021.0.0) +Spring Data Elasticsearch 4.2 GA (2021.0.0) Copyright (c) [2013-2021] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -26,3 +26,4 @@ conditions of the subcomponent's license, as noted in the LICENSE file. + From 8bc06c9f41a1e25ac2ac5147016029c8d799275a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 14:18:49 +0200 Subject: [PATCH 048/776] Release version 4.2 GA (2021.0.0). See #1750 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 21d27ce14..d02209336 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.2.0-SNAPSHOT + 4.2.0 org.springframework.data.build From 054235e590cb0818de8abdc64d48719fcecfb3c8 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 14:30:12 +0200 Subject: [PATCH 049/776] Prepare next development iteration. See #1750 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d02209336..41321b79a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.2.0 + 4.3.0-SNAPSHOT org.springframework.data.build From 728ba0af5bd4bc42c4fe6e01372451d33c375796 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 14 Apr 2021 14:30:15 +0200 Subject: [PATCH 050/776] After release cleanups. See #1750 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 41321b79a..bdec10b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.5.0 + 2.6.0-SNAPSHOT Spring Data Elasticsearch @@ -22,7 +22,7 @@ 7.12.0 2.13.3 4.1.52.Final - 2.5.0 + 2.6.0-SNAPSHOT 1.15.1 spring.data.elasticsearch @@ -480,8 +480,8 @@ - spring-libs-release - https://repo.spring.io/libs-release + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From 19ec4818562d526cb45a59a7683f05be256c87cc Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Thu, 15 Apr 2021 13:04:54 -0500 Subject: [PATCH 051/776] Migrate to main branch. See #1750. --- CI.adoc | 2 +- CONTRIBUTING.adoc | 2 +- Jenkinsfile | 10 +++++----- README.adoc | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CI.adoc b/CI.adoc index 613add516..56af9d15e 100644 --- a/CI.adoc +++ b/CI.adoc @@ -1,6 +1,6 @@ = Continuous Integration -image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=2020.0.0%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] +image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=2020.0.0%20(main)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F4.0.x&subject=Neumann%20(4.0.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2F3.2.x&subject=Moore%20(3.2.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index de521df6e..b98684b8b 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -1,6 +1,6 @@ = Spring Data contribution guidelines -You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here]. +You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here]. == Running the test locally diff --git a/Jenkinsfile b/Jenkinsfile index 00f58f40f..5a4152d81 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -3,7 +3,7 @@ pipeline { triggers { pollSCM 'H/10 * * * *' - upstream(upstreamProjects: "spring-data-commons/master", threshold: hudson.model.Result.SUCCESS) + upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS) } options { @@ -15,7 +15,7 @@ pipeline { stage("test: baseline (jdk8)") { when { anyOf { - branch 'master' + branch 'main' not { triggeredBy 'UpstreamCause' } } } @@ -44,7 +44,7 @@ pipeline { stage("Test other configurations") { when { allOf { - branch 'master' + branch 'main' not { triggeredBy 'UpstreamCause' } } } @@ -100,7 +100,7 @@ pipeline { stage('Release to artifactory') { when { anyOf { - branch 'master' + branch 'main' not { triggeredBy 'UpstreamCause' } } } @@ -132,7 +132,7 @@ pipeline { } stage('Publish documentation') { when { - branch 'master' + branch 'main' } agent { label 'data' diff --git a/README.adoc b/README.adoc index 30942c9c8..1758dbab4 100644 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,6 @@ image:https://spring.io/badges/spring-data-elasticsearch/ga.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] image:https://spring.io/badges/spring-data-elasticsearch/snapshot.svg[Spring Data Elasticsearch,link=https://projects.spring.io/spring-data-elasticsearch#quick-start] -= Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmaster&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] += Spring Data for Elasticsearch image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-elasticsearch%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-elasticsearch/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]] The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services. @@ -217,7 +217,7 @@ The generated documentation is available from `target/site/reference/html/index. == Examples -For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/master/elasticsearch/example[spring-data-examples] project. +For examples on using the Spring Data for Elasticsearch, see the https://github.com/spring-projects/spring-data-examples/tree/main/elasticsearch/example[spring-data-examples] project. == License From 7ace63485dd588e4674d4f22874bf020496bf86c Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 17 Apr 2021 14:53:12 +0200 Subject: [PATCH 052/776] DynamicMapping annotation should be applicable to any object field. Original Pull Request #1779 Closes #1767 --- .../core/index/MappingBuilder.java | 21 +++- .../index/MappingBuilderIntegrationTests.java | 31 +++++ .../core/index/MappingBuilderUnitTests.java | 111 +++++++++++------- 3 files changed, 114 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index fa984f1dd..4bc452d53 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -245,6 +245,7 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, Field fieldAnnotation = property.findAnnotation(Field.class); boolean isCompletionProperty = property.isCompletionProperty(); boolean isNestedOrObjectProperty = isNestedOrObjectProperty(property); + DynamicMapping dynamicMapping = property.findAnnotation(DynamicMapping.class); if (!isCompletionProperty && property.isEntity() && hasRelevantAnnotation(property)) { @@ -259,7 +260,7 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, : null; mapEntity(builder, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(), - fieldAnnotation, property.findAnnotation(DynamicMapping.class)); + fieldAnnotation, dynamicMapping); return; } } @@ -274,9 +275,9 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, if (isRootObject && fieldAnnotation != null && property.isIdProperty()) { applyDefaultIdFieldMapping(builder, property); } else if (multiField != null) { - addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty); + addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty, dynamicMapping); } else if (fieldAnnotation != null) { - addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty); + addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping); } } @@ -377,7 +378,7 @@ private void applyDisabledPropertyMapping(XContentBuilder builder, Elasticsearch * @throws IOException */ private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property, - Field annotation, boolean nestedOrObjectField) throws IOException { + Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { // build the property json, if empty skip it as this is no valid mapping XContentBuilder propertyBuilder = jsonBuilder().startObject(); @@ -389,6 +390,11 @@ private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersist } builder.startObject(property.getFieldName()); + + if (nestedOrObjectField && dynamicMapping != null) { + builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + } + addFieldMappingParameters(builder, annotation, nestedOrObjectField); builder.endObject(); } @@ -429,10 +435,15 @@ private void addJoinFieldMapping(XContentBuilder builder, ElasticsearchPersisten * @throws IOException */ private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property, - MultiField annotation, boolean nestedOrObjectField) throws IOException { + MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { // main field builder.startObject(property.getFieldName()); + + if (nestedOrObjectField && dynamicMapping != null) { + builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + } + addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField); // inner fields diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index 31b8727df..e03e9d395 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -296,6 +296,16 @@ void shouldWriteMappingForDisabledProperty() { indexOps.delete(); } + @Test // #1767 + @DisplayName("should write dynamic mapping entries") + void shouldWriteDynamicMappingEntries() { + + IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class); + indexOps.create(); + indexOps.putMapping(); + indexOps.delete(); + } + @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @Nullable @Id private String id; @@ -1082,4 +1092,25 @@ public void setDense_vector(@Nullable float[] dense_vector) { this.dense_vector = dense_vector; } } + + @Document(indexName = "dynamic-mapping") + @DynamicMapping(DynamicMappingValue.False) + static class DynamicMappingEntity { + + @Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author; + @Nullable @DynamicMapping(DynamicMappingValue.False) @Field( + type = FieldType.Object) private Map objectMap; + @Nullable @DynamicMapping(DynamicMappingValue.False) @Field( + type = FieldType.Nested) private List> nestedObjectMap; + + @Nullable + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } + } + } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 29a58e7c9..ab60ed4f0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.elasticsearch.search.suggest.completion.context.ContextMapping; @@ -415,23 +416,42 @@ public void shouldSetFieldMappingProperties() throws JSONException { assertEquals(expected, mapping, false); } - @Test + @Test // DATAES-148, #1767 void shouldWriteDynamicMappingSettings() throws JSONException { String expected = "{\n" + // - " \"dynamic\": \"false\",\n" + // - " \"properties\": {\n" + // - " \"author\": {\n" + // - " \"dynamic\": \"strict\",\n" + // - " \"type\": \"object\",\n" + // - " \"properties\": {}\n" + // + " \"dynamic\": \"false\",\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"author\": {\n" + // + " \"type\": \"object\",\n" + // + " \"dynamic\": \"strict\",\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // " }\n" + // + " },\n" + // + " \"objectMap\": {\n" + // + " \"type\": \"object\",\n" + // + " \"dynamic\": \"false\"\n" + // + " },\n" + // + " \"nestedObjectMap\": {\n" + // + " \"type\": \"nested\",\n" + // + " \"dynamic\": \"false\"\n" + // " }\n" + // - "}\n"; + " }\n" + // + "}"; // String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class); - assertEquals(expected, mapping, false); + assertEquals(expected, mapping, true); } @Test // DATAES-784 @@ -608,39 +628,38 @@ void shouldWriteTypeHintEntries() throws JSONException { assertEquals(expected, mapping, false); } - @Test // #1727 - @DisplayName("should map according to the annotated properties") - void shouldMapAccordingToTheAnnotatedProperties() throws JSONException { - - String expected = "{\n" + - " \"properties\": {\n" + // - " \"field1\": {\n" + // - " \"type\": \"date\",\n" + // - " \"format\": \"date_optional_time||epoch_millis\"\n" + // - " },\n" + // - " \"field2\": {\n" + // - " \"type\": \"date\",\n" + // - " \"format\": \"basic_date\"\n" + // - " },\n" + // - " \"field3\": {\n" + // - " \"type\": \"date\",\n" + // - " \"format\": \"basic_date||basic_time\"\n" + // - " },\n" + // - " \"field4\": {\n" + // - " \"type\": \"date\",\n" + // - " \"format\": \"date_optional_time||epoch_millis||dd.MM.uuuu\"\n" + // - " },\n" + // - " \"field5\": {\n" + // - " \"type\": \"date\",\n" + // - " \"format\": \"dd.MM.uuuu\"\n" + // - " }\n" + // - " }\n" + // - "}"; // - - String mapping = getMappingBuilder().buildPropertyMapping(DateFormatsEntity.class); - - assertEquals(expected, mapping, false); - } + @Test // #1727 + @DisplayName("should map according to the annotated properties") + void shouldMapAccordingToTheAnnotatedProperties() throws JSONException { + + String expected = "{\n" + " \"properties\": {\n" + // + " \"field1\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"date_optional_time||epoch_millis\"\n" + // + " },\n" + // + " \"field2\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"basic_date\"\n" + // + " },\n" + // + " \"field3\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"basic_date||basic_time\"\n" + // + " },\n" + // + " \"field4\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"date_optional_time||epoch_millis||dd.MM.uuuu\"\n" + // + " },\n" + // + " \"field5\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"dd.MM.uuuu\"\n" + // + " }\n" + // + " }\n" + // + "}"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DateFormatsEntity.class); + + assertEquals(expected, mapping, false); + } @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @@ -666,7 +685,6 @@ public void setMessage(@Nullable String message) { } } - static class FieldNameEntity { @Document(indexName = "fieldname-index") @@ -1199,6 +1217,10 @@ static class FieldMappingParameters { static class ConfigureDynamicMappingEntity { @Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author; + @Nullable @DynamicMapping(DynamicMappingValue.False) @Field( + type = FieldType.Object) private Map objectMap; + @Nullable @DynamicMapping(DynamicMappingValue.False) @Field( + type = FieldType.Nested) private List> nestedObjectMap; @Nullable public Author getAuthor() { @@ -1474,7 +1496,8 @@ static class DateFormatsEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Date) private LocalDateTime field1; @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date) private LocalDateTime field2; - @Nullable @Field(type = FieldType.Date, format = { DateFormat.basic_date, DateFormat.basic_time }) private LocalDateTime field3; + @Nullable @Field(type = FieldType.Date, + format = { DateFormat.basic_date, DateFormat.basic_time }) private LocalDateTime field3; @Nullable @Field(type = FieldType.Date, pattern = "dd.MM.uuuu") private LocalDateTime field4; @Nullable @Field(type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") private LocalDateTime field5; From 79087c4adad3af60c45e839f3a059ab2b42fc1fc Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 18 Apr 2021 14:08:45 +0200 Subject: [PATCH 053/776] Custom property names must be used in SourceFilter and source fields. Original Pull Request #1780 Closes #1778 --- .../core/convert/ElasticsearchConverter.java | 33 ++++------- .../MappingElasticsearchConverter.java | 59 ++++++++++++++++++- .../core/query/AbstractQuery.java | 9 +++ .../data/elasticsearch/core/query/Query.java | 7 +++ .../core/CriteriaQueryMappingUnitTests.java | 49 +++++++++------ 5 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java index a4a40a15e..284543220 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java @@ -19,7 +19,6 @@ import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; -import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; @@ -64,7 +63,13 @@ default String convertId(Object idValue) { return idValue.toString(); } - return getConversionService().convert(idValue, String.class); + String converted = getConversionService().convert(idValue, String.class); + + if (converted == null) { + return idValue.toString(); + } + + return converted; } /** @@ -86,29 +91,15 @@ default Document mapObject(@Nullable Object source) { // region query /** - * Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and - * the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a + * Updates a {@link Query} by renaming the property names in the query to the correct mapped field names and the + * values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a * {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. If - * domainClass is null or query is not a {@link CriteriaQuery}, it's a noop. + * domainClass is null it's a noop. * - * @param query the query that is internally updated + * @param query the query that is internally updated, must not be {@literal null} * @param domainClass the class of the object that is searched with the query */ - default void updateQuery(Query query, @Nullable Class domainClass) { - - if (domainClass != null && query instanceof CriteriaQuery) { - updateCriteriaQuery((CriteriaQuery) query, domainClass); - } - } + void updateQuery(Query query, @Nullable Class domainClass); - /** - * Updates a {@link CriteriaQuery} by renaming the property names in the query to the correct mapped field names and - * the values to the converted values if the {@link ElasticsearchPersistentProperty} for a property has a - * {@link org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter}. - * - * @param criteriaQuery the query that is internally updated, must not be {@literal null} - * @param domainClass the class of the object that is searched with the query, must not be {@literal null} - */ - void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass); // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 18286bb1c..c080d1e1f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -42,8 +42,11 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; import org.springframework.data.elasticsearch.core.query.Field; +import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; +import org.springframework.data.elasticsearch.core.query.SourceFilter; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; @@ -1024,7 +1027,61 @@ private static Collection asCollection(Object source) { // region queries @Override - public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass) { + public void updateQuery(Query query, @Nullable Class domainClass) { + + Assert.notNull(query, "query must not be null"); + + if (domainClass != null) { + + updateFieldsAndSourceFilter(query, domainClass); + + if (query instanceof CriteriaQuery) { + updateCriteriaQuery((CriteriaQuery) query, domainClass); + } + } + } + + private void updateFieldsAndSourceFilter(Query query, Class domainClass) { + + ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(domainClass); + + if (persistentEntity != null) { + List fields = query.getFields(); + + if (!fields.isEmpty()) { + query.setFields(updateFieldNames(fields, persistentEntity)); + } + + SourceFilter sourceFilter = query.getSourceFilter(); + + if (sourceFilter != null) { + + String[] includes = null; + String[] excludes = null; + + if (sourceFilter.getIncludes() != null) { + includes = updateFieldNames(Arrays.asList(sourceFilter.getIncludes()), persistentEntity) + .toArray(new String[] {}); + } + + if (sourceFilter.getExcludes() != null) { + excludes = updateFieldNames(Arrays.asList(sourceFilter.getExcludes()), persistentEntity) + .toArray(new String[] {}); + } + + query.addSourceFilter(new FetchSourceFilter(includes, excludes)); + } + } + } + + private List updateFieldNames(List fields, ElasticsearchPersistentEntity persistentEntity) { + return fields.stream().map(fieldName -> { + ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName); + return persistentProperty != null ? persistentProperty.getFieldName() : fieldName; + }).collect(Collectors.toList()); + } + + private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class domainClass) { Assert.notNull(criteriaQuery, "criteriaQuery must not be null"); Assert.notNull(domainClass, "domainClass must not be null"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index 8384d757a..1d3e9fb49 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -96,6 +96,15 @@ public List getFields() { return fields; } + @Override + public void setFields(List fields) { + + Assert.notNull(fields, "fields must not be null"); + + this.fields.clear(); + this.fields.addAll(fields); + } + @Override public void addSourceFilter(SourceFilter sourceFilter) { this.sourceFilter = sourceFilter; 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 25aeb0bf0..cbb10eb98 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 @@ -103,6 +103,13 @@ static Query findAll() { */ List getFields(); + /** + * Set fields to be returned as part of search request + * @param fields must not be {@literal null} + * @since 4.3 + */ + void setFields(List fields); + /** * Add source filter to be added as part of search request * diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index a54ae7b63..e27eedead 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -15,7 +15,6 @@ */ package org.springframework.data.elasticsearch.core; -import static org.assertj.core.api.Assertions.*; import static org.skyscreamer.jsonassert.JSONAssert.*; import java.time.LocalDate; @@ -25,6 +24,7 @@ import java.util.List; import java.util.Objects; +import org.assertj.core.api.SoftAssertions; import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -44,6 +44,9 @@ import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.data.elasticsearch.core.query.SourceFilter; import org.springframework.lang.Nullable; /** @@ -356,9 +359,7 @@ void shouldMapNamesAndValueInNestedEntities() throws JSONException { " }\n" + // "}\n"; // - CriteriaQuery criteriaQuery = new CriteriaQuery( - new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)) - ); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3))); mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); @@ -389,9 +390,7 @@ void shouldMapNamesAndValueInNestedEntitiesWithSubfields() throws JSONException " }\n" + // "}\n"; // - CriteriaQuery criteriaQuery = new CriteriaQuery( - new Criteria("persons.nickName.keyword").is("Foobar") - ); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.nickName.keyword").is("Foobar")); mappingElasticsearchConverter.updateQuery(criteriaQuery, House.class); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); @@ -417,14 +416,33 @@ void shouldMapNamesAndValueInObjectEntities() throws JSONException { " }\n" + // "}\n"; // - CriteriaQuery criteriaQuery = new CriteriaQuery( - new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3)) - ); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("persons.birthDate").is(LocalDate.of(1999, 10, 3))); mappingElasticsearchConverter.updateQuery(criteriaQuery, ObjectWithPerson.class); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString(); assertEquals(expected, queryString, false); } + + @Test // #1778 + @DisplayName("should map names in source fields and SourceFilters") + void shouldMapNamesInSourceFieldsAndSourceFilters() { + + Query query = Query.findAll(); + // Note: we don't care if these filters make sense here, this test is only about name mapping + query.addFields("firstName", "lastName"); + query.addSourceFilter(new FetchSourceFilter(new String[] { "firstName" }, new String[] { "lastName" })); + + mappingElasticsearchConverter.updateQuery(query, Person.class); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(query.getFields()).containsExactly("first-name", "last-name"); + SourceFilter sourceFilter = query.getSourceFilter(); + softly.assertThat(sourceFilter).isNotNull(); + softly.assertThat(sourceFilter.getIncludes()).containsExactly("first-name"); + softly.assertThat(sourceFilter.getExcludes()).containsExactly("last-name"); + softly.assertAll(); + } + // endregion // region helper functions @@ -442,7 +460,8 @@ static class Person { @Nullable @Id String id; @Nullable @Field(name = "first-name") String firstName; @Nullable @Field(name = "last-name") String lastName; - @Nullable @MultiField(mainField = @Field(name="nick-name"), otherFields = {@InnerField(suffix = "keyword", type = FieldType.Keyword)}) String nickName; + @Nullable @MultiField(mainField = @Field(name = "nick-name"), + otherFields = { @InnerField(suffix = "keyword", type = FieldType.Keyword) }) String nickName; @Nullable @Field(name = "created-date", type = FieldType.Date, format = DateFormat.epoch_millis) Date createdDate; @Nullable @Field(name = "birth-date", type = FieldType.Date, format = {}, pattern = "dd.MM.uuuu") LocalDate birthDate; @@ -450,16 +469,12 @@ static class Person { static class House { @Nullable @Id String id; - @Nullable - @Field(name = "per-sons", type = FieldType.Nested) - List persons; + @Nullable @Field(name = "per-sons", type = FieldType.Nested) List persons; } static class ObjectWithPerson { @Nullable @Id String id; - @Nullable - @Field(name = "per-sons", type = FieldType.Object) - List persons; + @Nullable @Field(name = "per-sons", type = FieldType.Object) List persons; } static class GeoShapeEntity { From e193e1672b7d237b2f491a4c27d3ed99adb0dde9 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 19 Apr 2021 18:02:02 +0200 Subject: [PATCH 054/776] Remove deprecated code. Original Pull Request #1782 Closes #1781 --- .../elasticsearch/annotations/Parent.java | 36 --- .../client/NodeClientFactoryBean.java | 173 --------------- .../reactive/ReactiveElasticsearchClient.java | 52 ----- .../client/util/RequestConverters.java | 21 +- .../config/ElasticsearchNamespaceHandler.java | 1 - .../config/EnableElasticsearchAuditing.java | 10 - .../EnableReactiveElasticsearchAuditing.java | 10 - .../NodeClientBeanDefinitionParser.java | 57 ----- .../core/AbstractDefaultIndexOperations.java | 24 -- .../core/DefaultIndexOperations.java | 22 -- .../core/DefaultTransportIndexOperations.java | 20 -- .../elasticsearch/core/EntityOperations.java | 84 ------- .../elasticsearch/core/IndexOperations.java | 31 --- .../elasticsearch/core/RequestFactory.java | 70 +----- .../core/convert/DateTimeConverters.java | 38 ---- .../core/geo/CustomGeoModule.java | 20 -- .../core/index/MappingBuilder.java | 6 - .../ElasticsearchPersistentEntity.java | 14 -- .../ElasticsearchPersistentProperty.java | 18 +- .../SimpleElasticsearchPersistentEntity.java | 33 --- ...SimpleElasticsearchPersistentProperty.java | 12 - .../core/query/AliasBuilder.java | 81 ------- .../elasticsearch/core/query/AliasQuery.java | 97 --------- .../elasticsearch/core/query/Criteria.java | 29 +-- .../query/ElasticsearchStringQuery.java | 14 -- .../AbstractElasticsearchRepository.java | 38 ---- .../ElasticsearchEntityInformation.java | 7 - ...MappingElasticsearchEntityInformation.java | 12 - ...veElasticsearchClientIntegrationTests.java | 4 +- .../core/ElasticsearchTemplateTests.java | 206 ------------------ .../core/convert/DateTimeConvertersTests.java | 25 --- .../config/namespace/namespace.xml | 8 +- 32 files changed, 19 insertions(+), 1254 deletions(-) delete mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/Parent.java delete mode 100644 src/main/java/org/springframework/data/elasticsearch/client/NodeClientFactoryBean.java delete mode 100644 src/main/java/org/springframework/data/elasticsearch/config/NodeClientBeanDefinitionParser.java delete mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/AliasBuilder.java delete mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/AliasQuery.java delete mode 100644 src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Parent.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Parent.java deleted file mode 100644 index 34f424142..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Parent.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2014-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.annotations; - -import java.lang.annotation.*; - -import org.springframework.data.annotation.Persistent; - -/** - * Parent - * - * @author Philipp Jardas - * @deprecated since 4.1, not supported anymore by Elasticsearch - */ -@Deprecated -@Persistent -@Inherited -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface Parent { - - String type(); -} diff --git a/src/main/java/org/springframework/data/elasticsearch/client/NodeClientFactoryBean.java b/src/main/java/org/springframework/data/elasticsearch/client/NodeClientFactoryBean.java deleted file mode 100644 index f8276911b..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/client/NodeClientFactoryBean.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.client; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; - -import org.elasticsearch.client.Client; -import org.elasticsearch.client.node.NodeClient; -import org.elasticsearch.common.logging.LogConfigurator; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.node.InternalSettingsPreparer; -import org.elasticsearch.node.Node; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.transport.Netty4Plugin; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.FactoryBeanNotInitializedException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; - -/** - * NodeClientFactoryBean - * - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Ilkang Na - * @author Peter-Josef Meisch - * @deprecated since 4.1, we're not supporting embedded Node clients anymore, use the REST client - */ -@Deprecated -public class NodeClientFactoryBean implements FactoryBean, InitializingBean, DisposableBean { - - private static final Logger logger = LoggerFactory.getLogger(NodeClientFactoryBean.class); - private boolean local; - private boolean enableHttp; - private @Nullable String clusterName; - private @Nullable Node node; - private @Nullable NodeClient nodeClient; - private @Nullable String pathData; - private @Nullable String pathHome; - private @Nullable String pathConfiguration; - - public static class TestNode extends Node { - - private static final String DEFAULT_NODE_NAME = "spring-data-elasticsearch-nodeclientfactorybean-test"; - - public TestNode(Settings preparedSettings, Collection> classpathPlugins) { - - super(InternalSettingsPreparer.prepareEnvironment(preparedSettings, Collections.emptyMap(), null, - () -> DEFAULT_NODE_NAME), classpathPlugins, false); - } - - protected void registerDerivedNodeNameWithLogger(String nodeName) { - try { - LogConfigurator.setNodeName(nodeName); - } catch (Exception e) { - // nagh - just forget about it - } - } - } - - NodeClientFactoryBean() {} - - public NodeClientFactoryBean(boolean local) { - this.local = local; - } - - @Override - public NodeClient getObject() { - - if (nodeClient == null) { - throw new FactoryBeanNotInitializedException(); - } - - return nodeClient; - } - - @Override - public Class getObjectType() { - return NodeClient.class; - } - - @Override - public boolean isSingleton() { - return true; - } - - @Override - public void afterPropertiesSet() throws Exception { - - Settings settings = Settings.builder() // - .put(loadConfig()) // - .put("transport.type", "netty4") // - .put("http.type", "netty4") // - .put("path.home", this.pathHome) // - .put("path.data", this.pathData) // - .put("cluster.name", this.clusterName) // - .put("node.max_local_storage_nodes", 100) // - .build(); - node = new TestNode(settings, Collections.singletonList(Netty4Plugin.class)); - nodeClient = (NodeClient) node.start().client(); - } - - private Settings loadConfig() throws IOException { - if (!StringUtils.isEmpty(pathConfiguration)) { - InputStream stream = getClass().getClassLoader().getResourceAsStream(pathConfiguration); - if (stream != null) { - return Settings.builder().loadFromStream(pathConfiguration, - getClass().getClassLoader().getResourceAsStream(pathConfiguration), false).build(); - } - logger.error(String.format("Unable to read node configuration from file [%s]", pathConfiguration)); - } - return Settings.builder().build(); - } - - public void setLocal(boolean local) { - this.local = local; - } - - public void setEnableHttp(boolean enableHttp) { - this.enableHttp = enableHttp; - } - - public void setClusterName(String clusterName) { - this.clusterName = clusterName; - } - - public void setPathData(String pathData) { - this.pathData = pathData; - } - - public void setPathHome(String pathHome) { - this.pathHome = pathHome; - } - - public void setPathConfiguration(String configuration) { - this.pathConfiguration = configuration; - } - - @Override - public void destroy() { - try { - // NodeClient.close() is a noop, no need to call it here - nodeClient = null; - logger.info("Closing elasticSearch node"); - if (node != null) { - node.close(); - node = null; - } - } catch (final Exception e) { - logger.error("Error closing ElasticSearch client: ", e); - } - } -} 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 201513ef0..e2c628803 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 @@ -1102,58 +1102,6 @@ default Mono refreshIndex(RefreshRequest refreshRequest) { */ Mono refreshIndex(HttpHeaders headers, RefreshRequest refreshRequest); - /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the - * {@literal indices} API. - * - * @param consumer never {@literal null}. - * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index - * does not exist. - * @see Indices - * Put Mapping API on elastic.co - * @deprecated since 4.1, use {@link #putMapping(Consumer)} - */ - @Deprecated - default Mono updateMapping( - Consumer consumer) { - return putMapping(consumer); - } - - /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the - * {@literal indices} API. - * - * @param putMappingRequest must not be {@literal null}. - * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index - * does not exist. - * @see Indices - * Put Mapping API on elastic.co - * @deprecated since 4.1, use {@link #putMapping(PutMappingRequest)} - */ - @Deprecated - default Mono updateMapping( - org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { - return putMapping(putMappingRequest); - } - - /** - * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the - * {@literal indices} API. - * - * @param headers Use {@link HttpHeaders} to provide eg. authentication data. Must not be {@literal null}. - * @param putMappingRequest must not be {@literal null}. - * @return a {@link Mono} signalling operation completion or an {@link Mono#error(Throwable) error} if eg. the index - * does not exist. - * @see Indices - * Put Mapping API on elastic.co - * @deprecated since 4.1, use {@link #putMapping(HttpHeaders, PutMappingRequest)} - */ - @Deprecated - default Mono updateMapping(HttpHeaders headers, - org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest putMappingRequest) { - return putMapping(headers, putMappingRequest); - } - /** * Execute the given {@link org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest} against the * {@literal indices} API. 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 f77b77c82..a8c6045ec 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 @@ -841,8 +841,7 @@ public static Request putMapping(PutMappingRequest putMappingRequest) { RequestConverters.Params parameters = new RequestConverters.Params(request) // .withTimeout(putMappingRequest.timeout()) // - .withMasterTimeout(putMappingRequest.masterNodeTimeout()) // - .withIncludeTypeName(false); + .withMasterTimeout(putMappingRequest.masterNodeTimeout()); request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE)); return request; } @@ -853,8 +852,7 @@ public static Request putMapping(org.elasticsearch.client.indices.PutMappingRequ new RequestConverters.Params(request) // .withTimeout(putMappingRequest.timeout()) // - .withMasterTimeout(putMappingRequest.masterNodeTimeout()) // - .withIncludeTypeName(false); + .withMasterTimeout(putMappingRequest.masterNodeTimeout()); request.setEntity(RequestConverters.createEntity(putMappingRequest, RequestConverters.REQUEST_BODY_CONTENT_TYPE)); return request; } @@ -880,7 +878,6 @@ public static Request getMapping(GetMappingsRequest getMappingsRequest) { parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout()); parameters.withIndicesOptions(getMappingsRequest.indicesOptions()); parameters.withLocal(getMappingsRequest.local()); - parameters.withIncludeTypeName(false); return request; } @@ -893,7 +890,6 @@ public static Request getMapping(org.elasticsearch.client.indices.GetMappingsReq parameters.withMasterTimeout(getMappingsRequest.masterNodeTimeout()); parameters.withIndicesOptions(getMappingsRequest.indicesOptions()); parameters.withLocal(getMappingsRequest.local()); - parameters.withIncludeTypeName(false); return request; } @@ -1000,7 +996,6 @@ public static Request getFieldMapping(GetFieldMappingsRequest getFieldMappingsRe RequestConverters.Params parameters = new Params(request); parameters.withIndicesOptions(getFieldMappingsRequest.indicesOptions()); parameters.withIncludeDefaults(getFieldMappingsRequest.includeDefaults()); - parameters.withIncludeTypeName(false); return request; } @@ -1372,18 +1367,6 @@ Params withWaitForEvents(Priority waitForEvents) { } return this; } - - /** - * sets the include_type_name parameter. Needed for Elasticsearch 7 to be used with the mapping types still - * available. Will be removed again when Elasticsearch drops the support for this parameter in Elasticsearch 8. - * - * @deprecated since 4.0 - * @since 4.0 - */ - @Deprecated - Params withIncludeTypeName(boolean includeTypeName) { - return putParam("include_type_name", String.valueOf(includeTypeName)); - } } /** diff --git a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchNamespaceHandler.java b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchNamespaceHandler.java index cd69b38fa..92b72ddde 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchNamespaceHandler.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchNamespaceHandler.java @@ -35,7 +35,6 @@ public void init() { RepositoryBeanDefinitionParser parser = new RepositoryBeanDefinitionParser(extension); registerBeanDefinitionParser("repositories", parser); - registerBeanDefinitionParser("node-client", new NodeClientBeanDefinitionParser()); registerBeanDefinitionParser("transport-client", new TransportClientBeanDefinitionParser()); registerBeanDefinitionParser("rest-client", new RestClientBeanDefinitionParser()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/config/EnableElasticsearchAuditing.java b/src/main/java/org/springframework/data/elasticsearch/config/EnableElasticsearchAuditing.java index ce91ff450..39686ff1d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/EnableElasticsearchAuditing.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/EnableElasticsearchAuditing.java @@ -41,32 +41,22 @@ /** * Configures the {@link AuditorAware} bean to be used to lookup the current principal. - * - * @return */ String auditorAwareRef() default ""; /** * Configures whether the creation and modification dates are set. Defaults to {@literal true}. - * - * @return */ boolean setDates() default true; /** * Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}. - * - * @return */ boolean modifyOnCreate() default true; /** * Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be * used for setting creation and modification dates. - * - * @return - * @deprecated since 4.1 */ - @Deprecated String dateTimeProviderRef() default ""; } diff --git a/src/main/java/org/springframework/data/elasticsearch/config/EnableReactiveElasticsearchAuditing.java b/src/main/java/org/springframework/data/elasticsearch/config/EnableReactiveElasticsearchAuditing.java index 42bc6388b..fedb405e0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/EnableReactiveElasticsearchAuditing.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/EnableReactiveElasticsearchAuditing.java @@ -41,32 +41,22 @@ /** * Configures the {@link AuditorAware} bean to be used to lookup the current principal. - * - * @return */ String auditorAwareRef() default ""; /** * Configures whether the creation and modification dates are set. Defaults to {@literal true}. - * - * @return */ boolean setDates() default true; /** * Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}. - * - * @return */ boolean modifyOnCreate() default true; /** * Configures a {@link DateTimeProvider} bean name that allows customizing the {@link org.joda.time.DateTime} to be * used for setting creation and modification dates. - * - * @return - * @deprecated since 4.1 */ - @Deprecated String dateTimeProviderRef() default ""; } diff --git a/src/main/java/org/springframework/data/elasticsearch/config/NodeClientBeanDefinitionParser.java b/src/main/java/org/springframework/data/elasticsearch/config/NodeClientBeanDefinitionParser.java deleted file mode 100644 index d0f250ad1..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/config/NodeClientBeanDefinitionParser.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.data.elasticsearch.config; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.data.elasticsearch.client.NodeClientFactoryBean; -import org.w3c.dom.Element; - -/** - * NodeClientBeanDefinitionParser - * - * @author Rizwan Idrees - * @author Mohsin Husen - */ - -public class NodeClientBeanDefinitionParser extends AbstractBeanDefinitionParser { - - @Override - protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(NodeClientFactoryBean.class); - setLocalSettings(element, builder); - return getSourcedBeanDefinition(builder, element, parserContext); - } - - private void setLocalSettings(Element element, BeanDefinitionBuilder builder) { - builder.addPropertyValue("local", Boolean.valueOf(element.getAttribute("local"))); - builder.addPropertyValue("clusterName", element.getAttribute("cluster-name")); - builder.addPropertyValue("enableHttp", Boolean.valueOf(element.getAttribute("http-enabled"))); - builder.addPropertyValue("pathData", element.getAttribute("path-data")); - builder.addPropertyValue("pathHome", element.getAttribute("path-home")); - builder.addPropertyValue("pathConfiguration", element.getAttribute("path-configuration")); - } - - private AbstractBeanDefinition getSourcedBeanDefinition(BeanDefinitionBuilder builder, Element source, - ParserContext context) { - AbstractBeanDefinition definition = builder.getBeanDefinition(); - definition.setSource(context.extractSource(source)); - return definition; - } -} 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 ee5e2af3a..f7ef23b1a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -36,7 +36,6 @@ import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; -import org.springframework.data.elasticsearch.core.query.AliasQuery; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -176,31 +175,8 @@ public void refresh() { protected abstract void doRefresh(IndexCoordinates indexCoordinates); - @Override - @Deprecated - public boolean addAlias(AliasQuery query) { - return doAddAlias(query, getIndexCoordinates()); - } - - @Deprecated - protected abstract boolean doAddAlias(AliasQuery query, IndexCoordinates index); - - @Override - public List queryForAlias() { - return doQueryForAlias(getIndexCoordinates()); - } - protected abstract List doQueryForAlias(IndexCoordinates index); - @Override - @Deprecated - public boolean removeAlias(AliasQuery query) { - return doRemoveAlias(query, getIndexCoordinates()); - } - - @Deprecated - protected abstract boolean doRemoveAlias(AliasQuery query, IndexCoordinates index); - @Override public Map> getAliases(String... aliasNames) { 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 ae0121243..fdc8159ed 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java @@ -53,7 +53,6 @@ import org.springframework.data.elasticsearch.core.index.Settings; 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; import org.springframework.util.Assert; @@ -141,27 +140,6 @@ protected Map doGetMapping(IndexCoordinates index) { }); } - @Override - @Deprecated - protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) { - - IndicesAliasesRequest request = requestFactory.indicesAddAliasesRequest(query, index); - return restTemplate - .execute(client -> client.indices().updateAliases(request, RequestOptions.DEFAULT).isAcknowledged()); - } - - @Override - @Deprecated - protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) { - - Assert.notNull(index, "No index defined for Alias"); - Assert.notNull(query.getAliasName(), "No alias defined"); - - IndicesAliasesRequest indicesAliasesRequest = requestFactory.indicesRemoveAliasesRequest(query, index); - return restTemplate.execute( - client -> client.indices().updateAliases(indicesAliasesRequest, RequestOptions.DEFAULT).isAcknowledged()); - } - @Override protected List doQueryForAlias(IndexCoordinates index) { 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 a13924666..acb244601 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java @@ -23,7 +23,6 @@ import java.util.Map; import java.util.Set; -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; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; @@ -59,7 +58,6 @@ import org.springframework.data.elasticsearch.core.index.Settings; 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; import org.springframework.util.Assert; @@ -146,24 +144,6 @@ protected Map doGetMapping(IndexCoordinates index) { return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap(); } - @Override - protected boolean doAddAlias(AliasQuery query, IndexCoordinates index) { - IndicesAliasesRequest.AliasActions aliasAction = requestFactory.aliasAction(query, index); - return client.admin().indices().prepareAliases().addAliasAction(aliasAction).execute().actionGet().isAcknowledged(); - } - - @Override - @Deprecated - protected boolean doRemoveAlias(AliasQuery query, IndexCoordinates index) { - - Assert.notNull(index, "No index defined for Alias"); - Assert.notNull(query.getAliasName(), "No alias defined"); - - IndicesAliasesRequestBuilder indicesAliasesRequestBuilder = requestFactory - .indicesRemoveAliasesRequestBuilder(client, query, index); - return indicesAliasesRequestBuilder.execute().actionGet().isAcknowledged(); - } - @Override protected List doQueryForAlias(IndexCoordinates index) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java index 1e3a4b7ec..5a52c1ad9 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java @@ -93,22 +93,6 @@ AdaptibleEntity forEntity(T entity, ConversionService conversionService, return AdaptibleMappedEntity.of(entity, context, conversionService, routingResolver); } - /** - * Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using - * preferred values for index and type if provided, otherwise fall back to index and type defined on entity level. - * - * @param entity the entity to determine the index name. Can be {@literal null} if {@code index} and {@literal type} - * are provided. - * @param index index name override can be {@literal null}. - * @param type index type override can be {@literal null}. - * @return the {@link IndexCoordinates} containing index name and index type. - * @deprecated since 4.1, use {@link EntityOperations#determineIndex(Entity, String)} - */ - @Deprecated - IndexCoordinates determineIndex(Entity entity, @Nullable String index, @Nullable String type) { - return determineIndex(entity.getPersistentEntity(), index, type); - } - /** * Determine index name and type name from {@link Entity} with {@code index} and {@code type} overrides. Allows using * preferred values for index and type if provided, otherwise fall back to index and type defined on entity level. @@ -122,24 +106,6 @@ IndexCoordinates determineIndex(Entity entity, @Nullable String index) { return determineIndex(entity.getPersistentEntity(), index); } - /** - * Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type} - * overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type - * defined on entity level. - * - * @param persistentEntity the entity to determine the index name. Can be {@literal null} if {@code index} and - * {@literal type} are provided. - * @param index index name override can be {@literal null}. - * @param type index type override can be {@literal null}. - * @return the {@link IndexCoordinates} containing index name and index type. - * @deprecated since 4.1, use {@link EntityOperations#determineIndex(ElasticsearchPersistentEntity, String)} - */ - @Deprecated - IndexCoordinates determineIndex(ElasticsearchPersistentEntity persistentEntity, @Nullable String index, - @Nullable String type) { - return determineIndex(persistentEntity, index); - } - /** * Determine index name and type name from {@link ElasticsearchPersistentEntity} with {@code index} and {@code type} * overrides. Allows using preferred values for index and type if provided, otherwise fall back to index and type @@ -234,25 +200,6 @@ default ElasticsearchPersistentEntity getRequiredPersistentEntity() { */ interface AdaptibleEntity extends Entity { - /** - * Returns whether the entity has a parent. - * - * @return {@literal true} if the entity has a parent that has an {@literal id}. - * @deprecated since 4.1, not supported anymore by Elasticsearch - */ - @Deprecated - boolean hasParent(); - - /** - * Returns the parent Id. Can be {@literal null}. - * - * @return can be {@literal null}. - * @deprecated since 4.1, not supported anymore by Elasticsearch - */ - @Deprecated - @Nullable - Object getParentId(); - /** * Populates the identifier of the backing entity if it has an identifier property and there's no identifier * currently present. @@ -339,24 +286,6 @@ public Object getId() { return map.get(ID_FIELD); } - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#hasParent() - */ - @Override - public boolean hasParent() { - return false; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#getParentId() - */ - @Override - public Entity getParentId() { - return null; - } - /* * (non-Javadoc) * @see org.springframework.data.elasticsearch.core.EntityOperations.AdaptibleEntity#populateIdIfNecessary(java.lang.Object) @@ -581,19 +510,6 @@ static AdaptibleEntity of(T bean, new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver); } - @Override - public boolean hasParent() { - return getRequiredPersistentEntity().getParentIdProperty() != null; - } - - @Deprecated - @Override - public Object getParentId() { - - ElasticsearchPersistentProperty parentProperty = getRequiredPersistentEntity().getParentIdProperty(); - return propertyAccessor.getProperty(parentProperty); - } - @Nullable @Override public T populateIdIfNecessary(@Nullable Object id) { 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 cb1f39f19..f5d1c6f05 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexOperations.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Set; -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; @@ -30,7 +29,6 @@ import org.springframework.data.elasticsearch.core.index.Settings; 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; /** @@ -190,35 +188,6 @@ default boolean putMapping(Class clazz) { // endregion // region aliases - /** - * Add an alias. - * - * @param query query defining the alias - * @return true if the alias was created - * @deprecated since 4.1 use {@link #alias(AliasActions)} - */ - @Deprecated - boolean addAlias(AliasQuery query); - - /** - * Get the alias information for a specified index. - * - * @return alias information - * @deprecated since 4.1, use {@link #getAliases(String...)} or {@link #getAliasesForIndex(String...)}. - */ - @Deprecated - List queryForAlias(); - - /** - * Remove an alias. - * - * @param query query defining the alias - * @return true if the alias was removed - * @deprecated since 4.1 use {@link #alias(AliasActions)} - */ - @Deprecated - boolean removeAlias(AliasQuery query); - /** * Executes the given {@link AliasActions}. * 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 ca2ca2314..c42b93f6b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -24,7 +24,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import org.elasticsearch.action.DocWriteRequest; @@ -136,37 +135,6 @@ public RequestFactory(ElasticsearchConverter elasticsearchConverter) { } // region alias - @Deprecated - public IndicesAliasesRequest.AliasActions aliasAction(AliasQuery query, IndexCoordinates index) { - - Assert.notNull(index, "No index defined for Alias"); - Assert.notNull(query.getAliasName(), "No alias defined"); - - String[] indexNames = index.getIndexNames(); - IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.add() - .alias(query.getAliasName()).indices(indexNames); - - if (query.getFilterBuilder() != null) { - aliasAction.filter(query.getFilterBuilder()); - } else if (query.getFilter() != null) { - aliasAction.filter(query.getFilter()); - } - - if (!StringUtils.isEmpty(query.getRouting())) { - aliasAction.routing(query.getRouting()); - } - - if (!StringUtils.isEmpty(query.getSearchRouting())) { - aliasAction.searchRouting(query.getSearchRouting()); - } - - if (!StringUtils.isEmpty(query.getIndexRouting())) { - aliasAction.indexRouting(query.getIndexRouting()); - } - - return aliasAction; - } - public GetAliasesRequest getAliasesRequest(IndexCoordinates index) { String[] indexNames = index.getIndexNames(); @@ -182,14 +150,6 @@ public GetAliasesRequest getAliasesRequest(@Nullable String[] aliasNames, @Nulla return getAliasesRequest; } - @Deprecated - public IndicesAliasesRequest indicesAddAliasesRequest(AliasQuery query, IndexCoordinates index) { - IndicesAliasesRequest.AliasActions aliasAction = aliasAction(query, index); - IndicesAliasesRequest request = new IndicesAliasesRequest(); - request.addAliasAction(aliasAction); - return request; - } - public IndicesAliasesRequest indicesAliasesRequest(AliasActions aliasActions) { IndicesAliasesRequest request = new IndicesAliasesRequest(); @@ -257,27 +217,6 @@ public IndicesAliasesRequestBuilder indicesAliasesRequestBuilder(Client client, indicesAliasesRequest(aliasActions).getAliasActions().forEach(requestBuilder::addAliasAction); return requestBuilder; } - - @Deprecated - public IndicesAliasesRequest indicesRemoveAliasesRequest(AliasQuery query, IndexCoordinates index) { - - String[] indexNames = index.getIndexNames(); - IndicesAliasesRequest.AliasActions aliasAction = IndicesAliasesRequest.AliasActions.remove() // - .indices(indexNames) // - .alias(query.getAliasName()); - - return Requests.indexAliasesRequest() // - .addAliasAction(aliasAction); - } - - @Deprecated - IndicesAliasesRequestBuilder indicesRemoveAliasesRequestBuilder(Client client, AliasQuery query, - IndexCoordinates index) { - - String indexName = index.getIndexName(); - return client.admin().indices().prepareAliases().removeAlias(indexName, query.getAliasName()); - } - // endregion // region bulk @@ -355,7 +294,8 @@ public BulkRequestBuilder bulkRequestBuilder(Client client, List queries, Bul // region index management - public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map settings, @Nullable Document mapping) { + public CreateIndexRequest createIndexRequest(IndexCoordinates index, Map settings, + @Nullable Document mapping) { Assert.notNull(index, "index must not be null"); Assert.notNull(settings, "settings must not be null"); @@ -1276,10 +1216,10 @@ private SortBuilder getSortBuilder(Sort.Order order, @Nullable ElasticsearchP private QueryRescorerBuilder getQueryRescorerBuilder(RescorerQuery rescorerQuery) { - QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery()); - Assert.notNull("queryBuilder", "Could not build query for rescorerQuery"); + QueryBuilder queryBuilder = getQuery(rescorerQuery.getQuery()); + Assert.notNull("queryBuilder", "Could not build query for rescorerQuery"); - QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder); + QueryRescorerBuilder builder = new QueryRescorerBuilder(queryBuilder); if (rescorerQuery.getScoreMode() != ScoreMode.Default) { builder.setScoreMode(QueryRescoreMode.valueOf(rescorerQuery.getScoreMode().name())); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java index 47bb6fe1e..48cd5ade1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java @@ -34,44 +34,6 @@ public final class DateTimeConverters { private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC); - /** - * @deprecated since 4.1 - */ - @Deprecated - public enum JodaDateTimeConverter implements Converter { - INSTANCE; - - @Override - public String convert(ReadableInstant source) { - if (source == null) { - return null; - } - return formatter.print(source); - } - - } - - /** - * @deprecated since 4.1 - */ - @Deprecated - public enum JodaLocalDateTimeConverter implements Converter { - INSTANCE; - - @Override - public String convert(LocalDateTime source) { - if (source == null) { - return null; - } - return formatter.print(source.toDateTime(DateTimeZone.UTC)); - } - - } - - /** - * @deprecated since 4.1 - */ - @Deprecated public enum JavaDateConverter implements Converter { INSTANCE; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java b/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java index 1db995f2c..d5560640e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/geo/CustomGeoModule.java @@ -11,26 +11,6 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import org.springframework.data.geo.Point; -/** - * @author Artur Konaczak - * @deprecated since 4.1, not used anymore - */ -@Deprecated -public class CustomGeoModule extends SimpleModule { - - private static final long serialVersionUID = 1L; - - /** - * Creates a new {@link org.springframework.data.elasticsearch.core.geo.CustomGeoModule} registering serializers and deserializers for spring data commons geo-spatial types. - */ - public CustomGeoModule() { - - super("Spring Data Elasticsearch Geo", new Version(1, 0, 0, null, "org.springframework.data.elasticsearch", "spring-data-elasticsearch-geo")); - addSerializer(Point.class, new PointSerializer()); - addDeserializer(Point.class, new PointDeserializer()); - } -} - class PointSerializer extends JsonSerializer { @Override public void serialize(Point value, JsonGenerator gen, SerializerProvider serializers) throws IOException { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 4bc452d53..79e921a97 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -116,12 +116,6 @@ public String buildPropertyMapping(Class clazz) throws MappingException { // Dynamic templates addDynamicTemplatesMapping(builder, entity); - // Parent - String parentType = entity.getParentType(); - if (hasText(parentType)) { - builder.startObject(FIELD_PARENT).field(FIELD_PARAM_TYPE, parentType).endObject(); - } - mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class)); builder.endObject() // root object diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 28550d508..650a25360 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -56,20 +56,6 @@ public interface ElasticsearchPersistentEntity extends PersistentEntitypotential parent property of the owning - * {@link ElasticsearchPersistentEntity}. This method is mainly used by {@link ElasticsearchPersistentEntity} - * implementation to discover parent property candidates on {@link ElasticsearchPersistentEntity} creation you should - * rather call {@link ElasticsearchPersistentEntity#getScoreProperty()} to determine whether the current property is - * the parent property of that {@link ElasticsearchPersistentEntity} under consideration. * * @return - * @since 3.1 - * @deprecated since 4.1, not supported anymore by Elasticsearch */ - @Deprecated - boolean isParentProperty(); + String getFieldName(); /** * Returns whether the current property is a {@link SeqNoPrimaryTerm} property. @@ -113,7 +99,7 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty SUPPORTED_ID_PROPERTY_NAMES = Arrays.asList("id", "document"); - private final boolean isParent; private final boolean isId; private final boolean isSeqNoPrimaryTerm; private final @Nullable String annotatedFieldName; @@ -85,7 +83,6 @@ public SimpleElasticsearchPersistentProperty(Property property, : fieldNamingStrategy; this.isId = super.isIdProperty() || (SUPPORTED_ID_PROPERTY_NAMES.contains(getFieldName()) && !hasExplicitFieldName()); - this.isParent = isAnnotationPresent(Parent.class); this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType()); boolean isField = isAnnotationPresent(Field.class); @@ -94,10 +91,6 @@ public SimpleElasticsearchPersistentProperty(Property property, throw new MappingException(String.format("Version property %s must be of type Long!", property.getName())); } - if (isParent && !getType().equals(String.class)) { - throw new MappingException(String.format("Parent property %s must be of type String!", property.getName())); - } - if (isField && isAnnotationPresent(MultiField.class)) { throw new MappingException("@Field annotation must not be used on a @MultiField property."); } @@ -283,11 +276,6 @@ public boolean isImmutable() { return false; } - @Override - public boolean isParentProperty() { - return isParent; - } - @Override public boolean isSeqNoPrimaryTermProperty() { return isSeqNoPrimaryTerm; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AliasBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AliasBuilder.java deleted file mode 100644 index 9a40a2fe3..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AliasBuilder.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.query; - -import java.util.Map; - -import org.elasticsearch.index.query.QueryBuilder; -import org.springframework.data.elasticsearch.core.index.AliasActions; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * @author Mohsin Husen - * @author Peter-Josef Meisch - * @deprecated since 4.1, use {@link org.springframework.data.elasticsearch.core.IndexOperations#alias(AliasActions)}. - */ -public class AliasBuilder { - - @Nullable private String aliasName; - @Nullable private QueryBuilder filterBuilder; - @Nullable private Map filter; - @Nullable private String searchRouting; - @Nullable private String indexRouting; - @Nullable private String routing; - - public AliasBuilder withAliasName(String aliasName) { - this.aliasName = aliasName; - return this; - } - - public AliasBuilder withFilterBuilder(QueryBuilder filterBuilder) { - this.filterBuilder = filterBuilder; - return this; - } - - public AliasBuilder withFilter(Map filter) { - this.filter = filter; - return this; - } - - public AliasBuilder withSearchRouting(String searchRouting) { - this.searchRouting = searchRouting; - return this; - } - - public AliasBuilder withIndexRouting(String indexRouting) { - this.indexRouting = indexRouting; - return this; - } - - public AliasBuilder withRouting(String routing) { - this.routing = routing; - return this; - } - - public AliasQuery build() { - - Assert.notNull(aliasName, "aliasName must not be null"); - - AliasQuery aliasQuery = new AliasQuery(aliasName); - aliasQuery.setFilterBuilder(filterBuilder); - aliasQuery.setFilter(filter); - aliasQuery.setSearchRouting(searchRouting); - aliasQuery.setIndexRouting(indexRouting); - aliasQuery.setRouting(routing); - return aliasQuery; - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AliasQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AliasQuery.java deleted file mode 100644 index 736f9e3b2..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AliasQuery.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.query; - -import java.util.Map; - -import org.elasticsearch.index.query.QueryBuilder; -import org.springframework.data.elasticsearch.core.index.AliasActions; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * AliasQuery is useful for creating new alias or deleting existing ones - * - * @author Mohsin Husen - * @author Peter-Josef Meisch - * @deprecated since 4.1, use {@link org.springframework.data.elasticsearch.core.IndexOperations#alias(AliasActions)} - */ -@Deprecated -public class AliasQuery { - - public AliasQuery(String aliasName) { - - Assert.notNull(aliasName, "aliasName must not be null"); - - this.aliasName = aliasName; - } - - private String aliasName; - @Nullable private QueryBuilder filterBuilder; - @Nullable private Map filter; - @Nullable private String searchRouting; - @Nullable private String indexRouting; - @Nullable private String routing; - - public String getAliasName() { - return aliasName; - } - - @Nullable - public QueryBuilder getFilterBuilder() { - return filterBuilder; - } - - public void setFilterBuilder(QueryBuilder filterBuilder) { - this.filterBuilder = filterBuilder; - } - - @Nullable - public Map getFilter() { - return filter; - } - - public void setFilter(Map filter) { - this.filter = filter; - } - - @Nullable - public String getSearchRouting() { - return searchRouting; - } - - public void setSearchRouting(String searchRouting) { - this.searchRouting = searchRouting; - } - - @Nullable - public String getIndexRouting() { - return indexRouting; - } - - public void setIndexRouting(String indexRouting) { - this.indexRouting = indexRouting; - } - - @Nullable - public String getRouting() { - return routing; - } - - public void setRouting(String routing) { - this.routing = routing; - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java b/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java index 507b3a9ee..63b2848d5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java @@ -54,8 +54,6 @@ public class Criteria { public static final String CRITERIA_VALUE_SEPARATOR = " "; - /** @deprecated since 4.1, use {@link #CRITERIA_VALUE_SEPARATOR} */ - @SuppressWarnings("SpellCheckingInspection") public static final String CRITERIA_VALUE_SEPERATOR = CRITERIA_VALUE_SEPARATOR; private @Nullable Field field; private float boost = Float.NaN; @@ -112,7 +110,7 @@ public Criteria(Field field) { /** * Creates a Criteria for the given field, sets it's criteriaChain to the given value and adds itself to the end of * the chain. - * + * * @param criteriaChain the chain to add to * @param fieldName field to create the Criteria for */ @@ -123,7 +121,7 @@ protected Criteria(List criteriaChain, String fieldName) { /** * Creates a Criteria for the given field, sets it's criteriaChain to the given value and adds itself to the end of * the chain. - * + * * @param criteriaChain the chain to add to * @param field field to create the Criteria for */ @@ -174,16 +172,6 @@ public Set getFilterCriteriaEntries() { return Collections.unmodifiableSet(this.filterCriteriaEntries); } - /** - * Conjunction to be used with this criteria (AND | OR) - * - * @deprecated since 4.1, use {@link #getOperator()} - */ - @Deprecated - public String getConjunctionOperator() { - return Operator.AND.name(); - } - public Operator getOperator() { return Operator.AND; } @@ -332,7 +320,7 @@ public Criteria or(Criteria criteria) { /** * adds a Criteria as subCriteria - * + * * @param criteria the criteria to add, must not be {@literal null} * @return this object * @since 4.1 @@ -570,7 +558,7 @@ public Criteria greaterThan(Object lowerBound) { /** * Add a {@link OperationKey#MATCHES} entry to the {@link #queryCriteriaEntries}. This will build a match query with * the OR operator. - * + * * @param value the value to match * @return this object * @since 4.1 @@ -733,7 +721,7 @@ public Criteria within(String geoLocation, String distance) { /** * Adds a new filter CriteriaEntry for GEO_INTERSECTS. - * + * * @param geoShape the GeoJson shape * @return this object */ @@ -869,11 +857,6 @@ public OrCriteria(List criteriaChain, Field field) { super(criteriaChain, field); } - @Override - public String getConjunctionOperator() { - return Operator.OR.name(); - } - @Override public Operator getOperator() { return Operator.OR; @@ -882,7 +865,7 @@ public Operator getOperator() { /** * a list of {@link Criteria} objects that belong to one query. - * + * * @since 4.1 */ public static class CriteriaChain extends LinkedList {} diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java index 6ddda3d50..32733c295 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java @@ -48,20 +48,6 @@ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQue private final GenericConversionService conversionService = new GenericConversionService(); - { - if (!conversionService.canConvert(java.util.Date.class, String.class)) { - conversionService.addConverter(DateTimeConverters.JavaDateConverter.INSTANCE); - } - if (ClassUtils.isPresent("org.joda.time.DateTimeZone", ElasticsearchStringQuery.class.getClassLoader())) { - if (!conversionService.canConvert(org.joda.time.ReadableInstant.class, String.class)) { - conversionService.addConverter(DateTimeConverters.JodaDateTimeConverter.INSTANCE); - } - if (!conversionService.canConvert(org.joda.time.LocalDateTime.class, String.class)) { - conversionService.addConverter(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE); - } - } - } - public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations, String query) { super(queryMethod, elasticsearchOperations); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java deleted file mode 100644 index 6b858a938..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/AbstractElasticsearchRepository.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.repository.support; - -import org.springframework.data.elasticsearch.core.ElasticsearchOperations; - -/** - * Elasticsearch specific repository implementation. Likely to be used as target within - * {@link ElasticsearchRepositoryFactory} - * - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Ryan Henszey - * @author Sascha Woo - * @author Peter-Josef Meisch - * @deprecated since 4.1, derive from {@link SimpleElasticsearchRepository} instead - */ -@Deprecated -public abstract class AbstractElasticsearchRepository extends SimpleElasticsearchRepository { - - public AbstractElasticsearchRepository(ElasticsearchEntityInformation metadata, - ElasticsearchOperations elasticsearchOperations) { - super(metadata, elasticsearchOperations); - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchEntityInformation.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchEntityInformation.java index 1cbb6d4f4..824a7e17f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchEntityInformation.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchEntityInformation.java @@ -41,11 +41,4 @@ public interface ElasticsearchEntityInformation extends EntityInformation @Nullable VersionType getVersionType(); - - /** - * @deprecated since 4.1, not supported anymore by Elasticsearch - */ - @Deprecated - @Nullable - String getParentId(T entity); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/MappingElasticsearchEntityInformation.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/MappingElasticsearchEntityInformation.java index d0635e078..03b8093ed 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/MappingElasticsearchEntityInformation.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/MappingElasticsearchEntityInformation.java @@ -74,16 +74,4 @@ public VersionType getVersionType() { return persistentEntity.getVersionType(); } - @Deprecated - @Override - public String getParentId(T entity) { - - ElasticsearchPersistentProperty parentProperty = persistentEntity.getParentIdProperty(); - try { - return parentProperty != null ? (String) persistentEntity.getPropertyAccessor(entity).getProperty(parentProperty) - : null; - } catch (Exception e) { - throw new IllegalStateException("failed to load parent ID: " + e, e); - } - } } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java index eea2e77a8..0605c17e9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientIntegrationTests.java @@ -923,7 +923,7 @@ public void updateMapping() { Map jsonMap = Collections.singletonMap("properties", Collections.singletonMap("message", Collections.singletonMap("type", "text"))); - client.indices().updateMapping(request -> request.indices(INDEX_I).source(jsonMap)) // + client.indices().putMapping(request -> request.indices(INDEX_I).source(jsonMap)) // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -935,7 +935,7 @@ public void updateMappingNonExistingIndex() { Map jsonMap = Collections.singletonMap("properties", Collections.singletonMap("message", Collections.singletonMap("type", "text"))); - client.indices().updateMapping(request -> request.indices(INDEX_I).source(jsonMap)) // + client.indices().putMapping(request -> request.indices(INDEX_I).source(jsonMap)) // .as(StepVerifier::create) // .verifyError(ElasticsearchStatusException.class); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index c8ec98352..1cd35b169 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -35,7 +35,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; @@ -46,7 +45,6 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.cluster.metadata.AliasMetadata; import org.elasticsearch.common.lucene.search.function.CombineFunction; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.index.VersionType; @@ -89,7 +87,6 @@ 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.join.JoinField; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; @@ -2804,209 +2801,6 @@ private List getIndexQueries(List sampleEntities) { return indexQueries; } - @Test - public void shouldAddAlias() { - - // given - String aliasName = "test-alias"; - AliasQuery aliasQuery = new AliasBuilder() // - .withAliasName(aliasName) // - .build(); - - // when - indexOperations.addAlias(aliasQuery); - - // then - List aliases = indexOperations.queryForAlias(); - assertThat(aliases).isNotNull(); - assertThat(aliases.get(0).alias()).isEqualTo(aliasName); - } - - @Test // DATAES-864 - void shouldAddAliasesWithAliasActions() { - - AliasActions aliasActions = new AliasActions(); - aliasActions.add(new AliasAction.Add(AliasActionParameters.builder() - .withIndices(indexOperations.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); - - indexOperations.alias(aliasActions); - - List aliases = indexOperations.queryForAlias(); - assertThat(aliases).hasSize(2); - assertThat(aliases.stream().map(AliasMetadata::alias).collect(Collectors.toList())).contains("aliasA", "aliasB"); - } - - @Test // DATAES-864 - void shouldRemoveAliasesWithAliasActions() { - - AliasActions aliasActions = new AliasActions(); - aliasActions.add(new AliasAction.Add(AliasActionParameters.builder() - .withIndices(indexOperations.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); - - indexOperations.alias(aliasActions); - - aliasActions = new AliasActions(); - aliasActions.add(new AliasAction.Remove(AliasActionParameters.builder() - .withIndices(indexOperations.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); - - indexOperations.alias(aliasActions); - - List aliases = indexOperations.queryForAlias(); - assertThat(aliases).hasSize(0); - } - - @Test // DATAES-864 - void shouldGetAliasData() { - AliasActions aliasActions = new AliasActions(); - aliasActions.add(new AliasAction.Add(AliasActionParameters.builder() - .withIndices(indexOperations.getIndexCoordinates().getIndexNames()).withAliases("aliasA", "aliasB").build())); - - indexOperations.alias(aliasActions); - - Map> aliasDatas = indexOperations.getAliases("aliasA"); - - Set aliasData = aliasDatas.get(indexOperations.getIndexCoordinates().getIndexName()); - - assertThat(aliasData.stream().map(AliasData::getAlias)).containsExactly("aliasA"); - } - - @Test // DATAES-70 - public void shouldAddAliasForVariousRoutingValues() { - - // given - String alias1 = "test-alias-1"; - String alias2 = "test-alias-2"; - - AliasQuery aliasQuery1 = new AliasBuilder() // - .withAliasName(alias1) // - .withIndexRouting("0") // - .build(); - - AliasQuery aliasQuery2 = new AliasBuilder() // - .withAliasName(alias2) // - .withSearchRouting("1") // - .build(); - - // when - IndexCoordinates index = IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY); - indexOperations.addAlias(aliasQuery1); - indexOperations.addAlias(aliasQuery2); - - String documentId = nextIdAsString(); - SampleEntity entity = SampleEntity.builder() // - .id(documentId) // - .message("some message") // - .version(System.currentTimeMillis()) // - .build(); - - IndexQuery indexQuery = new IndexQueryBuilder() // - .withId(entity.getId()) // - .withObject(entity) // - .build(); - - operations.index(indexQuery, IndexCoordinates.of(alias1)); - - // then - List aliasMetaData = indexOperations.queryForAlias(); - assertThat(aliasMetaData).isNotEmpty(); - - AliasMetadata aliasMetaData1 = aliasMetaData.get(0); - assertThat(aliasMetaData1).isNotNull(); - if (aliasMetaData1.alias().equals(alias1)) { - assertThat(aliasMetaData1.indexRouting()).isEqualTo("0"); - } else if (aliasMetaData1.alias().equals(alias2)) { - assertThat(aliasMetaData1.searchRouting()).isEqualTo("1"); - } else { - fail("unknown alias"); - } - - AliasMetadata aliasMetaData2 = aliasMetaData.get(1); - assertThat(aliasMetaData2).isNotNull(); - if (aliasMetaData2.alias().equals(alias1)) { - assertThat(aliasMetaData2.indexRouting()).isEqualTo("0"); - } else if (aliasMetaData2.alias().equals(alias2)) { - assertThat(aliasMetaData2.searchRouting()).isEqualTo("1"); - } else { - fail("unknown alias"); - } - - // cleanup - indexOperations.removeAlias(aliasQuery1); - indexOperations.removeAlias(aliasQuery2); - } - - @Test // DATAES-70 - public void shouldAddAliasWithGivenRoutingValue() { - - // given - String alias = "test-alias"; - IndexCoordinates index = IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY); - - AliasQuery aliasQuery = new AliasBuilder() // - .withAliasName(alias) // - .withRouting("0") // - .build(); - - // when - indexOperations.addAlias(aliasQuery); - - String documentId = nextIdAsString(); - SampleEntity sampleEntity = SampleEntity.builder() // - .id(documentId) // - .message("some message") // - .version(System.currentTimeMillis()) // - .build(); - - IndexQuery indexQuery = new IndexQueryBuilder() // - .withId(sampleEntity.getId()) // - .withObject(sampleEntity) // - .build(); - - operations.index(indexQuery, IndexCoordinates.of(alias)); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)).refresh(); - - NativeSearchQuery query = new NativeSearchQueryBuilder() // - .withQuery(matchAllQuery()) // - .build(); - - long count = operations.count(query, IndexCoordinates.of(alias)); - - // then - List aliases = indexOperations.queryForAlias(); - assertThat(aliases).isNotNull(); - AliasMetadata aliasMetaData = aliases.get(0); - assertThat(aliasMetaData.alias()).isEqualTo(alias); - assertThat(aliasMetaData.searchRouting()).isEqualTo("0"); - assertThat(aliasMetaData.indexRouting()).isEqualTo("0"); - assertThat(count).isEqualTo(1); - - // cleanup - indexOperations.removeAlias(aliasQuery); - } - - @Test // DATAES-541 - public void shouldRemoveAlias() { - - // given - String aliasName = "test-alias"; - IndexCoordinates index = IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY); - - AliasQuery aliasQuery = new AliasBuilder() // - .withAliasName(aliasName) // - .build(); - - // when - indexOperations.addAlias(aliasQuery); - List aliases = indexOperations.queryForAlias(); - assertThat(aliases).isNotNull(); - assertThat(aliases.get(0).alias()).isEqualTo(aliasName); - - // then - indexOperations.removeAlias(aliasQuery); - aliases = indexOperations.queryForAlias(); - assertThat(aliases).isEmpty(); - } - @Document(indexName = INDEX_2_NAME) class ResultAggregator { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java index af15e9f9e..d7eb9a4b9 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java @@ -31,31 +31,6 @@ */ public class DateTimeConvertersTests { - @Test - public void testJodaDateTimeConverterWithNullValue() { - assertThat(DateTimeConverters.JodaDateTimeConverter.INSTANCE.convert(null)).isNull(); - } - - @Test - public void testJodaDateTimeConverter() { - DateTime dateTime = new DateTime(2013, 1, 24, 6, 35, 0, DateTimeZone.UTC); - assertThat(DateTimeConverters.JodaDateTimeConverter.INSTANCE.convert(dateTime)) - .isEqualTo("2013-01-24T06:35:00.000Z"); - } - - @Test - public void testJodaLocalDateTimeConverterWithNullValue() { - assertThat(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE.convert(null)).isNull(); - } - - @Test - public void testJodaLocalDateTimeConverter() { - LocalDateTime dateTime = new LocalDateTime(new DateTime(2013, 1, 24, 6, 35, 0, DateTimeZone.UTC).getMillis(), - DateTimeZone.UTC); - assertThat(DateTimeConverters.JodaLocalDateTimeConverter.INSTANCE.convert(dateTime)) - .isEqualTo("2013-01-24T06:35:00.000Z"); - } - @Test public void testJavaDateConverterWithNullValue() { assertThat(DateTimeConverters.JavaDateConverter.INSTANCE.convert(null)).isNull(); diff --git a/src/test/resources/org/springframework/data/elasticsearch/config/namespace/namespace.xml b/src/test/resources/org/springframework/data/elasticsearch/config/namespace/namespace.xml index 16c1968be..3d01395f7 100644 --- a/src/test/resources/org/springframework/data/elasticsearch/config/namespace/namespace.xml +++ b/src/test/resources/org/springframework/data/elasticsearch/config/namespace/namespace.xml @@ -5,13 +5,9 @@ xsi:schemaLocation="http://www.springframework.org/schema/data/elasticsearch%20https://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd%20http://www.springframework.org/schema/beans%20https://www.springframework.org/schema/beans/spring-beans.xsd"> - - - + @@ -19,7 +15,7 @@ - + From 84b441eadcfb1bdfaedc745e013b7571cc41bcb8 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Tue, 20 Apr 2021 10:40:58 -0500 Subject: [PATCH 055/776] Authenticate with artifactory. See #1750. --- Jenkinsfile | 7 +- LICENSE.txt | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++ ci/clean.sh | 2 +- ci/verify.sh | 2 +- pom.xml | 14 +--- settings.xml | 29 ++++++++ 6 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 LICENSE.txt create mode 100644 settings.xml diff --git a/Jenkinsfile b/Jenkinsfile index 5a4152d81..b247df884 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -26,6 +26,7 @@ pipeline { environment { DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') } steps { @@ -57,6 +58,7 @@ pipeline { environment { DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') } steps { @@ -80,6 +82,7 @@ pipeline { environment { DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') } steps { @@ -117,7 +120,7 @@ pipeline { script { docker.withRegistry('', 'hub.docker.com-springbuildmaster') { docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') { - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' + + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' + '-Dartifactory.server=https://repo.spring.io ' + "-Dartifactory.username=${ARTIFACTORY_USR} " + "-Dartifactory.password=${ARTIFACTORY_PSW} " + @@ -147,7 +150,7 @@ pipeline { script { docker.withRegistry('', 'hub.docker.com-springbuildmaster') { docker.image('adoptopenjdk/openjdk8:latest').inside('-v $HOME:/tmp/jenkins-home') { - sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' + + sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,distribute -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch-non-root ' + '-Dartifactory.server=https://repo.spring.io ' + "-Dartifactory.username=${ARTIFACTORY_USR} " + "-Dartifactory.password=${ARTIFACTORY_PSW} " + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..ff7737963 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/ci/clean.sh b/ci/clean.sh index 26f14033f..e78b1e148 100755 --- a/ci/clean.sh +++ b/ci/clean.sh @@ -3,4 +3,4 @@ set -euo pipefail MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \ - ./mvnw clean -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch + ./mvnw -s settings.xml clean -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch diff --git a/ci/verify.sh b/ci/verify.sh index 744718c47..e3ea30862 100755 --- a/ci/verify.sh +++ b/ci/verify.sh @@ -6,5 +6,5 @@ mkdir -p /tmp/jenkins-home/.m2/spring-data-elasticsearch chown -R 1001:1001 . MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \ - ./mvnw \ + ./mvnw -s settings.xml \ -P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-elasticsearch \ No newline at end of file diff --git a/pom.xml b/pom.xml index bdec10b2c..eb4fe2df3 100644 --- a/pom.xml +++ b/pom.xml @@ -484,11 +484,10 @@ https://repo.spring.io/libs-snapshot - - local-maven-repo - file:///${project.basedir}/src/test/resources/local-maven-repo - - + + local-maven-repo + file:///${project.basedir}/src/test/resources/local-maven-repo + @@ -496,11 +495,6 @@ spring-plugins-release https://repo.spring.io/plugins-release - - bintray-plugins - bintray-plugins - https://jcenter.bintray.com - diff --git a/settings.xml b/settings.xml new file mode 100644 index 000000000..b3227cc11 --- /dev/null +++ b/settings.xml @@ -0,0 +1,29 @@ + + + + + spring-plugins-release + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + spring-libs-snapshot + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + spring-libs-milestone + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + spring-libs-release + ${env.ARTIFACTORY_USR} + ${env.ARTIFACTORY_PSW} + + + + \ No newline at end of file From 8b7f0f8327a30f0c3017d500564344a8bce8a0b2 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 23 Apr 2021 09:38:05 +0200 Subject: [PATCH 056/776] Fix documentation. Original Pull Request #1786 Closes #1785 --- src/main/asciidoc/reference/elasticsearch-auditing.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/asciidoc/reference/elasticsearch-auditing.adoc b/src/main/asciidoc/reference/elasticsearch-auditing.adoc index f465e4e74..7783db346 100644 --- a/src/main/asciidoc/reference/elasticsearch-auditing.adoc +++ b/src/main/asciidoc/reference/elasticsearch-auditing.adoc @@ -30,11 +30,15 @@ public class Person implements Persistable { @Id private Long id; private String lastName; private String firstName; + @CreatedDate @Field(type = FieldType.Date, format = DateFormat.basic_date_time) private Instant createdDate; + @CreatedBy private String createdBy @Field(type = FieldType.Date, format = DateFormat.basic_date_time) + @LastModifiedDate private Instant lastModifiedDate; + @LastModifiedBy private String lastModifiedBy; public Long getId() { // <.> From 91742b111435e54ea0cb4ff54725d2830013a869 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 25 Apr 2021 21:57:13 +0200 Subject: [PATCH 057/776] Allow disabling TypeHints. Original Pull Request #1788 Closes #1788 --- .../elasticsearch-object-mapping.adoc | 18 +- .../elasticsearch/annotations/Document.java | 7 + .../annotations/WriteTypeHint.java | 40 + .../ElasticsearchConfigurationSupport.java | 15 +- .../MappingElasticsearchConverter.java | 1458 +++++++++-------- .../core/index/MappingBuilder.java | 17 +- .../ElasticsearchPersistentEntity.java | 14 +- .../ElasticsearchPersistentProperty.java | 1 + .../SimpleElasticsearchMappingContext.java | 15 +- .../SimpleElasticsearchPersistentEntity.java | 60 +- ...SimpleElasticsearchPersistentProperty.java | 18 +- ...appingElasticsearchConverterUnitTests.java | 107 +- .../core/index/MappingBuilderUnitTests.java | 136 ++ ...pleElasticsearchPersistentEntityTests.java | 130 +- ...sticsearchPersistentPropertyUnitTests.java | 22 +- 15 files changed, 1305 insertions(+), 753 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/WriteTypeHint.java diff --git a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc index f0ed04b19..1653f0b61 100644 --- a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc +++ b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc @@ -34,8 +34,6 @@ The following annotations are available: The most important attributes are: ** `indexName`: the name of the index to store this entity in. This can contain a SpEL template expression like `"log-#{T(java.time.LocalDate).now().toString()}"` -** `type`: [line-through]#the mapping type. -If not set, the lowercased simple name of the class is used.# (deprecated since version 4.0) ** `createIndex`: flag whether to create an index on repository bootstrapping. Default value is _true_. See <> @@ -170,6 +168,22 @@ public class Person { NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration. +===== Disabling Type Hints + +It may be necessary to disable writing of type hints when the index that should be used already exists without having the type hints defined in its mapping and with the mapping mode set to strict. In this case, writing the type hint will produce an error, as the field cannot be added automatically. + +Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see <>). + +As an alternativ they can be disabled for a single index with the `@Document` annotation: +==== +[source,java] +---- +@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE) +---- +==== + +WARNING: We strongly advise against disabling Type Hints. Only do this if you are forced to. Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely. + ==== Geospatial Types Geospatial types like `Point` & `GeoPoint` are converted into _lat/lon_ pairs. diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index ae53f190d..c1a7b8ff1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -105,4 +105,11 @@ * Configuration of version management. */ VersionType versionType() default VersionType.EXTERNAL; + + /** + * Defines if type hints should be written. {@see WriteTypeHint}. + * + * @since 4.3 + */ + WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT; } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/WriteTypeHint.java b/src/main/java/org/springframework/data/elasticsearch/annotations/WriteTypeHint.java new file mode 100644 index 000000000..0aae5f9f7 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/WriteTypeHint.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.annotations; + +import org.springframework.data.mapping.context.MappingContext; + +/** + * Defines if type hints should be written. Used by {@link Document} annotation. + * + * @author Peter-Josef Meisch + * @since 4.3 + */ +public enum WriteTypeHint { + + /** + * Use the global settings from the {@link MappingContext}. + */ + DEFAULT, + /** + * Always write type hints for the entity. + */ + TRUE, + /** + * Never write type hints for the entity. + */ + FALSE +} diff --git a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java index bf7ca3dbf..d6a3e8152 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java @@ -26,7 +26,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.data.annotation.Persistent; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; @@ -72,6 +71,7 @@ public SimpleElasticsearchMappingContext elasticsearchMappingContext( mappingContext.setInitialEntitySet(getInitialEntitySet()); mappingContext.setSimpleTypeHolder(elasticsearchCustomConversions.getSimpleTypeHolder()); mappingContext.setFieldNamingStrategy(fieldNamingStrategy()); + mappingContext.setWriteTypeHints(writeTypeHints()); return mappingContext; } @@ -171,4 +171,17 @@ protected RefreshPolicy refreshPolicy() { protected FieldNamingStrategy fieldNamingStrategy() { return PropertyNameFieldNamingStrategy.INSTANCE; } + + /** + * Flag specifiying if type hints (_class fields) should be written in the index. It is strongly advised to keep the + * default value of {@literal true}. If you need to write to an existing index that does not have a mapping defined + * for these fields and that has a strict mapping set, then it might be necessary to disable type hints. But notice + * that in this case reading polymorphic types may fail. + * + * @return flag if type hints should be written + * @since 4.3 + */ + protected boolean writeTypeHints() { + return true; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index c080d1e1f..86643e544 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -95,15 +95,8 @@ public class MappingElasticsearchConverter private final MappingContext, ElasticsearchPersistentProperty> mappingContext; private final GenericConversionService conversionService; - // don't access directly, use getConversions(). to prevent null access private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList()); - private final EntityInstantiators instantiators = new EntityInstantiators(); - - private final ElasticsearchTypeMapper typeMapper; - - private final ConcurrentHashMap propertyWarnings = new ConcurrentHashMap<>(); - private final SpELContext spELContext; public MappingElasticsearchConverter( MappingContext, ElasticsearchPersistentProperty> mappingContext) { @@ -118,8 +111,6 @@ public MappingElasticsearchConverter( this.mappingContext = mappingContext; this.conversionService = conversionService != null ? conversionService : new DefaultConversionService(); - this.typeMapper = ElasticsearchTypeMapper.create(mappingContext); - this.spELContext = new SpELContext(new MapAccessor()); } @Override @@ -157,871 +148,1007 @@ private CustomConversions getConversions() { return conversions; } - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ @Override public void afterPropertiesSet() { DateFormatterRegistrar.addDateConverters(conversionService); getConversions().registerConvertersIn(conversionService); } - // region read + // region read/write - @SuppressWarnings("unchecked") @Override public R read(Class type, Document source) { - TypeInformation typeHint = ClassTypeInformation.from((Class) ClassUtils.getUserClass(type)); - R r = read(typeHint, source); + Reader reader = new Reader(mappingContext, conversionService, getConversions()); + return reader.read(type, source); + } - if (r == null) { - throw new ConversionException("could not convert into object of class " + type); - } + @Override + public void write(Object source, Document sink) { + + Assert.notNull(source, "source to map must not be null"); - return r; + Writer writer = new Writer(mappingContext, conversionService, getConversions()); + writer.write(source, sink); } - protected R readEntity(ElasticsearchPersistentEntity entity, Map source) { + /** + * base class for {@link Reader} and {@link Writer} keeping the common properties + */ + private static class Base { + + protected final MappingContext, ElasticsearchPersistentProperty> mappingContext; + protected final ElasticsearchTypeMapper typeMapper; + protected final GenericConversionService conversionService; + protected final CustomConversions conversions; + protected final ConcurrentHashMap propertyWarnings = new ConcurrentHashMap<>(); + + private Base( + MappingContext, ElasticsearchPersistentProperty> mappingContext, + GenericConversionService conversionService, CustomConversions conversions) { + this.mappingContext = mappingContext; + this.conversionService = conversionService; + this.conversions = conversions; + this.typeMapper = ElasticsearchTypeMapper.create(mappingContext); + } + } + + /** + * Class to do the actual writing. The methods originally were in the MappingElasticsearchConverter class, but are + * refactored to allow for keeping state during the conversion of an object. + */ + private static class Reader extends Base { - ElasticsearchPersistentEntity targetEntity = computeClosestEntity(entity, source); + private final SpELContext spELContext; + private final EntityInstantiators instantiators = new EntityInstantiators(); - SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext); - MapValueAccessor accessor = new MapValueAccessor(source); + public Reader( + MappingContext, ElasticsearchPersistentProperty> mappingContext, + GenericConversionService conversionService, CustomConversions conversions) { - PreferredConstructor persistenceConstructor = entity - .getPersistenceConstructor(); + super(mappingContext, conversionService, conversions); + this.spELContext = new SpELContext(new MapAccessor()); + } - ParameterValueProvider propertyValueProvider = persistenceConstructor != null - && persistenceConstructor.hasParameters() ? getParameterProvider(entity, accessor, evaluator) - : NoOpParameterValueProvider.INSTANCE; + @SuppressWarnings("unchecked") + R read(Class type, Document source) { - EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity); + TypeInformation typeHint = ClassTypeInformation.from((Class) ClassUtils.getUserClass(type)); + R r = read(typeHint, source); - @SuppressWarnings({ "unchecked" }) - R instance = (R) instantiator.createInstance(targetEntity, propertyValueProvider); + if (r == null) { + throw new ConversionException("could not convert into object of class " + type); + } - if (!targetEntity.requiresPropertyPopulation()) { - return instance; + return r; } - ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator); - R result = readProperties(targetEntity, instance, valueProvider); - - if (source instanceof Document) { - Document document = (Document) source; - if (document.hasId()) { - ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty(); - PersistentPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor<>( - targetEntity.getPropertyAccessor(result), conversionService); - // Only deal with String because ES generated Ids are strings ! - if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { - propertyAccessor.setProperty(idProperty, document.getId()); - } + @Nullable + @SuppressWarnings("unchecked") + private R read(TypeInformation type, Map source) { + + Assert.notNull(source, "Source must not be null!"); + + TypeInformation typeToUse = typeMapper.readType(source, type); + Class rawType = typeToUse.getType(); + + if (conversions.hasCustomReadTarget(source.getClass(), rawType)) { + return conversionService.convert(source, rawType); } - if (document.hasVersion()) { - long version = document.getVersion(); - ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty(); - // Only deal with Long because ES versions are longs ! - if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) { - // check that a version was actually returned in the response, -1 would indicate that - // a search didn't request the version ids in the response, which would be an issue - Assert.isTrue(version != -1, "Version in response is -1"); - targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version); - } + if (Document.class.isAssignableFrom(rawType)) { + return (R) source; } - if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) { - if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) { - SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm()); - ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty(); - targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm); - } + if (typeToUse.isMap()) { + return readMap(typeToUse, source); + } + + if (typeToUse.equals(ClassTypeInformation.OBJECT)) { + return (R) source; + } + // Retrieve persistent entity info + + ElasticsearchPersistentEntity entity = mappingContext.getPersistentEntity(typeToUse); + + if (entity == null) { + throw new MappingException(String.format(INVALID_TYPE_TO_READ, source, typeToUse.getType())); } - } - if (source instanceof SearchDocument) { - SearchDocument searchDocument = (SearchDocument) source; - populateScriptFields(result, searchDocument); + return readEntity(entity, source); } - return result; + @SuppressWarnings("unchecked") + private R readMap(TypeInformation type, Map source) { - } + Assert.notNull(source, "Document must not be null!"); - private ParameterValueProvider getParameterProvider( - ElasticsearchPersistentEntity entity, MapValueAccessor source, SpELExpressionEvaluator evaluator) { + Class mapType = typeMapper.readType(source, type).getType(); - ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator); + TypeInformation keyType = type.getComponentType(); + TypeInformation valueType = type.getMapValueType(); - // TODO: Support for non-static inner classes via ObjectPath - // noinspection ConstantConditions - PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider<>( - entity, provider, null); + Class rawKeyType = keyType != null ? keyType.getType() : null; + Class rawValueType = valueType != null ? valueType.getType() : null; - return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider); - } + Map map = CollectionFactory.createMap(mapType, rawKeyType, source.keySet().size()); - private boolean isAssignedSeqNo(long seqNo) { - return seqNo >= 0; - } + for (Entry entry : source.entrySet()) { - private boolean isAssignedPrimaryTerm(long primaryTerm) { - return primaryTerm > 0; - } + if (typeMapper.isTypeKey(entry.getKey())) { + continue; + } - protected R readProperties(ElasticsearchPersistentEntity entity, R instance, - ElasticsearchPropertyValueProvider valueProvider) { + Object key = entry.getKey(); - PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance), - conversionService); + if (rawKeyType != null && !rawKeyType.isAssignableFrom(key.getClass())) { + key = conversionService.convert(key, rawKeyType); + } - for (ElasticsearchPersistentProperty prop : entity) { + Object value = entry.getValue(); + TypeInformation defaultedValueType = valueType != null ? valueType : ClassTypeInformation.OBJECT; - if (entity.isConstructorArgument(prop) || !prop.isReadable()) { - continue; + if (value instanceof Map) { + map.put(key, read(defaultedValueType, (Map) value)); + } else if (value instanceof List) { + map.put(key, + readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List) value)); + } else { + map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType)); + } } - Object value = valueProvider.getPropertyValue(prop); - if (value != null) { - accessor.setProperty(prop, value); - } + return (R) map; } - return accessor.getBean(); - } + private R readEntity(ElasticsearchPersistentEntity entity, Map source) { - @Nullable - protected R readValue(@Nullable Object value, ElasticsearchPersistentProperty property, TypeInformation type) { + ElasticsearchPersistentEntity targetEntity = computeClosestEntity(entity, source); - if (value == null) { - return null; - } + SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(source, spELContext); + MapValueAccessor accessor = new MapValueAccessor(source); + + PreferredConstructor persistenceConstructor = entity + .getPersistenceConstructor(); + + ParameterValueProvider propertyValueProvider = persistenceConstructor != null + && persistenceConstructor.hasParameters() ? getParameterProvider(entity, accessor, evaluator) + : NoOpParameterValueProvider.INSTANCE; - Class rawType = type.getType(); + EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity); - if (property.hasPropertyConverter()) { - value = propertyConverterRead(property, value); - } else if (TemporalAccessor.class.isAssignableFrom(property.getType()) - && !getConversions().hasCustomReadTarget(value.getClass(), rawType)) { + @SuppressWarnings({ "unchecked" }) + R instance = (R) instantiator.createInstance(targetEntity, propertyValueProvider); - // log at most 5 times - String propertyName = property.getOwner().getType().getSimpleName() + '.' + property.getName(); - String key = propertyName + "-read"; - int count = propertyWarnings.computeIfAbsent(key, k -> 0); - if (count < 5) { - LOGGER.warn( - "Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for reading!" - + " It cannot be mapped from a complex object in Elasticsearch!", - property.getType().getSimpleName(), propertyName); - propertyWarnings.put(key, count + 1); + if (!targetEntity.requiresPropertyPopulation()) { + return instance; } - } - return readValue(value, type); - } + ElasticsearchPropertyValueProvider valueProvider = new ElasticsearchPropertyValueProvider(accessor, evaluator); + R result = readProperties(targetEntity, instance, valueProvider); + + if (source instanceof Document) { + Document document = (Document) source; + if (document.hasId()) { + ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty(); + PersistentPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor<>( + targetEntity.getPropertyAccessor(result), conversionService); + // Only deal with String because ES generated Ids are strings ! + if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { + propertyAccessor.setProperty(idProperty, document.getId()); + } + } - @Nullable - @SuppressWarnings("unchecked") - private T readValue(Object value, TypeInformation type) { - - Class rawType = type.getType(); - - if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { - return (T) conversionService.convert(value, rawType); - } else if (value instanceof List) { - return (T) readCollectionOrArray(type, (List) value); - } else if (value.getClass().isArray()) { - return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value)); - } else if (value instanceof Map) { - return (T) read(type, (Map) value); - } else { - return (T) getPotentiallyConvertedSimpleRead(value, rawType); - } - } + if (document.hasVersion()) { + long version = document.getVersion(); + ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty(); + // Only deal with Long because ES versions are longs ! + if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) { + // check that a version was actually returned in the response, -1 would indicate that + // a search didn't request the version ids in the response, which would be an issue + Assert.isTrue(version != -1, "Version in response is -1"); + targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version); + } + } - @Nullable - @SuppressWarnings("unchecked") - private R read(TypeInformation type, Map source) { + if (targetEntity.hasSeqNoPrimaryTermProperty() && document.hasSeqNo() && document.hasPrimaryTerm()) { + if (isAssignedSeqNo(document.getSeqNo()) && isAssignedPrimaryTerm(document.getPrimaryTerm())) { + SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(document.getSeqNo(), document.getPrimaryTerm()); + ElasticsearchPersistentProperty property = targetEntity.getRequiredSeqNoPrimaryTermProperty(); + targetEntity.getPropertyAccessor(result).setProperty(property, seqNoPrimaryTerm); + } + } + } - Assert.notNull(source, "Source must not be null!"); + if (source instanceof SearchDocument) { + SearchDocument searchDocument = (SearchDocument) source; + populateScriptFields(result, searchDocument); + } - TypeInformation typeToUse = typeMapper.readType(source, type); - Class rawType = typeToUse.getType(); + return result; - if (conversions.hasCustomReadTarget(source.getClass(), rawType)) { - return conversionService.convert(source, rawType); } - if (Document.class.isAssignableFrom(rawType)) { - return (R) source; - } + private ParameterValueProvider getParameterProvider( + ElasticsearchPersistentEntity entity, MapValueAccessor source, SpELExpressionEvaluator evaluator) { - if (typeToUse.isMap()) { - return readMap(typeToUse, source); - } + ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator); - if (typeToUse.equals(ClassTypeInformation.OBJECT)) { - return (R) source; + // TODO: Support for non-static inner classes via ObjectPath + // noinspection ConstantConditions + PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider<>( + entity, provider, null); + + return new ConverterAwareSpELExpressionParameterValueProvider(evaluator, conversionService, parameterProvider); } - // Retrieve persistent entity info - ElasticsearchPersistentEntity entity = mappingContext.getPersistentEntity(typeToUse); + private boolean isAssignedSeqNo(long seqNo) { + return seqNo >= 0; + } - if (entity == null) { - throw new MappingException(String.format(INVALID_TYPE_TO_READ, source, typeToUse.getType())); + private boolean isAssignedPrimaryTerm(long primaryTerm) { + return primaryTerm > 0; } - return readEntity(entity, source); - } + protected R readProperties(ElasticsearchPersistentEntity entity, R instance, + ElasticsearchPropertyValueProvider valueProvider) { - private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) { - ElasticsearchPersistentPropertyConverter propertyConverter = Objects - .requireNonNull(property.getPropertyConverter()); + PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance), + conversionService); - if (source instanceof String[]) { - // convert to a List - source = Arrays.asList((String[]) source); - } + for (ElasticsearchPersistentProperty prop : entity) { - if (source instanceof List) { - source = ((List) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toList()); - } else if (source instanceof Set) { - source = ((Set) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toSet()); - } else { - source = convertOnRead(propertyConverter, source); - } - return source; - } + if (entity.isConstructorArgument(prop) || !prop.isReadable()) { + continue; + } + + Object value = valueProvider.getPropertyValue(prop); + if (value != null) { + accessor.setProperty(prop, value); + } + } - private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) { - if (String.class.isAssignableFrom(source.getClass())) { - source = propertyConverter.read((String) source); + return accessor.getBean(); } - return source; - } - /** - * Reads the given {@link Collection} into a collection of the given {@link TypeInformation}. - * - * @param targetType must not be {@literal null}. - * @param source must not be {@literal null}. - * @return the converted {@link Collection} or array, will never be {@literal null}. - */ - @SuppressWarnings("unchecked") - @Nullable - private Object readCollectionOrArray(TypeInformation targetType, Collection source) { + @Nullable + protected R readValue(@Nullable Object value, ElasticsearchPersistentProperty property, + TypeInformation type) { - Assert.notNull(targetType, "Target type must not be null!"); + if (value == null) { + return null; + } - Class collectionType = targetType.isSubTypeOf(Collection.class) // - ? targetType.getType() // - : List.class; + Class rawType = type.getType(); - TypeInformation componentType = targetType.getComponentType() != null // - ? targetType.getComponentType() // - : ClassTypeInformation.OBJECT; - Class rawComponentType = componentType.getType(); + if (property.hasPropertyConverter()) { + value = propertyConverterRead(property, value); + } else if (TemporalAccessor.class.isAssignableFrom(property.getType()) + && !conversions.hasCustomReadTarget(value.getClass(), rawType)) { - Collection items = targetType.getType().isArray() // - ? new ArrayList<>(source.size()) // - : CollectionFactory.createCollection(collectionType, rawComponentType, source.size()); + // log at most 5 times + String propertyName = property.getOwner().getType().getSimpleName() + '.' + property.getName(); + String key = propertyName + "-read"; + int count = propertyWarnings.computeIfAbsent(key, k -> 0); + if (count < 5) { + LOGGER.warn( + "Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for reading!" + + " It cannot be mapped from a complex object in Elasticsearch!", + property.getType().getSimpleName(), propertyName); + propertyWarnings.put(key, count + 1); + } + } - if (source.isEmpty()) { - return getPotentiallyConvertedSimpleRead(items, targetType); + return readValue(value, type); } - for (Object element : source) { + @Nullable + @SuppressWarnings("unchecked") + private T readValue(Object value, TypeInformation type) { - if (element instanceof Map) { - items.add(read(componentType, (Map) element)); - } else { + Class rawType = type.getType(); - if (!Object.class.equals(rawComponentType) && element instanceof Collection) { - if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) { - throw new MappingException( - String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType)); - } - } - if (element instanceof List) { - items.add(readCollectionOrArray(componentType, (Collection) element)); - } else { - items.add(getPotentiallyConvertedSimpleRead(element, rawComponentType)); - } + if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { + return (T) conversionService.convert(value, rawType); + } else if (value instanceof List) { + return (T) readCollectionOrArray(type, (List) value); + } else if (value.getClass().isArray()) { + return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value)); + } else if (value instanceof Map) { + return (T) read(type, (Map) value); + } else { + return (T) getPotentiallyConvertedSimpleRead(value, rawType); } } - return getPotentiallyConvertedSimpleRead(items, targetType.getType()); - } + private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) { + ElasticsearchPersistentPropertyConverter propertyConverter = Objects + .requireNonNull(property.getPropertyConverter()); - @SuppressWarnings("unchecked") - private R readMap(TypeInformation type, Map source) { + if (source instanceof String[]) { + // convert to a List + source = Arrays.asList((String[]) source); + } - Assert.notNull(source, "Document must not be null!"); + if (source instanceof List) { + source = ((List) source).stream().map(it -> convertOnRead(propertyConverter, it)) + .collect(Collectors.toList()); + } else if (source instanceof Set) { + source = ((Set) source).stream().map(it -> convertOnRead(propertyConverter, it)).collect(Collectors.toSet()); + } else { + source = convertOnRead(propertyConverter, source); + } + return source; + } - Class mapType = typeMapper.readType(source, type).getType(); + private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) { + if (String.class.isAssignableFrom(source.getClass())) { + source = propertyConverter.read((String) source); + } + return source; + } - TypeInformation keyType = type.getComponentType(); - TypeInformation valueType = type.getMapValueType(); + /** + * Reads the given {@link Collection} into a collection of the given {@link TypeInformation}. + * + * @param targetType must not be {@literal null}. + * @param source must not be {@literal null}. + * @return the converted {@link Collection} or array, will never be {@literal null}. + */ + @SuppressWarnings("unchecked") + @Nullable + private Object readCollectionOrArray(TypeInformation targetType, Collection source) { - Class rawKeyType = keyType != null ? keyType.getType() : null; - Class rawValueType = valueType != null ? valueType.getType() : null; + Assert.notNull(targetType, "Target type must not be null!"); - Map map = CollectionFactory.createMap(mapType, rawKeyType, source.keySet().size()); + Class collectionType = targetType.isSubTypeOf(Collection.class) // + ? targetType.getType() // + : List.class; - for (Entry entry : source.entrySet()) { + TypeInformation componentType = targetType.getComponentType() != null // + ? targetType.getComponentType() // + : ClassTypeInformation.OBJECT; + Class rawComponentType = componentType.getType(); - if (typeMapper.isTypeKey(entry.getKey())) { - continue; + Collection items = targetType.getType().isArray() // + ? new ArrayList<>(source.size()) // + : CollectionFactory.createCollection(collectionType, rawComponentType, source.size()); + + if (source.isEmpty()) { + return getPotentiallyConvertedSimpleRead(items, targetType); } - Object key = entry.getKey(); + for (Object element : source) { + + if (element instanceof Map) { + items.add(read(componentType, (Map) element)); + } else { - if (rawKeyType != null && !rawKeyType.isAssignableFrom(key.getClass())) { - key = conversionService.convert(key, rawKeyType); + if (!Object.class.equals(rawComponentType) && element instanceof Collection) { + if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) { + throw new MappingException( + String.format(INCOMPATIBLE_TYPES, element, element.getClass(), rawComponentType)); + } + } + if (element instanceof List) { + items.add(readCollectionOrArray(componentType, (Collection) element)); + } else { + items.add(getPotentiallyConvertedSimpleRead(element, rawComponentType)); + } + } } - Object value = entry.getValue(); - TypeInformation defaultedValueType = valueType != null ? valueType : ClassTypeInformation.OBJECT; + return getPotentiallyConvertedSimpleRead(items, targetType.getType()); + } - if (value instanceof Map) { - map.put(key, read(defaultedValueType, (Map) value)); - } else if (value instanceof List) { - map.put(key, - readCollectionOrArray(valueType != null ? valueType : ClassTypeInformation.LIST, (List) value)); - } else { - map.put(key, getPotentiallyConvertedSimpleRead(value, rawValueType)); - } + @Nullable + private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, TypeInformation targetType) { + return getPotentiallyConvertedSimpleRead(value, targetType.getType()); } - return (R) map; - } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Nullable + private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class target) { - @Nullable - private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, TypeInformation targetType) { - return getPotentiallyConvertedSimpleRead(value, targetType.getType()); - } + if (target == null || value == null || ClassUtils.isAssignableValue(target, value)) { + return value; + } - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Nullable - private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class target) { + if (conversions.hasCustomReadTarget(value.getClass(), target)) { + return conversionService.convert(value, target); + } - if (target == null || value == null || ClassUtils.isAssignableValue(target, value)) { - return value; - } + if (Enum.class.isAssignableFrom(target)) { + return Enum.valueOf((Class) target, value.toString()); + } - if (getConversions().hasCustomReadTarget(value.getClass(), target)) { return conversionService.convert(value, target); } - if (Enum.class.isAssignableFrom(target)) { - return Enum.valueOf((Class) target, value.toString()); - } - - return conversionService.convert(value, target); - } - - private void populateScriptFields(T result, SearchDocument searchDocument) { - Map> fields = searchDocument.getFields(); - if (!fields.isEmpty()) { - for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) { - ScriptedField scriptedField = field.getAnnotation(ScriptedField.class); - if (scriptedField != null) { - String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name(); - Object value = searchDocument.getFieldValue(name); - if (value != null) { - field.setAccessible(true); - try { - field.set(result, value); - } catch (IllegalArgumentException e) { - throw new MappingException("failed to set scripted field: " + name + " with value: " + value, e); - } catch (IllegalAccessException e) { - throw new MappingException("failed to access scripted field: " + name, e); + private void populateScriptFields(T result, SearchDocument searchDocument) { + Map> fields = searchDocument.getFields(); + if (!fields.isEmpty()) { + for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) { + ScriptedField scriptedField = field.getAnnotation(ScriptedField.class); + if (scriptedField != null) { + String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name(); + Object value = searchDocument.getFieldValue(name); + if (value != null) { + field.setAccessible(true); + try { + field.set(result, value); + } catch (IllegalArgumentException e) { + throw new MappingException("failed to set scripted field: " + name + " with value: " + value, e); + } catch (IllegalAccessException e) { + throw new MappingException("failed to access scripted field: " + name, e); + } } } } } } - } - // endregion - // region write - @Override - public void write(Object source, Document sink) { + /** + * Compute the type to use by checking the given entity against the store type; + */ + private ElasticsearchPersistentEntity computeClosestEntity(ElasticsearchPersistentEntity entity, + Map source) { - Assert.notNull(source, "source to map must not be null"); + TypeInformation typeToUse = typeMapper.readType(source); - if (source instanceof Map) { - // noinspection unchecked - sink.putAll((Map) source); - return; - } + if (typeToUse == null) { + return entity; + } - Class entityType = ClassUtils.getUserClass(source.getClass()); - TypeInformation typeInformation = ClassTypeInformation.from(entityType); + if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike() + && !entity.getTypeInformation().isMap() + && !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) { + return entity; + } - if (requiresTypeHint(entityType)) { - typeMapper.writeType(typeInformation, sink); + return mappingContext.getRequiredPersistentEntity(typeToUse); } - writeInternal(source, sink, typeInformation); - } + class ElasticsearchPropertyValueProvider implements PropertyValueProvider { - /** - * Internal write conversion method which should be used for nested invocations. - * - * @param source the object to write - * @param sink the write destination - * @param typeInformation type information for the source - */ - @SuppressWarnings("unchecked") - protected void writeInternal(@Nullable Object source, Map sink, - @Nullable TypeInformation typeInformation) { + final MapValueAccessor accessor; + final SpELExpressionEvaluator evaluator; - if (null == source) { - return; - } + ElasticsearchPropertyValueProvider(MapValueAccessor accessor, SpELExpressionEvaluator evaluator) { + this.accessor = accessor; + this.evaluator = evaluator; + } - Class entityType = source.getClass(); - Optional> customTarget = conversions.getCustomWriteTarget(entityType, Map.class); + @Override + public T getPropertyValue(ElasticsearchPersistentProperty property) { - if (customTarget.isPresent()) { - Map result = conversionService.convert(source, Map.class); + String expression = property.getSpelExpression(); + Object value = expression != null ? evaluator.evaluate(expression) : accessor.get(property); - if (result != null) { - sink.putAll(result); + if (value == null) { + return null; + } + + return readValue(value, property, property.getTypeInformation()); } - return; } - if (Map.class.isAssignableFrom(entityType)) { - writeMapInternal((Map) source, sink, ClassTypeInformation.MAP); - return; - } + /** + * Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw + * resolved SpEL value. + * + * @author Mark Paluch + */ + private class ConverterAwareSpELExpressionParameterValueProvider + extends SpELExpressionParameterValueProvider { + + /** + * Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}. + * + * @param evaluator must not be {@literal null}. + * @param conversionService must not be {@literal null}. + * @param delegate must not be {@literal null}. + */ + public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, + ConversionService conversionService, ParameterValueProvider delegate) { + + super(evaluator, conversionService, delegate); + } - if (Collection.class.isAssignableFrom(entityType)) { - writeCollectionInternal((Collection) source, ClassTypeInformation.LIST, (Collection) sink); - return; + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter) + */ + @Override + protected T potentiallyConvertSpelValue(Object object, + PreferredConstructor.Parameter parameter) { + return readValue(object, parameter.getType()); + } } - ElasticsearchPersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityType); - addCustomTypeKeyIfNecessary(source, sink, typeInformation); - writeInternal(source, sink, entity); + enum NoOpParameterValueProvider implements ParameterValueProvider { + + INSTANCE; + + @Override + public T getParameterValue(PreferredConstructor.Parameter parameter) { + return null; + } + } } /** - * Internal write conversion method which should be used for nested invocations. - * - * @param source the object to write - * @param sink the write destination - * @param entity entity for the source + * Class to do the actual writing. The methods originally were in the MappingElasticsearchConverter class, but are + * refactored to allow for keeping state during the conversion of an object. */ - protected void writeInternal(@Nullable Object source, Map sink, - @Nullable ElasticsearchPersistentEntity entity) { + static private class Writer extends Base { - if (source == null) { - return; - } + private boolean writeTypeHints = true; - if (null == entity) { - throw new MappingException("No mapping metadata found for entity of type " + source.getClass().getName()); + public Writer( + MappingContext, ElasticsearchPersistentProperty> mappingContext, + GenericConversionService conversionService, CustomConversions conversions) { + super(mappingContext, conversionService, conversions); } - PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); - writeProperties(entity, accessor, new MapValueAccessor(sink)); - } + void write(Object source, Document sink) { - protected void writeProperties(ElasticsearchPersistentEntity entity, PersistentPropertyAccessor accessor, - MapValueAccessor sink) { + if (source instanceof Map) { + // noinspection unchecked + sink.putAll((Map) source); + return; + } - for (ElasticsearchPersistentProperty property : entity) { + Class entityType = ClassUtils.getUserClass(source.getClass()); + ElasticsearchPersistentEntity entity = mappingContext.getPersistentEntity(entityType); - if (!property.isWritable()) { - continue; + if (entity != null) { + writeTypeHints = entity.writeTypeHints(); } - Object value = accessor.getProperty(property); + TypeInformation typeInformation = ClassTypeInformation.from(entityType); - if (value == null) { + if (writeTypeHints && requiresTypeHint(entityType)) { + typeMapper.writeType(typeInformation, sink); + } - if (property.storeNullValue()) { - sink.set(property, null); - } + writeInternal(source, sink, typeInformation); + } + + /** + * Internal write conversion method which should be used for nested invocations. + * + * @param source the object to write + * @param sink the write destination + * @param typeInformation type information for the source + */ + @SuppressWarnings("unchecked") + private void writeInternal(@Nullable Object source, Map sink, + @Nullable TypeInformation typeInformation) { - continue; + if (null == source) { + return; } - if (property.hasPropertyConverter()) { - value = propertyConverterWrite(property, value); - sink.set(property, value); - } else if (TemporalAccessor.class.isAssignableFrom(property.getActualType()) - && !getConversions().hasCustomWriteTarget(value.getClass())) { + Class entityType = source.getClass(); + Optional> customTarget = conversions.getCustomWriteTarget(entityType, Map.class); - // log at most 5 times - String propertyName = entity.getType().getSimpleName() + '.' + property.getName(); - String key = propertyName + "-write"; - int count = propertyWarnings.computeIfAbsent(key, k -> 0); - if (count < 5) { - LOGGER.warn( - "Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for writing!" - + " It will be mapped to a complex object in Elasticsearch!", - property.getType().getSimpleName(), propertyName); - propertyWarnings.put(key, count + 1); - } - } else if (!isSimpleType(value)) { - writeProperty(property, value, sink); - } else { - Object writeSimpleValue = getPotentiallyConvertedSimpleWrite(value, Object.class); - if (writeSimpleValue != null) { - sink.set(property, writeSimpleValue); + if (customTarget.isPresent()) { + Map result = conversionService.convert(source, Map.class); + + if (result != null) { + sink.putAll(result); } + return; } - } - } - private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) { - ElasticsearchPersistentPropertyConverter propertyConverter = Objects - .requireNonNull(property.getPropertyConverter()); + if (Map.class.isAssignableFrom(entityType)) { + writeMapInternal((Map) source, sink, ClassTypeInformation.MAP); + return; + } - if (value instanceof List) { - value = ((List) value).stream().map(propertyConverter::write).collect(Collectors.toList()); - } else if (value instanceof Set) { - value = ((Set) value).stream().map(propertyConverter::write).collect(Collectors.toSet()); - } else { - value = propertyConverter.write(value); + if (Collection.class.isAssignableFrom(entityType)) { + writeCollectionInternal((Collection) source, ClassTypeInformation.LIST, (Collection) sink); + return; + } + + ElasticsearchPersistentEntity entity = mappingContext.getRequiredPersistentEntity(entityType); + addCustomTypeKeyIfNecessary(source, sink, typeInformation); + writeInternal(source, sink, entity); } - return value; - } - @SuppressWarnings("unchecked") - protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) { + /** + * Internal write conversion method which should be used for nested invocations. + * + * @param source the object to write + * @param sink the write destination + * @param entity entity for the source + */ + private void writeInternal(@Nullable Object source, Map sink, + @Nullable ElasticsearchPersistentEntity entity) { + + if (source == null) { + return; + } - Optional> customWriteTarget = getConversions().getCustomWriteTarget(value.getClass()); + if (null == entity) { + throw new MappingException("No mapping metadata found for entity of type " + source.getClass().getName()); + } - if (customWriteTarget.isPresent()) { - Class writeTarget = customWriteTarget.get(); - sink.set(property, conversionService.convert(value, writeTarget)); - return; + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); + writeProperties(entity, accessor, new MapValueAccessor(sink)); } - TypeInformation valueType = ClassTypeInformation.from(value.getClass()); - TypeInformation type = property.getTypeInformation(); + /** + * Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document. + * + * @param type must not be {@literal null}. + * @return {@literal true} if not a simple type, {@link Collection} or type with custom write target. + */ + private boolean requiresTypeHint(Class type) { - if (valueType.isCollectionLike()) { - List collectionInternal = createCollection(asCollection(value), property); - sink.set(property, collectionInternal); - return; + return !isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type) + && !conversions.hasCustomWriteTarget(type, Document.class); } - if (valueType.isMap()) { - Map mapDbObj = createMap((Map) value, property); - sink.set(property, mapDbObj); - return; + private boolean isSimpleType(Object value) { + return isSimpleType(value.getClass()); } - // Lookup potential custom target type - Optional> basicTargetType = conversions.getCustomWriteTarget(value.getClass()); + private boolean isSimpleType(Class type) { + return !Map.class.isAssignableFrom(type) && conversions.isSimpleType(type); + } - if (basicTargetType.isPresent()) { + /** + * Writes the given {@link Map} to the given {@link Document} considering the given {@link TypeInformation}. + * + * @param source must not be {@literal null}. + * @param sink must not be {@literal null}. + * @param propertyType must not be {@literal null}. + */ + private Map writeMapInternal(Map source, Map sink, + TypeInformation propertyType) { - sink.set(property, conversionService.convert(value, basicTargetType.get())); - return; - } + for (Map.Entry entry : source.entrySet()) { - ElasticsearchPersistentEntity entity = valueType.isSubTypeOf(property.getType()) - ? mappingContext.getRequiredPersistentEntity(value.getClass()) - : mappingContext.getRequiredPersistentEntity(type); + Object key = entry.getKey(); + Object value = entry.getValue(); - Object existingValue = sink.get(property); - Map document = existingValue instanceof Map ? (Map) existingValue - : Document.create(); + if (isSimpleType(key.getClass())) { - addCustomTypeKeyIfNecessary(value, document, ClassTypeInformation.from(property.getRawType())); - writeInternal(value, document, entity); - sink.set(property, document); - } + String simpleKey = potentiallyConvertMapKey(key); + if (value == null || isSimpleType(value)) { + sink.put(simpleKey, getPotentiallyConvertedSimpleWrite(value, Object.class)); + } else if (value instanceof Collection || value.getClass().isArray()) { + sink.put(simpleKey, + writeCollectionInternal(asCollection(value), propertyType.getMapValueType(), new ArrayList<>())); + } else { + Map document = Document.create(); + TypeInformation valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType() + : ClassTypeInformation.OBJECT; + writeInternal(value, document, valueTypeInfo); - /** - * Writes the given {@link Collection} using the given {@link ElasticsearchPersistentProperty} information. - * - * @param collection must not be {@literal null}. - * @param property must not be {@literal null}. - */ - protected List createCollection(Collection collection, ElasticsearchPersistentProperty property) { - return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>(collection.size())); - } + sink.put(simpleKey, document); + } + } else { + throw new MappingException("Cannot use a complex object as a key value."); + } + } - /** - * Writes the given {@link Map} using the given {@link ElasticsearchPersistentProperty} information. - * - * @param map must not {@literal null}. - * @param property must not be {@literal null}. - */ - protected Map createMap(Map map, ElasticsearchPersistentProperty property) { + return sink; + } - Assert.notNull(map, "Given map must not be null!"); - Assert.notNull(property, "PersistentProperty must not be null!"); + /** + * Populates the given {@link Collection sink} with converted values from the given {@link Collection source}. + * + * @param source the collection to create a {@link Collection} for, must not be {@literal null}. + * @param type the {@link TypeInformation} to consider or {@literal null} if unknown. + * @param sink the {@link Collection} to write to. + */ + @SuppressWarnings("unchecked") + private List writeCollectionInternal(Collection source, @Nullable TypeInformation type, + Collection sink) { - return writeMapInternal(map, new LinkedHashMap<>(map.size()), property.getTypeInformation()); - } + TypeInformation componentType = null; - /** - * Writes the given {@link Map} to the given {@link Document} considering the given {@link TypeInformation}. - * - * @param source must not be {@literal null}. - * @param sink must not be {@literal null}. - * @param propertyType must not be {@literal null}. - */ - protected Map writeMapInternal(Map source, Map sink, - TypeInformation propertyType) { + List collection = sink instanceof List ? (List) sink : new ArrayList<>(sink); - for (Map.Entry entry : source.entrySet()) { + if (type != null) { + componentType = type.getComponentType(); + } - Object key = entry.getKey(); - Object value = entry.getValue(); + for (Object element : source) { - if (isSimpleType(key.getClass())) { + Class elementType = element == null ? null : element.getClass(); - String simpleKey = potentiallyConvertMapKey(key); - if (value == null || isSimpleType(value)) { - sink.put(simpleKey, getPotentiallyConvertedSimpleWrite(value, Object.class)); - } else if (value instanceof Collection || value.getClass().isArray()) { - sink.put(simpleKey, - writeCollectionInternal(asCollection(value), propertyType.getMapValueType(), new ArrayList<>())); + if (elementType == null || conversions.isSimpleType(elementType)) { + collection.add(getPotentiallyConvertedSimpleWrite(element, + componentType != null ? componentType.getType() : Object.class)); + } else if (element instanceof Collection || elementType.isArray()) { + collection.add(writeCollectionInternal(asCollection(element), componentType, new ArrayList<>())); } else { Map document = Document.create(); - TypeInformation valueTypeInfo = propertyType.isMap() ? propertyType.getMapValueType() - : ClassTypeInformation.OBJECT; - writeInternal(value, document, valueTypeInfo); - - sink.put(simpleKey, document); + writeInternal(element, document, componentType); + collection.add(document); } - } else { - throw new MappingException("Cannot use a complex object as a key value."); } + + return collection; } - return sink; - } + private void writeProperties(ElasticsearchPersistentEntity entity, PersistentPropertyAccessor accessor, + MapValueAccessor sink) { - /** - * Populates the given {@link Collection sink} with converted values from the given {@link Collection source}. - * - * @param source the collection to create a {@link Collection} for, must not be {@literal null}. - * @param type the {@link TypeInformation} to consider or {@literal null} if unknown. - * @param sink the {@link Collection} to write to. - */ - @SuppressWarnings("unchecked") - private List writeCollectionInternal(Collection source, @Nullable TypeInformation type, - Collection sink) { + for (ElasticsearchPersistentProperty property : entity) { - TypeInformation componentType = null; + if (!property.isWritable()) { + continue; + } - List collection = sink instanceof List ? (List) sink : new ArrayList<>(sink); + Object value = accessor.getProperty(property); - if (type != null) { - componentType = type.getComponentType(); - } + if (value == null) { - for (Object element : source) { + if (property.storeNullValue()) { + sink.set(property, null); + } - Class elementType = element == null ? null : element.getClass(); + continue; + } - if (elementType == null || conversions.isSimpleType(elementType)) { - collection.add(getPotentiallyConvertedSimpleWrite(element, - componentType != null ? componentType.getType() : Object.class)); - } else if (element instanceof Collection || elementType.isArray()) { - collection.add(writeCollectionInternal(asCollection(element), componentType, new ArrayList<>())); - } else { - Map document = Document.create(); - writeInternal(element, document, componentType); - collection.add(document); + if (property.hasPropertyConverter()) { + value = propertyConverterWrite(property, value); + sink.set(property, value); + } else if (TemporalAccessor.class.isAssignableFrom(property.getActualType()) + && !conversions.hasCustomWriteTarget(value.getClass())) { + + // log at most 5 times + String propertyName = entity.getType().getSimpleName() + '.' + property.getName(); + String key = propertyName + "-write"; + int count = propertyWarnings.computeIfAbsent(key, k -> 0); + if (count < 5) { + LOGGER.warn( + "Type {} of property {} is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for writing!" + + " It will be mapped to a complex object in Elasticsearch!", + property.getType().getSimpleName(), propertyName); + propertyWarnings.put(key, count + 1); + } + } else if (!isSimpleType(value)) { + writeProperty(property, value, sink); + } else { + Object writeSimpleValue = getPotentiallyConvertedSimpleWrite(value, Object.class); + if (writeSimpleValue != null) { + sink.set(property, writeSimpleValue); + } + } } } - return collection; - } + @SuppressWarnings("unchecked") + protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) { - /** - * Returns a {@link String} representation of the given {@link Map} key - * - * @param key the key to convert - */ - private String potentiallyConvertMapKey(Object key) { + Optional> customWriteTarget = conversions.getCustomWriteTarget(value.getClass()); - if (key instanceof String) { - return (String) key; - } + if (customWriteTarget.isPresent()) { + Class writeTarget = customWriteTarget.get(); + sink.set(property, conversionService.convert(value, writeTarget)); + return; + } - if (conversions.hasCustomWriteTarget(key.getClass(), String.class)) { - Object potentiallyConvertedSimpleWrite = getPotentiallyConvertedSimpleWrite(key, Object.class); + TypeInformation valueType = ClassTypeInformation.from(value.getClass()); + TypeInformation type = property.getTypeInformation(); - if (potentiallyConvertedSimpleWrite == null) { - return key.toString(); + if (valueType.isCollectionLike()) { + List collectionInternal = createCollection(asCollection(value), property); + sink.set(property, collectionInternal); + return; } - return (String) potentiallyConvertedSimpleWrite; - } - return key.toString(); - } - /** - * Checks whether we have a custom conversion registered for the given value into an arbitrary simple Elasticsearch - * type. Returns the converted value if so. If not, we perform special enum handling or simply return the value as is. - * - * @param value value to convert - */ - @Nullable - private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nullable Class typeHint) { + if (valueType.isMap()) { + Map mapDbObj = createMap((Map) value, property); + sink.set(property, mapDbObj); + return; + } + + // Lookup potential custom target type + Optional> basicTargetType = conversions.getCustomWriteTarget(value.getClass()); + + if (basicTargetType.isPresent()) { + + sink.set(property, conversionService.convert(value, basicTargetType.get())); + return; + } - if (value == null) { - return null; + ElasticsearchPersistentEntity entity = valueType.isSubTypeOf(property.getType()) + ? mappingContext.getRequiredPersistentEntity(value.getClass()) + : mappingContext.getRequiredPersistentEntity(type); + + Object existingValue = sink.get(property); + Map document = existingValue instanceof Map ? (Map) existingValue + : Document.create(); + + addCustomTypeKeyIfNecessary(value, document, ClassTypeInformation.from(property.getRawType())); + writeInternal(value, document, entity); + sink.set(property, document); } - if (typeHint != null && Object.class != typeHint) { + /** + * Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the + * same as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of + * the property. + * + * @param source must not be {@literal null}. + * @param sink must not be {@literal null}. + * @param type type to compare to + */ + private void addCustomTypeKeyIfNecessary(Object source, Map sink, + @Nullable TypeInformation type) { + + if (!writeTypeHints) { + return; + } - if (conversionService.canConvert(value.getClass(), typeHint)) { - value = conversionService.convert(value, typeHint); + Class reference; - if (value == null) { - return null; - } + if (type == null) { + reference = Object.class; + } else { + TypeInformation actualType = type.getActualType(); + reference = actualType == null ? Object.class : actualType.getType(); + } + Class valueType = ClassUtils.getUserClass(source.getClass()); + + boolean notTheSameClass = !valueType.equals(reference); + if (notTheSameClass) { + typeMapper.writeType(valueType, sink); } } - Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); + /** + * Returns a {@link String} representation of the given {@link Map} key + * + * @param key the key to convert + */ + private String potentiallyConvertMapKey(Object key) { - if (customTarget.isPresent()) { - return conversionService.convert(value, customTarget.get()); - } + if (key instanceof String) { + return (String) key; + } - if (ObjectUtils.isArray(value)) { + if (conversions.hasCustomWriteTarget(key.getClass(), String.class)) { + Object potentiallyConvertedSimpleWrite = getPotentiallyConvertedSimpleWrite(key, Object.class); - if (value instanceof byte[]) { - return value; + if (potentiallyConvertedSimpleWrite == null) { + return key.toString(); + } + return (String) potentiallyConvertedSimpleWrite; } - return asCollection(value); + return key.toString(); } - return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; - } + /** + * Checks whether we have a custom conversion registered for the given value into an arbitrary simple Elasticsearch + * type. Returns the converted value if so. If not, we perform special enum handling or simply return the value as + * is. + * + * @param value value to convert + */ + @Nullable + private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nullable Class typeHint) { - /** - * @deprecated since 4.2, use {@link #getPotentiallyConvertedSimpleWrite(Object, Class)} instead. - */ - @Nullable - @Deprecated - protected Object getWriteSimpleValue(Object value) { - Optional> customTarget = getConversions().getCustomWriteTarget(value.getClass()); + if (value == null) { + return null; + } - if (customTarget.isPresent()) { - return conversionService.convert(value, customTarget.get()); - } + if (typeHint != null && Object.class != typeHint) { - return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; - } + if (conversionService.canConvert(value.getClass(), typeHint)) { + value = conversionService.convert(value, typeHint); - /** - * @deprecated since 4.2, use {@link #writeInternal(Object, Map, TypeInformation)} instead. - */ - @Deprecated - protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, - @SuppressWarnings("unused") TypeInformation typeHint, Object value) { + if (value == null) { + return null; + } + } + } - Document document = Document.create(); - writeInternal(value, document, property.getTypeInformation()); + Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); - return document; - } + if (customTarget.isPresent()) { + return conversionService.convert(value, customTarget.get()); + } - // endregion + if (ObjectUtils.isArray(value)) { - // region helper methods + if (value instanceof byte[]) { + return value; + } + return asCollection(value); + } - /** - * Adds custom typeInformation information to the given {@link Map} if necessary. That is if the value is not the same - * as the one given. This is usually the case if you store a subtype of the actual declared typeInformation of the - * property. - * - * @param source must not be {@literal null}. - * @param sink must not be {@literal null}. - * @param type type to compare to - */ - protected void addCustomTypeKeyIfNecessary(Object source, Map sink, - @Nullable TypeInformation type) { + return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; + } - Class reference; + private Object propertyConverterWrite(ElasticsearchPersistentProperty property, Object value) { + ElasticsearchPersistentPropertyConverter propertyConverter = Objects + .requireNonNull(property.getPropertyConverter()); - if (type == null) { - reference = Object.class; - } else { - TypeInformation actualType = type.getActualType(); - reference = actualType == null ? Object.class : actualType.getType(); + if (value instanceof List) { + value = ((List) value).stream().map(propertyConverter::write).collect(Collectors.toList()); + } else if (value instanceof Set) { + value = ((Set) value).stream().map(propertyConverter::write).collect(Collectors.toSet()); + } else { + value = propertyConverter.write(value); + } + return value; } - Class valueType = ClassUtils.getUserClass(source.getClass()); - boolean notTheSameClass = !valueType.equals(reference); - if (notTheSameClass) { - typeMapper.writeType(valueType, sink); + /** + * Writes the given {@link Collection} using the given {@link ElasticsearchPersistentProperty} information. + * + * @param collection must not be {@literal null}. + * @param property must not be {@literal null}. + */ + protected List createCollection(Collection collection, ElasticsearchPersistentProperty property) { + return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>(collection.size())); } - } - /** - * Check if a given type requires a type hint (aka {@literal _class} attribute) when writing to the document. - * - * @param type must not be {@literal null}. - * @return {@literal true} if not a simple type, {@link Collection} or type with custom write target. - */ - public boolean requiresTypeHint(Class type) { + /** + * Writes the given {@link Map} using the given {@link ElasticsearchPersistentProperty} information. + * + * @param map must not {@literal null}. + * @param property must not be {@literal null}. + */ + protected Map createMap(Map map, ElasticsearchPersistentProperty property) { - return !isSimpleType(type) && !ClassUtils.isAssignable(Collection.class, type) - && !conversions.hasCustomWriteTarget(type, Document.class); - } + Assert.notNull(map, "Given map must not be null!"); + Assert.notNull(property, "PersistentProperty must not be null!"); - /** - * Compute the type to use by checking the given entity against the store type; - */ - private ElasticsearchPersistentEntity computeClosestEntity(ElasticsearchPersistentEntity entity, - Map source) { + return writeMapInternal(map, new LinkedHashMap<>(map.size()), property.getTypeInformation()); + } - TypeInformation typeToUse = typeMapper.readType(source); + /** + * @deprecated since 4.2, use {@link #getPotentiallyConvertedSimpleWrite(Object, Class)} instead. + */ + @Nullable + @Deprecated + protected Object getWriteSimpleValue(Object value) { + Optional> customTarget = conversions.getCustomWriteTarget(value.getClass()); - if (typeToUse == null) { - return entity; - } + if (customTarget.isPresent()) { + return conversionService.convert(value, customTarget.get()); + } - if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike() - && !entity.getTypeInformation().isMap() - && !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) { - return entity; + return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum) value).name() : value; } - return mappingContext.getRequiredPersistentEntity(typeToUse); - } + /** + * @deprecated since 4.2, use {@link #writeInternal(Object, Map, TypeInformation)} instead. + */ + @Deprecated + protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, + @SuppressWarnings("unused") TypeInformation typeHint, Object value) { - private boolean isSimpleType(Object value) { - return isSimpleType(value.getClass()); - } + Document document = Document.create(); + writeInternal(value, document, property.getTypeInformation()); - private boolean isSimpleType(Class type) { - return !Map.class.isAssignableFrom(type) && getConversions().isSimpleType(type); - } + return document; + } - /** - * Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a - * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element - * collection for everything else. - * - * @param source object to convert - */ - private static Collection asCollection(Object source) { + /** + * Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a + * {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element + * collection for everything else. + * + * @param source object to convert + */ + private static Collection asCollection(Object source) { - if (source instanceof Collection) { - return (Collection) source; - } + if (source instanceof Collection) { + return (Collection) source; + } - return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); + return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); + } } // endregion @@ -1260,71 +1387,4 @@ private Map getAsMap(Object result) { } } - class ElasticsearchPropertyValueProvider implements PropertyValueProvider { - - final MapValueAccessor accessor; - final SpELExpressionEvaluator evaluator; - - ElasticsearchPropertyValueProvider(MapValueAccessor accessor, SpELExpressionEvaluator evaluator) { - this.accessor = accessor; - this.evaluator = evaluator; - } - - @Override - public T getPropertyValue(ElasticsearchPersistentProperty property) { - - String expression = property.getSpelExpression(); - Object value = expression != null ? evaluator.evaluate(expression) : accessor.get(property); - - if (value == null) { - return null; - } - - return readValue(value, property, property.getTypeInformation()); - } - } - - /** - * Extension of {@link SpELExpressionParameterValueProvider} to recursively trigger value conversion on the raw - * resolved SpEL value. - * - * @author Mark Paluch - */ - private class ConverterAwareSpELExpressionParameterValueProvider - extends SpELExpressionParameterValueProvider { - - /** - * Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}. - * - * @param evaluator must not be {@literal null}. - * @param conversionService must not be {@literal null}. - * @param delegate must not be {@literal null}. - */ - public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, - ConversionService conversionService, ParameterValueProvider delegate) { - - super(evaluator, conversionService, delegate); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.model.SpELExpressionParameterValueProvider#potentiallyConvertSpelValue(java.lang.Object, org.springframework.data.mapping.PreferredConstructor.Parameter) - */ - @Override - protected T potentiallyConvertSpelValue(Object object, - PreferredConstructor.Parameter parameter) { - return readValue(object, parameter.getType()); - } - } - - enum NoOpParameterValueProvider implements ParameterValueProvider { - - INSTANCE; - - @Override - public T getParameterValue(PreferredConstructor.Parameter parameter) { - return null; - } - } - } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 79e921a97..2cc1df4df 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -95,6 +95,8 @@ public class MappingBuilder { private final ElasticsearchConverter elasticsearchConverter; + private boolean writeTypeHints = true; + public MappingBuilder(ElasticsearchConverter elasticsearchConverter) { this.elasticsearchConverter = elasticsearchConverter; } @@ -111,6 +113,8 @@ public String buildPropertyMapping(Class clazz) throws MappingException { ElasticsearchPersistentEntity entity = elasticsearchConverter.getMappingContext() .getRequiredPersistentEntity(clazz); + writeTypeHints = entity.writeTypeHints(); + XContentBuilder builder = jsonBuilder().startObject(); // Dynamic templates @@ -128,11 +132,14 @@ public String buildPropertyMapping(Class clazz) throws MappingException { } private void writeTypeHintMapping(XContentBuilder builder) throws IOException { - builder.startObject(TYPEHINT_PROPERTY) // - .field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // - .field(FIELD_PARAM_INDEX, false) // - .field(FIELD_PARAM_DOC_VALUES, false) // - .endObject(); + + if (writeTypeHints) { + builder.startObject(TYPEHINT_PROPERTY) // + .field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // + .field(FIELD_PARAM_INDEX, false) // + .field(FIELD_PARAM_DOC_VALUES, false) // + .endObject(); + } } private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 650a25360..3a53b4b1c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -17,11 +17,11 @@ import org.elasticsearch.index.VersionType; import org.springframework.data.elasticsearch.annotations.Field; -import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.join.JoinField; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.lang.Nullable; /** @@ -148,4 +148,16 @@ default ElasticsearchPersistentProperty getRequiredSeqNoPrimaryTermProperty() { */ @Nullable String resolveRouting(T bean); + + /** + * @return the {@link FieldNamingStrategy} for the entity + * @since 4.3 + */ + FieldNamingStrategy getFieldNamingStrategy(); + + /** + * @return true if type hints on this entity should be written. + * @since 4.3 + */ + boolean writeTypeHints(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java index 74b88a553..1195ad33a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentProperty.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.core.mapping; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.mapping.PersistentProperty; import org.springframework.lang.Nullable; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchMappingContext.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchMappingContext.java index 81f90c66a..9fd20c7e7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchMappingContext.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchMappingContext.java @@ -37,6 +37,7 @@ public class SimpleElasticsearchMappingContext private static final FieldNamingStrategy DEFAULT_NAMING_STRATEGY = PropertyNameFieldNamingStrategy.INSTANCE; private FieldNamingStrategy fieldNamingStrategy = DEFAULT_NAMING_STRATEGY; + private boolean writeTypeHints = true; /** * Configures the {@link FieldNamingStrategy} to be used to determine the field name if no manual mapping is applied. @@ -50,6 +51,15 @@ public void setFieldNamingStrategy(@Nullable FieldNamingStrategy fieldNamingStra this.fieldNamingStrategy = fieldNamingStrategy == null ? DEFAULT_NAMING_STRATEGY : fieldNamingStrategy; } + /** + * Sets the flag if type hints should be written in Entities created by this instance. + * + * @since 4.3 + */ + public void setWriteTypeHints(boolean writeTypeHints) { + this.writeTypeHints = writeTypeHints; + } + @Override protected boolean shouldCreatePersistentEntityFor(TypeInformation type) { return !ElasticsearchSimpleTypes.HOLDER.isSimpleType(type.getType()); @@ -57,12 +67,13 @@ protected boolean shouldCreatePersistentEntityFor(TypeInformation type) { @Override protected SimpleElasticsearchPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new SimpleElasticsearchPersistentEntity<>(typeInformation); + return new SimpleElasticsearchPersistentEntity<>(typeInformation, + new SimpleElasticsearchPersistentEntity.ContextConfiguration(fieldNamingStrategy, writeTypeHints)); } @Override protected ElasticsearchPersistentProperty createPersistentProperty(Property property, SimpleElasticsearchPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { - return new SimpleElasticsearchPersistentProperty(property, owner, simpleTypeHolder, fieldNamingStrategy); + return new SimpleElasticsearchPersistentProperty(property, owner, simpleTypeHolder); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index eaeebb38f..74bb804c2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -34,6 +34,7 @@ import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.data.util.Lazy; @@ -66,10 +67,9 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit private static final Logger LOGGER = LoggerFactory.getLogger(SimpleElasticsearchPersistentEntity.class); private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + private @Nullable final Document document; private @Nullable String indexName; private final Lazy settingsParameter; - @Deprecated private @Nullable String parentType; - @Deprecated private @Nullable ElasticsearchPersistentProperty parentIdProperty; private @Nullable ElasticsearchPersistentProperty seqNoPrimaryTermProperty; private @Nullable ElasticsearchPersistentProperty joinFieldProperty; private @Nullable VersionType versionType; @@ -77,18 +77,21 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit private final Map fieldNamePropertyCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap routingExpressions = new ConcurrentHashMap<>(); private @Nullable String routing; + private final ContextConfiguration contextConfiguration; private final ConcurrentHashMap indexNameExpressions = new ConcurrentHashMap<>(); private final Lazy indexNameEvaluationContext = Lazy.of(this::getIndexNameEvaluationContext); - public SimpleElasticsearchPersistentEntity(TypeInformation typeInformation) { + public SimpleElasticsearchPersistentEntity(TypeInformation typeInformation, + ContextConfiguration contextConfiguration) { super(typeInformation); + this.contextConfiguration = contextConfiguration; Class clazz = typeInformation.getType(); - org.springframework.data.elasticsearch.annotations.Document document = AnnotatedElementUtils - .findMergedAnnotation(clazz, org.springframework.data.elasticsearch.annotations.Document.class); + document = AnnotatedElementUtils.findMergedAnnotation(clazz, + org.springframework.data.elasticsearch.annotations.Document.class); // need a Lazy here, because we need the persistent properties available this.settingsParameter = Lazy.of(() -> buildSettingsParameter(clazz)); @@ -159,7 +162,31 @@ public boolean isCreateIndexAndMapping() { return createIndexAndMapping; } - // endregion + @Override + public FieldNamingStrategy getFieldNamingStrategy() { + return contextConfiguration.getFieldNamingStrategy(); + } + + @Override + public boolean writeTypeHints() { + + boolean writeTypeHints = contextConfiguration.writeTypeHints; + + if (document != null) { + switch (document.writeTypeHint()) { + case TRUE: + writeTypeHints = true; + break; + case FALSE: + writeTypeHints = false; + break; + case DEFAULT: + break; + } + } + + return writeTypeHints; + } @Override public void addPersistentProperty(ElasticsearchPersistentProperty property) { @@ -215,6 +242,7 @@ private void warnAboutBothSeqNoPrimaryTermAndVersionProperties() { * (non-Javadoc) * @see org.springframework.data.mapping.model.BasicPersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory) */ + @SuppressWarnings("SpellCheckingInspection") @Override public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) { @@ -327,6 +355,7 @@ private EvaluationContext getIndexNameEvaluationContext() { ExpressionDependencies expressionDependencies = expression != null ? ExpressionDependencies.discover(expression) : ExpressionDependencies.none(); + // noinspection ConstantConditions return getEvaluationContext(null, expressionDependencies); } @@ -350,6 +379,7 @@ public String resolveRouting(T bean) { Expression expression = routingExpressions.computeIfAbsent(routing, PARSER::parseExpression); ExpressionDependencies expressionDependencies = ExpressionDependencies.discover(expression); + // noinspection ConstantConditions EvaluationContext context = getEvaluationContext(null, expressionDependencies); context.setVariable("entity", bean); @@ -525,4 +555,22 @@ Settings toSettings() { } // endregion + + /** + * Configuration settings passed in from the creating {@link SimpleElasticsearchMappingContext}. + */ + static class ContextConfiguration { + + private final FieldNamingStrategy fieldNamingStrategy; + private final boolean writeTypeHints; + + ContextConfiguration(FieldNamingStrategy fieldNamingStrategy, boolean writeTypeHints) { + this.fieldNamingStrategy = fieldNamingStrategy; + this.writeTypeHints = writeTypeHints; + } + + public FieldNamingStrategy getFieldNamingStrategy() { + return fieldNamingStrategy; + } + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 11b7c2799..1d171715c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -64,23 +64,20 @@ public class SimpleElasticsearchPersistentProperty extends private static final Logger LOGGER = LoggerFactory.getLogger(SimpleElasticsearchPersistentProperty.class); private static final List SUPPORTED_ID_PROPERTY_NAMES = Arrays.asList("id", "document"); + private static final PropertyNameFieldNamingStrategy DEFAULT_FIELD_NAMING_STRATEGY = PropertyNameFieldNamingStrategy.INSTANCE; private final boolean isId; private final boolean isSeqNoPrimaryTerm; private final @Nullable String annotatedFieldName; @Nullable private ElasticsearchPersistentPropertyConverter propertyConverter; private final boolean storeNullValue; - private final FieldNamingStrategy fieldNamingStrategy; public SimpleElasticsearchPersistentProperty(Property property, - PersistentEntity owner, SimpleTypeHolder simpleTypeHolder, - @Nullable FieldNamingStrategy fieldNamingStrategy) { + PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { super(property, owner, simpleTypeHolder); this.annotatedFieldName = getAnnotatedFieldName(); - this.fieldNamingStrategy = fieldNamingStrategy == null ? PropertyNameFieldNamingStrategy.INSTANCE - : fieldNamingStrategy; this.isId = super.isIdProperty() || (SUPPORTED_ID_PROPERTY_NAMES.contains(getFieldName()) && !hasExplicitFieldName()); this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType()); @@ -248,6 +245,7 @@ private String getAnnotatedFieldName() { public String getFieldName() { if (annotatedFieldName == null) { + FieldNamingStrategy fieldNamingStrategy = getFieldNamingStrategy(); String fieldName = fieldNamingStrategy.getFieldName(this); if (!StringUtils.hasText(fieldName)) { @@ -261,6 +259,16 @@ public String getFieldName() { return annotatedFieldName; } + private FieldNamingStrategy getFieldNamingStrategy() { + PersistentEntity owner = getOwner(); + + if (owner instanceof ElasticsearchPersistentEntity) { + return ((ElasticsearchPersistentEntity) owner).getFieldNamingStrategy(); + } + + return DEFAULT_FIELD_NAMING_STRATEGY; + } + @Override public boolean isIdProperty() { return isId; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 413ade735..1d5e456eb 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -26,7 +26,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import org.json.JSONException; @@ -1176,8 +1175,74 @@ void shouldReadGeoJsonProperties() { } } - private String pointTemplate(String name, Point point) { - return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getY(), point.getX()); + @Test // #1454 + @DisplayName("should write type hints if configured") + void shouldWriteTypeHintsIfConfigured() throws JSONException { + + ((SimpleElasticsearchMappingContext) mappingElasticsearchConverter.getMappingContext()).setWriteTypeHints(true); + PersonWithCars person = new PersonWithCars(); + person.setId("42"); + person.setName("Smith"); + Car car1 = new Car(); + car1.setModel("Ford Mustang"); + Car car2 = new ElectricCar(); + car2.setModel("Porsche Taycan"); + person.setCars(Arrays.asList(car1, car2)); + + String expected = "{\n" + // + " \"_class\": \"org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$PersonWithCars\",\n" + + " \"id\": \"42\",\n" + // + " \"name\": \"Smith\",\n" + // + " \"cars\": [\n" + // + " {\n" + // + " \"model\": \"Ford Mustang\"\n" + // + " },\n" + // + " {\n" + // + " \"_class\": \"org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$ElectricCar\",\n" + + " \"model\": \"Porsche Taycan\"\n" + // + " }\n" + // + " ]\n" + // + "}\n"; // + + Document document = Document.create(); + + mappingElasticsearchConverter.write(person, document); + + assertEquals(expected, document.toJson(), true); + } + + @Test // #1454 + @DisplayName("should not write type hints if configured") + void shouldNotWriteTypeHintsIfNotConfigured() throws JSONException { + + ((SimpleElasticsearchMappingContext) mappingElasticsearchConverter.getMappingContext()).setWriteTypeHints(false); + PersonWithCars person = new PersonWithCars(); + person.setId("42"); + person.setName("Smith"); + Car car1 = new Car(); + car1.setModel("Ford Mustang"); + Car car2 = new ElectricCar(); + car2.setModel("Porsche Taycan"); + person.setCars(Arrays.asList(car1, car2)); + + String expected = "{\n" + // + " \"id\": \"42\",\n" + // + " \"name\": \"Smith\",\n" + // + " \"cars\": [\n" + // + " {\n" + // + " \"model\": \"Ford Mustang\"\n" + // + " },\n" + // + " {\n" + // + " \"model\": \"Porsche Taycan\"\n" + // + " }\n" + // + " ]\n" + // + "}\n"; // + + Document document = Document.create(); + + mappingElasticsearchConverter.write(person, document); + + assertEquals(expected, document.toJson(), true); } private Map writeToMap(Object source) { @@ -1187,6 +1252,7 @@ private Map writeToMap(Object source) { return sink; } + // region entities public static class Sample { @Nullable public @ReadOnlyProperty String readOnly; @Nullable public @Transient String annotatedTransientProperty; @@ -2008,4 +2074,39 @@ public void setSaved(@Nullable String saved) { } } + private static class ElectricCar extends Car {} + + private static class PersonWithCars { + @Id @Nullable String id; + @Field(type = FieldType.Text) @Nullable private String name; + @Field(type = FieldType.Nested) @Nullable private List cars; + + @Nullable + public String getId() { + return id; + } + + public void setId(@Nullable String id) { + this.id = id; + } + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public List getCars() { + return cars; + } + + public void setCars(@Nullable List cars) { + this.cars = cars; + } + } + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index ab60ed4f0..39b61e899 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -44,6 +44,7 @@ import org.springframework.data.elasticsearch.core.MappingContextBaseTests; import org.springframework.data.elasticsearch.core.completion.Completion; import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.geo.Box; import org.springframework.data.geo.Circle; @@ -661,6 +662,119 @@ void shouldMapAccordingToTheAnnotatedProperties() throws JSONException { assertEquals(expected, mapping, false); } + @Test // #1454 + @DisplayName("should write type hints when context is configured to do so") + void shouldWriteTypeHintsWhenContextIsConfiguredToDoSo() throws JSONException { + + ((SimpleElasticsearchMappingContext) (elasticsearchConverter.get().getMappingContext())).setWriteTypeHints(true); + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"title\": {\n" + // + " \"type\": \"text\"\n" + // + " },\n" + // + " \"authors\": {\n" + // + " \"type\": \"nested\",\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(Magazine.class); + + assertEquals(expected, mapping, true); + } + + @Test // #1454 + @DisplayName("should not write type hints when context is configured to not do so") + void shouldNotWriteTypeHintsWhenContextIsConfiguredToNotDoSo() throws JSONException { + + ((SimpleElasticsearchMappingContext) (elasticsearchConverter.get().getMappingContext())).setWriteTypeHints(false); + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"title\": {\n" + // + " \"type\": \"text\"\n" + // + " },\n" + // + " \"authors\": {\n" + // + " \"type\": \"nested\",\n" + // + " \"properties\": {\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(Magazine.class); + + assertEquals(expected, mapping, true); + } + + @Test // #1454 + @DisplayName("should write type hints when context is configured to not do so but entity should") + void shouldWriteTypeHintsWhenContextIsConfiguredToNotDoSoButEntityShould() throws JSONException { + + ((SimpleElasticsearchMappingContext) (elasticsearchConverter.get().getMappingContext())).setWriteTypeHints(false); + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"title\": {\n" + // + " \"type\": \"text\"\n" + // + " },\n" + // + " \"authors\": {\n" + // + " \"type\": \"nested\",\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(MagazineWithTypeHints.class); + + assertEquals(expected, mapping, true); + } + + @Test // #1454 + @DisplayName("should not write type hints when context is configured to do so but entity should not") + void shouldNotWriteTypeHintsWhenContextIsConfiguredToDoSoButEntityShouldNot() throws JSONException { + + ((SimpleElasticsearchMappingContext) (elasticsearchConverter.get().getMappingContext())).setWriteTypeHints(true); + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"title\": {\n" + // + " \"type\": \"text\"\n" + // + " },\n" + // + " \"authors\": {\n" + // + " \"type\": \"nested\",\n" + // + " \"properties\": {\n" + // + " }\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(MagazineWithoutTypeHints.class); + + assertEquals(expected, mapping, true); + } + + // region entities @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @Nullable @Id private String id; @@ -1555,4 +1669,26 @@ public void setField5(@Nullable LocalDateTime field5) { this.field5 = field5; } } + + @Document(indexName = "magazine") + private static class Magazine { + @Id @Nullable private String id; + @Field(type = Text) @Nullable private String title; + @Field(type = Nested) @Nullable private List authors; + } + + @Document(indexName = "magazine-without-type-hints", writeTypeHint = WriteTypeHint.FALSE) + private static class MagazineWithoutTypeHints { + @Id @Nullable private String id; + @Field(type = Text) @Nullable private String title; + @Field(type = Nested) @Nullable private List authors; + } + + @Document(indexName = "magazine-with-type-hints", writeTypeHint = WriteTypeHint.TRUE) + private static class MagazineWithTypeHints { + @Id @Nullable private String id; + @Field(type = Text) @Nullable private String title; + @Field(type = Nested) @Nullable private List authors; + } + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java index bae5f44cc..596e24696 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntityTests.java @@ -28,10 +28,14 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.Setting; +import org.springframework.data.elasticsearch.annotations.WriteTypeHint; import org.springframework.data.elasticsearch.core.MappingContextBaseTests; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.Property; +import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; @@ -52,13 +56,16 @@ public class SimpleElasticsearchPersistentEntityTests extends MappingContextBase @DisplayName("properties setup") class PropertiesTests { + private final SimpleElasticsearchPersistentEntity.ContextConfiguration contextConfiguration = new SimpleElasticsearchPersistentEntity.ContextConfiguration( + PropertyNameFieldNamingStrategy.INSTANCE, true); + @Test public void shouldThrowExceptionGivenVersionPropertyIsNotLong() { TypeInformation typeInformation = ClassTypeInformation .from(EntityWithWrongVersionType.class); SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + typeInformation, contextConfiguration); assertThatThrownBy(() -> createProperty(entity, "version")).isInstanceOf(MappingException.class); } @@ -69,7 +76,7 @@ public void shouldThrowExceptionGivenMultipleVersionPropertiesArePresent() { TypeInformation typeInformation = ClassTypeInformation .from(EntityWithMultipleVersionField.class); SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + typeInformation, contextConfiguration); SimpleElasticsearchPersistentProperty persistentProperty1 = createProperty(entity, "version1"); SimpleElasticsearchPersistentProperty persistentProperty2 = createProperty(entity, "version2"); entity.addPersistentProperty(persistentProperty1); @@ -98,7 +105,7 @@ void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty( TypeInformation typeInformation = ClassTypeInformation .from(EntityWithoutSeqNoPrimaryTerm.class); SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + typeInformation, contextConfiguration); assertThat(entity.hasSeqNoPrimaryTermProperty()).isFalse(); } @@ -109,7 +116,7 @@ void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() { TypeInformation typeInformation = ClassTypeInformation .from(EntityWithSeqNoPrimaryTerm.class); SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + typeInformation, contextConfiguration); entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); @@ -123,7 +130,7 @@ void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() { TypeInformation typeInformation = ClassTypeInformation .from(EntityWithSeqNoPrimaryTerm.class); SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + typeInformation, contextConfiguration); entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); EntityWithSeqNoPrimaryTerm instance = new EntityWithSeqNoPrimaryTerm(); SeqNoPrimaryTerm seqNoPrimaryTerm = new SeqNoPrimaryTerm(1, 2); @@ -142,7 +149,7 @@ void shouldNotAllowMoreThanOneSeqNoPrimaryTermProperties() { TypeInformation typeInformation = ClassTypeInformation .from(EntityWithSeqNoPrimaryTerm.class); SimpleElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - typeInformation); + typeInformation, contextConfiguration); entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm")); assertThatThrownBy(() -> entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm2"))) @@ -165,10 +172,9 @@ class SettingsTests { @DisplayName("should error if index sorting parameters do not have the same number of arguments") void shouldErrorIfIndexSortingParametersDoNotHaveTheSameNumberOfArguments() { - assertThatThrownBy(() -> { - elasticsearchConverter.get().getMappingContext() - .getRequiredPersistentEntity(SettingsInvalidSortParameterSizes.class).getDefaultSettings(); - }).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> elasticsearchConverter.get().getMappingContext() + .getRequiredPersistentEntity(SettingsInvalidSortParameterSizes.class).getDefaultSettings()) + .isInstanceOf(IllegalArgumentException.class); } @Test // #1719 @@ -190,6 +196,75 @@ void shouldWriteSortParametersToSettingsObject() throws JSONException { } } + @Nested + @DisplayName("configuration") + class ConfigurationTests { + + @Test // #1454 + @DisplayName("should return FieldNamingStrategy from context configuration") + void shouldReturnFieldNamingStrategyFromContextConfiguration() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + FieldNamingStrategy fieldNamingStrategy = new FieldNamingStrategy() { + @Override + public String getFieldName(PersistentProperty property) { + return property.getName() + "foo"; + } + }; + context.setFieldNamingStrategy(fieldNamingStrategy); + SimpleElasticsearchPersistentEntity persistentEntity = context + .getRequiredPersistentEntity(FieldNameEntity.class); + + assertThat(persistentEntity.getFieldNamingStrategy()).isSameAs(fieldNamingStrategy); + } + + @Test // #1454 + @DisplayName("should write type hints on default context settings") + void shouldWriteTypeHintsOnDefaultContextSettings() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + SimpleElasticsearchPersistentEntity entity = context + .getRequiredPersistentEntity(DisableTypeHintNoSetting.class); + + assertThat(entity.writeTypeHints()).isTrue(); + } + + @Test // #1454 + @DisplayName("should not write type hints when configured in context settings") + void shouldNotWriteTypeHintsWhenConfiguredInContextSettings() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + context.setWriteTypeHints(false); + SimpleElasticsearchPersistentEntity entity = context + .getRequiredPersistentEntity(DisableTypeHintNoSetting.class); + + assertThat(entity.writeTypeHints()).isFalse(); + } + + @Test // #1454 + @DisplayName("should not write type hints when configured explicitly on entity") + void shouldNotWriteTypeHintsWhenConfiguredExplicitlyOnEntity() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + SimpleElasticsearchPersistentEntity entity = context + .getRequiredPersistentEntity(DisableTypeHintExplicitSetting.class); + + assertThat(entity.writeTypeHints()).isFalse(); + } + + @Test // #1454 + @DisplayName("should write type hints when configured explicitly on entity and global setting is false") + void shouldWriteTypeHintsWhenConfiguredExplicitlyOnEntityAndGlobalSettingIsFalse() { + + SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); + context.setWriteTypeHints(false); + SimpleElasticsearchPersistentEntity entity = context + .getRequiredPersistentEntity(EnableTypeHintExplicitSetting.class); + + assertThat(entity.writeTypeHints()).isTrue(); + } + } + // region helper functions private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasticsearchPersistentEntity entity, String fieldName) { @@ -198,7 +273,7 @@ private static SimpleElasticsearchPersistentProperty createProperty(SimpleElasti java.lang.reflect.Field field = ReflectionUtils.findField(entity.getType(), fieldName); assertThat(field).isNotNull(); Property property = Property.of(type, field); - return new SimpleElasticsearchPersistentProperty(property, entity, SimpleTypeHolder.DEFAULT, null); + return new SimpleElasticsearchPersistentProperty(property, entity, SimpleTypeHolder.DEFAULT); } // endregion @@ -275,16 +350,29 @@ private static class SettingsInvalidSortParameterSizes { @Nullable @Field(name = "second-field", type = FieldType.Keyword) private String secondField; } -@Document(indexName = "dontcare") -// property names here, not field names -@Setting(sortFields = { "secondField", "firstField" }, sortModes = { Setting.SortMode.max, Setting.SortMode.min }, - sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc }, - sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first }) -private static class SettingsValidSortParameterSizes { - @Nullable @Id private String id; - @Nullable @Field(name = "first_field", type = FieldType.Keyword) private String firstField; - @Nullable @Field(name = "second_field", type = FieldType.Keyword) private String secondField; -} + @Document(indexName = "dontcare") + // property names here, not field names + @Setting(sortFields = { "secondField", "firstField" }, sortModes = { Setting.SortMode.max, Setting.SortMode.min }, + sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc }, + sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first }) + private static class SettingsValidSortParameterSizes { + @Nullable @Id private String id; + @Nullable @Field(name = "first_field", type = FieldType.Keyword) private String firstField; + @Nullable @Field(name = "second_field", type = FieldType.Keyword) private String secondField; + } + private static class DisableTypeHintNoSetting { + @Nullable @Id String id; + } + + @Document(indexName = "foo", writeTypeHint = WriteTypeHint.FALSE) + private static class DisableTypeHintExplicitSetting { + @Nullable @Id String id; + } + + @Document(indexName = "foo", writeTypeHint = WriteTypeHint.TRUE) + private static class EnableTypeHintExplicitSetting { + @Nullable @Id String id; + } // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java index fd9a76092..8ac934880 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java @@ -36,6 +36,7 @@ import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.Property; +import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; import org.springframework.data.util.ClassTypeInformation; @@ -200,20 +201,22 @@ void shouldRequirePatternForCustomDateFormat() { @DisplayName("should use default FieldNamingStrategy") void shouldUseDefaultFieldNamingStrategy() { + SimpleElasticsearchPersistentEntity.ContextConfiguration contextConfiguration = new SimpleElasticsearchPersistentEntity.ContextConfiguration( + PropertyNameFieldNamingStrategy.INSTANCE, true); + ElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - ClassTypeInformation.from(FieldNamingStrategyEntity.class)); + ClassTypeInformation.from(FieldNamingStrategyEntity.class), contextConfiguration); ClassTypeInformation type = ClassTypeInformation.from(FieldNamingStrategyEntity.class); java.lang.reflect.Field field = ReflectionUtils.findField(FieldNamingStrategyEntity.class, "withoutCustomFieldName"); SimpleElasticsearchPersistentProperty property = new SimpleElasticsearchPersistentProperty(Property.of(type, field), - entity, SimpleTypeHolder.DEFAULT, null); + entity, SimpleTypeHolder.DEFAULT); assertThat(property.getFieldName()).isEqualTo("withoutCustomFieldName"); field = ReflectionUtils.findField(FieldNamingStrategyEntity.class, "withCustomFieldName"); - property = new SimpleElasticsearchPersistentProperty(Property.of(type, field), entity, SimpleTypeHolder.DEFAULT, - null); + property = new SimpleElasticsearchPersistentProperty(Property.of(type, field), entity, SimpleTypeHolder.DEFAULT); assertThat(property.getFieldName()).isEqualTo("CUStomFIEldnAME"); } @@ -223,25 +226,27 @@ void shouldUseDefaultFieldNamingStrategy() { void shouldUseCustomFieldNamingStrategy() { FieldNamingStrategy fieldNamingStrategy = new SnakeCaseFieldNamingStrategy(); + SimpleElasticsearchPersistentEntity.ContextConfiguration contextConfiguration = new SimpleElasticsearchPersistentEntity.ContextConfiguration( + fieldNamingStrategy, true); ElasticsearchPersistentEntity entity = new SimpleElasticsearchPersistentEntity<>( - ClassTypeInformation.from(FieldNamingStrategyEntity.class)); + ClassTypeInformation.from(FieldNamingStrategyEntity.class), contextConfiguration); ClassTypeInformation type = ClassTypeInformation.from(FieldNamingStrategyEntity.class); java.lang.reflect.Field field = ReflectionUtils.findField(FieldNamingStrategyEntity.class, "withoutCustomFieldName"); SimpleElasticsearchPersistentProperty property = new SimpleElasticsearchPersistentProperty(Property.of(type, field), - entity, SimpleTypeHolder.DEFAULT, fieldNamingStrategy); + entity, SimpleTypeHolder.DEFAULT); assertThat(property.getFieldName()).isEqualTo("without_custom_field_name"); field = ReflectionUtils.findField(FieldNamingStrategyEntity.class, "withCustomFieldName"); - property = new SimpleElasticsearchPersistentProperty(Property.of(type, field), entity, SimpleTypeHolder.DEFAULT, - fieldNamingStrategy); + property = new SimpleElasticsearchPersistentProperty(Property.of(type, field), entity, SimpleTypeHolder.DEFAULT); assertThat(property.getFieldName()).isEqualTo("CUStomFIEldnAME"); } + // region entities static class FieldNameProperty { @Nullable @Field(name = "by-name") String fieldProperty; } @@ -319,4 +324,5 @@ public void setWithCustomFieldName(String withCustomFieldName) { this.withCustomFieldName = withCustomFieldName; } } + // endregion } From a2ca312fb2812bd34781206e47be31e9e43dac00 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 26 Apr 2021 22:26:39 +0200 Subject: [PATCH 058/776] Search with MoreLikeThisQuery should use Pageable. Original Pull Request #1789 Closes #1787 --- .../core/AbstractElasticsearchTemplate.java | 2 +- .../core/query/MoreLikeThisQuery.java | 4 ++ .../core/ElasticsearchTemplateTests.java | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index 4caf7f13b..6703696fd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -419,7 +419,7 @@ public SearchHits search(MoreLikeThisQuery query, Class clazz, IndexCo Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery"); MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index); - return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).build(), clazz, index); + return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index); } @Override 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 9bdf6876c..8fc2d7227 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 @@ -23,6 +23,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * MoreLikeThisQuery @@ -176,6 +177,9 @@ public Pageable getPageable() { } public void setPageable(Pageable pageable) { + + Assert.notNull(pageable, "pageable must not be null"); + this.pageable = pageable; } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 1cd35b169..48fedcada 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -1113,6 +1113,49 @@ public void shouldReturnSimilarResultsGivenMoreLikeThisQuery() { assertThat(content).contains(sampleEntity); } + @Test // #1787 + @DisplayName("should use Pageable on MoreLikeThis queries") + void shouldUsePageableOnMoreLikeThisQueries() { + + String sampleMessage = "So we build a web site or an application and want to add search to it, " + + "and then it hits us: getting search working is hard. We want our search solution to be fast," + + " we want a painless setup and a completely free search schema, we want to be able to index data simply using JSON over HTTP, " + + "we want our search server to be always available, we want to be able to start with one machine and scale to hundreds, " + + "we want real-time search, we want simple multi-tenancy, and we want a solution that is built for the cloud."; + String referenceId = nextIdAsString(); + Collection ids = IntStream.rangeClosed(1, 10).mapToObj(i -> nextIdAsString()).collect(Collectors.toList()); + ids.add(referenceId); + ids.stream() + .map(id -> getIndexQuery(SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build())) + .forEach(indexQuery -> operations.index(indexQuery, index)); + indexOperations.refresh(); + + MoreLikeThisQuery moreLikeThisQuery = new MoreLikeThisQuery(); + moreLikeThisQuery.setId(referenceId); + moreLikeThisQuery.addFields("message"); + moreLikeThisQuery.setMinDocFreq(1); + moreLikeThisQuery.setPageable(PageRequest.of(0, 5)); + + SearchHits searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index); + + assertThat(searchHits.getTotalHits()).isEqualTo(10); + assertThat(searchHits.getSearchHits()).hasSize(5); + + Collection returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId).collect(Collectors.toList()); + + moreLikeThisQuery.setPageable(PageRequest.of(1, 5)); + + searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index); + + assertThat(searchHits.getTotalHits()).isEqualTo(10); + assertThat(searchHits.getSearchHits()).hasSize(5); + + searchHits.getSearchHits().stream().map(SearchHit::getId).forEach(returnedIds::add); + + assertThat(returnedIds).hasSize(10); + assertThat(ids).containsAll(returnedIds); + } + @Test // DATAES-167 public void shouldReturnResultsWithScanAndScrollForGivenCriteriaQuery() { @@ -3545,6 +3588,7 @@ void shouldReturnExplanationWhenRequested() { assertThat(explanation).isNotNull(); } + // region entities @Document(indexName = INDEX_NAME_SAMPLE_ENTITY) @Setting(shards = 1, replicas = 0, refreshInterval = "-1") static class SampleEntity { @@ -4322,4 +4366,5 @@ public void setText(@Nullable String text) { this.text = text; } } + //endregion } From f8fbf7721a23d291346bf500bdeb83ff36ef6559 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 28 Apr 2021 21:46:10 +0200 Subject: [PATCH 059/776] Escape strings with quotes in custom query parameters. (#1793) Original Pull Request #1793 Closes #1790 --- .../query/ElasticsearchStringQuery.java | 36 +--------- .../ReactiveElasticsearchStringQuery.java | 26 +------ .../repository/support/StringQueryUtil.java | 72 +++++++++++++++++++ .../ElasticsearchStringQueryUnitTests.java | 17 ++++- ...tiveElasticsearchStringQueryUnitTests.java | 17 +++++ 5 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java index 32733c295..81cc926b0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java @@ -15,22 +15,16 @@ */ package org.springframework.data.elasticsearch.repository.query; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.domain.PageRequest; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHitSupport; import org.springframework.data.elasticsearch.core.SearchHits; -import org.springframework.data.elasticsearch.core.convert.DateTimeConverters; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.StringQuery; +import org.springframework.data.elasticsearch.repository.support.StringQueryUtil; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.util.StreamUtils; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.NumberUtils; /** * ElasticsearchStringQuery @@ -43,11 +37,8 @@ */ public class ElasticsearchStringQuery extends AbstractElasticsearchRepositoryQuery { - private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)"); private String query; - private final GenericConversionService conversionService = new GenericConversionService(); - public ElasticsearchStringQuery(ElasticsearchQueryMethod queryMethod, ElasticsearchOperations elasticsearchOperations, String query) { super(queryMethod, elasticsearchOperations); @@ -104,31 +95,8 @@ public Object execute(Object[] parameters) { } protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) { - String queryString = replacePlaceholders(this.query, parameterAccessor); + String queryString = StringQueryUtil.replacePlaceholders(this.query, parameterAccessor); return new StringQuery(queryString); } - private String replacePlaceholders(String input, ParametersParameterAccessor accessor) { - - Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input); - String result = input; - while (matcher.find()) { - - String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)"; - int index = NumberUtils.parseNumber(matcher.group(1), Integer.class); - result = result.replaceAll(placeholder, getParameterWithIndex(accessor, index)); - } - return result; - } - - private String getParameterWithIndex(ParametersParameterAccessor accessor, int index) { - Object parameter = accessor.getBindableValue(index); - if (parameter == null) { - return "null"; - } - if (conversionService.canConvert(parameter.getClass(), String.class)) { - return conversionService.convert(parameter, String.class); - } - return parameter.toString(); - } } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java index a20625825..110a2d8ba 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java @@ -15,15 +15,11 @@ */ package org.springframework.data.elasticsearch.repository.query; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.query.StringQuery; +import org.springframework.data.elasticsearch.repository.support.StringQueryUtil; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.NumberUtils; -import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl @@ -32,7 +28,6 @@ */ public class ReactiveElasticsearchStringQuery extends AbstractReactiveElasticsearchRepositoryQuery { - private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)"); private final String query; public ReactiveElasticsearchStringQuery(ReactiveElasticsearchQueryMethod queryMethod, @@ -52,27 +47,10 @@ public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQuery @Override protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor) { - String queryString = replacePlaceholders(this.query, parameterAccessor); + String queryString = StringQueryUtil.replacePlaceholders(this.query, parameterAccessor); return new StringQuery(queryString); } - private String replacePlaceholders(String input, ElasticsearchParameterAccessor accessor) { - - Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input); - String result = input; - while (matcher.find()) { - - String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)"; - int index = NumberUtils.parseNumber(matcher.group(1), Integer.class); - result = result.replaceAll(placeholder, getParameterWithIndex(accessor, index)); - } - return result; - } - - private String getParameterWithIndex(ElasticsearchParameterAccessor accessor, int index) { - return ObjectUtils.nullSafeToString(accessor.getBindableValue(index)); - } - @Override boolean isCountQuery() { return queryMethod.hasCountQueryAnnotation(); diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java new file mode 100644 index 000000000..27ad71b47 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.support; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.repository.query.ParameterAccessor; +import org.springframework.util.NumberUtils; + +/** + * @author Peter-Josef Meisch + */ +final public class StringQueryUtil { + + private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)"); + private static final GenericConversionService conversionService = new GenericConversionService(); + + private StringQueryUtil() {} + + public static String replacePlaceholders(String input, ParameterAccessor accessor) { + + Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input); + String result = input; + while (matcher.find()) { + + String placeholder = Pattern.quote(matcher.group()) + "(?!\\d+)"; + int index = NumberUtils.parseNumber(matcher.group(1), Integer.class); + result = result.replaceAll(placeholder, Matcher.quoteReplacement(getParameterWithIndex(accessor, index))); + } + return result; + } + + private static String getParameterWithIndex(ParameterAccessor accessor, int index) { + + Object parameter = accessor.getBindableValue(index); + String parameterValue = "null"; + + // noinspection ConstantConditions + if (parameter != null) { + + if (conversionService.canConvert(parameter.getClass(), String.class)) { + String converted = conversionService.convert(parameter, String.class); + + if (converted != null) { + parameterValue = converted; + } + } else { + parameterValue = parameter.toString(); + } + } + + parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\"")); + return parameterValue; + + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java index 6e63773a1..d1c221b9f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -37,6 +38,7 @@ import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; @@ -82,6 +84,17 @@ public void shouldReplaceRepeatedParametersCorrectly() throws Exception { .isEqualTo("name:(zero, eleven, one, two, three, four, five, six, seven, eight, nine, ten, eleven, zero, one)"); } + @Test // #1790 + @DisplayName("should escape Strings in query parameters") + void shouldEscapeStringsInQueryParameters() throws Exception { + + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByPrefix", "hello \"Stranger\""); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()) + .isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}"); + } + private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args) throws NoSuchMethodException { @@ -90,7 +103,6 @@ private org.springframework.data.elasticsearch.core.query.Query createQuery(Stri ElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod); return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args)); } - private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) { return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery()); } @@ -110,6 +122,9 @@ private interface SampleRepository extends Repository { @Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)") Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, String arg7, String arg8, String arg9, String arg10, String arg11); + + @Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}") + SearchHits findByPrefix(String prefix); } /** diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java index bf17c3c39..9a24e0af3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -41,6 +42,7 @@ import org.springframework.data.elasticsearch.annotations.MultiField; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; @@ -119,6 +121,17 @@ public void shouldReplaceRepeatedParametersCorrectly() throws Exception { .isEqualTo("name:(zero, eleven, one, two, three, four, five, six, seven, eight, nine, ten, eleven, zero, one)"); } + @Test // #1790 + @DisplayName("should escape Strings in query parameters") + void shouldEscapeStringsInQueryParameters() throws Exception { + + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByPrefix", "hello \"Stranger\""); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()) + .isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}"); + } + private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args) throws NoSuchMethodException { @@ -163,6 +176,10 @@ Person findWithQuiteSomeParameters(String arg0, String arg1, String arg2, String @Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)") Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, String arg7, String arg8, String arg9, String arg10, String arg11); + + @Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}") + Flux> findByPrefix(String prefix); + } /** From 775bf664012f6aa61e45f762019980c8d35c81cb Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 30 Apr 2021 06:48:07 +0200 Subject: [PATCH 060/776] Refactor DefaultReactiveElasticsearchClient to do request customization with the WebClient. (#1795) Original Pull Request #1795 Closes #1794 --- .../DefaultReactiveElasticsearchClient.java | 26 +++++++------- .../reactive/DefaultWebClientProvider.java | 36 ++++++++++++++----- .../client/reactive/HostProvider.java | 4 +-- .../reactive/MultiNodeHostProvider.java | 8 +---- .../reactive/SingleNodeHostProvider.java | 8 +---- .../client/reactive/WebClientProvider.java | 16 +++++++-- .../ReactiveMockClientTestsUtils.java | 9 +++-- 7 files changed, 66 insertions(+), 41 deletions(-) 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 a7e69670f..e91b4443e 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 @@ -281,15 +281,23 @@ private static WebClientProvider getWebClientProvider(ClientConfiguration client scheme = "https"; } - ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); - WebClientProvider provider = WebClientProvider.create(scheme, connector); + WebClientProvider provider = WebClientProvider.create(scheme, new ReactorClientHttpConnector(httpClient)); if (clientConfiguration.getPathPrefix() != null) { provider = provider.withPathPrefix(clientConfiguration.getPathPrefix()); } - provider = provider.withDefaultHeaders(clientConfiguration.getDefaultHeaders()) // - .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()); + provider = provider // + .withDefaultHeaders(clientConfiguration.getDefaultHeaders()) // + .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) // + .withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> { + HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get(); + + if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) { + httpHeaders.addAll(suppliedHeaders); + } + })); + return provider; } @@ -584,12 +592,6 @@ private RequestBodySpec sendRequest(WebClient webClient, String logId, Request r request.getOptions().getHeaders().forEach(it -> theHeaders.add(it.getName(), it.getValue())); } } - - // plus the ones from the supplier - HttpHeaders suppliedHeaders = headersSupplier.get(); - if (suppliedHeaders != null && suppliedHeaders != HttpHeaders.EMPTY) { - theHeaders.addAll(suppliedHeaders); - } }); if (request.getEntity() != null) { @@ -599,8 +601,8 @@ private RequestBodySpec sendRequest(WebClient webClient, String logId, Request r ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters(), body::get); - requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue())); - requestBodySpec.body(Mono.fromSupplier(body), String.class); + requestBodySpec.contentType(MediaType.valueOf(request.getEntity().getContentType().getValue())) + .body(Mono.fromSupplier(body), String.class); } else { ClientLogger.logRequest(logId, request.getMethod().toUpperCase(), request.getEndpoint(), request.getParameters()); } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java index b94718646..8a486658a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/DefaultWebClientProvider.java @@ -48,6 +48,7 @@ class DefaultWebClientProvider implements WebClientProvider { private final HttpHeaders headers; private final @Nullable String pathPrefix; private final Function webClientConfigurer; + private final Consumer> requestConfigurer; /** * Create new {@link DefaultWebClientProvider} with empty {@link HttpHeaders} and no-op {@literal error listener}. @@ -56,7 +57,7 @@ class DefaultWebClientProvider implements WebClientProvider { * @param connector can be {@literal null}. */ DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector) { - this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null, Function.identity()); + this(scheme, connector, e -> {}, HttpHeaders.EMPTY, null, Function.identity(), requestHeadersSpec -> {}); } /** @@ -66,18 +67,21 @@ class DefaultWebClientProvider implements WebClientProvider { * @param connector can be {@literal null}. * @param errorListener must not be {@literal null}. * @param headers must not be {@literal null}. - * @param pathPrefix can be {@literal null} + * @param pathPrefix can be {@literal null}. * @param webClientConfigurer must not be {@literal null}. + * @param requestConfigurer must not be {@literal null}. */ private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector connector, Consumer errorListener, HttpHeaders headers, @Nullable String pathPrefix, - Function webClientConfigurer) { + Function webClientConfigurer, Consumer> requestConfigurer) { Assert.notNull(scheme, "Scheme must not be null! A common scheme would be 'http'."); Assert.notNull(errorListener, "errorListener must not be null! You may want use a no-op one 'e -> {}' instead."); Assert.notNull(headers, "headers must not be null! Think about using 'HttpHeaders.EMPTY' as an alternative."); Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null! You may want use a no-op one 'Function.identity()' instead."); + Assert.notNull(requestConfigurer, + "requestConfigurer must not be null! You may want use a no-op one 'r -> {}' instead.\""); this.cachedClients = new ConcurrentHashMap<>(); this.scheme = scheme; @@ -86,6 +90,7 @@ private DefaultWebClientProvider(String scheme, @Nullable ClientHttpConnector co this.headers = headers; this.pathPrefix = pathPrefix; this.webClientConfigurer = webClientConfigurer; + this.requestConfigurer = requestConfigurer; } @Override @@ -106,6 +111,7 @@ public Consumer getErrorListener() { return this.errorListener; } + @Nullable @Override public String getPathPrefix() { return pathPrefix; @@ -120,7 +126,17 @@ public WebClientProvider withDefaultHeaders(HttpHeaders headers) { merged.addAll(this.headers); merged.addAll(headers); - return new DefaultWebClientProvider(scheme, connector, errorListener, merged, pathPrefix, webClientConfigurer); + return new DefaultWebClientProvider(scheme, connector, errorListener, merged, pathPrefix, webClientConfigurer, + requestConfigurer); + } + + @Override + public WebClientProvider withRequestConfigurer(Consumer> requestConfigurer) { + + Assert.notNull(requestConfigurer, "requestConfigurer must not be null."); + + return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer, + requestConfigurer); } @Override @@ -129,7 +145,8 @@ public WebClientProvider withErrorListener(Consumer errorListener) { Assert.notNull(errorListener, "Error listener must not be null."); Consumer listener = this.errorListener.andThen(errorListener); - return new DefaultWebClientProvider(scheme, this.connector, listener, headers, pathPrefix, webClientConfigurer); + return new DefaultWebClientProvider(scheme, this.connector, listener, headers, pathPrefix, webClientConfigurer, + requestConfigurer); } @Override @@ -137,18 +154,21 @@ public WebClientProvider withPathPrefix(String pathPrefix) { Assert.notNull(pathPrefix, "pathPrefix must not be null."); return new DefaultWebClientProvider(this.scheme, this.connector, this.errorListener, this.headers, pathPrefix, - webClientConfigurer); + webClientConfigurer, requestConfigurer); } @Override public WebClientProvider withWebClientConfigurer(Function webClientConfigurer) { - return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer); + return new DefaultWebClientProvider(scheme, connector, errorListener, headers, pathPrefix, webClientConfigurer, + requestConfigurer); } protected WebClient createWebClientForSocketAddress(InetSocketAddress socketAddress) { - Builder builder = WebClient.builder().defaultHeaders(it -> it.addAll(getDefaultHeaders())); + Builder builder = WebClient.builder() // + .defaultHeaders(it -> it.addAll(getDefaultHeaders())) // + .defaultRequest(requestConfigurer); if (connector != null) { builder = builder.clientConnector(connector); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java index a5e49a62e..f122738a6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/HostProvider.java @@ -54,9 +54,9 @@ static HostProvider provider(WebClientProvider clientProvider, Supplier { private final static Logger LOG = LoggerFactory.getLogger(MultiNodeHostProvider.class); private final WebClientProvider clientProvider; - private final Supplier headersSupplier; private final Map hosts; - MultiNodeHostProvider(WebClientProvider clientProvider, Supplier headersSupplier, - InetSocketAddress... endpoints) { + MultiNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress... endpoints) { this.clientProvider = clientProvider; - this.headersSupplier = headersSupplier; this.hosts = new ConcurrentHashMap<>(); for (InetSocketAddress endpoint : endpoints) { this.hosts.put(endpoint, new ElasticsearchHost(endpoint, State.UNKNOWN)); @@ -166,7 +161,6 @@ private Flux> checkNodes(@Nullable State state) Mono clientResponseMono = createWebClient(host) // .head().uri("/") // - .headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) // .exchangeToMono(Mono::just) // .timeout(Duration.ofSeconds(1)) // .doOnError(throwable -> { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/SingleNodeHostProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/SingleNodeHostProvider.java index 227b29201..b576d3653 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/SingleNodeHostProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/SingleNodeHostProvider.java @@ -19,12 +19,10 @@ import java.net.InetSocketAddress; import java.util.Collections; -import java.util.function.Supplier; import org.springframework.data.elasticsearch.client.ElasticsearchHost; import org.springframework.data.elasticsearch.client.ElasticsearchHost.State; import org.springframework.data.elasticsearch.client.NoReachableHostException; -import org.springframework.http.HttpHeaders; import org.springframework.web.reactive.function.client.WebClient; /** @@ -38,15 +36,12 @@ class SingleNodeHostProvider implements HostProvider { private final WebClientProvider clientProvider; - private final Supplier headersSupplier; private final InetSocketAddress endpoint; private volatile ElasticsearchHost state; - SingleNodeHostProvider(WebClientProvider clientProvider, Supplier headersSupplier, - InetSocketAddress endpoint) { + SingleNodeHostProvider(WebClientProvider clientProvider, InetSocketAddress endpoint) { this.clientProvider = clientProvider; - this.headersSupplier = headersSupplier; this.endpoint = endpoint; this.state = new ElasticsearchHost(this.endpoint, State.UNKNOWN); } @@ -60,7 +55,6 @@ public Mono clusterInfo() { return createWebClient(endpoint) // .head().uri("/") // - .headers(httpHeaders -> httpHeaders.addAll(headersSupplier.get())) // .exchangeToMono(it -> { if (it.statusCode().isError()) { state = ElasticsearchHost.offline(endpoint); diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java index 994667536..5c092a248 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/WebClientProvider.java @@ -101,7 +101,7 @@ static WebClientProvider create(String scheme, @Nullable ClientHttpConnector con /** * Obtain the {@link String pathPrefix} to be used. - * + * * @return the pathPrefix if set. * @since 4.0 */ @@ -126,7 +126,7 @@ static WebClientProvider create(String scheme, @Nullable ClientHttpConnector con /** * Create a new instance of {@link WebClientProvider} where HTTP requests are called with the given path prefix. - * + * * @param pathPrefix Path prefix to add to requests * @return new instance of {@link WebClientProvider} * @since 4.0 @@ -136,10 +136,20 @@ static WebClientProvider create(String scheme, @Nullable ClientHttpConnector con /** * Create a new instance of {@link WebClientProvider} calling the given {@link Function} to configure the * {@link WebClient}. - * + * * @param webClientConfigurer configuration function * @return new instance of {@link WebClientProvider} * @since 4.0 */ WebClientProvider withWebClientConfigurer(Function webClientConfigurer); + + /** + * Create a new instance of {@link WebClientProvider} calling the given {@link Consumer} to configure the requests of + * this {@link WebClient}. + * + * @param requestConfigurer request configuration callback + * @return new instance of {@link WebClientProvider} + * @since 4.3 + */ + WebClientProvider withRequestConfigurer(Consumer> requestConfigurer); } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java index 8d17705f9..9f9cecc95 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveMockClientTestsUtils.java @@ -83,10 +83,10 @@ public static > MockDelegatingElasticsearchHostProvide if (hosts.length == 1) { // noinspection unchecked - delegate = (T) new SingleNodeHostProvider(clientProvider, HttpHeaders::new, getInetSocketAddress(hosts[0])) {}; + delegate = (T) new SingleNodeHostProvider(clientProvider, getInetSocketAddress(hosts[0])) {}; } else { // noinspection unchecked - delegate = (T) new MultiNodeHostProvider(clientProvider, HttpHeaders::new, Arrays.stream(hosts) + delegate = (T) new MultiNodeHostProvider(clientProvider, Arrays.stream(hosts) .map(ReactiveMockClientTestsUtils::getInetSocketAddress).toArray(InetSocketAddress[]::new)) {}; } @@ -297,6 +297,11 @@ public WebClientProvider withWebClientConfigurer(Function throw new UnsupportedOperationException("not implemented"); } + @Override + public WebClientProvider withRequestConfigurer(Consumer> requestConfigurer) { + throw new UnsupportedOperationException("not implemented"); + } + public Send when(String host) { InetSocketAddress inetSocketAddress = getInetSocketAddress(host); return new CallbackImpl(get(host), headersUriSpecMap.get(inetSocketAddress), From 5b6789539c83ee9ffe439ff3534280d77d157a50 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 30 Apr 2021 21:18:25 +0200 Subject: [PATCH 061/776] Upgrade to Elasticsearch 7.12.1. Original Pull Request #1796 Closes #1792 --- pom.xml | 2 +- src/main/asciidoc/preface.adoc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index eb4fe2df3..edca9801b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 2.6 - 7.12.0 + 7.12.1 2.13.3 4.1.52.Final 2.6.0-SNAPSHOT diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index 49b75f3ce..e6987b0fb 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -34,7 +34,8 @@ The following table shows the Elasticsearch versions that are used by Spring Dat [cols="^,^,^,^,^",options="header"] |=== | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot -| 2021.0 (Pascal)footnote:cdv[Currently in development] | 4.2.xfootnote:cdv[] | 7.12.0 | 5.3.xfootnote:cdv[] | 2.4.xfootnote:cdv[] +| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.12.1 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] +| 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x | 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x | Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x | Moore | 3.2.x |6.8.12 | 5.2.12| 2.2.x From 502ce0b6aa987eceedddf26338d044e648dbd652 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 1 May 2021 21:20:07 +0200 Subject: [PATCH 062/776] Add requestCache parameter to Query implementations. Original Pull Request #1799 Closes #1564 --- .../core/ReactiveElasticsearchTemplate.java | 30 +++++++---- .../elasticsearch/core/RequestFactory.java | 8 +++ .../core/query/AbstractQuery.java | 12 +++++ .../data/elasticsearch/core/query/Query.java | 16 ++++++ .../core/RequestFactoryTests.java | 50 +++++++++++++++++++ 5 files changed, 105 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 47d129f6c..552bd4544 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -732,13 +732,15 @@ public Mono> searchForPage(Query query, Class entityType, C private Flux doFind(Query query, Class clazz, IndexCoordinates index) { return Flux.defer(() -> { + SearchRequest request = requestFactory.searchRequest(query, clazz, index); - request = prepareSearchRequest(request); + boolean useScroll = !(query.getPageable().isPaged() || query.isLimiting()); + request = prepareSearchRequest(request, useScroll); - if (query.getPageable().isPaged() || query.isLimiting()) { - return doFind(request); - } else { + if (useScroll) { return doScroll(request); + } else { + return doFind(request); } }); } @@ -747,7 +749,7 @@ private Mono doFindForResponse(Query query, Class cla return Mono.defer(() -> { SearchRequest request = requestFactory.searchRequest(query, clazz, index); - request = prepareSearchRequest(request); + request = prepareSearchRequest(request, false); return doFindForResponse(request); }); } @@ -782,7 +784,7 @@ private Flux doSuggest(SuggestBuilder suggestion, IndexCoordinates inde private Flux doAggregate(Query query, Class entityType, IndexCoordinates index) { return Flux.defer(() -> { SearchRequest request = requestFactory.searchRequest(query, entityType, index); - request = prepareSearchRequest(request); + request = prepareSearchRequest(request, false); return doAggregate(request); }); } @@ -801,7 +803,7 @@ private Mono doCount(Query query, Class entityType, IndexCoordinates in return Mono.defer(() -> { SearchRequest request = requestFactory.searchRequest(query, entityType, index); - request = prepareSearchRequest(request); + request = prepareSearchRequest(request, false); return doCount(request); }); } @@ -890,15 +892,21 @@ protected Flux doScroll(SearchRequest request) { * {@link SearchRequest#indicesOptions(IndicesOptions) indices options} if applicable. * * @param request the generated {@link SearchRequest}. + * @param useScroll * @return never {@literal null}. */ - protected SearchRequest prepareSearchRequest(SearchRequest request) { + protected SearchRequest prepareSearchRequest(SearchRequest request, boolean useScroll) { - if (indicesOptions == null) { - return request; + if (indicesOptions != null) { + request = request.indicesOptions(indicesOptions); } - return request.indicesOptions(indicesOptions); + // request_cache is not allowed on scroll requests. + if (useScroll) { + request = request.requestCache(null); + } + return request; + } // endregion diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java index c42b93f6b..386784d62 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -1001,6 +1001,10 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz query.getRescorerQueries().forEach(rescorer -> sourceBuilder.addRescorer(getQueryRescorerBuilder(rescorer))); + if (query.getRequestCache() != null) { + request.requestCache(query.getRequestCache()); + } + request.source(sourceBuilder); return request; } @@ -1089,6 +1093,10 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli query.getRescorerQueries().forEach(rescorer -> searchRequestBuilder.addRescorer(getQueryRescorerBuilder(rescorer))); + if (query.getRequestCache() != null) { + searchRequestBuilder.setRequestCache(query.getRequestCache()); + } + return searchRequestBuilder; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index 1d3e9fb49..3e5f1a386 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -65,6 +65,7 @@ abstract class AbstractQuery implements Query { private boolean explain = false; @Nullable private List searchAfter; protected List rescorerQueries = new ArrayList<>(); + @Nullable protected Boolean requestCache; @Override @Nullable @@ -328,4 +329,15 @@ public void setRescorerQueries(List rescorerQueryList) { public List getRescorerQueries() { return rescorerQueries; } + + @Override + public void setRequestCache(@Nullable Boolean value) { + this.requestCache = value; + } + + @Override + @Nullable + public Boolean getRequestCache() { + return this.requestCache; + } } 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 cbb10eb98..1f255ffb1 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 @@ -105,6 +105,7 @@ static Query findAll() { /** * Set fields to be returned as part of search request + * * @param fields must not be {@literal null} * @since 4.3 */ @@ -344,4 +345,19 @@ default boolean getExplain() { default List getRescorerQueries() { return Collections.emptyList(); } + + /** + * sets the request_cache value for the query. + * + * @param value new value + * @since 4.3 + */ + void setRequestCache(@Nullable Boolean value); + + /** + * @return the request_cache value for this query. + * @since 4.3 + */ + @Nullable + Boolean getRequestCache(); } 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 ffc7ef184..001811b97 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RequestFactoryTests.java @@ -81,6 +81,7 @@ * @author Roman Puchkovskiy * @author Peer Mueller */ +@SuppressWarnings("ConstantConditions") @ExtendWith(MockitoExtension.class) class RequestFactoryTests { @@ -595,6 +596,54 @@ void shouldBuildSearchWithRescorerQuery() throws JSONException { assertEquals(expected, searchRequest, false); } + @Test // #1564 + @DisplayName("should not set request_cache on default SearchRequest") + void shouldNotSetRequestCacheOnDefaultSearchRequest() { + + when(client.prepareSearch(any())).thenReturn(new SearchRequestBuilder(client, SearchAction.INSTANCE)); + Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + + SearchRequest searchRequest = requestFactory.searchRequest(query, Person.class, IndexCoordinates.of("persons")); + SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, Person.class, + IndexCoordinates.of("persons")); + + assertThat(searchRequest.requestCache()).isNull(); + assertThat(searchRequestBuilder.request().requestCache()).isNull(); + } + + @Test // #1564 + @DisplayName("should set request_cache true on SearchRequest") + void shouldSetRequestCacheTrueOnSearchRequest() { + + when(client.prepareSearch(any())).thenReturn(new SearchRequestBuilder(client, SearchAction.INSTANCE)); + Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + query.setRequestCache(true); + + SearchRequest searchRequest = requestFactory.searchRequest(query, Person.class, IndexCoordinates.of("persons")); + SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, Person.class, + IndexCoordinates.of("persons")); + + assertThat(searchRequest.requestCache()).isTrue(); + assertThat(searchRequestBuilder.request().requestCache()).isTrue(); + } + + @Test // #1564 + @DisplayName("should set request_cache false on SearchRequest") + void shouldSetRequestCacheFalseOnSearchRequest() { + + when(client.prepareSearch(any())).thenReturn(new SearchRequestBuilder(client, SearchAction.INSTANCE)); + Query query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); + query.setRequestCache(false); + + SearchRequest searchRequest = requestFactory.searchRequest(query, Person.class, IndexCoordinates.of("persons")); + SearchRequestBuilder searchRequestBuilder = requestFactory.searchRequestBuilder(client, query, Person.class, + IndexCoordinates.of("persons")); + + assertThat(searchRequest.requestCache()).isFalse(); + assertThat(searchRequestBuilder.request().requestCache()).isFalse(); + } + + // region entities static class Person { @Nullable @Id String id; @Nullable @Field(name = "last-name") String lastName; @@ -644,4 +693,5 @@ public void setLocation(@Nullable GeoPoint location) { static class EntityWithSeqNoPrimaryTerm { @Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm; } + // endregion } From 159687e241689759267950a2c8e96c77b5567c30 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 3 May 2021 21:45:22 +0200 Subject: [PATCH 063/776] Improve handling of immutable classes. Original Pull Request #1801 Closes #1800 --- .../core/AbstractElasticsearchTemplate.java | 42 +++++-- .../core/ElasticsearchRestTemplate.java | 6 +- .../core/ElasticsearchTemplate.java | 4 +- .../elasticsearch/core/EntityOperations.java | 9 +- .../core/IndexedObjectInformation.java | 7 +- .../core/ReactiveElasticsearchTemplate.java | 31 ++++- .../elasticsearch/core/join/JoinField.java | 2 + .../SimpleElasticsearchPersistentEntity.java | 14 --- ...SimpleElasticsearchPersistentProperty.java | 5 - .../core/ElasticsearchTemplateTests.java | 109 +++++++++++++----- ...ElasticsearchTemplateIntegrationTests.java | 98 ++++++++++++++-- ...ImmutableElasticsearchRepositoryTests.java | 10 +- 12 files changed, 255 insertions(+), 82 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index 6703696fd..c5e48fb1c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -188,7 +188,7 @@ public T save(T entity, IndexCoordinates index) { IndexQuery query = getIndexQuery(entityAfterBeforeConvert); doIndex(query, index); - T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index); + T entityAfterAfterSave = (T) maybeCallbackAfterSave(query.getObject(), index); return entityAfterAfterSave; } @@ -215,13 +215,18 @@ public Iterable save(Iterable entities, IndexCoordinates index) { List indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery) .collect(Collectors.toList()); - if (!indexQueries.isEmpty()) { - List indexedObjectInformations = bulkIndex(indexQueries, index); - Iterator iterator = indexedObjectInformations.iterator(); - entities.forEach(entity -> updateIndexedObject(entity, iterator.next())); + if (indexQueries.isEmpty()) { + return Collections.emptyList(); } - return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList()); + List indexedObjectInformations = bulkIndex(indexQueries, index); + Iterator iterator = indexedObjectInformations.iterator(); + + // noinspection unchecked + return indexQueries.stream() // + .map(IndexQuery::getObject) // + .map(entity -> (T) updateIndexedObject(entity, iterator.next())) // + .collect(Collectors.toList()); // } @Override @@ -419,7 +424,9 @@ public SearchHits search(MoreLikeThisQuery query, Class clazz, IndexCo Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery"); MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index); - return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index); + return search( + new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), + clazz, index); } @Override @@ -611,7 +618,7 @@ protected List checkForBulkOperationFailure(BulkRespon }).collect(Collectors.toList()); } - protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) { + protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() .getPersistentEntity(entity.getClass()); @@ -621,22 +628,30 @@ protected void updateIndexedObject(Object entity, IndexedObjectInformation index ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); // Only deal with text because ES generated Ids are strings! - if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { + if (indexedObjectInformation.getId() != null && idProperty != null + && idProperty.getType().isAssignableFrom(String.class)) { propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); } if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null && persistentEntity.hasSeqNoPrimaryTermProperty()) { ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty(); + // noinspection ConstantConditions propertyAccessor.setProperty(seqNoPrimaryTermProperty, new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm())); } if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) { ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); + // noinspection ConstantConditions propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion()); } + + // noinspection unchecked + T updatedEntity = (T) propertyAccessor.getBean(); + return updatedEntity; } + return entity; } ElasticsearchPersistentEntity getRequiredPersistentEntity(Class clazz) { @@ -807,13 +822,16 @@ protected T maybeCallbackAfterConvert(T entity, Document document, IndexCoor protected void updateIndexedObjectsWithQueries(List queries, List indexedObjectInformations) { + for (int i = 0; i < queries.size(); i++) { Object query = queries.get(i); + if (query instanceof IndexQuery) { IndexQuery indexQuery = (IndexQuery) query; Object queryObject = indexQuery.getObject(); + if (queryObject != null) { - updateIndexedObject(queryObject, indexedObjectInformations.get(i)); + indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformations.get(i))); } } } @@ -848,6 +866,10 @@ public T doWith(@Nullable Document document) { } T entity = reader.read(type, document); + IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( + document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(), + document.getVersion()); + entity = updateIndexedObject(entity, indexedObjectInformation); return maybeCallbackAfterConvert(entity, document, index); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index f4826db23..d65cb1045 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -157,9 +157,10 @@ public String doIndex(IndexQuery query, IndexCoordinates index) { IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT)); Object queryObject = query.getObject(); + if (queryObject != null) { - updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), indexResponse.getSeqNo(), - indexResponse.getPrimaryTerm(), indexResponse.getVersion())); + query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), + indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion()))); } return indexResponse.getId(); @@ -168,6 +169,7 @@ public String doIndex(IndexQuery query, IndexCoordinates index) { @Override @Nullable public T get(String id, Class clazz, IndexCoordinates index) { + GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index); GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT)); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 70911a12e..6d3489fbe 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -177,8 +177,8 @@ public String doIndex(IndexQuery query, IndexCoordinates index) { Object queryObject = query.getObject(); if (queryObject != null) { - updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(), - response.getPrimaryTerm(), response.getVersion())); + query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(), + response.getPrimaryTerm(), response.getVersion()))); } return documentId; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java index 5a52c1ad9..56e5da428 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/EntityOperations.java @@ -477,7 +477,6 @@ public ElasticsearchPersistentEntity getPersistentEntity() { */ private static class AdaptibleMappedEntity extends MappedEntity implements AdaptibleEntity { - private final T bean; private final ElasticsearchPersistentEntity entity; private final ConvertingPropertyAccessor propertyAccessor; private final IdentifierAccessor identifierAccessor; @@ -490,7 +489,6 @@ private AdaptibleMappedEntity(T bean, ElasticsearchPersistentEntity entity, super(entity, identifierAccessor, propertyAccessor); - this.bean = bean; this.entity = entity; this.propertyAccessor = propertyAccessor; this.identifierAccessor = identifierAccessor; @@ -510,6 +508,11 @@ static AdaptibleEntity of(T bean, new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver); } + @Override + public T getBean() { + return propertyAccessor.getBean(); + } + @Nullable @Override public T populateIdIfNecessary(@Nullable Object id) { @@ -584,7 +587,7 @@ public T incrementVersion() { @Override public String getRouting() { - String routing = routingResolver.getRouting(bean); + String routing = routingResolver.getRouting(propertyAccessor.getBean()); if (routing != null) { return routing; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java b/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java index 18c239949..0ae1fd061 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/IndexedObjectInformation.java @@ -25,12 +25,12 @@ * @since 4.1 */ public class IndexedObjectInformation { - private final String id; + @Nullable private final String id; @Nullable private final Long seqNo; @Nullable private final Long primaryTerm; @Nullable private final Long version; - private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long primaryTerm, + private IndexedObjectInformation(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm, @Nullable Long version) { this.id = id; this.seqNo = seqNo; @@ -38,11 +38,12 @@ private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long this.version = version; } - public static IndexedObjectInformation of(String id, @Nullable Long seqNo, @Nullable Long primaryTerm, + public static IndexedObjectInformation of(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm, @Nullable Long version) { return new IndexedObjectInformation(id, seqNo, primaryTerm, version); } + @Nullable public String getId() { return id; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 552bd4544..67ebf03b8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -275,27 +275,42 @@ public Flux saveAll(Mono> entitiesPubli } private T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { - AdaptibleEntity adaptibleEntity = operations.forEntity(entity, converter.getConversionService(), - routingResolver); - adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId()); - ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(entity.getClass()); + ElasticsearchPersistentEntity persistentEntity = converter.getMappingContext() + .getPersistentEntity(entity.getClass()); + if (persistentEntity != null) { PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(entity); + ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); + + // Only deal with text because ES generated Ids are strings! + if (indexedObjectInformation.getId() != null && idProperty != null + && idProperty.getType().isAssignableFrom(String.class)) { + propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId()); + } if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null && persistentEntity.hasSeqNoPrimaryTermProperty()) { ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty(); + // noinspection ConstantConditions propertyAccessor.setProperty(seqNoPrimaryTermProperty, new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm())); } if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) { ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); + // noinspection ConstantConditions propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion()); } - } + // noinspection unchecked + T updatedEntity = (T) propertyAccessor.getBean(); + return updatedEntity; + } else { + AdaptibleEntity adaptibleEntity = operations.forEntity(entity, converter.getConversionService(), + routingResolver); + adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId()); + } return entity; } @@ -457,7 +472,7 @@ public Mono get(String id, Class entityType, IndexCoordinates index) { DocumentCallback callback = new ReadDocumentCallback<>(converter, entityType, index); - return doGet(id, index).flatMap(it -> callback.toEntity(DocumentAdapters.from(it))); + return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response))); } private Mono doGet(String id, IndexCoordinates index) { @@ -1097,6 +1112,10 @@ public Mono toEntity(@Nullable Document document) { } T entity = reader.read(type, document); + IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( + document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(), + document.getVersion()); + entity = updateIndexedObject(entity, indexedObjectInformation); return maybeCallAfterConvert(entity, document, index); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java b/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java index c3538ef5e..034905be0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java @@ -15,6 +15,7 @@ */ package org.springframework.data.elasticsearch.core.join; +import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.lang.Nullable; /** @@ -35,6 +36,7 @@ public JoinField(String name) { this(name, null); } + @PersistenceConstructor public JoinField(String name, @Nullable ID parent) { this.name = name; this.parent = parent; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index 74bb804c2..9b4822a6e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -35,7 +35,6 @@ import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.FieldNamingStrategy; -import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; import org.springframework.data.spel.ExpressionDependencies; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; @@ -238,19 +237,6 @@ private void warnAboutBothSeqNoPrimaryTermAndVersionProperties() { getType()); } - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.model.BasicPersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory) - */ - @SuppressWarnings("SpellCheckingInspection") - @Override - public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) { - - // Do nothing to avoid the usage of ClassGeneratingPropertyAccessorFactory for now - // DATACMNS-1322 switches to proper immutability behavior which Spring Data Elasticsearch - // cannot yet implement - } - @Nullable @Override public ElasticsearchPersistentProperty getPersistentPropertyWithFieldName(String fieldName) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 1d171715c..1ecb9dcb1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -279,11 +279,6 @@ protected Association createAssociation() { throw new UnsupportedOperationException(); } - @Override - public boolean isImmutable() { - return false; - } - @Override public boolean isSeqNoPrimaryTermProperty() { return isSeqNoPrimaryTerm; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 48fedcada..64bab266a 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -60,7 +60,6 @@ import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -137,7 +136,8 @@ public abstract class ElasticsearchTemplateTests { @BeforeEach public void before() { indexOperations = operations.indexOps(SampleEntity.class); - deleteIndices(); + + operations.indexOps(IndexCoordinates.of("*")).delete(); indexOperations.create(); indexOperations.putMapping(SampleEntity.class); @@ -155,29 +155,6 @@ public void before() { indexOpsJoinEntity.putMapping(SampleJoinEntity.class); } - @AfterEach - public void after() { - - deleteIndices(); - } - - private void deleteIndices() { - - indexOperations.delete(); - operations.indexOps(SampleEntityUUIDKeyed.class).delete(); - operations.indexOps(UseServerConfigurationEntity.class).delete(); - operations.indexOps(SampleMappingEntity.class).delete(); - operations.indexOps(Book.class).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).delete(); - operations.indexOps(SearchHitsEntity.class).delete(); - operations.indexOps(HighlightEntity.class).delete(); - operations.indexOps(OptimisticEntity.class).delete(); - operations.indexOps(OptimisticAndVersionedEntity.class).delete(); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).delete(); - } - @Test // DATAES-106 public void shouldReturnCountForGivenCriteriaQuery() { @@ -1126,7 +1103,8 @@ void shouldUsePageableOnMoreLikeThisQueries() { Collection ids = IntStream.rangeClosed(1, 10).mapToObj(i -> nextIdAsString()).collect(Collectors.toList()); ids.add(referenceId); ids.stream() - .map(id -> getIndexQuery(SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build())) + .map(id -> getIndexQuery( + SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build())) .forEach(indexQuery -> operations.index(indexQuery, index)); indexOperations.refresh(); @@ -1141,7 +1119,8 @@ void shouldUsePageableOnMoreLikeThisQueries() { assertThat(searchHits.getTotalHits()).isEqualTo(10); assertThat(searchHits.getSearchHits()).hasSize(5); - Collection returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId).collect(Collectors.toList()); + Collection returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId) + .collect(Collectors.toList()); moreLikeThisQuery.setPageable(PageRequest.of(1, 5)); @@ -3588,6 +3567,24 @@ void shouldReturnExplanationWhenRequested() { assertThat(explanation).isNotNull(); } + @Test // #1800 + @DisplayName("should work with immutable classes") + void shouldWorkWithImmutableClasses() { + + ImmutableEntity entity = new ImmutableEntity(null, "some text", null); + + ImmutableEntity saved = operations.save(entity); + + assertThat(saved).isNotNull(); + assertThat(saved.getId()).isNotEmpty(); + SeqNoPrimaryTerm seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm(); + assertThat(seqNoPrimaryTerm).isNotNull(); + + ImmutableEntity retrieved = operations.get(saved.getId(), ImmutableEntity.class); + + assertThat(retrieved).isEqualTo(saved); + } + // region entities @Document(indexName = INDEX_NAME_SAMPLE_ENTITY) @Setting(shards = 1, replicas = 0, refreshInterval = "-1") @@ -4366,5 +4363,61 @@ public void setText(@Nullable String text) { this.text = text; } } - //endregion + + @Document(indexName = "immutable-class") + private static final class ImmutableEntity { + @Id private final String id; + @Field(type = FieldType.Text) private final String text; + @Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm; + + public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.id = id; + this.text = text; + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + public String getId() { + return id; + } + + public String getText() { + return text; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ImmutableEntity that = (ImmutableEntity) o; + + if (!id.equals(that.id)) + return false; + if (!text.equals(that.text)) + return false; + return seqNoPrimaryTerm != null ? seqNoPrimaryTerm.equals(that.seqNoPrimaryTerm) : that.seqNoPrimaryTerm == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + text.hashCode(); + result = 31 * result + (seqNoPrimaryTerm != null ? seqNoPrimaryTerm.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ImmutableEntity{" + "id='" + id + '\'' + ", text='" + text + '\'' + ", seqNoPrimaryTerm=" + + seqNoPrimaryTerm + '}'; + } + } + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index 90755add8..0fb532bf1 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -64,6 +65,7 @@ import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; @@ -1149,6 +1151,26 @@ void shouldReturnInformationListOfAllIndices() { }).verifyComplete(); } + @Test // #1800 + @DisplayName("should work with immutable classes") + void shouldWorkWithImmutableClasses() { + + ImmutableEntity entity = new ImmutableEntity(null, "some text", null); + AtomicReference savedEntity = new AtomicReference<>(); + + template.save(entity).as(StepVerifier::create).consumeNextWith(saved -> { + assertThat(saved).isNotNull(); + savedEntity.set(saved); + assertThat(saved.getId()).isNotEmpty(); + SeqNoPrimaryTerm seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm(); + assertThat(seqNoPrimaryTerm).isNotNull(); + }).verifyComplete(); + + template.get(savedEntity.get().getId(), ImmutableEntity.class).as(StepVerifier::create) + .consumeNextWith(retrieved -> { + assertThat(retrieved).isEqualTo(savedEntity.get()); + }).verifyComplete(); + } // endregion // region Helper functions @@ -1243,8 +1265,10 @@ public void setMessage(@Nullable String message) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; Message message1 = (Message) o; @@ -1301,14 +1325,19 @@ public void setVersion(@Nullable java.lang.Long version) { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; SampleEntity that = (SampleEntity) o; - if (rate != that.rate) return false; - if (id != null ? !id.equals(that.id) : that.id != null) return false; - if (message != null ? !message.equals(that.message) : that.message != null) return false; + if (rate != that.rate) + return false; + if (id != null ? !id.equals(that.id) : that.id != null) + return false; + if (message != null ? !message.equals(that.message) : that.message != null) + return false; return version != null ? version.equals(that.version) : that.version == null; } @@ -1440,5 +1469,60 @@ public void setId(@Nullable String id) { } } + @Document(indexName = "immutable-class") + private static final class ImmutableEntity { + @Id private final String id; + @Field(type = FieldType.Text) private final String text; + @Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm; + + public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { + this.id = id; + this.text = text; + this.seqNoPrimaryTerm = seqNoPrimaryTerm; + } + + public String getId() { + return id; + } + + public String getText() { + return text; + } + + @Nullable + public SeqNoPrimaryTerm getSeqNoPrimaryTerm() { + return seqNoPrimaryTerm; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ImmutableEntity that = (ImmutableEntity) o; + + if (!id.equals(that.id)) + return false; + if (!text.equals(that.text)) + return false; + return seqNoPrimaryTerm != null ? seqNoPrimaryTerm.equals(that.seqNoPrimaryTerm) : that.seqNoPrimaryTerm == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + text.hashCode(); + result = 31 * result + (seqNoPrimaryTerm != null ? seqNoPrimaryTerm.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ImmutableEntity{" + "id='" + id + '\'' + ", text='" + text + '\'' + ", seqNoPrimaryTerm=" + + seqNoPrimaryTerm + '}'; + } + } // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java index 1f1b15666..7cb305172 100644 --- a/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/immutable/ImmutableElasticsearchRepositoryTests.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.IndexOperations; @@ -95,11 +96,16 @@ public void shouldSaveAndFindImmutableDocument() { static class ImmutableEntity { private final String id, name; - public ImmutableEntity(String name) { - this.id = null; + @PersistenceConstructor + public ImmutableEntity(String id, String name) { + this.id = id; this.name = name; } + public ImmutableEntity(String name) { + this(null, name); + } + public String getId() { return id; } From be93ebd6a6f9a92ded63ebb5355831076b24e2eb Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 7 May 2021 08:14:13 +0200 Subject: [PATCH 064/776] Can use @ScriptedFields annotated property as ctor parameter in records and Kotlin data classes. Original Pull Request #1802 Closes #1488 --- .../core/document/DocumentAdapters.java | 94 ++++++++++--------- .../mapping/KebabCaseFieldNamingStrategy.java | 28 ++++++ .../core/ElasticsearchTemplateTests.java | 85 ++++++++++++++++- 3 files changed, 163 insertions(+), 44 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/mapping/KebabCaseFieldNamingStrategy.java diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index a60be02a0..394836998 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -23,7 +23,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -63,36 +62,38 @@ * @author Matt Gilene * @since 4.0 */ -public class DocumentAdapters { +public final class DocumentAdapters { + + private DocumentAdapters() {} /** * Create a {@link Document} from {@link GetResponse}. *

- * Returns a {@link Document} using the source if available. + * Returns a {@link Document} using the getResponse if available. * - * @param source the source {@link GetResponse}. - * @return the adapted {@link Document}, null if source.isExists() returns false. + * @param getResponse the getResponse {@link GetResponse}. + * @return the adapted {@link Document}, null if getResponse.isExists() returns false. */ @Nullable - public static Document from(GetResponse source) { + public static Document from(GetResponse getResponse) { - Assert.notNull(source, "GetResponse must not be null"); + Assert.notNull(getResponse, "GetResponse must not be null"); - if (!source.isExists()) { + if (!getResponse.isExists()) { return null; } - if (source.isSourceEmpty()) { - return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), - source.getPrimaryTerm()); + if (getResponse.isSourceEmpty()) { + return fromDocumentFields(getResponse, getResponse.getIndex(), getResponse.getId(), getResponse.getVersion(), + getResponse.getSeqNo(), getResponse.getPrimaryTerm()); } - Document document = Document.from(source.getSourceAsMap()); - document.setIndex(source.getIndex()); - document.setId(source.getId()); - document.setVersion(source.getVersion()); - document.setSeqNo(source.getSeqNo()); - document.setPrimaryTerm(source.getPrimaryTerm()); + Document document = Document.from(getResponse.getSourceAsMap()); + document.setIndex(getResponse.getIndex()); + document.setId(getResponse.getId()); + document.setVersion(getResponse.getVersion()); + document.setSeqNo(getResponse.getSeqNo()); + document.setPrimaryTerm(getResponse.getPrimaryTerm()); return document; } @@ -188,9 +189,10 @@ public static SearchDocument from(SearchHit source) { if (sourceRef == null || sourceRef.length() == 0) { return new SearchDocumentAdapter( - source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source, - source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()), - innerHits, nestedMetaData, explanation, matchedQueries); + fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), + source.getPrimaryTerm()), + source.getScore(), source.getSortValues(), source.getFields(), highlightFields, innerHits, nestedMetaData, + explanation, matchedQueries); } Document document = Document.from(source.getSourceAsMap()); @@ -203,8 +205,8 @@ public static SearchDocument from(SearchHit source) { document.setSeqNo(source.getSeqNo()); document.setPrimaryTerm(source.getPrimaryTerm()); - return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields, - document, innerHits, nestedMetaData, explanation, matchedQueries); + return new SearchDocumentAdapter(document, source.getScore(), source.getSortValues(), source.getFields(), + highlightFields, innerHits, nestedMetaData, explanation, matchedQueries); } @Nullable @@ -243,6 +245,10 @@ private static List from(@Nullable String[] matchedQueries) { * * @param documentFields the {@link DocumentField}s backing the {@link Document}. * @param index the index where the Document was found + * @param id the document id + * @param version the document version + * @param seqNo the seqNo if the document + * @param primaryTerm the primaryTerm of the document * @return the adapted {@link Document}. */ public static Document fromDocumentFields(Iterable documentFields, String index, String id, @@ -261,10 +267,13 @@ public static Document fromDocumentFields(Iterable documentFields return new DocumentFieldAdapter(fields, index, id, version, seqNo, primaryTerm); } - // TODO: Performance regarding keys/values/entry-set + /** + * Adapter for a collection of {@link DocumentField}s. + */ static class DocumentFieldAdapter implements Document { private final Collection documentFields; + private final Map documentFieldMap; private final String index; private final String id; private final long version; @@ -274,6 +283,8 @@ static class DocumentFieldAdapter implements Document { DocumentFieldAdapter(Collection documentFields, String index, String id, long version, long seqNo, long primaryTerm) { this.documentFields = documentFields; + this.documentFieldMap = new LinkedHashMap<>(documentFields.size()); + documentFields.forEach(documentField -> documentFieldMap.put(documentField.getName(), documentField)); this.index = index; this.id = id; this.version = version; @@ -353,14 +364,7 @@ public boolean isEmpty() { @Override public boolean containsKey(Object key) { - - for (DocumentField documentField : documentFields) { - if (documentField.getName().equals(key)) { - return true; - } - } - - return false; + return documentFieldMap.containsKey(key); } @Override @@ -380,11 +384,9 @@ public boolean containsValue(Object value) { @Override @Nullable public Object get(Object key) { - return documentFields.stream() // - .filter(documentField -> documentField.getName().equals(key)) // - .map(DocumentField::getValue).findFirst() // - .orElse(null); // + DocumentField documentField = documentFieldMap.get(key); + return documentField != null ? documentField.getValue() : null; } @Override @@ -409,17 +411,18 @@ public void clear() { @Override public Set keySet() { - return documentFields.stream().map(DocumentField::getName).collect(Collectors.toCollection(LinkedHashSet::new)); + return documentFieldMap.keySet(); } @Override public Collection values() { - return documentFields.stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList()); + return documentFieldMap.values().stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList()); } @Override public Set> entrySet() { - return documentFields.stream().collect(Collectors.toMap(DocumentField::getName, DocumentFieldAdapter::getValue)) + return documentFieldMap.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, entry -> DocumentFieldAdapter.getValue(entry.getValue()))) .entrySet(); } @@ -458,7 +461,6 @@ public String toJson() { } } - @Override public String toString() { return getClass().getSimpleName() + '@' + this.id + '#' + this.version + ' ' + toJson(); @@ -494,14 +496,14 @@ static class SearchDocumentAdapter implements SearchDocument { @Nullable private final Explanation explanation; @Nullable private final List matchedQueries; - SearchDocumentAdapter(float score, Object[] sortValues, Map fields, - Map> highlightFields, Document delegate, Map innerHits, + SearchDocumentAdapter(Document delegate, float score, Object[] sortValues, Map fields, + Map> highlightFields, Map innerHits, @Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation, @Nullable List matchedQueries) { + this.delegate = delegate; this.score = score; this.sortValues = sortValues; - this.delegate = delegate; fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues())); this.highlightFields.putAll(highlightFields); this.innerHits.putAll(innerHits); @@ -646,7 +648,13 @@ public boolean containsValue(Object value) { @Override public Object get(Object key) { - return delegate.get(key); + + if (delegate.containsKey(key)) { + return delegate.get(key); + } + + // fallback to fields + return fields.get(key); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/KebabCaseFieldNamingStrategy.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/KebabCaseFieldNamingStrategy.java new file mode 100644 index 000000000..090972637 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/KebabCaseFieldNamingStrategy.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.mapping; + +import org.springframework.data.mapping.model.CamelCaseSplittingFieldNamingStrategy; + +/** + * @author Peter-Josef Meisch + * @since 4.3 + */ +public class KebabCaseFieldNamingStrategy extends CamelCaseSplittingFieldNamingStrategy { + public KebabCaseFieldNamingStrategy() { + super("-"); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 64bab266a..fc59cee2e 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; +import static org.springframework.data.elasticsearch.annotations.FieldType.Integer; import static org.springframework.data.elasticsearch.core.document.Document.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; import static org.springframework.data.elasticsearch.utils.IndexBuilder.*; @@ -3585,6 +3586,31 @@ void shouldWorkWithImmutableClasses() { assertThat(retrieved).isEqualTo(saved); } + @Test // #1488 + @DisplayName("should set scripted fields on immutable objects") + void shouldSetScriptedFieldsOnImmutableObjects() { + + ImmutableWithScriptedEntity entity = new ImmutableWithScriptedEntity("42", 42, null); + operations.save(entity); + + Map params = new HashMap<>(); + params.put("factor", 2); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + .withSourceFilter(new FetchSourceFilter(new String[] { "*" }, new String[] {})) + .withScriptField( + new ScriptField("scriptedRate", new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params))) + .build(); + + SearchHits searchHits = operations.search(searchQuery, + ImmutableWithScriptedEntity.class); + + assertThat(searchHits.getTotalHits()).isEqualTo(1); + ImmutableWithScriptedEntity foundEntity = searchHits.getSearchHit(0).getContent(); + assertThat(foundEntity.getId()).isEqualTo("42"); + assertThat(foundEntity.getRate()).isEqualTo(42); + assertThat(foundEntity.getScriptedRate()).isEqualTo(84.0); + } + // region entities @Document(indexName = INDEX_NAME_SAMPLE_ENTITY) @Setting(shards = 1, replicas = 0, refreshInterval = "-1") @@ -4366,7 +4392,7 @@ public void setText(@Nullable String text) { @Document(indexName = "immutable-class") private static final class ImmutableEntity { - @Id private final String id; + @Id @Nullable private final String id; @Field(type = FieldType.Text) private final String text; @Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm; @@ -4376,6 +4402,7 @@ public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryT this.seqNoPrimaryTerm = seqNoPrimaryTerm; } + @Nullable public String getId() { return id; } @@ -4419,5 +4446,61 @@ public String toString() { + seqNoPrimaryTerm + '}'; } } + + @Document(indexName = "immutable-scripted") + public static final class ImmutableWithScriptedEntity { + @Id private final String id; + @Field(type = Integer) @Nullable private final int rate; + @Nullable @ScriptedField private final Double scriptedRate; + + public ImmutableWithScriptedEntity(String id, int rate, @Nullable java.lang.Double scriptedRate) { + this.id = id; + this.rate = rate; + this.scriptedRate = scriptedRate; + } + + public String getId() { + return id; + } + + public int getRate() { + return rate; + } + + @Nullable + public Double getScriptedRate() { + return scriptedRate; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ImmutableWithScriptedEntity that = (ImmutableWithScriptedEntity) o; + + if (rate != that.rate) + return false; + if (!id.equals(that.id)) + return false; + return scriptedRate != null ? scriptedRate.equals(that.scriptedRate) : that.scriptedRate == null; + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = 31 * result + rate; + result = 31 * result + (scriptedRate != null ? scriptedRate.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ImmutableWithScriptedEntity{" + "id='" + id + '\'' + ", rate=" + rate + ", scriptedRate=" + scriptedRate + + '}'; + } + } // endregion } From 3a900599f2353027a9c22736dba1ed56fdfa81de Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 10 May 2021 18:46:35 +0200 Subject: [PATCH 065/776] Add documentation about FieldType.Auto. Original Pull Request #1807 Closes #1803 --- src/main/asciidoc/reference/elasticsearch-object-mapping.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc index 1653f0b61..2a90e4883 100644 --- a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc +++ b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc @@ -47,7 +47,8 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu * `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference): ** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used. ** `type`: The field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. -See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types] +See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]. If the field type is not specified, it defaults to `FieldType.Auto`. This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored +(check the Elasticsearch documentation for dynamic mapping rules). ** `format`: One or more built-in date formats, see the next section <>. ** `pattern`: One or more custom date formats, see the next section <>. ** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_. From df0d65eda2de394ad2d53f3b0538c74d85ec3edb Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Tue, 11 May 2021 23:21:26 +0200 Subject: [PATCH 066/776] Add pipeline aggregations to NativeSearchQuery. Original Pull Request #1809 Closes #1255 --- .../elasticsearch/core/RequestFactory.java | 17 ++-- .../core/query/NativeSearchQuery.java | 11 +++ .../core/query/NativeSearchQueryBuilder.java | 14 +++ ...ElasticsearchTemplateAggregationTests.java | 88 ++++++++++++++++++- 4 files changed, 122 insertions(+), 8 deletions(-) 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 386784d62..20e1d4a7b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -77,7 +77,6 @@ import org.elasticsearch.index.reindex.UpdateByQueryRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequestBuilder; import org.elasticsearch.script.Script; -import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; @@ -1119,9 +1118,11 @@ private void prepareNativeSearch(NativeSearchQuery query, SearchSourceBuilder so } if (!isEmpty(query.getAggregations())) { - for (AbstractAggregationBuilder aggregationBuilder : query.getAggregations()) { - sourceBuilder.aggregation(aggregationBuilder); - } + query.getAggregations().forEach(sourceBuilder::aggregation); + } + + if (!isEmpty(query.getPipelineAggregations())) { + query.getPipelineAggregations().forEach(sourceBuilder::aggregation); } } @@ -1144,9 +1145,11 @@ private void prepareNativeSearch(SearchRequestBuilder searchRequestBuilder, Nati } if (!isEmpty(nativeSearchQuery.getAggregations())) { - for (AbstractAggregationBuilder aggregationBuilder : nativeSearchQuery.getAggregations()) { - searchRequestBuilder.addAggregation(aggregationBuilder); - } + nativeSearchQuery.getAggregations().forEach(searchRequestBuilder::addAggregation); + } + + if (!isEmpty(nativeSearchQuery.getPipelineAggregations())) { + nativeSearchQuery.getPipelineAggregations().forEach(searchRequestBuilder::addAggregation); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java index 5201ea73b..63ec22aff 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQuery.java @@ -22,6 +22,7 @@ import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.aggregations.PipelineAggregationBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; @@ -48,6 +49,7 @@ public class NativeSearchQuery extends AbstractQuery { private final List scriptFields = new ArrayList<>(); @Nullable private CollapseBuilder collapseBuilder; @Nullable private List> aggregations; + @Nullable private List pipelineAggregations; @Nullable private HighlightBuilder highlightBuilder; @Nullable private HighlightBuilder.Field[] highlightFields; @Nullable private List indicesBoost; @@ -143,6 +145,11 @@ public List> getAggregations() { return aggregations; } + @Nullable + public List getPipelineAggregations() { + return pipelineAggregations; + } + public void addAggregation(AbstractAggregationBuilder aggregationBuilder) { if (aggregations == null) { @@ -156,6 +163,10 @@ public void setAggregations(List> aggregations) { this.aggregations = aggregations; } + public void setPipelineAggregations(List pipelineAggregationBuilders) { + this.pipelineAggregations = pipelineAggregationBuilders; + } + @Nullable public List getIndicesBoost() { return indicesBoost; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 33abb43b1..93c84ca4e 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -27,6 +27,7 @@ import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.script.mustache.SearchTemplateRequestBuilder; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; +import org.elasticsearch.search.aggregations.PipelineAggregationBuilder; import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortBuilder; @@ -55,6 +56,7 @@ public class NativeSearchQueryBuilder { private final List scriptFields = new ArrayList<>(); private final List> sortBuilders = new ArrayList<>(); private final List> aggregationBuilders = new ArrayList<>(); + private final List pipelineAggregationBuilders = new ArrayList<>(); @Nullable private HighlightBuilder highlightBuilder; @Nullable private HighlightBuilder.Field[] highlightFields; private Pageable pageable = Pageable.unpaged(); @@ -105,6 +107,14 @@ public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder agg return this; } + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder addAggregation(PipelineAggregationBuilder pipelineAggregationBuilder) { + this.pipelineAggregationBuilders.add(pipelineAggregationBuilder); + return this; + } + public NativeSearchQueryBuilder withHighlightBuilder(HighlightBuilder highlightBuilder) { this.highlightBuilder = highlightBuilder; return this; @@ -239,6 +249,10 @@ public NativeSearchQuery build() { nativeSearchQuery.setAggregations(aggregationBuilders); } + if (!isEmpty(pipelineAggregationBuilders)) { + nativeSearchQuery.setPipelineAggregations(pipelineAggregationBuilders); + } + if (minScore > 0) { nativeSearchQuery.setMinScore(minScore); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java index 86bef45bc..e98d640dd 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/aggregation/ElasticsearchTemplateAggregationTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.elasticsearch.search.aggregations.AggregationBuilders.*; +import static org.elasticsearch.search.aggregations.PipelineAggregatorBuilders.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Integer; @@ -26,9 +27,14 @@ import java.util.List; import org.elasticsearch.action.search.SearchType; +import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.pipeline.InternalStatsBucket; +import org.elasticsearch.search.aggregations.pipeline.ParsedStatsBucket; +import org.elasticsearch.search.aggregations.pipeline.StatsBucket; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -109,7 +115,7 @@ public void after() { indexOperations.delete(); } - @Test + @Test // DATAES-96 public void shouldReturnAggregatedResponseForGivenSearchQuery() { // given @@ -130,6 +136,56 @@ public void shouldReturnAggregatedResponseForGivenSearchQuery() { assertThat(searchHits.hasSearchHits()).isFalse(); } + @Test // #1255 + @DisplayName("should work with pipeline aggregations") + void shouldWorkWithPipelineAggregations() { + + IndexInitializer.init(operations.indexOps(PipelineAggsEntity.class)); + operations.save( // + new PipelineAggsEntity("1-1", "one"), // + new PipelineAggsEntity("2-1", "two"), // + new PipelineAggsEntity("2-2", "two"), // + new PipelineAggsEntity("3-1", "three"), // + new PipelineAggsEntity("3-2", "three"), // + new PipelineAggsEntity("3-3", "three") // + ); // + + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // + .withQuery(matchAllQuery()) // + .withSearchType(SearchType.DEFAULT) // + .addAggregation(terms("keyword_aggs").field("keyword")) // + .addAggregation(statsBucket("keyword_bucket_stats", "keyword_aggs._count")) // + .withMaxResults(0) // + .build(); + + SearchHits searchHits = operations.search(searchQuery, PipelineAggsEntity.class); + + Aggregations aggregations = searchHits.getAggregations(); + assertThat(aggregations).isNotNull(); + assertThat(aggregations.asMap().get("keyword_aggs")).isNotNull(); + Aggregation keyword_bucket_stats = aggregations.asMap().get("keyword_bucket_stats"); + assertThat(keyword_bucket_stats).isInstanceOf(StatsBucket.class); + if (keyword_bucket_stats instanceof ParsedStatsBucket) { + // Rest client + ParsedStatsBucket statsBucket = (ParsedStatsBucket) keyword_bucket_stats; + assertThat(statsBucket.getMin()).isEqualTo(1.0); + assertThat(statsBucket.getMax()).isEqualTo(3.0); + assertThat(statsBucket.getAvg()).isEqualTo(2.0); + assertThat(statsBucket.getSum()).isEqualTo(6.0); + assertThat(statsBucket.getCount()).isEqualTo(3L); + } + if (keyword_bucket_stats instanceof InternalStatsBucket) { + // transport client + InternalStatsBucket statsBucket = (InternalStatsBucket) keyword_bucket_stats; + assertThat(statsBucket.getMin()).isEqualTo(1.0); + assertThat(statsBucket.getMax()).isEqualTo(3.0); + assertThat(statsBucket.getAvg()).isEqualTo(2.0); + assertThat(statsBucket.getSum()).isEqualTo(6.0); + assertThat(statsBucket.getCount()).isEqualTo(3L); + } + } + + // region entities @Document(indexName = "test-index-articles-core-aggregation") static class ArticleEntity { @@ -256,4 +312,34 @@ public IndexQuery buildIndex() { } } + @Document(indexName = "pipeline-aggs") + static class PipelineAggsEntity { + @Id private String id; + @Field(type = Keyword) private String keyword; + + public PipelineAggsEntity() {} + + public PipelineAggsEntity(String id, String keyword) { + this.id = id; + this.keyword = keyword; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + } + // endregion + } From 38b1763b34db0320172dd1768e52fc552271c367 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Thu, 13 May 2021 10:26:24 +0200 Subject: [PATCH 067/776] datatype detection support in mapping. Original Pull Request #1810 Closes #638 --- .../reference/elasticsearch-misc.adoc | 10 ++ .../annotations/DynamicTemplates.java | 10 +- .../elasticsearch/annotations/Mapping.java | 25 ++++- .../core/index/MappingBuilder.java | 18 ++- .../index/MappingBuilderIntegrationTests.java | 19 ++++ .../core/index/MappingBuilderUnitTests.java | 104 ++++++++++++++++++ 6 files changed, 178 insertions(+), 8 deletions(-) diff --git a/src/main/asciidoc/reference/elasticsearch-misc.adoc b/src/main/asciidoc/reference/elasticsearch-misc.adoc index 042759e7c..372dd04d3 100644 --- a/src/main/asciidoc/reference/elasticsearch-misc.adoc +++ b/src/main/asciidoc/reference/elasticsearch-misc.adoc @@ -46,6 +46,16 @@ class Entity { <.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements ==== +[[elasticsearch.misc.mappings]] +== Index Mapping + +When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties: + +* `mappingPath` a classpath resource in JSON format which is used as the mapping, no other mapping processing is done. +* `enabled` when set to false, this flag is written to the mapping and no further processing is done. +* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`. +* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection. + [[elasticsearch.misc.filter]] == Filter Builder diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java index d2cea3f39..107ee94be 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java @@ -9,19 +9,17 @@ import org.springframework.data.annotation.Persistent; /** - * Elasticsearch dynamic templates mapping. - * This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field} - * with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation. - * DynamicTemplates annotation is omitted if {@link Mapping} annotation is used. + * Elasticsearch dynamic templates mapping. This annotation is handy if you prefer apply dynamic templates on fields + * with annotation e.g. {@link Field} with type = FieldType.Object etc. instead of static mapping on Document via + * {@link Mapping} annotation. DynamicTemplates annotation is omitted if {@link Mapping} annotation is used. * * @author Petr Kukral */ @Persistent @Inherited @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ ElementType.TYPE }) public @interface DynamicTemplates { String mappingPath() default ""; - } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java index b334b46f1..c41e1300e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java @@ -38,8 +38,31 @@ /** * whether mappings are enabled - * + * * @since 4.2 */ boolean enabled() default true; + /** + * whether date_detection is enabled + * + * @since 4.3 + */ + Detection dateDetection() default Detection.DEFAULT; + + /** + * whether numeric_detection is enabled + * + * @since 4.3 + */ + Detection numericDetection() default Detection.DEFAULT; + + /** + * custom dynamic date formats + * @since 4.3 + */ + String[] dynamicDateFormats() default {}; + + enum Detection { + DEFAULT, TRUE, FALSE; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 2cc1df4df..43b4bb4c5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -92,6 +92,9 @@ public class MappingBuilder { private static final String JOIN_TYPE_RELATIONS = "relations"; private static final String MAPPING_ENABLED = "enabled"; + private static final String DATE_DETECTION = "date_detection"; + private static final String NUMERIC_DETECTION = "numeric_detection"; + private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats"; private final ElasticsearchConverter elasticsearchConverter; @@ -147,11 +150,24 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException { if (entity != null && entity.isAnnotationPresent(Mapping.class)) { + Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class); - if (!entity.getRequiredAnnotation(Mapping.class).enabled()) { + if (!mappingAnnotation.enabled()) { builder.field(MAPPING_ENABLED, false); return; } + + if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) { + builder.field(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name())); + } + + if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) { + builder.field(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name())); + } + + if (mappingAnnotation.dynamicDateFormats().length > 0) { + builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats()); + } } boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index e03e9d395..de0e9e4bb 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -306,6 +306,17 @@ void shouldWriteDynamicMappingEntries() { indexOps.delete(); } + @Test // #638 + @DisplayName("should write dynamic detection values") + void shouldWriteDynamicDetectionValues() { + + IndexOperations indexOps = operations.indexOps(DynamicDetectionMapping.class); + indexOps.create(); + indexOps.putMapping(); + indexOps.delete(); + } + + // region entities @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @Nullable @Id private String id; @@ -1113,4 +1124,12 @@ public void setAuthor(Author author) { } } + @Document(indexName = "dynamic-detection-mapping-true") + @Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE, + dynamicDateFormats = { "MM/dd/yyyy" }) + private static class DynamicDetectionMapping { + @Id @Nullable private String id; + } + // endregion + } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 39b61e899..7fbaca7ff 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -774,6 +774,87 @@ void shouldNotWriteTypeHintsWhenContextIsConfiguredToDoSoButEntityShouldNot() th assertEquals(expected, mapping, true); } + @Test // #638 + @DisplayName("should not write dynamic detection mapping entries in default setting") + void shouldNotWriteDynamicDetectionMappingEntriesInDefaultSetting() throws JSONException { + + String expected = "{\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // + " }\n" + // + "}"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingDefault.class); + + assertEquals(expected, mapping, true); + } + + @Test // #638 + @DisplayName("should write dynamic detection mapping entries when set to false") + void shouldWriteDynamicDetectionMappingEntriesWhenSetToFalse() throws JSONException { + + String expected = "{\n" + // + " \"date_detection\": false," + // + " \"numeric_detection\": false," + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // + " }\n" + // + "}"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingFalse.class); + + assertEquals(expected, mapping, true); + } + + @Test // #638 + @DisplayName("should write dynamic detection mapping entries when set to true") + void shouldWriteDynamicDetectionMappingEntriesWhenSetToTrue() throws JSONException { + + String expected = "{\n" + // + " \"date_detection\": true," + // + " \"numeric_detection\": true," + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // + " }\n" + // + "}"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingTrue.class); + + assertEquals(expected, mapping, true); + } + + @Test // #638 + @DisplayName("should write dynamic date formats") + void shouldWriteDynamicDateFormats() throws JSONException { + + String expected = "{\n" + // + " \"dynamic_date_formats\": [\"date1\",\"date2\"]," + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " }\n" + // + " }\n" + // + "}"; // + + String mapping = getMappingBuilder().buildPropertyMapping(DynamicDateFormatsMapping.class); + + assertEquals(expected, mapping, true); + } + // region entities @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @@ -1690,5 +1771,28 @@ private static class MagazineWithTypeHints { @Field(type = Text) @Nullable private String title; @Field(type = Nested) @Nullable private List authors; } + + @Document(indexName = "dynamic-field-mapping-default") + private static class DynamicDetectionMappingDefault { + @Id @Nullable private String id; + } + + @Document(indexName = "dynamic-dateformats-mapping") + @Mapping(dynamicDateFormats = {"date1", "date2"}) + private static class DynamicDateFormatsMapping { + @Id @Nullable private String id; + } + + @Document(indexName = "dynamic-detection-mapping-true") + @Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE) + private static class DynamicDetectionMappingTrue { + @Id @Nullable private String id; + } + + @Document(indexName = "dynamic-detection-mapping-false") + @Mapping(dateDetection = Mapping.Detection.FALSE, numericDetection = Mapping.Detection.FALSE) + private static class DynamicDetectionMappingFalse { + @Id @Nullable private String id; + } // endregion } From e96d09fa51e7ee8450513657f51dbd55c4a54641 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Thu, 13 May 2021 16:48:57 +0200 Subject: [PATCH 068/776] SearchPage result in StringQuery methods. Original Pull Request #1812 Closes #1811 --- .../query/ElasticsearchStringQuery.java | 11 +++++++-- .../CustomMethodRepositoryBaseTests.java | 24 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java index 81cc926b0..3f5e9ed83 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java @@ -74,7 +74,12 @@ public Object execute(Object[] parameters) { } else if (queryMethod.isPageQuery()) { stringQuery.setPageable(accessor.getPageable()); SearchHits searchHits = elasticsearchOperations.search(stringQuery, clazz, index); - result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()); + if (queryMethod.isSearchPageMethod()) { + result = SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable()); + } else { + result = SearchHitSupport + .unwrapSearchHits(SearchHitSupport.searchPageFor(searchHits, stringQuery.getPageable())); + } } else if (queryMethod.isStreamQuery()) { if (accessor.getPageable().isUnpaged()) { stringQuery.setPageable(PageRequest.of(0, DEFAULT_STREAM_BATCH_SIZE)); @@ -91,7 +96,9 @@ public Object execute(Object[] parameters) { result = elasticsearchOperations.searchOne(stringQuery, clazz, index); } - return queryMethod.isNotSearchHitMethod() ? SearchHitSupport.unwrapSearchHits(result) : result; + return (queryMethod.isNotSearchHitMethod() && queryMethod.isNotSearchPageMethod()) + ? SearchHitSupport.unwrapSearchHits(result) + : result; } protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) { diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java index 4284f03ab..81665558f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java @@ -1593,11 +1593,27 @@ void shouldReturnSearchPage() { assertThat((nextPageable.getPageNumber())).isEqualTo(1); } + @Test // #1811 + @DisplayName("should return SearchPage with query") + void shouldReturnSearchPageWithQuery() { + List entities = createSampleEntities("abc", 20); + repository.saveAll(entities); + + SearchPage searchPage = repository.searchWithQueryByMessage("Message", PageRequest.of(0, 10)); + + assertThat(searchPage).isNotNull(); + SearchHits searchHits = searchPage.getSearchHits(); + assertThat(searchHits).isNotNull(); + assertThat((searchHits.getTotalHits())).isEqualTo(20); + assertThat(searchHits.getSearchHits()).hasSize(10); + Pageable nextPageable = searchPage.nextPageable(); + assertThat((nextPageable.getPageNumber())).isEqualTo(1); + } + private List createSampleEntities(String type, int numberOfEntities) { List entities = new ArrayList<>(); for (int i = 0; i < numberOfEntities; i++) { - SampleEntity entity = new SampleEntity(); entity.setId(UUID.randomUUID().toString()); entity.setAvailable(true); @@ -1633,8 +1649,7 @@ void shouldStreamSearchHitsWithQueryAnnotatedMethod() { @Document(indexName = "test-index-sample-repositories-custom-method") static class SampleEntity { - @Nullable - @Id private String id; + @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; @Nullable @Field(type = Text, store = true, fielddata = true) private String message; @Nullable @Field(type = Keyword) private String keyword; @@ -1836,6 +1851,9 @@ public interface SampleCustomMethodRepository extends ElasticsearchRepository searchByMessage(String message, Pageable pageable); + @Query("{\"match\": {\"message\": \"?0\"}}") + SearchPage searchWithQueryByMessage(String message, Pageable pageable); + @CountQuery("{\"bool\" : {\"must\" : {\"term\" : {\"type\" : \"?0\"}}}}") long countWithQueryByType(String type); } From 06f2103c2ef60d91bb73fb201295ab8e02f9174f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 14 May 2021 11:51:51 +0200 Subject: [PATCH 069/776] Updated changelog. See #1774 --- src/main/resources/changelog.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index a3615b6e1..a433ddcde 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,15 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.1.9 (2021-05-14) +------------------------------------- +* #1811 - StringQuery execution crashes on return type `SearchPage`. +* #1790 - Custom Query with string parameter which contains double quotes. +* #1787 - Search with MoreLikeThisQuery should use Pageable. +* #1785 - Fix documentation about auditing. +* #1767 - DynamicMapping annotation should be applicable to any object field. + + Changes in version 4.2.0 (2021-04-14) ------------------------------------- * #1771 - Remove `@Persistent` from entity-scan include filters. @@ -1601,5 +1610,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From a830e768071ccd767fb187c993b4796b19d40d75 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 14 May 2021 12:23:19 +0200 Subject: [PATCH 070/776] Updated changelog. See #1775 --- src/main/resources/changelog.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index a433ddcde..dd4d9d8d4 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,18 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.2.1 (2021-05-14) +------------------------------------- +* #1811 - StringQuery execution crashes on return type `SearchPage`. +* #1805 - Upgrade to Elasticsearch 7.12.1. +* #1794 - Refactor `DefaultReactiveElasticsearchClient` to do request customization with the `WebClient`. +* #1790 - Custom Query with string parameter which contains double quotes. +* #1787 - Search with MoreLikeThisQuery should use Pageable. +* #1785 - Fix documentation about auditing. +* #1778 - Custom property names must be used in SourceFilter and source fields. +* #1767 - DynamicMapping annotation should be applicable to any object field. + + Changes in version 4.1.9 (2021-05-14) ------------------------------------- * #1811 - StringQuery execution crashes on return type `SearchPage`. @@ -1611,5 +1623,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 33bc36d111cc34882b0b52e88e16ad0327c01b97 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 15 May 2021 22:31:45 +0200 Subject: [PATCH 071/776] Add inner_hits support to the collapse field in NativeSearchQuery. Original Pull Request #1815 Closes #1498 --- .../core/query/NativeSearchQueryBuilder.java | 8 ++++ .../core/ElasticsearchTemplateTests.java | 39 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 93c84ca4e..86673df38 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -102,6 +102,14 @@ public NativeSearchQueryBuilder withCollapseField(String collapseField) { return this; } + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withCollapseBuilder(@Nullable CollapseBuilder collapseBuilder) { + this.collapseBuilder = collapseBuilder; + return this; + } + public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder aggregationBuilder) { this.aggregationBuilders.add(aggregationBuilder); return this; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index fc59cee2e..902bd90d6 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -49,6 +49,7 @@ import org.elasticsearch.common.lucene.search.function.CombineFunction; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.index.VersionType; +import org.elasticsearch.index.query.InnerHitBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder; @@ -56,6 +57,7 @@ import org.elasticsearch.join.query.ParentIdQueryBuilder; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; +import org.elasticsearch.search.collapse.CollapseBuilder; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; @@ -2811,6 +2813,39 @@ public void shouldReturnDocumentWithCollapsedField() { assertThat(searchHits.getSearchHit(1).getContent().getMessage()).isEqualTo("message 2"); } + @Test // #1493 + @DisplayName("should return document with collapse field and inner hits") + public void shouldReturnDocumentWithCollapsedFieldAndInnerHits() { + + // given + SampleEntity sampleEntity = SampleEntity.builder().id(nextIdAsString()).message("message 1").rate(1) + .version(System.currentTimeMillis()).build(); + SampleEntity sampleEntity2 = SampleEntity.builder().id(nextIdAsString()).message("message 2").rate(2) + .version(System.currentTimeMillis()).build(); + SampleEntity sampleEntity3 = SampleEntity.builder().id(nextIdAsString()).message("message 1").rate(1) + .version(System.currentTimeMillis()).build(); + + List indexQueries = getIndexQueries(Arrays.asList(sampleEntity, sampleEntity2, sampleEntity3)); + + operations.bulkIndex(indexQueries, index); + indexOperations.refresh(); + + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) + .withCollapseBuilder(new CollapseBuilder("rate").setInnerHits(new InnerHitBuilder("innerHits"))).build(); + + // when + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + + // then + assertThat(searchHits).isNotNull(); + assertThat(searchHits.getTotalHits()).isEqualTo(3); + assertThat(searchHits.getSearchHits()).hasSize(2); + assertThat(searchHits.getSearchHit(0).getContent().getMessage()).isEqualTo("message 1"); + assertThat(searchHits.getSearchHit(0).getInnerHits("innerHits").getTotalHits()).isEqualTo(2); + assertThat(searchHits.getSearchHit(1).getContent().getMessage()).isEqualTo("message 2"); + assertThat(searchHits.getSearchHit(1).getInnerHits("innerHits").getTotalHits()).isEqualTo(1); + } + private IndexQuery getIndexQuery(SampleEntity sampleEntity) { return new IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity) .withVersion(sampleEntity.getVersion()).build(); @@ -3597,8 +3632,8 @@ void shouldSetScriptedFieldsOnImmutableObjects() { params.put("factor", 2); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withSourceFilter(new FetchSourceFilter(new String[] { "*" }, new String[] {})) - .withScriptField( - new ScriptField("scriptedRate", new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params))) + .withScriptField(new ScriptField("scriptedRate", + new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params))) .build(); SearchHits searchHits = operations.search(searchQuery, From 25b323c00dcd4011bed4033bdbcaf57db3da7638 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Tue, 18 May 2021 21:47:47 +0200 Subject: [PATCH 072/776] source_filter and fields fixes. Original Pull Request #1819 Closes #1817 --- ...elasticsearch-migration-guide-4.2-4.3.adoc | 15 ++++++ .../asciidoc/reference/migration-guides.adoc | 2 + .../elasticsearch/core/RequestFactory.java | 46 ++++++++----------- .../data/elasticsearch/core/query/Query.java | 2 +- .../core/CriteriaQueryMappingUnitTests.java | 4 +- .../core/ElasticsearchTemplateTests.java | 33 +------------ .../core/SourceFilterIntegrationTests.java | 24 +++------- 7 files changed, 48 insertions(+), 78 deletions(-) create mode 100644 src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc new file mode 100644 index 000000000..ff3467ac5 --- /dev/null +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc @@ -0,0 +1,15 @@ +[[elasticsearch-migration-guide-4.2-4.3]] += Upgrading from 4.2.x to 4.3.x + +This section describes breaking changes from version 4.2.x to 4.3.x and how removed features can be replaced by new introduced features. + +[[elasticsearch-migration-guide-4.2-4.3.deprecations]] +== Deprecations + +[[elasticsearch-migration-guide-4.2-4.3.breaking-changes]] +== Breaking Changes + +=== Handling of field and sourceFilter properties of Query + +Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`. This was not correct, as these are different things for Elasticsearch. This has been corrected. As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's +`_source' and should be changed to use the `sourceFilter`. diff --git a/src/main/asciidoc/reference/migration-guides.adoc b/src/main/asciidoc/reference/migration-guides.adoc index ba5d1e0f7..1fe24a41a 100644 --- a/src/main/asciidoc/reference/migration-guides.adoc +++ b/src/main/asciidoc/reference/migration-guides.adoc @@ -8,4 +8,6 @@ include::elasticsearch-migration-guide-3.2-4.0.adoc[] include::elasticsearch-migration-guide-4.0-4.1.adoc[] include::elasticsearch-migration-guide-4.1-4.2.adoc[] + +include::elasticsearch-migration-guide-4.2-4.3.adoc[] :leveloffset: -1 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 20e1d4a7b..4451ae9c0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -681,6 +681,7 @@ private List getMultiRequestItems(Query searchQuery, Class item = item.routing(searchQuery.getRoute()); } + // note: multiGet does not have fields, need to set sourceContext to filter if (fetchSourceContext != null) { item.fetchSourceContext(fetchSourceContext); } @@ -929,11 +930,6 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz sourceBuilder.seqNoAndPrimaryTerm(true); } - if (query.getSourceFilter() != null) { - SourceFilter sourceFilter = query.getSourceFilter(); - sourceBuilder.fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes()); - } - if (query.getPageable().isPaged()) { sourceBuilder.from((int) query.getPageable().getOffset()); sourceBuilder.size(query.getPageable().getPageSize()); @@ -942,8 +938,14 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz sourceBuilder.size(INDEX_MAX_RESULT_WINDOW); } + if (query.getSourceFilter() != null) { + sourceBuilder.fetchSource(getFetchSourceContext(query)); + SourceFilter sourceFilter = query.getSourceFilter(); + sourceBuilder.fetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes()); + } + if (!query.getFields().isEmpty()) { - sourceBuilder.fetchSource(query.getFields().toArray(new String[0]), null); + query.getFields().forEach(sourceBuilder::fetchField); } if (query.getIndicesOptions() != null) { @@ -1023,11 +1025,6 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli searchRequestBuilder.seqNoAndPrimaryTerm(true); } - if (query.getSourceFilter() != null) { - SourceFilter sourceFilter = query.getSourceFilter(); - searchRequestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes()); - } - if (query.getPageable().isPaged()) { searchRequestBuilder.setFrom((int) query.getPageable().getOffset()); searchRequestBuilder.setSize(query.getPageable().getPageSize()); @@ -1036,8 +1033,13 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli searchRequestBuilder.setSize(INDEX_MAX_RESULT_WINDOW); } + if (query.getSourceFilter() != null) { + SourceFilter sourceFilter = query.getSourceFilter(); + searchRequestBuilder.setFetchSource(sourceFilter.getIncludes(), sourceFilter.getExcludes()); + } + if (!query.getFields().isEmpty()) { - searchRequestBuilder.setFetchSource(query.getFields().toArray(new String[0]), null); + query.getFields().forEach(searchRequestBuilder::addFetchField); } if (query.getIndicesOptions() != null) { @@ -1599,24 +1601,16 @@ public static WriteRequest.RefreshPolicy toElasticsearchRefreshPolicy(RefreshPol } } + @Nullable private FetchSourceContext getFetchSourceContext(Query searchQuery) { - FetchSourceContext fetchSourceContext = null; - SourceFilter sourceFilter = searchQuery.getSourceFilter(); - if (!isEmpty(searchQuery.getFields())) { - if (sourceFilter == null) { - sourceFilter = new FetchSourceFilter(toArray(searchQuery.getFields()), null); - } else { - ArrayList arrayList = new ArrayList<>(); - Collections.addAll(arrayList, sourceFilter.getIncludes()); - sourceFilter = new FetchSourceFilter(toArray(arrayList), null); - } + SourceFilter sourceFilter = searchQuery.getSourceFilter(); - fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes()); - } else if (sourceFilter != null) { - fetchSourceContext = new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes()); + if (sourceFilter != null) { + return new FetchSourceContext(true, sourceFilter.getIncludes(), sourceFilter.getExcludes()); } - return fetchSourceContext; + + return null; } // endregion 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 1f255ffb1..ae96d335e 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 @@ -99,7 +99,7 @@ static Query findAll() { /** * Get fields to be returned as part of search request * - * @return + * @return maybe empty, never null */ List getFields(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index e27eedead..294e99866 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -44,7 +44,7 @@ import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.Criteria; import org.springframework.data.elasticsearch.core.query.CriteriaQuery; -import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; +import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SourceFilter; import org.springframework.lang.Nullable; @@ -430,7 +430,7 @@ void shouldMapNamesInSourceFieldsAndSourceFilters() { Query query = Query.findAll(); // Note: we don't care if these filters make sense here, this test is only about name mapping query.addFields("firstName", "lastName"); - query.addSourceFilter(new FetchSourceFilter(new String[] { "firstName" }, new String[] { "lastName" })); + query.addSourceFilter(new FetchSourceFilterBuilder().withIncludes("firstName").withExcludes("lastName").build()); mappingElasticsearchConverter.updateQuery(query, Person.class); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 902bd90d6..1e9b4a27a 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -994,35 +994,6 @@ public void shouldDeleteGivenCriteriaQuery() { assertThat(sampleEntities).isEmpty(); } - @Test - public void shouldReturnSpecifiedFields() { - - // given - String documentId = nextIdAsString(); - String message = "some test message"; - String type = "some type"; - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(message).type(type) - .version(System.currentTimeMillis()).location(new GeoPoint(1.2, 3.4)).build(); - - IndexQuery indexQuery = getIndexQuery(sampleEntity); - - operations.index(indexQuery, index); - indexOperations.refresh(); - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withFields("message") - .build(); - - // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); - - // then - assertThat(searchHits).isNotNull(); - assertThat(searchHits.getTotalHits()).isEqualTo(1); - final SampleEntity actual = searchHits.getSearchHit(0).getContent(); - assertThat(actual.message).isEqualTo(message); - assertThat(actual.getType()).isNull(); - assertThat(actual.getLocation()).isNull(); - } - @Test public void shouldReturnFieldsBasedOnSourceFilter() { @@ -2666,7 +2637,7 @@ public void shouldRespectSourceFilterWithScanAndScrollForGivenSearchQuery() { indexOperations.refresh(); // then - SourceFilter sourceFilter = new FetchSourceFilter(new String[] { "id" }, new String[] {}); + SourceFilter sourceFilter = new FetchSourceFilterBuilder().withIncludes("id").build(); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPageable(PageRequest.of(0, 10)).withSourceFilter(sourceFilter).build(); @@ -3631,7 +3602,7 @@ void shouldSetScriptedFieldsOnImmutableObjects() { Map params = new HashMap<>(); params.put("factor", 2); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) - .withSourceFilter(new FetchSourceFilter(new String[] { "*" }, new String[] {})) + .withSourceFilter(new FetchSourceFilterBuilder().withIncludes("*").build()) .withScriptField(new ScriptField("scriptedRate", new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params))) .build(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java index 0bd7f4a18..762ce9deb 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/SourceFilterIntegrationTests.java @@ -29,6 +29,7 @@ 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.query.FetchSourceFilterBuilder; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SourceFilter; @@ -66,28 +67,15 @@ void tearDown() { indexOps.delete(); } - @Test // #1659 - @DisplayName("should only return requested fields on search") - void shouldOnlyReturnRequestedFieldsOnSearch() { - - Query query = Query.findAll(); - query.addFields("field2"); - - SearchHits searchHits = operations.search(query, Entity.class); - - assertThat(searchHits).hasSize(1); - Entity entity = searchHits.getSearchHit(0).getContent(); - assertThat(entity.getField1()).isNull(); - assertThat(entity.getField2()).isEqualTo("two"); - assertThat(entity.getField3()).isNull(); - } - @Test // #1659, #1678 @DisplayName("should only return requested fields on multiget") void shouldOnlyReturnRequestedFieldsOnGMultiGet() { - Query query = new NativeSearchQueryBuilder().withIds(Collections.singleton("42")).build(); - query.addFields("field2"); + // multiget has no fields, need sourcefilter here + Query query = new NativeSearchQueryBuilder() // + .withIds(Collections.singleton("42")) // + .withSourceFilter(new FetchSourceFilterBuilder().withIncludes("field2").build()) // + .build(); // List> entities = operations.multiGet(query, Entity.class); From 0836411d45b121c0c15f69199a35f79b5701333e Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 19 May 2021 21:38:48 +0200 Subject: [PATCH 073/776] Add runtime fields to index mapping. Original Pull Request: #1820 Closes: #1816 --- .../reference/elasticsearch-misc.adoc | 16 +++++++- .../elasticsearch/annotations/Mapping.java | 12 ++++++ .../core/AbstractDefaultIndexOperations.java | 2 - .../core/index/MappingBuilder.java | 14 +++++++ .../index/MappingBuilderIntegrationTests.java | 19 +++++++++ .../core/index/MappingBuilderUnitTests.java | 41 ++++++++++++++++++- .../resources/mappings/runtime-fields.json | 8 ++++ 7 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/mappings/runtime-fields.json diff --git a/src/main/asciidoc/reference/elasticsearch-misc.adoc b/src/main/asciidoc/reference/elasticsearch-misc.adoc index 372dd04d3..8853ed66e 100644 --- a/src/main/asciidoc/reference/elasticsearch-misc.adoc +++ b/src/main/asciidoc/reference/elasticsearch-misc.adoc @@ -51,10 +51,24 @@ class Entity { When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties: -* `mappingPath` a classpath resource in JSON format which is used as the mapping, no other mapping processing is done. +* `mappingPath` a classpath resource in JSON format; if this is not empty it is used as the mapping, no other mapping processing is done. * `enabled` when set to false, this flag is written to the mapping and no further processing is done. * `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`. * `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection. +* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example: +==== +[source,json] +---- +{ + "day_of_week": { + "type": "keyword", + "script": { + "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))" + } + } +} +---- +==== [[elasticsearch.misc.filter]] == Filter Builder diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java index c41e1300e..d2827e55b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java @@ -27,6 +27,7 @@ * Elasticsearch Mapping * * @author Mohsin Husen + * @author Peter-Josef Meisch */ @Persistent @Inherited @@ -42,6 +43,7 @@ * @since 4.2 */ boolean enabled() default true; + /** * whether date_detection is enabled * @@ -58,10 +60,20 @@ /** * custom dynamic date formats + * * @since 4.3 */ String[] dynamicDateFormats() default {}; + /** + * classpath to a JSON file containing the values for a runtime mapping definition. The file must contain the JSON + * object that is written as the value of the runtime property. {@see elasticsearch doc} + * + * @since 4.3 + */ + String runtimeFieldsPath() default ""; + enum Detection { DEFAULT, TRUE, FALSE; } 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 f7ef23b1a..54ea56bb2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -219,8 +219,6 @@ protected Document buildMapping(Class clazz) { if (hasText(mappings)) { return Document.parse(mappings); } - } else { - LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field"); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 43b4bb4c5..1530b1352 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -95,6 +95,7 @@ public class MappingBuilder { private static final String DATE_DETECTION = "date_detection"; private static final String NUMERIC_DETECTION = "numeric_detection"; private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats"; + private static final String RUNTIME = "runtime"; private final ElasticsearchConverter elasticsearchConverter; @@ -168,6 +169,10 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten if (mappingAnnotation.dynamicDateFormats().length > 0) { builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats()); } + + if (StringUtils.hasText(mappingAnnotation.runtimeFieldsPath())) { + addRuntimeFields(builder, mappingAnnotation.runtimeFieldsPath()); + } } boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField); @@ -222,6 +227,15 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten } + private void addRuntimeFields(XContentBuilder builder, String runtimeFieldsPath) throws IOException { + + ClassPathResource runtimeFields = new ClassPathResource(runtimeFieldsPath); + + if (runtimeFields.exists()) { + builder.rawField(RUNTIME, runtimeFields.getInputStream(), XContentType.JSON); + } + } + private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, ElasticsearchPersistentProperty property) throws IOException { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index de0e9e4bb..98f6a264b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -25,6 +25,7 @@ import java.lang.Integer; import java.lang.Object; import java.math.BigDecimal; +import java.time.Instant; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -316,6 +317,16 @@ void shouldWriteDynamicDetectionValues() { indexOps.delete(); } + @Test // #1816 + @DisplayName("should write runtime fields") + void shouldWriteRuntimeFields() { + + IndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class); + indexOps.create(); + indexOps.putMapping(); + indexOps.delete(); + } + // region entities @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @@ -1130,6 +1141,14 @@ public void setAuthor(Author author) { private static class DynamicDetectionMapping { @Id @Nullable private String id; } + + @Document(indexName = "runtime-fields") + @Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json") + private static class RuntimeFieldEntity { + @Id @Nullable private String id; + @Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; + } + // endregion } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 7fbaca7ff..7ed2405c0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -26,6 +26,7 @@ import java.lang.Integer; import java.lang.Object; import java.math.BigDecimal; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; @@ -855,7 +856,38 @@ void shouldWriteDynamicDateFormats() throws JSONException { assertEquals(expected, mapping, true); } + @Test // #1816 + @DisplayName("should write runtime fields") + void shouldWriteRuntimeFields() throws JSONException { + + String expected = "{\n" + // + " \"runtime\": {\n" + // + " \"day_of_week\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"script\": {\n" + // + " \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + // + " }\n" + // + " }\n" + // + " },\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"@timestamp\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"epoch_millis\"\n" + // + " }\n" + // + " }\n" + // + "}\n"; // + + String mapping = getMappingBuilder().buildPropertyMapping(RuntimeFieldEntity.class); + + assertEquals(expected, mapping, true); + } // region entities + @Document(indexName = "ignore-above-index") static class IgnoreAboveEntity { @Nullable @Id private String id; @@ -1778,7 +1810,7 @@ private static class DynamicDetectionMappingDefault { } @Document(indexName = "dynamic-dateformats-mapping") - @Mapping(dynamicDateFormats = {"date1", "date2"}) + @Mapping(dynamicDateFormats = { "date1", "date2" }) private static class DynamicDateFormatsMapping { @Id @Nullable private String id; } @@ -1794,5 +1826,12 @@ private static class DynamicDetectionMappingTrue { private static class DynamicDetectionMappingFalse { @Id @Nullable private String id; } + + @Document(indexName = "runtime-fields") + @Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json") + private static class RuntimeFieldEntity { + @Id @Nullable private String id; + @Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; + } // endregion } diff --git a/src/test/resources/mappings/runtime-fields.json b/src/test/resources/mappings/runtime-fields.json new file mode 100644 index 000000000..97fe9c5d4 --- /dev/null +++ b/src/test/resources/mappings/runtime-fields.json @@ -0,0 +1,8 @@ +{ + "day_of_week": { + "type": "keyword", + "script": { + "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))" + } + } +} From 5ed655e0aaa3268dde6bde816497740617397c1a Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 19 May 2021 23:40:11 +0200 Subject: [PATCH 074/776] Fix recative mapping creation. Original Pull Request #1821 --- .../elasticsearch/core/AbstractDefaultIndexOperations.java | 1 + .../elasticsearch/core/DefaultReactiveIndexOperations.java | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) 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 54ea56bb2..0ac274c70 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java @@ -210,6 +210,7 @@ protected Document buildMapping(Class clazz) { // load mapping specified in Mapping annotation if present Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); + if (mappingAnnotation != null) { String mappingPath = mappingAnnotation.mappingPath(); 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 a1aae3f59..712df1ef3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -183,7 +183,11 @@ public Mono createMapping(Class clazz) { Mapping mappingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Mapping.class); if (mappingAnnotation != null) { - return loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); + String mappingPath = mappingAnnotation.mappingPath(); + + if (hasText(mappingPath)) { + return loadDocument(mappingAnnotation.mappingPath(), "@Mapping"); + } } String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz); From e8f73b75baa0d8b578f4cf5ec2a091c27d2614ba Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 21 May 2021 15:29:08 +0200 Subject: [PATCH 075/776] Add Blockhound to test setup. Original Pull Request #1823 Closes #1822 --- pom.xml | 66 +++++++++++++------ .../BlockHoundIntegrationCustomizer.java | 38 +++++++++++ .../blockhound/BlockHoundTests.java | 47 +++++++++++++ ...eactiveMappingBuilderIntegrationTests.java | 65 ++++++++++++++++++ ...ockhound.integration.BlockHoundIntegration | 1 + 5 files changed, 196 insertions(+), 21 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java create mode 100644 src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration diff --git a/pom.xml b/pom.xml index edca9801b..a4ad2c39d 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ 4.1.52.Final 2.6.0-SNAPSHOT 1.15.1 + 1.0.6.RELEASE spring.data.elasticsearch @@ -256,6 +257,14 @@ test + + io.projectreactor.tools + blockhound-junit-platform + ${blockhound-junit} + test + + + + org.skyscreamer @@ -448,9 +457,7 @@ - ci - @@ -473,9 +480,26 @@ - + + jdk13+ + + + [13,) + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -XX:+AllowRedefinitionToAddDeleteMethods + + + + + diff --git a/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java new file mode 100644 index 000000000..89a8c6900 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.blockhound; + +import reactor.blockhound.BlockHound; +import reactor.blockhound.integration.BlockHoundIntegration; + +import org.elasticsearch.Build; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.springframework.data.elasticsearch.support.VersionInfo; + +/** + * @author Peter-Josef Meisch + */ +public class BlockHoundIntegrationCustomizer implements BlockHoundIntegration { + @Override + public void applyTo(BlockHound.Builder builder) { + // Elasticsearch classes reading from the classpath on initialization, needed for parsing Elasticsearch responses + builder.allowBlockingCallsInside(XContentBuilder.class.getName(), "") + .allowBlockingCallsInside(Build.class.getName(), ""); + + // Spring Data Elasticsearch classes reading from the classpath + builder.allowBlockingCallsInside(VersionInfo.class.getName(), "logVersions"); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundTests.java b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundTests.java new file mode 100644 index 000000000..4bd0392b1 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.blockhound; + +import static org.assertj.core.api.Assertions.*; + +import reactor.blockhound.BlockingOperationError; +import reactor.core.publisher.Mono; + +import java.time.Duration; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author Peter-Josef Meisch + */ +public class BlockHoundTests { + + @Test // #1822 + @DisplayName("should fail if BlockHound is not installed") + void shouldFailIfBlockHoundIsNotInstalled() { + + assertThatThrownBy(() -> { + Mono.delay(Duration.ofMillis(1)).doOnNext(it -> { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }).block(); // should throw an exception about Thread.sleep + }).hasCauseInstanceOf(BlockingOperationError.class); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java new file mode 100644 index 000000000..c163aefef --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.index; + +import static org.springframework.data.elasticsearch.annotations.FieldType.*; + +import java.time.Instant; + +import org.junit.jupiter.api.DisplayName; +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.DateFormat; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; +import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; +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; + +/** + * @author Peter-Josef Meisch + */ +@SpringIntegrationTest +@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) +public class ReactiveMappingBuilderIntegrationTests { + + @Autowired private ReactiveElasticsearchOperations operations; + + @Test // #1822 + @DisplayName("should write runtime fields") + void shouldWriteRuntimeFields() { + + ReactiveIndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class); + + indexOps.create().block(); + indexOps.putMapping().block(); + indexOps.delete().block(); + } + + // region entities + @Document(indexName = "runtime-fields") + @Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json") + private static class RuntimeFieldEntity { + @Id @Nullable private String id; + @Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp; + } + // endregion +} diff --git a/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration b/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration new file mode 100644 index 000000000..1cd5f8f92 --- /dev/null +++ b/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration @@ -0,0 +1 @@ +org.springframework.data.elasticsearch.blockhound.BlockHoundIntegrationCustomizer From 7582617a263a732f10ced36968dae265acb094db Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 22 May 2021 17:16:16 +0200 Subject: [PATCH 076/776] Fix reactive blocking calls. Original Pull Request #1825 Closes #1824 --- .../core/AbstractElasticsearchTemplate.java | 15 ++- .../core/DefaultReactiveIndexOperations.java | 5 +- .../core/ReactiveElasticsearchTemplate.java | 17 ++- .../core/ReactiveResourceUtil.java | 2 +- .../core/index/MappingBuilder.java | 42 +++++-- .../core/index/ReactiveMappingBuilder.java | 78 +++++++++++++ .../elasticsearch/support/VersionInfo.java | 110 +++++++++--------- .../BlockHoundIntegrationCustomizer.java | 9 +- ...eactiveElasticsearchTemplateUnitTests.java | 4 - ....java => ReactiveMappingBuilderTests.java} | 56 ++++++--- .../support/VersionInfoTest.java | 41 +++++++ 11 files changed, 281 insertions(+), 98 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java rename src/test/java/org/springframework/data/elasticsearch/core/index/{ReactiveMappingBuilderIntegrationTests.java => ReactiveMappingBuilderTests.java} (51%) create mode 100644 src/test/java/org/springframework/data/elasticsearch/support/VersionInfoTest.java diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index c5e48fb1c..f42cd0fec 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -101,7 +101,10 @@ protected void initialize(ElasticsearchConverter elasticsearchConverter) { this.routingResolver = new DefaultRoutingResolver((SimpleElasticsearchMappingContext) mappingContext); requestFactory = new RequestFactory(elasticsearchConverter); - VersionInfo.logVersions(getClusterVersion()); + + // initialize the VersionInfo class in the initialization phase + // noinspection ResultOfMethodCallIgnored + VersionInfo.versionProperties(); } /** @@ -166,6 +169,16 @@ public void setRefreshPolicy(@Nullable RefreshPolicy refreshPolicy) { public RefreshPolicy getRefreshPolicy() { return refreshPolicy; } + + /** + * logs the versions of the different Elasticsearch components. + * + * @since 4.3 + */ + public void logVersions() { + VersionInfo.logVersions(getClusterVersion()); + } + // endregion // region DocumentOperations 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 712df1ef3..d2558113b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java @@ -49,8 +49,8 @@ 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.ReactiveMappingBuilder; import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; @@ -190,8 +190,7 @@ public Mono createMapping(Class clazz) { } } - String mapping = new MappingBuilder(converter).buildPropertyMapping(clazz); - return Mono.just(Document.parse(mapping)); + return new ReactiveMappingBuilder(converter).buildReactivePropertyMapping(clazz).map(Document::parse); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 67ebf03b8..be256ee3e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -142,7 +142,9 @@ public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, Elastic this.operations = new EntityOperations(this.mappingContext); this.requestFactory = new RequestFactory(converter); - logVersions(); + // initialize the VersionInfo class in the initialization phase + // noinspection ResultOfMethodCallIgnored + VersionInfo.versionProperties(); } private ReactiveElasticsearchTemplate copy() { @@ -155,11 +157,14 @@ private ReactiveElasticsearchTemplate copy() { return copy; } - private void logVersions() { - getClusterVersion() // - .doOnSuccess(VersionInfo::logVersions) // - .doOnError(e -> VersionInfo.logVersions(null)) // - .subscribe(); + /** + * logs the versions of the different Elasticsearch components. + * + * @return a Mono signalling finished execution + * @since 4.3 + */ + public Mono logVersions() { + return getClusterVersion().doOnNext(VersionInfo::logVersions).then(); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java index 1598b17fe..168da2f8f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveResourceUtil.java @@ -63,7 +63,7 @@ public static Mono readFileFromClasspath(String url) { String line; while ((line = br.readLine()) != null) { - sb.append(line); + sb.append(line).append('\n'); } sink.next(sb.toString()); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 1530b1352..a5153bb63 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -97,7 +97,7 @@ public class MappingBuilder { private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats"; private static final String RUNTIME = "runtime"; - private final ElasticsearchConverter elasticsearchConverter; + protected final ElasticsearchConverter elasticsearchConverter; private boolean writeTypeHints = true; @@ -113,9 +113,16 @@ public MappingBuilder(ElasticsearchConverter elasticsearchConverter) { */ public String buildPropertyMapping(Class clazz) throws MappingException { + ElasticsearchPersistentEntity entity = elasticsearchConverter.getMappingContext() + .getRequiredPersistentEntity(clazz); + + return buildPropertyMapping(entity, getRuntimeFields(entity)); + } + + protected String buildPropertyMapping(ElasticsearchPersistentEntity entity, + @Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) { + try { - ElasticsearchPersistentEntity entity = elasticsearchConverter.getMappingContext() - .getRequiredPersistentEntity(clazz); writeTypeHints = entity.writeTypeHints(); @@ -124,7 +131,8 @@ public String buildPropertyMapping(Class clazz) throws MappingException { // Dynamic templates addDynamicTemplatesMapping(builder, entity); - mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class)); + mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class), + runtimeFields); builder.endObject() // root object .close(); @@ -148,7 +156,8 @@ private void writeTypeHintMapping(XContentBuilder builder) throws IOException { private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, - @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException { + @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping, + @Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) throws IOException { if (entity != null && entity.isAnnotationPresent(Mapping.class)) { Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class); @@ -170,8 +179,8 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats()); } - if (StringUtils.hasText(mappingAnnotation.runtimeFieldsPath())) { - addRuntimeFields(builder, mappingAnnotation.runtimeFieldsPath()); + if (runtimeFields != null) { + builder.field(RUNTIME, runtimeFields); } } @@ -227,13 +236,22 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten } - private void addRuntimeFields(XContentBuilder builder, String runtimeFieldsPath) throws IOException { + @Nullable + private org.springframework.data.elasticsearch.core.document.Document getRuntimeFields( + @Nullable ElasticsearchPersistentEntity entity) { - ClassPathResource runtimeFields = new ClassPathResource(runtimeFieldsPath); + if (entity != null) { + Mapping mappingAnnotation = entity.findAnnotation(Mapping.class); + if (mappingAnnotation != null) { + String runtimeFieldsPath = mappingAnnotation.runtimeFieldsPath(); - if (runtimeFields.exists()) { - builder.rawField(RUNTIME, runtimeFields.getInputStream(), XContentType.JSON); + if (hasText(runtimeFieldsPath)) { + String jsonString = ResourceUtil.readFileFromClasspath(runtimeFieldsPath); + return org.springframework.data.elasticsearch.core.document.Document.parse(jsonString); + } + } } + return null; } private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, @@ -291,7 +309,7 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, : null; mapEntity(builder, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(), - fieldAnnotation, dynamicMapping); + fieldAnnotation, dynamicMapping, null); return; } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java new file mode 100644 index 000000000..f06b71494 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.index; + +import static org.springframework.util.StringUtils.*; + +import reactor.core.publisher.Mono; + +import org.springframework.data.elasticsearch.annotations.Mapping; +import org.springframework.data.elasticsearch.core.ReactiveResourceUtil; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; +import org.springframework.data.mapping.MappingException; +import org.springframework.lang.Nullable; + +/** + * Subclass of {@link MappingBuilder} with specialized methods TO inhibit blocking CALLS + * + * @author Peter-Josef Meisch + * @since 4.3 + */ +public class ReactiveMappingBuilder extends MappingBuilder { + + public ReactiveMappingBuilder(ElasticsearchConverter elasticsearchConverter) { + super(elasticsearchConverter); + } + + @Override + public String buildPropertyMapping(Class clazz) throws MappingException { + throw new UnsupportedOperationException( + "Use ReactiveMappingBuilder.buildReactivePropertyMapping() instead of buildPropertyMapping()"); + } + + public Mono buildReactivePropertyMapping(Class clazz) throws MappingException { + ElasticsearchPersistentEntity entity = elasticsearchConverter.getMappingContext() + .getRequiredPersistentEntity(clazz); + + return getRuntimeFields(entity) // + .switchIfEmpty(Mono.just(Document.create())) // + .map(document -> { + if (document.isEmpty()) { + return buildPropertyMapping(entity, null); + } else { + return buildPropertyMapping(entity, document); + } + }); + } + + private Mono getRuntimeFields(@Nullable ElasticsearchPersistentEntity entity) { + + if (entity != null) { + Mapping mappingAnnotation = entity.findAnnotation(Mapping.class); + if (mappingAnnotation != null) { + String runtimeFieldsPath = mappingAnnotation.runtimeFieldsPath(); + + if (hasText(runtimeFieldsPath)) { + return ReactiveResourceUtil.readFileFromClasspath(runtimeFieldsPath).map(Document::parse); + } + } + } + + return Mono.empty(); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/support/VersionInfo.java b/src/main/java/org/springframework/data/elasticsearch/support/VersionInfo.java index 43e574398..e7e001bb6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/support/VersionInfo.java +++ b/src/main/java/org/springframework/data/elasticsearch/support/VersionInfo.java @@ -15,9 +15,9 @@ */ package org.springframework.data.elasticsearch.support; +import java.io.IOException; import java.io.InputStream; import java.util.Properties; -import java.util.concurrent.atomic.AtomicBoolean; import org.elasticsearch.Version; import org.slf4j.Logger; @@ -35,70 +35,76 @@ public final class VersionInfo { private static final Logger LOG = LoggerFactory.getLogger(VersionInfo.class); - private static final AtomicBoolean initialized = new AtomicBoolean(false); - private static final String VERSION_PROPERTIES = "versions.properties"; + protected static final String VERSION_PROPERTIES = "versions.properties"; public static final String VERSION_SPRING_DATA_ELASTICSEARCH = "version.spring-data-elasticsearch"; public static final String VERSION_ELASTICSEARCH_CLIENT = "version.elasticsearch-client"; + private static Properties versionProperties; + + public static Properties versionProperties() { + return versionProperties; + } + + static { + try { + versionProperties = loadVersionProperties(); + } catch (IOException e) { + LOG.error("Could not load {}", VERSION_PROPERTIES, e); + versionProperties = new Properties(); + versionProperties.put(VERSION_SPRING_DATA_ELASTICSEARCH, "0.0.0"); + versionProperties.put(VERSION_ELASTICSEARCH_CLIENT, "0.0.0"); + } + } + /** - * logs the relevant version info the first time it is called. Does nothing after the first call - * + * logs the relevant version info. + * * @param clusterVersion the version of the cluster */ public static void logVersions(@Nullable String clusterVersion) { - if (!initialized.getAndSet(true)) { - try { - InputStream resource = VersionInfo.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES); - if (resource != null) { - Properties properties = new Properties(); - properties.load(resource); - - String versionSpringDataElasticsearch = properties.getProperty(VERSION_SPRING_DATA_ELASTICSEARCH); - Version versionESBuilt = Version.fromString(properties.getProperty(VERSION_ELASTICSEARCH_CLIENT)); - Version versionESUsed = Version.CURRENT; - Version versionESCluster = clusterVersion != null ? Version.fromString(clusterVersion) : null; - - LOG.info("Version Spring Data Elasticsearch: {}", versionSpringDataElasticsearch.toString()); - LOG.info("Version Elasticsearch Client in build: {}", versionESBuilt.toString()); - LOG.info("Version Elasticsearch Client used: {}", versionESUsed.toString()); - - if (differInMajorOrMinor(versionESBuilt, versionESUsed)) { - LOG.warn("Version mismatch in between Elasticsearch Clients build/use: {} - {}", versionESBuilt, - versionESUsed); - } - - if (versionESCluster != null) { - LOG.info("Version Elasticsearch cluster: {}", versionESCluster.toString()); - - if (differInMajorOrMinor(versionESUsed, versionESCluster)) { - LOG.warn("Version mismatch in between Elasticsearch Client and Cluster: {} - {}", versionESUsed, - versionESCluster); - } - } - } else { - LOG.warn("cannot load {}", VERSION_PROPERTIES); - } - } catch (Exception e) { - LOG.warn("Could not log version info: {} - {}", e.getClass().getSimpleName(), e.getMessage()); + try { + + String versionSpringDataElasticsearch = versionProperties.getProperty(VERSION_SPRING_DATA_ELASTICSEARCH); + Version versionESBuilt = Version.fromString(versionProperties.getProperty(VERSION_ELASTICSEARCH_CLIENT)); + Version versionESUsed = Version.CURRENT; + Version versionESCluster = clusterVersion != null ? Version.fromString(clusterVersion) : null; + + LOG.info("Version Spring Data Elasticsearch: {}", versionSpringDataElasticsearch.toString()); + LOG.info("Version Elasticsearch Client in build: {}", versionESBuilt.toString()); + LOG.info("Version Elasticsearch Client used: {}", versionESUsed.toString()); + + if (differInMajorOrMinor(versionESBuilt, versionESUsed)) { + LOG.warn("Version mismatch in between Elasticsearch Clients build/use: {} - {}", versionESBuilt, versionESUsed); } - } - } + if (versionESCluster != null) { + LOG.info("Version Elasticsearch cluster: {}", versionESCluster.toString()); - public static Properties versionProperties() throws Exception { - try { - InputStream resource = VersionInfo.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES); - if (resource != null) { - Properties properties = new Properties(); - properties.load(resource); - return properties; - } else { - throw new IllegalStateException("Resource not found"); + if (differInMajorOrMinor(versionESUsed, versionESCluster)) { + LOG.warn("Version mismatch in between Elasticsearch Client and Cluster: {} - {}", versionESUsed, + versionESCluster); + } } } catch (Exception e) { - LOG.error("Could not load {}", VERSION_PROPERTIES, e); - throw e; + LOG.warn("Could not log version info: {} - {}", e.getClass().getSimpleName(), e.getMessage()); + } + } + + /** + * gets the version properties from the classpath resource. + * + * @return version properties + * @throws IOException when an error occurs + */ + private static Properties loadVersionProperties() throws IOException { + InputStream resource = VersionInfo.class.getClassLoader().getResourceAsStream(VERSION_PROPERTIES); + if (resource != null) { + Properties properties = new Properties(); + properties.load(resource); + return properties; + } else { + throw new IllegalStateException("Resource not found"); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java index 89a8c6900..17a0c3e5c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java +++ b/src/test/java/org/springframework/data/elasticsearch/blockhound/BlockHoundIntegrationCustomizer.java @@ -16,23 +16,26 @@ package org.springframework.data.elasticsearch.blockhound; import reactor.blockhound.BlockHound; +import reactor.blockhound.BlockingOperationError; import reactor.blockhound.integration.BlockHoundIntegration; import org.elasticsearch.Build; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.springframework.data.elasticsearch.support.VersionInfo; /** * @author Peter-Josef Meisch */ public class BlockHoundIntegrationCustomizer implements BlockHoundIntegration { + @Override public void applyTo(BlockHound.Builder builder) { // Elasticsearch classes reading from the classpath on initialization, needed for parsing Elasticsearch responses builder.allowBlockingCallsInside(XContentBuilder.class.getName(), "") .allowBlockingCallsInside(Build.class.getName(), ""); - // Spring Data Elasticsearch classes reading from the classpath - builder.allowBlockingCallsInside(VersionInfo.class.getName(), "logVersions"); + builder.blockingMethodCallback(it -> { + throw new BlockingOperationError(it); + }); + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java index ddf2c984a..fcaaa6712 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateUnitTests.java @@ -74,10 +74,6 @@ public class ReactiveElasticsearchTemplateUnitTests { @BeforeEach public void setUp() { - - when(client.info()).thenReturn(Mono.just(new MainResponse("mockNodename", org.elasticsearch.Version.CURRENT, - new ClusterName("mockCluster"), "mockUuid", null))); - template = new ReactiveElasticsearchTemplate(client); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderTests.java similarity index 51% rename from src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderTests.java index c163aefef..81833e027 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilderTests.java @@ -15,43 +15,67 @@ */ package org.springframework.data.elasticsearch.core.index; +import static org.skyscreamer.jsonassert.JSONAssert.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + import java.time.Instant; +import org.json.JSONException; import org.junit.jupiter.api.DisplayName; 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.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.Mapping; -import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; -import org.springframework.data.elasticsearch.core.ReactiveIndexOperations; -import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; -import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.core.MappingContextBaseTests; import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; /** * @author Peter-Josef Meisch */ -@SpringIntegrationTest -@ContextConfiguration(classes = { ReactiveElasticsearchRestTemplateConfiguration.class }) -public class ReactiveMappingBuilderIntegrationTests { +public class ReactiveMappingBuilderTests extends MappingContextBaseTests { - @Autowired private ReactiveElasticsearchOperations operations; + ReactiveMappingBuilder getReactiveMappingBuilder() { + return new ReactiveMappingBuilder(elasticsearchConverter.get()); + } - @Test // #1822 + @Test // #1822, #1824 @DisplayName("should write runtime fields") - void shouldWriteRuntimeFields() { + void shouldWriteRuntimeFields() throws JSONException { + + ReactiveMappingBuilder mappingBuilder = getReactiveMappingBuilder(); + + String expected = "{\n" + // + " \"runtime\": {\n" + // + " \"day_of_week\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"script\": {\n" + // + " \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + + // + " }\n" + // + " }\n" + // + " },\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // + " \"@timestamp\": {\n" + // + " \"type\": \"date\",\n" + // + " \"format\": \"epoch_millis\"\n" + // + " }\n" + // + " }\n" + // + "}\n"; // - ReactiveIndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class); + String mapping = Mono.defer(() -> mappingBuilder.buildReactivePropertyMapping(RuntimeFieldEntity.class)) + .subscribeOn(Schedulers.parallel()).block(); - indexOps.create().block(); - indexOps.putMapping().block(); - indexOps.delete().block(); + assertEquals(expected, mapping, true); } // region entities diff --git a/src/test/java/org/springframework/data/elasticsearch/support/VersionInfoTest.java b/src/test/java/org/springframework/data/elasticsearch/support/VersionInfoTest.java new file mode 100644 index 000000000..039372f2e --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/support/VersionInfoTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.support; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.util.Properties; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author Peter-Josef Meisch + */ +class VersionInfoTest { + + @Test // #1824 + @DisplayName("should read version properties") + void shouldReadVersionProperties() throws IOException { + + Properties properties = VersionInfo.versionProperties(); + + assertThat(properties).isNotNull(); + assertThat(properties.getProperty("version.spring-data-elasticsearch")).isNotNull(); + assertThat(properties.getProperty("version.elasticsearch-client")).isNotNull(); + } +} From 67d084beeadc585d14cad8a510081ce436c5c862 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 24 May 2021 12:29:53 +0200 Subject: [PATCH 077/776] Improve integration test time. Original Pull Request #1827 Closes #1826 --- pom.xml | 2 +- .../core/ElasticsearchTemplate.java | 29 +- .../core/ElasticsearchRestTemplateTests.java | 48 +- .../core/ElasticsearchTemplateTests.java | 639 ++++++++---------- .../ElasticsearchTransportTemplateTests.java | 18 +- ...ElasticsearchTemplateIntegrationTests.java | 347 ++++------ .../index/MappingBuilderIntegrationTests.java | 37 +- .../query/CriteriaQueryIntegrationTests.java | 200 ++---- ...riteriaQueryTransportIntegrationTests.java | 10 +- .../junit/jupiter/ClusterConnection.java | 34 +- .../SpringDataElasticsearchExtension.java | 5 +- .../junit/jupiter/SpringIntegrationTest.java | 7 +- .../repositories/cdi/CdiRepositoryTests.java | 6 +- .../ComplexCustomMethodRepositoryTests.java | 36 +- ...xCustomMethodRepositoryTransportTests.java | 9 +- ...stomMethodRepositoryManualWiringTests.java | 35 +- ...dRepositoryManualWiringTransportTests.java | 9 +- .../CustomMethodRepositoryBaseTests.java | 27 +- .../CustomMethodRepositoryRestTests.java | 9 +- .../CustomMethodRepositoryTests.java | 14 +- .../doubleid/DoubleIDRepositoryTests.java | 38 +- ...ettingAndMappingEntityRepositoryTests.java | 49 +- .../utils/IndexNameProvider.java | 44 ++ 23 files changed, 776 insertions(+), 876 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/utils/IndexNameProvider.java diff --git a/pom.xml b/pom.xml index a4ad2c39d..14734d8f2 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.13.3 4.1.52.Final 2.6.0-SNAPSHOT - 1.15.1 + 1.15.3 1.0.6.RELEASE spring.data.elasticsearch diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 6d3489fbe..c0960ff1d 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -15,7 +15,10 @@ */ package org.springframework.data.elasticsearch.core; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.elasticsearch.action.ActionFuture; @@ -290,12 +293,21 @@ public ByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) public List doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index) { - BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queries, bulkOptions, index); - bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder); - final List indexedObjectInformations = checkForBulkOperationFailure( - bulkRequestBuilder.execute().actionGet()); - updateIndexedObjectsWithQueries(queries, indexedObjectInformations); - return indexedObjectInformations; + + // do it in batches; test code on some machines kills the transport node when the size gets too much + Collection> queryLists = partitionBasedOnSize(queries, 2500); + List allIndexedObjectInformations = new ArrayList<>(queries.size()); + + queryLists.forEach(queryList -> { + BulkRequestBuilder bulkRequestBuilder = requestFactory.bulkRequestBuilder(client, queryList, bulkOptions, index); + bulkRequestBuilder = prepareWriteRequestBuilder(bulkRequestBuilder); + final List indexedObjectInformations = checkForBulkOperationFailure( + bulkRequestBuilder.execute().actionGet()); + updateIndexedObjectsWithQueries(queryList, indexedObjectInformations); + allIndexedObjectInformations.addAll(indexedObjectInformations); + }); + + return allIndexedObjectInformations; } // endregion @@ -411,6 +423,11 @@ protected String getClusterVersion() { public Client getClient() { return client; } + + Collection> partitionBasedOnSize(List inputList, int size) { + final AtomicInteger counter = new AtomicInteger(0); + return inputList.stream().collect(Collectors.groupingBy(s -> counter.getAndIncrement() / size)).values(); + } // endregion /** diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java index b767fc4b0..481fcc046 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java @@ -18,10 +18,8 @@ import static org.assertj.core.api.Assertions.*; import static org.elasticsearch.index.query.QueryBuilders.*; import static org.skyscreamer.jsonassert.JSONAssert.*; -import static org.springframework.data.elasticsearch.annotations.FieldType.*; import static org.springframework.data.elasticsearch.utils.IdGenerator.*; -import java.lang.Object; import java.time.Duration; import java.util.Collections; import java.util.HashMap; @@ -37,16 +35,16 @@ import org.json.JSONException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.data.annotation.Id; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; -import org.springframework.data.elasticsearch.annotations.Document; -import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; -import org.springframework.lang.Nullable; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -64,10 +62,19 @@ * @author Peter-Josef Meisch * @author Farid Faoudi */ -@ContextConfiguration(classes = { ElasticsearchRestTemplateConfiguration.class }) +@ContextConfiguration(classes = { ElasticsearchRestTemplateTests.Config.class }) @DisplayName("ElasticsearchRestTemplate") public class ElasticsearchRestTemplateTests extends ElasticsearchTemplateTests { + @Configuration + @Import({ ElasticsearchRestTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("rest-template"); + } + } + @Test public void shouldThrowExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() { @@ -75,35 +82,10 @@ public void shouldThrowExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document .create(); UpdateQuery updateQuery = UpdateQuery.builder(nextIdAsString()).withDocument(document).build(); - assertThatThrownBy(() -> operations.update(updateQuery, index)) + assertThatThrownBy(() -> operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()))) .isInstanceOf(UncategorizedElasticsearchException.class); } - @Document(indexName = "test-index-sample-core-rest-template") - static class SampleEntity { - @Nullable @Id private String id; - @Nullable - @Field(type = Text, store = true, fielddata = true) private String type; - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public String getType() { - return type; - } - - public void setType(@Nullable String type) { - this.type = type; - } - } - @Test // DATAES-768 void shouldUseAllOptionsFromUpdateQuery() { Map doc = new HashMap<>(); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 1e9b4a27a..831de08e8 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -65,6 +65,7 @@ import org.elasticsearch.search.sort.SortOrder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -74,7 +75,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Order; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -94,6 +94,7 @@ import org.springframework.data.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.core.query.RescorerQuery.ScoreMode; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.util.StreamUtils; import org.springframework.lang.Nullable; @@ -127,35 +128,27 @@ @SpringIntegrationTest public abstract class ElasticsearchTemplateTests { - protected static final String INDEX_NAME_JOIN_SAMPLE_ENTITY = "test-index-sample-join-template"; - private static final String INDEX_NAME_SAMPLE_ENTITY = "test-index-sample-core-template"; private static final String INDEX_1_NAME = "test-index-1"; private static final String INDEX_2_NAME = "test-index-2"; private static final String INDEX_3_NAME = "test-index-3"; - protected final IndexCoordinates index = IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY); + @Autowired protected ElasticsearchOperations operations; - protected IndexOperations indexOperations; + private IndexOperations indexOperations; + + @Autowired protected IndexNameProvider indexNameProvider; @BeforeEach public void before() { + + indexNameProvider.increment(); indexOperations = operations.indexOps(SampleEntity.class); + indexOperations.createWithMapping(); + } + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { operations.indexOps(IndexCoordinates.of("*")).delete(); - - indexOperations.create(); - indexOperations.putMapping(SampleEntity.class); - - IndexOperations indexOpsSampleEntityUUIDKeyed = operations.indexOps(SampleEntityUUIDKeyed.class); - indexOpsSampleEntityUUIDKeyed.create(); - indexOpsSampleEntityUUIDKeyed.putMapping(SampleEntityUUIDKeyed.class); - - IndexOperations indexOpsSearchHitsEntity = operations.indexOps(SearchHitsEntity.class); - indexOpsSearchHitsEntity.create(); - indexOpsSearchHitsEntity.putMapping(SearchHitsEntity.class); - - IndexOperations indexOpsJoinEntity = operations.indexOps(SampleJoinEntity.class); - indexOpsJoinEntity.create(); - indexOpsJoinEntity.putMapping(SampleJoinEntity.class); } @Test // DATAES-106 @@ -167,13 +160,14 @@ public void shouldReturnCountForGivenCriteriaQuery() { .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); // when - long count = operations.count(criteriaQuery, SampleEntity.class, index); + long count = operations.count(criteriaQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(count).isEqualTo(1); @@ -188,14 +182,14 @@ public void shouldReturnCountForGivenSearchQuery() { .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + ; NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when - long count = operations.count(searchQuery, SampleEntity.class, index); + long count = operations.count(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(count).isEqualTo(1); } @@ -208,10 +202,11 @@ public void shouldReturnObjectForGivenId() { SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message") .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); // when - SampleEntity sampleEntity1 = operations.get(documentId, SampleEntity.class, index); + SampleEntity sampleEntity1 = operations.get(documentId, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntity1).isEqualTo(sampleEntity); @@ -233,12 +228,12 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGet() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)).build(); - List> sampleEntities = operations.multiGet(query, SampleEntity.class, index); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntities).hasSize(2); @@ -262,15 +257,15 @@ public void shouldReturnNullObjectForNotExistingIdUsingMultiGet() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when List idsToSearch = Arrays.asList(documentId, nextIdAsString(), documentId2); assertThat(idsToSearch).hasSize(3); NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(idsToSearch).build(); - List> sampleEntities = operations.multiGet(query, SampleEntity.class, index); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntities).hasSize(3); @@ -309,13 +304,13 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when NativeSearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)) .withFields("message", "type").build(); - List> sampleEntities = operations.multiGet(query, SampleEntity.class, index); + List> sampleEntities = operations.multiGet(query, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntities).hasSize(2); @@ -331,13 +326,13 @@ public void shouldReturnSearchHitsForGivenSearchQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits).isNotNull(); @@ -355,14 +350,14 @@ public void shouldReturnSearchHitsUsingLocalPreferenceForGivenSearchQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQueryWithValidPreference = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPreference("_local").build(); // when - SearchHits searchHits = operations.search(searchQueryWithValidPreference, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQueryWithValidPreference, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits).isNotNull(); @@ -379,15 +374,14 @@ public void shouldThrowExceptionWhenInvalidPreferenceForSearchQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQueryWithInvalidPreference = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPreference("_only_nodes:oops").build(); // when - assertThatThrownBy(() -> operations.search(searchQueryWithInvalidPreference, SampleEntity.class, index)) - .isInstanceOf(Exception.class); + assertThatThrownBy(() -> operations.search(searchQueryWithInvalidPreference, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName()))).isInstanceOf(Exception.class); } @Test // DATAES-422 - Add support for IndicesOptions in search queries @@ -432,13 +426,13 @@ public void shouldDoBulkIndex() { .version(System.currentTimeMillis()).build(); indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(2); @@ -457,8 +451,7 @@ public void shouldDoBulkUpdate() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document .create(); @@ -471,10 +464,11 @@ public void shouldDoBulkUpdate() { queries.add(updateQuery); // when - operations.bulkUpdate(queries, index); + operations.bulkUpdate(queries, IndexCoordinates.of(indexNameProvider.indexName())); // then - SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, index); + SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); } @@ -488,15 +482,15 @@ public void shouldDeleteDocumentForGivenId() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); // when - operations.delete(documentId, index); - indexOperations.refresh(); + operations.delete(documentId, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(0); } @@ -511,15 +505,15 @@ public void shouldDeleteEntityForGivenId() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); // when - operations.delete(documentId, index); - indexOperations.refresh(); + operations.delete(documentId, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); - SearchHits sampleEntities = operations.search(searchQuery, SampleEntity.class, index); + SearchHits sampleEntities = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(sampleEntities.getTotalHits()).isEqualTo(0); } @@ -533,17 +527,16 @@ public void shouldDeleteDocumentForGivenQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); // when Query query = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); - operations.delete(query, SampleEntity.class, index); - indexOperations.refresh(); + operations.delete(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(0); } @@ -621,14 +614,14 @@ public void shouldFilterSearchResultsForGivenFilter() { .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withFilter(boolQuery().filter(termQuery("id", documentId))).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -656,14 +649,14 @@ public void shouldSortResultsGivenSortCriteria() { indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withSort(new FieldSortBuilder("rate").order(SortOrder.ASC)).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(3); @@ -692,15 +685,15 @@ public void shouldSortResultsGivenMultipleSortCriteria() { indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withSort(new FieldSortBuilder("rate").order(SortOrder.ASC)) .withSort(new FieldSortBuilder("message").order(SortOrder.ASC)).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(3); @@ -731,14 +724,14 @@ public void shouldSortResultsGivenNullFirstSortCriteria() { indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsFirst()))).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(3); @@ -769,14 +762,14 @@ public void shouldSortResultsGivenNullLastSortCriteria() { indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPageable(PageRequest.of(0, 10, Sort.by(Sort.Order.asc("message").nullsLast()))).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(3); @@ -793,8 +786,7 @@ public void shouldSortResultsByScore() { SampleEntity.builder().id("2").message("yellow green").build(), // SampleEntity.builder().id("3").message("blue").build()); - operations.bulkIndex(getIndexQueries(entities), index); - indexOperations.refresh(); + operations.bulkIndex(getIndexQueries(entities), IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // .withQuery(matchQuery("message", "green")) // @@ -802,7 +794,8 @@ public void shouldSortResultsByScore() { .build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(2); @@ -820,13 +813,13 @@ public void shouldExecuteStringQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); StringQuery stringQuery = new StringQuery(matchAllQuery().toString()); // when - SearchHits searchHits = operations.search(stringQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(stringQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -847,8 +840,7 @@ public void shouldUseScriptedFields() { indexQuery.setId(documentId); indexQuery.setObject(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); Map params = new HashMap<>(); params.put("factor", 2); @@ -857,7 +849,8 @@ public void shouldUseScriptedFields() { NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withScriptField( new ScriptField("scriptedRate", new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params))) .build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -874,13 +867,13 @@ public void shouldReturnPageableResultsGivenStringQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); StringQuery stringQuery = new StringQuery(matchAllQuery().toString(), PageRequest.of(0, 10)); // when - SearchHits searchHits = operations.search(stringQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(stringQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); @@ -900,14 +893,14 @@ public void shouldReturnSortedResultsGivenStringQuery() { indexQuery.setId(documentId); indexQuery.setObject(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); StringQuery stringQuery = new StringQuery(matchAllQuery().toString(), PageRequest.of(0, 10), - Sort.by(Order.asc("message"))); + Sort.by(Sort.Order.asc("message"))); // when - SearchHits searchHits = operations.search(stringQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(stringQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); @@ -923,13 +916,13 @@ public void shouldReturnObjectMatchingGivenStringQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); StringQuery stringQuery = new StringQuery(termQuery("id", documentId).toString()); // when - SearchHit sampleEntity1 = operations.searchOne(stringQuery, SampleEntity.class, index); + SearchHit sampleEntity1 = operations.searchOne(stringQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntity1).isNotNull(); @@ -958,12 +951,13 @@ public void shouldExecuteGivenCriteriaQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("test")); // when - SearchHit sampleEntity1 = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity1 = operations.searchOne(criteriaQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntity1).isNotNull(); @@ -979,17 +973,17 @@ public void shouldDeleteGivenCriteriaQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("test")); // when - operations.delete(criteriaQuery, SampleEntity.class, index); - indexOperations.refresh(); + operations.delete(criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then StringQuery stringQuery = new StringQuery(matchAllQuery().toString()); - SearchHits sampleEntities = operations.search(stringQuery, SampleEntity.class, index); + SearchHits sampleEntities = operations.search(stringQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(sampleEntities).isEmpty(); } @@ -1005,8 +999,7 @@ public void shouldReturnFieldsBasedOnSourceFilter() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); FetchSourceFilterBuilder sourceFilter = new FetchSourceFilterBuilder(); sourceFilter.withIncludes("message"); @@ -1015,7 +1008,8 @@ public void shouldReturnFieldsBasedOnSourceFilter() { .withSourceFilter(sourceFilter.build()).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits).isNotNull(); @@ -1039,15 +1033,14 @@ public void shouldReturnSimilarResultsGivenMoreLikeThisQuery() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); String documentId2 = nextIdAsString(); operations.index( getIndexQuery( SampleEntity.builder().id(documentId2).message(sampleMessage).version(System.currentTimeMillis()).build()), - index); - indexOperations.refresh(); + IndexCoordinates.of(indexNameProvider.indexName())); MoreLikeThisQuery moreLikeThisQuery = new MoreLikeThisQuery(); moreLikeThisQuery.setId(documentId2); @@ -1055,7 +1048,8 @@ public void shouldReturnSimilarResultsGivenMoreLikeThisQuery() { moreLikeThisQuery.setMinDocFreq(1); // when - SearchHits searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -1079,8 +1073,7 @@ void shouldUsePageableOnMoreLikeThisQueries() { ids.stream() .map(id -> getIndexQuery( SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build())) - .forEach(indexQuery -> operations.index(indexQuery, index)); - indexOperations.refresh(); + .forEach(indexQuery -> operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName()))); MoreLikeThisQuery moreLikeThisQuery = new MoreLikeThisQuery(); moreLikeThisQuery.setId(referenceId); @@ -1088,7 +1081,8 @@ void shouldUsePageableOnMoreLikeThisQueries() { moreLikeThisQuery.setMinDocFreq(1); moreLikeThisQuery.setPageable(PageRequest.of(0, 5)); - SearchHits searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(10); assertThat(searchHits.getSearchHits()).hasSize(5); @@ -1098,7 +1092,8 @@ void shouldUsePageableOnMoreLikeThisQueries() { moreLikeThisQuery.setPageable(PageRequest.of(1, 5)); - searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, index); + searchHits = operations.search(moreLikeThisQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(10); assertThat(searchHits.getSearchHits()).hasSize(5); @@ -1116,20 +1111,19 @@ public void shouldReturnResultsWithScanAndScrollForGivenCriteriaQuery() { List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); criteriaQuery.setPageable(PageRequest.of(0, 10)); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - criteriaQuery, SampleEntity.class, index); + criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); assertThat(sampleEntities).hasSize(30); @@ -1142,8 +1136,7 @@ public void shouldReturnResultsWithScanAndScrollForGivenSearchQuery() { List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then @@ -1151,12 +1144,12 @@ public void shouldReturnResultsWithScanAndScrollForGivenSearchQuery() { .withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); assertThat(sampleEntities).hasSize(30); @@ -1169,8 +1162,7 @@ public void shouldReturnResultsWithScanAndScrollForSpecifiedFieldsForCriteriaQue List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); @@ -1178,14 +1170,14 @@ public void shouldReturnResultsWithScanAndScrollForSpecifiedFieldsForCriteriaQue criteriaQuery.setPageable(PageRequest.of(0, 10)); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - criteriaQuery, SampleEntity.class, index); + criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); String scrollId = scroll.getScrollId(); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1198,22 +1190,21 @@ public void shouldReturnResultsWithScanAndScrollForSpecifiedFieldsForSearchCrite List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withFields("message") .withQuery(matchAllQuery()).withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); String scrollId = scroll.getScrollId(); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1226,22 +1217,21 @@ public void shouldReturnResultsForScanAndScrollWithCustomResultMapperForGivenCri List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); criteriaQuery.setPageable(PageRequest.of(0, 10)); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - criteriaQuery, SampleEntity.class, index); + criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); String scrollId = scroll.getScrollId(); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1254,22 +1244,21 @@ public void shouldReturnResultsForScanAndScrollWithCustomResultMapperForGivenSea List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); String scrollId = scroll.getScrollId(); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1282,22 +1271,21 @@ public void shouldReturnResultsWithScanAndScrollForGivenCriteriaQueryAndClass() List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); criteriaQuery.setPageable(PageRequest.of(0, 10)); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - criteriaQuery, SampleEntity.class, index); + criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); String scrollId = scroll.getScrollId(); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1310,22 +1298,21 @@ public void shouldReturnResultsWithScanAndScrollForGivenSearchQueryAndClass() { List entities = createSampleEntitiesWithMessage("Test message", 30); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); String scrollId = scroll.getScrollId(); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scrollId = scroll.getScrollId(); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scrollId, 1000, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1334,13 +1321,14 @@ public void shouldReturnResultsWithScanAndScrollForGivenSearchQueryAndClass() { @Test // DATAES-167, DATAES-831 public void shouldReturnAllResultsWithStreamForGivenCriteriaQuery() { - operations.bulkIndex(createSampleEntitiesWithMessage("Test message", 30), index); - indexOperations.refresh(); + operations.bulkIndex(createSampleEntitiesWithMessage("Test message", 30), + IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); criteriaQuery.setPageable(PageRequest.of(0, 10)); - long count = StreamUtils - .createStreamFromIterator(operations.searchForStream(criteriaQuery, SampleEntity.class, index)).count(); + long count = StreamUtils.createStreamFromIterator(operations.searchForStream(criteriaQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName()))).count(); assertThat(count).isEqualTo(30); } @@ -1348,14 +1336,14 @@ public void shouldReturnAllResultsWithStreamForGivenCriteriaQuery() { @Test // DATAES-831 void shouldLimitStreamResultToRequestedSize() { - operations.bulkIndex(createSampleEntitiesWithMessage("Test message", 30), index); - indexOperations.refresh(); + operations.bulkIndex(createSampleEntitiesWithMessage("Test message", 30), + IndexCoordinates.of(indexNameProvider.indexName())); CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); criteriaQuery.setMaxResults(10); - long count = StreamUtils - .createStreamFromIterator(operations.searchForStream(criteriaQuery, SampleEntity.class, index)).count(); + long count = StreamUtils.createStreamFromIterator(operations.searchForStream(criteriaQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName()))).count(); assertThat(count).isEqualTo(10); } @@ -1400,16 +1388,15 @@ public void shouldReturnListForGivenCriteria() { indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); // when - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); CriteriaQuery singleCriteriaQuery = new CriteriaQuery(new Criteria("message").contains("test")); CriteriaQuery multipleCriteriaQuery = new CriteriaQuery( new Criteria("message").contains("some").and("message").contains("message")); SearchHits sampleEntitiesForSingleCriteria = operations.search(singleCriteriaQuery, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); SearchHits sampleEntitiesForAndCriteria = operations.search(multipleCriteriaQuery, SampleEntity.class, - index); + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntitiesForSingleCriteria).hasSize(2); assertThat(sampleEntitiesForAndCriteria).hasSize(1); @@ -1437,11 +1424,11 @@ public void shouldReturnListForGivenStringQuery() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); // when - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); StringQuery stringQuery = new StringQuery(matchAllQuery().toString()); - SearchHits sampleEntities = operations.search(stringQuery, SampleEntity.class, index); + SearchHits sampleEntities = operations.search(stringQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntities).hasSize(3); @@ -1482,12 +1469,14 @@ public void shouldPutMappingWithCustomIndexName() { @DisplayName("should read mappings from alias") void shouldReadMappingsFromAlias() { - String aliasName = INDEX_NAME_SAMPLE_ENTITY + "alias"; + String indexName = indexNameProvider.indexName(); + String aliasName = "alias-" + indexName; + indexOperations.alias( // new AliasActions( // new AliasAction.Add( // AliasActionParameters.builder() // - .withIndices(INDEX_NAME_SAMPLE_ENTITY) // + .withIndices(indexName) // .withAliases(aliasName) // .build()) // ) // @@ -1528,8 +1517,7 @@ public void shouldDoPartialUpdateForExistingDocument() { IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document .create(); @@ -1539,10 +1527,11 @@ public void shouldDoPartialUpdateForExistingDocument() { .build(); // when - operations.update(updateQuery, index); + operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName())); // then - SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, index); + SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); } @@ -1558,8 +1547,7 @@ void shouldDoUpdateByQueryForExistingDocument() { final IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); final NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); @@ -1570,10 +1558,11 @@ void shouldDoUpdateByQueryForExistingDocument() { .build(); // when - operations.updateByQuery(updateQuery, index); + operations.updateByQuery(updateQuery, IndexCoordinates.of(indexNameProvider.indexName())); // then - SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, index); + SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); } @@ -1638,10 +1627,11 @@ public void shouldDoUpsertIfDocumentDoesNotExist() { .build(); // when - operations.update(updateQuery, index); + operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName())); // then - SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, index); + SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(indexedEntity.getMessage()).isEqualTo("test message"); } @@ -1658,7 +1648,6 @@ public void shouldPassIndicesOptionsForGivenSearchScrollQuery() { IndexCoordinates index = IndexCoordinates.of(INDEX_1_NAME); operations.index(idxQuery, index); - operations.indexOps(index).refresh(); // when NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) @@ -1691,8 +1680,7 @@ public void shouldReturnSameEntityForMultiSearch() { indexQueries.add(buildIndex(SampleEntity.builder().id("2").message("bc").build())); indexQueries.add(buildIndex(SampleEntity.builder().id("3").message("ac").build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when List queries = new ArrayList<>(); @@ -1702,7 +1690,8 @@ public void shouldReturnSameEntityForMultiSearch() { queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("message", "ac")).build()); // then - List> searchHits = operations.multiSearch(queries, SampleEntity.class, index); + List> searchHits = operations.multiSearch(queries, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); for (SearchHits sampleEntity : searchHits) { assertThat(sampleEntity.getTotalHits()).isEqualTo(1); } @@ -1721,9 +1710,10 @@ public void shouldReturnDifferentEntityForMultiSearch() { IndexCoordinates bookIndex = IndexCoordinates.of("test-index-book-core-template"); - operations.index(buildIndex(SampleEntity.builder().id("1").message("ab").build()), index); + operations.index(buildIndex(SampleEntity.builder().id("1").message("ab").build()), + IndexCoordinates.of(indexNameProvider.indexName())); operations.index(buildIndex(Book.builder().id("2").description("bc").build()), bookIndex); - indexOperations.refresh(); + bookIndexOperations.refresh(); // when @@ -1732,7 +1722,7 @@ public void shouldReturnDifferentEntityForMultiSearch() { queries.add(new NativeSearchQueryBuilder().withQuery(termQuery("description", "bc")).build()); List> searchHitsList = operations.multiSearch(queries, Lists.newArrayList(SampleEntity.class, clazz), - IndexCoordinates.of(index.getIndexName(), bookIndex.getIndexName())); + IndexCoordinates.of(indexNameProvider.indexName(), bookIndex.getIndexName())); // then SearchHits searchHits0 = searchHitsList.get(0); @@ -1745,30 +1735,6 @@ public void shouldReturnDifferentEntityForMultiSearch() { assertThat(searchHit1.getContent().getClass()).isEqualTo(clazz); } - @Test - public void shouldDeleteDocumentBySpecifiedTypeUsingDeleteQuery() { - - // given - String documentId = nextIdAsString(); - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message("some message") - .version(System.currentTimeMillis()).build(); - - IndexQuery indexQuery = getIndexQuery(sampleEntity); - - operations.index(indexQuery, index); - indexOperations.refresh(); - - // when - Query query = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); - operations.delete(query, SampleEntity.class, index); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)).refresh(); - - // then - NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", documentId)).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); - assertThat(searchHits.getTotalHits()).isEqualTo(0); - } - @Test public void shouldIndexDocumentForSpecifiedSource() { @@ -1779,8 +1745,9 @@ public void shouldIndexDocumentForSpecifiedSource() { indexQuery.setSource(documentSource); // when - operations.index(indexQuery, IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)); - indexOperations.refresh(); + IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); + operations.index(indexQuery, index); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("id", indexQuery.getId())) .build(); @@ -1799,7 +1766,7 @@ public void shouldThrowElasticsearchExceptionWhenNoDocumentSpecified() { indexQuery.setId("2333343434"); // when - assertThatThrownBy(() -> operations.index(indexQuery, IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY))) + assertThatThrownBy(() -> operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName()))) .isInstanceOf(InvalidDataAccessApiUsageException.class); } @@ -1808,15 +1775,16 @@ public void shouldReturnIndexName() { // given List entities = createSampleEntitiesWithMessage("Test message", 3); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + String indexName = indexNameProvider.indexName(); + operations.bulkIndex(entities, IndexCoordinates.of(indexName)); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "message")) .withPageable(PageRequest.of(0, 100)).build(); // then SearchHits searchHits = operations.search(searchQuery, SampleEntity.class); searchHits.forEach(searchHit -> { - assertThat(searchHit.getIndex()).isEqualTo(INDEX_NAME_SAMPLE_ENTITY); + assertThat(searchHit.getIndex()).isEqualTo(indexName); }); } @@ -1829,15 +1797,15 @@ public void shouldReturnDocumentAboveMinimalScoreGivenQuery() { indexQueries.add(buildIndex(SampleEntity.builder().id("2").message("bc").build())); indexQueries.add(buildIndex(SampleEntity.builder().id("3").message("ac").build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(boolQuery().must(wildcardQuery("message", "*a*")).should(wildcardQuery("message", "*b*"))) .withMinScore(2.0F).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getTotalHits()).isEqualTo(1); @@ -1854,14 +1822,14 @@ public void shouldReturnScores() { indexQueries.add(buildIndex(SampleEntity.builder().id("2").message("bc").build())); indexQueries.add(buildIndex(SampleEntity.builder().id("3").message("ac xz hi").build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "xz")) .withSort(SortBuilders.fieldSort("message")).withTrackScores(true).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits.getMaxScore()).isGreaterThan(0f); @@ -1881,12 +1849,13 @@ public void shouldDoIndexWithoutId() { indexQuery.setObject(sampleEntity); // when - String documentId = operations.index(indexQuery, index); + String documentId = operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(sampleEntity.getId()).isEqualTo(documentId); - SampleEntity result = operations.get(documentId, SampleEntity.class, index); + SampleEntity result = operations.get(documentId, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(result.getId()).isEqualTo(documentId); } @@ -1914,12 +1883,12 @@ public void shouldDoBulkIndexWithoutId() { indexQueries.add(indexQuery2); // when - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(2); @@ -1958,12 +1927,12 @@ public void shouldIndexMapWithIndexNameAndTypeAtRuntime() { indexQueries.add(indexQuery2); // when - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // then NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits searchHits = operations.search(searchQuery, Map.class, index); + SearchHits searchHits = operations.search(searchQuery, Map.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(2); assertThat(searchHits.getSearchHit(0).getContent().get("userId")).isEqualTo(person1.get("userId")); @@ -1984,8 +1953,8 @@ public void shouldIndexGteEntityWithVersionType() { IndexQueryBuilder indexQueryBuilder = new IndexQueryBuilder().withId(documentId).withVersion(entity.getVersion()) .withObject(entity); + IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); operations.index(indexQueryBuilder.build(), index); - indexOperations.refresh(); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when @@ -1996,7 +1965,6 @@ public void shouldIndexGteEntityWithVersionType() { // reindex with same version operations.index(indexQueryBuilder.build(), index); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)).refresh(); // reindex with version one below assertThatThrownBy(() -> operations.index(indexQueryBuilder.withVersion(entity.getVersion() - 1).build(), index)) @@ -2004,7 +1972,9 @@ public void shouldIndexGteEntityWithVersionType() { } @Test - public void shouldIndexSampleEntityWithIndexAndTypeAtRuntime() { + public void shouldIndexSampleEntityWithIndexAtRuntime() { + + String indexName = "custom-" + indexNameProvider.indexName(); // given String documentId = nextIdAsString(); @@ -2013,13 +1983,13 @@ public void shouldIndexSampleEntityWithIndexAndTypeAtRuntime() { IndexQuery indexQuery = new IndexQueryBuilder().withId(documentId).withObject(sampleEntity).build(); - operations.index(indexQuery, IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)).refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexName)); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexName)); // then assertThat(searchHits).isNotNull(); @@ -2035,12 +2005,13 @@ public void shouldReturnCountForGivenCriteriaQueryWithGivenIndexUsingCriteriaQue .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); // when - long count = operations.count(criteriaQuery, SampleEntity.class, index); + long count = operations.count(criteriaQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(count).isEqualTo(1); @@ -2055,12 +2026,12 @@ public void shouldReturnCountForGivenSearchQueryWithGivenIndexUsingSearchQuery() .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when - long count = operations.count(searchQuery, SampleEntity.class, index); + long count = operations.count(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(count).isEqualTo(1); @@ -2075,12 +2046,12 @@ public void shouldReturnCountForGivenCriteriaQueryWithGivenIndexAndTypeUsingCrit .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); // when - long count = operations.count(criteriaQuery, index); + long count = operations.count(criteriaQuery, IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(count).isEqualTo(1); @@ -2095,12 +2066,12 @@ public void shouldReturnCountForGivenSearchQueryWithGivenIndexAndTypeUsingSearch .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when - long count = operations.count(searchQuery, index); + long count = operations.count(searchQuery, IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(count).isEqualTo(1); @@ -2278,8 +2249,8 @@ public void shouldThrowAnExceptionForGivenCriteriaQueryWhenNoIndexSpecifiedForCo .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()); // when @@ -2296,8 +2267,8 @@ public void shouldThrowAnExceptionForGivenSearchQueryWhenNoIndexSpecifiedForCoun .version(System.currentTimeMillis()).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); + NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); // when @@ -2363,11 +2334,10 @@ public void shouldCreateIndexWithGivenClassAndSettings() { indexOperations.delete(); indexOperations.create(parse(settings)); indexOperations.putMapping(SampleEntity.class); - indexOperations.refresh(); // then Map map = indexOperations.getSettings(); - assertThat(operations.indexOps(IndexCoordinates.of(INDEX_NAME_SAMPLE_ENTITY)).exists()).isTrue(); + assertThat(operations.indexOps(IndexCoordinates.of(indexNameProvider.indexName())).exists()).isTrue(); assertThat(map.containsKey("index.number_of_replicas")).isTrue(); assertThat(map.containsKey("index.number_of_shards")).isTrue(); assertThat((String) map.get("index.number_of_replicas")).isEqualTo("0"); @@ -2376,8 +2346,11 @@ public void shouldCreateIndexWithGivenClassAndSettings() { @Test public void shouldTestResultsAcrossMultipleIndices() { + IndexCoordinates index1 = IndexCoordinates.of(INDEX_1_NAME); + IndexCoordinates index2 = IndexCoordinates.of(INDEX_2_NAME); + operations.indexOps(index1).delete(); + operations.indexOps(index2).delete(); - // given String documentId1 = nextIdAsString(); SampleEntity sampleEntity1 = SampleEntity.builder().id(documentId1).message("some message") .version(System.currentTimeMillis()).build(); @@ -2390,10 +2363,8 @@ public void shouldTestResultsAcrossMultipleIndices() { IndexQuery indexQuery2 = new IndexQueryBuilder().withId(sampleEntity2.getId()).withObject(sampleEntity2).build(); - operations.index(indexQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.index(indexQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(indexQuery1, index1); + operations.index(indexQuery2, index2); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); @@ -2411,17 +2382,19 @@ public void shouldTestResultsAcrossMultipleIndices() { */ public void shouldComposeObjectsReturnedFromHeterogeneousIndexes() { - // given + IndexCoordinates index1 = IndexCoordinates.of(INDEX_1_NAME); + IndexCoordinates index2 = IndexCoordinates.of(INDEX_2_NAME); + operations.indexOps(index1).delete(); + operations.indexOps(index2).delete(); + HetroEntity1 entity1 = new HetroEntity1(nextIdAsString(), "aFirstName"); HetroEntity2 entity2 = new HetroEntity2(nextIdAsString(), "aLastName"); IndexQuery indexQuery1 = new IndexQueryBuilder().withId(entity1.getId()).withObject(entity1).build(); IndexQuery indexQuery2 = new IndexQueryBuilder().withId(entity2.getId()).withObject(entity2).build(); - operations.index(indexQuery1, IndexCoordinates.of(INDEX_1_NAME)); - operations.index(indexQuery2, IndexCoordinates.of(INDEX_2_NAME)); - operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).refresh(); - operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).refresh(); + operations.index(indexQuery1, index1); + operations.index(indexQuery2, index2); // when NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); @@ -2474,18 +2447,17 @@ public void shouldDeleteOnlyDocumentsMatchedByDeleteQuery() { String remainingDocumentId = UUID.randomUUID().toString(); indexQueries.add(getIndexQuery(SampleEntity.builder().id(remainingDocumentId).message("some other message") .version(System.currentTimeMillis()).build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when Query query = new NativeSearchQueryBuilder().withQuery(idsQuery().addIds(documentIdToDelete)).build(); - operations.delete(query, SampleEntity.class, index); - indexOperations.refresh(); + operations.delete(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then // document with id "remainingDocumentId" should still be indexed NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(1); assertThat(searchHits.getSearchHit(0).getContent().getId()).isEqualTo(remainingDocumentId); } @@ -2505,18 +2477,17 @@ public void shouldDeleteOnlyDocumentsMatchedByCriteriaQuery() { String remainingDocumentId = UUID.randomUUID().toString(); indexQueries.add(getIndexQuery(SampleEntity.builder().id(remainingDocumentId).message("some other message") .version(System.currentTimeMillis()).build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("id").is(documentIdToDelete)); - operations.delete(criteriaQuery, SampleEntity.class, index); - indexOperations.refresh(); + operations.delete(criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); // then // document with id "remainingDocumentId" should still be indexed NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(1); assertThat(searchHits.getSearchHit(0).getContent().getId()).isEqualTo(remainingDocumentId); } @@ -2535,17 +2506,16 @@ public void shouldDeleteDocumentForGivenIdOnly() { String remainingDocumentId = UUID.randomUUID().toString(); indexQueries.add(getIndexQuery(SampleEntity.builder().id(remainingDocumentId).message("some other message") .version(System.currentTimeMillis()).build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when - operations.delete(documentIdToDelete, index); - indexOperations.refresh(); + operations.delete(documentIdToDelete, IndexCoordinates.of(indexNameProvider.indexName())); // then // document with id "remainingDocumentId" should still be indexed NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); assertThat(searchHits.getTotalHits()).isEqualTo(1L); assertThat(searchHits.getSearchHit(0).getContent().getId()).isEqualTo(remainingDocumentId); } @@ -2564,20 +2534,19 @@ public void shouldApplyCriteriaQueryToScanAndScrollForGivenCriteriaQuery() { indexQueries.add(getIndexQuery(SampleEntity.builder().id(UUID.randomUUID().toString()).message(notFindableMessage) .version(System.currentTimeMillis()).build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("message")); criteriaQuery.setPageable(PageRequest.of(0, 10)); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - criteriaQuery, SampleEntity.class, index); + criteriaQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); @@ -2602,20 +2571,19 @@ public void shouldApplySearchQueryToScanAndScrollForGivenSearchQuery() { indexQueries.add(getIndexQuery(SampleEntity.builder().id(UUID.randomUUID().toString()).message(notFindableMessage) .version(System.currentTimeMillis()).build())); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); // when NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("message", "message")) .withPageable(PageRequest.of(0, 10)).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); @@ -2633,8 +2601,7 @@ public void shouldRespectSourceFilterWithScanAndScrollForGivenSearchQuery() { List entities = createSampleEntitiesWithMessage("Test message", 3); // when - operations.bulkIndex(entities, index); - indexOperations.refresh(); + operations.bulkIndex(entities, IndexCoordinates.of(indexNameProvider.indexName())); // then SourceFilter sourceFilter = new FetchSourceFilterBuilder().withIncludes("id").build(); @@ -2643,12 +2610,12 @@ public void shouldRespectSourceFilterWithScanAndScrollForGivenSearchQuery() { .withPageable(PageRequest.of(0, 10)).withSourceFilter(sourceFilter).build(); SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } ((AbstractElasticsearchTemplate) operations).searchScrollClear(scroll.getScrollId()); assertThat(sampleEntities).hasSize(3); @@ -2680,8 +2647,7 @@ public void shouldSortResultsGivenSortCriteriaWithScanAndScroll() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withSort(new FieldSortBuilder("rate").order(SortOrder.ASC)) @@ -2689,12 +2655,12 @@ public void shouldSortResultsGivenSortCriteriaWithScanAndScroll() { // when SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } // then @@ -2727,8 +2693,7 @@ public void shouldSortResultsGivenSortCriteriaFromPageableWithScanAndScroll() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity1, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withPageable( @@ -2737,12 +2702,12 @@ public void shouldSortResultsGivenSortCriteriaFromPageableWithScanAndScroll() { // when SearchScrollHits scroll = ((AbstractElasticsearchTemplate) operations).searchScrollStart(1000, - searchQuery, SampleEntity.class, index); + searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); List> sampleEntities = new ArrayList<>(); while (scroll.hasSearchHits()) { sampleEntities.addAll(scroll.getSearchHits()); scroll = ((AbstractElasticsearchTemplate) operations).searchScrollContinue(scroll.getScrollId(), 1000, - SampleEntity.class, index); + SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); } // then @@ -2767,14 +2732,14 @@ public void shouldReturnDocumentWithCollapsedField() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withCollapseField("rate") .build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits).isNotNull(); @@ -2798,14 +2763,14 @@ public void shouldReturnDocumentWithCollapsedFieldAndInnerHits() { List indexQueries = getIndexQueries(Arrays.asList(sampleEntity, sampleEntity2, sampleEntity3)); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, IndexCoordinates.of(indexNameProvider.indexName())); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withCollapseBuilder(new CollapseBuilder("rate").setInnerHits(new InnerHitBuilder("innerHits"))).build(); // when - SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(searchQuery, SampleEntity.class, + IndexCoordinates.of(indexNameProvider.indexName())); // then assertThat(searchHits).isNotNull(); @@ -2890,21 +2855,24 @@ public void shouldIncludeDefaultsOnGetIndexSettings() { @Test // DATAES-714 void shouldReturnSortFieldsInSearchHits() { - IndexCoordinates index = IndexCoordinates.of("test-index-searchhits-entity-template"); + IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); + IndexOperations indexOperations = operations.indexOps(SearchHitsEntity.class); + indexOperations.delete(); + indexOperations.createWithMapping(); + SearchHitsEntity entity = new SearchHitsEntity(); entity.setId("1"); entity.setNumber(1000L); entity.setKeyword("thousands"); IndexQuery indexQuery = new IndexQueryBuilder().withId(entity.getId()).withObject(entity).build(); operations.index(indexQuery, index); - operations.indexOps(index).refresh(); NativeSearchQuery query = new NativeSearchQueryBuilder() // .withQuery(matchAllQuery()) // .withSort(new FieldSortBuilder("keyword").order(SortOrder.ASC)) .withSort(new FieldSortBuilder("number").order(SortOrder.DESC)).build(); - SearchHits searchHits = operations.search(query, SearchHitsEntity.class, index); + SearchHits searchHits = operations.search(query, SearchHitsEntity.class); assertThat(searchHits).isNotNull(); assertThat(searchHits.getSearchHits()).hasSize(1); @@ -2974,7 +2942,6 @@ void shouldRunRescoreQueryInSearchQuery() { List indexQueries = getIndexQueries(Arrays.asList(entity, entity2)); operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); NativeSearchQuery query = new NativeSearchQueryBuilder() // .withQuery(boolQuery().filter(existsQuery("rate")).should(termQuery("message", "message"))) // @@ -3008,10 +2975,9 @@ void shouldSaveEntityWithIndexCoordinates() { entity.setVersion(42L); entity.setMessage("message"); - operations.save(entity, index); - indexOperations.refresh(); + operations.save(entity, IndexCoordinates.of(indexNameProvider.indexName())); - SampleEntity result = operations.get(id, SampleEntity.class, index); + SampleEntity result = operations.get(id, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(result).isEqualTo(entity); } @@ -3025,9 +2991,8 @@ void shouldSaveEntityWithOutIndexCoordinates() { entity.setMessage("message"); operations.save(entity); - indexOperations.refresh(); - SampleEntity result = operations.get(id, SampleEntity.class, index); + SampleEntity result = operations.get(id, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(result).isEqualTo(entity); } @@ -3045,11 +3010,10 @@ void shouldSaveEntityIterableWithIndexCoordinates() { entity2.setVersion(43L); entity2.setMessage("message"); - operations.save(Arrays.asList(entity1, entity2), index); - indexOperations.refresh(); + operations.save(Arrays.asList(entity1, entity2), IndexCoordinates.of(indexNameProvider.indexName())); - SampleEntity result1 = operations.get(id1, SampleEntity.class, index); - SampleEntity result2 = operations.get(id2, SampleEntity.class, index); + SampleEntity result1 = operations.get(id1, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); + SampleEntity result2 = operations.get(id2, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(result1).isEqualTo(entity1); assertThat(result2).isEqualTo(entity2); @@ -3069,10 +3033,9 @@ void shouldSaveEntityIterableWithoutIndexCoordinates() { entity2.setMessage("message"); operations.save(Arrays.asList(entity1, entity2)); - indexOperations.refresh(); - SampleEntity result1 = operations.get(id1, SampleEntity.class, index); - SampleEntity result2 = operations.get(id2, SampleEntity.class, index); + SampleEntity result1 = operations.get(id1, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); + SampleEntity result2 = operations.get(id2, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); assertThat(result1).isEqualTo(entity1); assertThat(result2).isEqualTo(entity2); @@ -3087,7 +3050,6 @@ void shouldDoExistsWithEntity() { entity.setMessage("message"); operations.save(entity); - indexOperations.refresh(); assertThat(operations.exists("42", SampleEntity.class)).isTrue(); } @@ -3101,9 +3063,8 @@ void shouldDoExistsWithIndexCoordinates() { entity.setMessage("message"); operations.save(entity); - indexOperations.refresh(); - assertThat(operations.exists("42", index)).isTrue(); + assertThat(operations.exists("42", IndexCoordinates.of(indexNameProvider.indexName()))).isTrue(); } @Test // DATAES-876 @@ -3259,6 +3220,11 @@ void shouldSupportCRUDOpsForEntityWithJoinFields() throws Exception { String aId1 = java.util.UUID.randomUUID().toString(); String aId2 = java.util.UUID.randomUUID().toString(); + // default maps SampleEntity, need a new mapping here + IndexOperations indexOperations = operations.indexOps(SampleJoinEntity.class); + indexOperations.delete(); + indexOperations.createWithMapping(); + shouldSaveEntityWithJoinFields(qId1, qId2, aId1, aId2); shouldUpdateEntityWithJoinFields(qId1, qId2, aId1, aId2); shouldDeleteEntityWithJoinFields(qId2, aId2); @@ -3296,10 +3262,9 @@ private void shouldSaveEntityWithJoinFields(String qId1, String qId2, String aId myAJoinField2.setParent(qId1); sampleAnswerEntity2.setMyJoinField(myAJoinField2); + IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); operations.save( - Arrays.asList(sampleQuestionEntity1, sampleQuestionEntity2, sampleAnswerEntity1, sampleAnswerEntity2), - IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).refresh(); + Arrays.asList(sampleQuestionEntity1, sampleQuestionEntity2, sampleAnswerEntity1, sampleAnswerEntity2), index); SearchHits hits = operations.search( new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId1)).build(), @@ -3328,8 +3293,7 @@ private void shouldUpdateEntityWithJoinFields(String qId1, String qId2, String a queries.add(updateQuery); // when - operations.bulkUpdate(queries, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).refresh(); + operations.bulkUpdate(queries, IndexCoordinates.of(indexNameProvider.indexName())); SearchHits updatedHits = operations.search( new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(), @@ -3361,8 +3325,7 @@ public String apply(SearchHit sampleJoinEntitySearchHit) { private void shouldDeleteEntityWithJoinFields(String qId2, String aId2) throws Exception { Query query = new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).withRoute(qId2) .build(); - operations.delete(query, SampleJoinEntity.class, IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)); - operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).refresh(); + operations.delete(query, SampleJoinEntity.class, IndexCoordinates.of(indexNameProvider.indexName())); SearchHits deletedHits = operations.search( new NativeSearchQueryBuilder().withQuery(new ParentIdQueryBuilder("answer", qId2)).build(), @@ -3461,7 +3424,6 @@ void shouldTrackTotalHitsWithDefaultValue() { .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); operations.save(entities); - indexOperations.refresh(); queryAll.setTrackTotalHits(null); SearchHits searchHits = operations.search(queryAll, SampleEntity.class); @@ -3483,7 +3445,6 @@ void shouldTrackTotalHits() { .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); operations.save(entities); - indexOperations.refresh(); queryAll.setTrackTotalHits(true); queryAll.setTrackTotalHitsUpTo(12_345); @@ -3506,7 +3467,6 @@ void shouldTrackTotalHitsToSpecificValue() { .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); operations.save(entities); - indexOperations.refresh(); queryAll.setTrackTotalHits(null); queryAll.setTrackTotalHitsUpTo(12_345); @@ -3529,7 +3489,6 @@ void shouldTrackTotalHitsIsOff() { .mapToObj(i -> SampleEntity.builder().id("" + i).build()).collect(Collectors.toList()); operations.save(entities); - indexOperations.refresh(); queryAll.setTrackTotalHits(false); queryAll.setTrackTotalHitsUpTo(12_345); @@ -3618,7 +3577,7 @@ void shouldSetScriptedFieldsOnImmutableObjects() { } // region entities - @Document(indexName = INDEX_NAME_SAMPLE_ENTITY) + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 1, replicas = 0, refreshInterval = "-1") static class SampleEntity { @Nullable @Id private String id; @@ -3797,7 +3756,7 @@ public int hashCode() { } } - @Document(indexName = "test-index-uuid-keyed-core-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") private static class SampleEntityUUIDKeyed { @Nullable @Id private UUID id; @Nullable private String type; @@ -4008,7 +3967,7 @@ public void setName(@Nullable String name) { } } - @Document(indexName = "test-index-version-core-template", versionType = VersionType.EXTERNAL_GTE) + @Document(indexName = "#{@indexNameProvider.indexName()}", versionType = VersionType.EXTERNAL_GTE) private static class GTEVersionEntity { @Nullable @Version private Long version; @Nullable @Id private String id; @@ -4188,7 +4147,7 @@ public void setSomeField(String someField) { } } - @Document(indexName = "test-index-searchhits-entity-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SearchHitsEntity { @Nullable @Id private String id; @Nullable @Field(type = FieldType.Long) Long number; @@ -4361,7 +4320,7 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = INDEX_NAME_JOIN_SAMPLE_ENTITY) + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleJoinEntity { @Nullable @Id @Field(type = Keyword) private String uuid; @Nullable @JoinTypeRelations(relations = { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java index d95faed41..e5722fb25 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTransportTemplateTests.java @@ -41,6 +41,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -52,6 +55,7 @@ import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; @@ -60,10 +64,19 @@ * @author Sascha Woo * @author Farid Faoudi */ -@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +@ContextConfiguration(classes = { ElasticsearchTransportTemplateTests.Config.class }) @DisplayName("ElasticsearchTransportTemplate") public class ElasticsearchTransportTemplateTests extends ElasticsearchTemplateTests { + @Configuration + @Import({ ElasticsearchTemplateConfiguration.class }) + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("transport-template"); + } + } + @Autowired private Client client; @Test @@ -72,7 +85,8 @@ public void shouldThrowExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() org.springframework.data.elasticsearch.core.document.Document document = org.springframework.data.elasticsearch.core.document.Document .create(); UpdateQuery updateQuery = UpdateQuery.builder(nextIdAsString()).withDocument(document).build(); - assertThatThrownBy(() -> operations.update(updateQuery, index)).isInstanceOf(DocumentMissingException.class); + assertThatThrownBy(() -> operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName()))) + .isInstanceOf(DocumentMissingException.class); } @Override diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java index 0fb532bf1..bfc32188c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateIntegrationTests.java @@ -47,12 +47,13 @@ import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.json.JSONException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.dao.DataAccessResourceFailureException; @@ -78,6 +79,7 @@ import org.springframework.data.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; @@ -99,40 +101,29 @@ public class ReactiveElasticsearchTemplateIntegrationTests { @Configuration @Import({ ReactiveElasticsearchRestTemplateConfiguration.class }) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("reactive-template"); + } + } - static final String DEFAULT_INDEX = "reactive-template-test-index"; - static final String ALTERNATE_INDEX = "reactive-template-tests-alternate-index"; + @Autowired private ReactiveElasticsearchOperations operations; - @Autowired private ReactiveElasticsearchTemplate template; - private ReactiveIndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; // region Setup @BeforeEach - public void setUp() { - indexOperations = template.indexOps(SampleEntity.class); - - deleteIndices(); - - indexOperations.create() // - .then(indexOperations.putMapping(SampleEntity.class)) // - .then(indexOperations.refresh()) // - .block(); // - } + public void beforeEach() { - @AfterEach - public void after() { - deleteIndices(); + indexNameProvider.increment(); + operations.indexOps(SampleEntity.class).createWithMapping().block(); } - private void deleteIndices() { - template.indexOps(IndexCoordinates.of(DEFAULT_INDEX)).delete().block(); - template.indexOps(IndexCoordinates.of(ALTERNATE_INDEX)).delete().block(); - template.indexOps(IndexCoordinates.of("rx-template-test-index-this")).delete().block(); - template.indexOps(IndexCoordinates.of("rx-template-test-index-that")).delete().block(); - template.indexOps(IndexCoordinates.of("test-index-reactive-optimistic-entity-template")).delete().block(); - template.indexOps(IndexCoordinates.of("test-index-reactive-optimistic-and-versioned-entity-template")).delete() - .block(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete().block(); } // endregion @@ -140,7 +131,7 @@ private void deleteIndices() { @Test // DATAES-504 public void executeShouldProvideResource() { - Mono.from(template.execute(ReactiveElasticsearchClient::ping)) // + Mono.from(operations.execute(ReactiveElasticsearchClient::ping)) // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -149,7 +140,7 @@ public void executeShouldProvideResource() { @Test // DATAES-504 public void executeShouldConvertExceptions() { - Mono.from(template.execute(client -> { + Mono.from(operations.execute(client -> { throw new RuntimeException(new ConnectException("we're doomed")); })) // .as(StepVerifier::create) // @@ -162,13 +153,12 @@ public void insertWithIdShouldWork() { SampleEntity sampleEntity = randomEntity("foo bar"); - template.save(sampleEntity) // - .then(indexOperations.refresh()) // + operations.save(sampleEntity) // .block(); - template + operations .search(new CriteriaQuery(Criteria.where("message").is(sampleEntity.getMessage())), SampleEntity.class, - IndexCoordinates.of(DEFAULT_INDEX)) // + IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .expectNextCount(1) // .verifyComplete(); @@ -180,34 +170,33 @@ public void insertWithAutogeneratedIdShouldUpdateEntityId() { SampleEntity sampleEntity = new SampleEntity(); sampleEntity.setMessage("wohoo"); - template.save(sampleEntity) // + operations.save(sampleEntity) // .map(SampleEntity::getId) // - .flatMap(id -> indexOperations.refresh().thenReturn(id)) // - .flatMap(id -> documentWithIdExistsInIndex(id, DEFAULT_INDEX)).as(StepVerifier::create) // + .flatMap(id -> documentWithIdExistsInIndex(id, indexNameProvider.indexName())).as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); } private Mono documentWithIdExistsInIndex(String id, String index) { - return template.exists(id, IndexCoordinates.of(index)); + return operations.exists(id, IndexCoordinates.of(index)); } @Test // DATAES-504 public void insertWithExplicitIndexNameShouldOverwriteMetadata() { + String defaultIndexName = indexNameProvider.indexName(); + String alternateIndexName = defaultIndexName + "-alt"; + SampleEntity sampleEntity = randomEntity("in another index"); - IndexCoordinates alternateIndex = IndexCoordinates.of(ALTERNATE_INDEX); + IndexCoordinates alternateIndex = IndexCoordinates.of(alternateIndexName); - template.save(sampleEntity, alternateIndex) // + operations.save(sampleEntity, alternateIndex) // .as(StepVerifier::create)// .expectNextCount(1)// .verifyComplete(); - template.indexOps(IndexCoordinates.of(DEFAULT_INDEX)).refresh().block(); - template.indexOps(alternateIndex).refresh().block(); - - assertThat(documentWithIdExistsInIndex(sampleEntity.getId(), DEFAULT_INDEX).block()).isFalse(); - assertThat(documentWithIdExistsInIndex(sampleEntity.getId(), ALTERNATE_INDEX).block()).isTrue(); + assertThat(documentWithIdExistsInIndex(sampleEntity.getId(), defaultIndexName).block()).isFalse(); + assertThat(documentWithIdExistsInIndex(sampleEntity.getId(), alternateIndexName).block()).isTrue(); } @Test // DATAES-504 @@ -215,7 +204,7 @@ public void insertShouldAcceptPlainMapStructureAsSource() { Map map = new LinkedHashMap<>(Collections.singletonMap("foo", "bar")); - template.save(map, IndexCoordinates.of(ALTERNATE_INDEX)) // + operations.save(map, IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .consumeNextWith(actual -> { assertThat(map).containsKey("id"); @@ -225,14 +214,14 @@ public void insertShouldAcceptPlainMapStructureAsSource() { @Test // DATAES-504 public void insertShouldErrorOnNullEntity() { assertThatThrownBy(() -> { - template.save(null); + operations.save(null); }).isInstanceOf(IllegalArgumentException.class); } @Test // DATAES-519, DATAES-767, DATAES-822 public void getByIdShouldErrorWhenIndexDoesNotExist() { - template.get("foo", SampleEntity.class, IndexCoordinates.of("no-such-index")) // + operations.get("foo", SampleEntity.class, IndexCoordinates.of("no-such-index")) // .as(StepVerifier::create) // .expectError(ElasticsearchStatusException.class); } @@ -243,7 +232,7 @@ public void getByIdShouldReturnEntity() { SampleEntity sampleEntity = randomEntity("some message"); index(sampleEntity); - template.get(sampleEntity.getId(), SampleEntity.class) // + operations.get(sampleEntity.getId(), SampleEntity.class) // .as(StepVerifier::create) // .expectNext(sampleEntity) // .verifyComplete(); @@ -259,7 +248,7 @@ public void getByIdWhenIdIsAutogeneratedShouldHaveIdSetCorrectly() { assertThat(sampleEntity.getId()).isNotNull(); - template.get(sampleEntity.getId(), SampleEntity.class) // + operations.get(sampleEntity.getId(), SampleEntity.class) // .as(StepVerifier::create) // .consumeNextWith(it -> assertThat(it.getId()).isEqualTo(sampleEntity.getId())) // .verifyComplete(); @@ -271,7 +260,7 @@ public void getByIdShouldCompleteWhenNotingFound() { SampleEntity sampleEntity = randomEntity("some message"); index(sampleEntity); - template.get("foo", SampleEntity.class) // + operations.get("foo", SampleEntity.class) // .as(StepVerifier::create) // .verifyComplete(); } @@ -279,7 +268,7 @@ public void getByIdShouldCompleteWhenNotingFound() { @Test // DATAES-504 public void getByIdShouldErrorForNullId() { assertThatThrownBy(() -> { - template.get(null, SampleEntity.class); + operations.get(null, SampleEntity.class); }).isInstanceOf(IllegalArgumentException.class); } @@ -288,20 +277,19 @@ public void getByIdWithExplicitIndexNameShouldOverwriteMetadata() { SampleEntity sampleEntity = randomEntity("some message"); - IndexCoordinates defaultIndex = IndexCoordinates.of(DEFAULT_INDEX); - IndexCoordinates alternateIndex = IndexCoordinates.of(ALTERNATE_INDEX); + IndexCoordinates defaultIndex = IndexCoordinates.of(indexNameProvider.indexName()); + IndexCoordinates alternateIndex = IndexCoordinates.of(indexNameProvider.indexName() + "-alt"); - template.save(sampleEntity, alternateIndex) // - .then(indexOperations.refresh()) // - .then(template.indexOps(defaultIndex).refresh()) // - .then(template.indexOps(alternateIndex).refresh()) // + operations.save(sampleEntity, alternateIndex) // + .then(operations.indexOps(defaultIndex).refresh()) // + .then(operations.indexOps(alternateIndex).refresh()) // .block(); - template.get(sampleEntity.getId(), SampleEntity.class, defaultIndex) // + operations.get(sampleEntity.getId(), SampleEntity.class, defaultIndex) // .as(StepVerifier::create) // .verifyComplete(); - template.get(sampleEntity.getId(), SampleEntity.class, alternateIndex) // + operations.get(sampleEntity.getId(), SampleEntity.class, alternateIndex) // .as(StepVerifier::create)// .expectNextCount(1) // .verifyComplete(); @@ -310,7 +298,7 @@ public void getByIdWithExplicitIndexNameShouldOverwriteMetadata() { @Test // DATAES-519 public void existsShouldReturnFalseWhenIndexDoesNotExist() { - template.exists("foo", IndexCoordinates.of("no-such-index")) // + operations.exists("foo", IndexCoordinates.of("no-such-index")) // .as(StepVerifier::create) // .expectNext(false) // .verifyComplete(); @@ -322,7 +310,7 @@ public void existsShouldReturnTrueWhenFound() { SampleEntity sampleEntity = randomEntity("some message"); index(sampleEntity); - template.exists(sampleEntity.getId(), SampleEntity.class) // + operations.exists(sampleEntity.getId(), SampleEntity.class) // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); @@ -334,7 +322,7 @@ public void existsShouldReturnFalseWhenNotFound() { SampleEntity sampleEntity = randomEntity("some message"); index(sampleEntity); - template.exists("foo", SampleEntity.class) // + operations.exists("foo", SampleEntity.class) // .as(StepVerifier::create) // .expectNext(false) // .verifyComplete(); @@ -343,7 +331,7 @@ public void existsShouldReturnFalseWhenNotFound() { @Test // DATAES-519, DATAES-767 public void searchShouldCompleteWhenIndexDoesNotExist() { - template + operations .search(new CriteriaQuery(Criteria.where("message").is("some message")), SampleEntity.class, IndexCoordinates.of("no-such-index")) // .as(StepVerifier::create) // @@ -358,7 +346,7 @@ public void searchShouldApplyCriteria() { CriteriaQuery criteriaQuery = new CriteriaQuery(Criteria.where("message").is("some message")); - template.search(criteriaQuery, SampleEntity.class) // + operations.search(criteriaQuery, SampleEntity.class) // .map(SearchHit::getContent) // .as(StepVerifier::create) // .expectNext(sampleEntity) // @@ -373,7 +361,7 @@ public void searchShouldReturnEmptyFluxIfNothingFound() { CriteriaQuery criteriaQuery = new CriteriaQuery(Criteria.where("message").is("foo")); - template.search(criteriaQuery, SampleEntity.class) // + operations.search(criteriaQuery, SampleEntity.class) // .as(StepVerifier::create) // .verifyComplete(); } @@ -383,7 +371,7 @@ public void shouldAllowStringBasedQuery() { index(randomEntity("test message"), randomEntity("test test"), randomEntity("some message")); - template.search(new StringQuery(matchAllQuery().toString()), SampleEntity.class) // + operations.search(new StringQuery(matchAllQuery().toString()), SampleEntity.class) // .as(StepVerifier::create) // .expectNextCount(3) // .verifyComplete(); @@ -398,7 +386,7 @@ public void shouldExecuteGivenCriteriaQuery() { CriteriaQuery query = new CriteriaQuery(new Criteria("message").contains("test")); - template.search(query, SampleEntity.class) // + operations.search(query, SampleEntity.class) // .map(SearchHit::getContent) // .as(StepVerifier::create) // .assertNext(next -> { @@ -419,7 +407,7 @@ public void shouldReturnListForGivenCriteria() { CriteriaQuery query = new CriteriaQuery( new Criteria("message").contains("some").and("message").contains("message")); - template.search(query, SampleEntity.class) // + operations.search(query, SampleEntity.class) // .map(SearchHit::getContent) // .as(StepVerifier::create) // .expectNext(sampleEntity3) // @@ -439,7 +427,7 @@ public void shouldReturnListUsingLocalPreferenceForGivenCriteria() { new Criteria("message").contains("some").and("message").contains("message")); queryWithValidPreference.setPreference("_local"); - template.search(queryWithValidPreference, SampleEntity.class) // + operations.search(queryWithValidPreference, SampleEntity.class) // .map(SearchHit::getContent) // .as(StepVerifier::create) // .expectNext(sampleEntity3) // @@ -459,7 +447,7 @@ public void shouldThrowElasticsearchStatusExceptionWhenInvalidPreferenceForGiven new Criteria("message").contains("some").and("message").contains("message")); queryWithInvalidPreference.setPreference("_only_nodes:oops"); - template.search(queryWithInvalidPreference, SampleEntity.class) // + operations.search(queryWithInvalidPreference, SampleEntity.class) // .as(StepVerifier::create) // .expectError(UncategorizedElasticsearchException.class).verify(); } @@ -476,7 +464,7 @@ public void shouldReturnProjectedTargetEntity() { CriteriaQuery query = new CriteriaQuery( new Criteria("message").contains("some").and("message").contains("message")); - template.search(query, SampleEntity.class, Message.class) // + operations.search(query, SampleEntity.class, Message.class) // .map(SearchHit::getContent) // .as(StepVerifier::create) // .expectNext(new Message(sampleEntity3.getMessage())) // @@ -492,7 +480,7 @@ public void searchShouldApplyPagingCorrectly() { .addSort(Sort.by("message"))// .setPageable(PageRequest.of(0, 20)); - template.search(query, SampleEntity.class).as(StepVerifier::create) // + operations.search(query, SampleEntity.class).as(StepVerifier::create) // .expectNextCount(20) // .verifyComplete(); } @@ -506,7 +494,7 @@ public void findWithoutPagingShouldReadAll() { .addSort(Sort.by("message"))// .setPageable(Pageable.unpaged()); - template.search(query, SampleEntity.class).as(StepVerifier::create) // + operations.search(query, SampleEntity.class).as(StepVerifier::create) // .expectNextCount(100) // .verifyComplete(); } @@ -523,7 +511,7 @@ public void aggregateShouldReturnAggregations() { NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .addAggregation(AggregationBuilders.terms("messages").field("message")).build(); - template.aggregate(query, SampleEntity.class) // + operations.aggregate(query, SampleEntity.class) // .as(StepVerifier::create) // .consumeNextWith(aggregation -> { assertThat(aggregation.getName()).isEqualTo("messages"); @@ -538,7 +526,7 @@ public void aggregateShouldReturnAggregations() { @Test // DATAES-567, DATAES-767 public void aggregateShouldErrorWhenIndexDoesNotExist() { - template + operations .aggregate(new CriteriaQuery(Criteria.where("message").is("some message")), SampleEntity.class, IndexCoordinates.of("no-such-index")) // .as(StepVerifier::create) // @@ -548,7 +536,7 @@ public void aggregateShouldErrorWhenIndexDoesNotExist() { @Test // DATAES-519, DATAES-767 public void countShouldReturnZeroWhenIndexDoesNotExist() { - template.count(SampleEntity.class) // + operations.count(SampleEntity.class) // .as(StepVerifier::create) // .expectError(ElasticsearchStatusException.class); } @@ -558,7 +546,7 @@ public void countShouldReturnCountAllWhenGivenNoQuery() { index(randomEntity("test message"), randomEntity("test test"), randomEntity("some message")); - template.count(SampleEntity.class) // + operations.count(SampleEntity.class) // .as(StepVerifier::create) // .expectNext(3L) // .verifyComplete(); @@ -571,7 +559,7 @@ public void countShouldReturnCountMatchingDocuments() { CriteriaQuery query = new CriteriaQuery(new Criteria("message").contains("test")); - template.count(query, SampleEntity.class) // + operations.count(query, SampleEntity.class) // .as(StepVerifier::create) // .expectNext(2L) // .verifyComplete(); @@ -580,7 +568,7 @@ public void countShouldReturnCountMatchingDocuments() { @Test // DATAES-519, DATAES-767 public void deleteShouldErrorWhenIndexDoesNotExist() { - template.delete("does-not-exists", IndexCoordinates.of("no-such-index")) // + operations.delete("does-not-exists", IndexCoordinates.of("no-such-index")) // .as(StepVerifier::create)// .expectError(ElasticsearchStatusException.class); } @@ -591,7 +579,7 @@ public void deleteShouldRemoveExistingDocumentById() { SampleEntity sampleEntity = randomEntity("test message"); index(sampleEntity); - template.delete(sampleEntity.getId(), SampleEntity.class) // + operations.delete(sampleEntity.getId(), SampleEntity.class) // .as(StepVerifier::create)// .expectNext(sampleEntity.getId()) // .verifyComplete(); @@ -603,7 +591,7 @@ public void deleteShouldRemoveExistingDocumentByIdUsingIndexName() { SampleEntity sampleEntity = randomEntity("test message"); index(sampleEntity); - template.delete(sampleEntity.getId(), IndexCoordinates.of(DEFAULT_INDEX)) // + operations.delete(sampleEntity.getId(), IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create)// .expectNext(sampleEntity.getId()) // .verifyComplete(); @@ -615,7 +603,7 @@ public void deleteShouldRemoveExistingDocument() { SampleEntity sampleEntity = randomEntity("test message"); index(sampleEntity); - template.delete(sampleEntity) // + operations.delete(sampleEntity) // .as(StepVerifier::create)// .expectNext(sampleEntity.getId()) // .verifyComplete(); @@ -626,7 +614,7 @@ public void deleteShouldCompleteWhenNothingDeleted() { SampleEntity sampleEntity = randomEntity("test message"); - template.delete(sampleEntity) // + operations.delete(sampleEntity) // .as(StepVerifier::create)// .verifyComplete(); } @@ -636,7 +624,7 @@ public void deleteByQueryShouldReturnZeroWhenIndexDoesNotExist() { CriteriaQuery query = new CriteriaQuery(new Criteria("message").contains("test")); - template.delete(query, SampleEntity.class) // + operations.delete(query, SampleEntity.class) // .as(StepVerifier::create) // .consumeNextWith(byQueryResponse -> { assertThat(byQueryResponse.getDeleted()).isEqualTo(0L); @@ -650,25 +638,25 @@ public void shouldDeleteAcrossIndex() { IndexCoordinates thisIndex = IndexCoordinates.of(indexPrefix + "-this"); IndexCoordinates thatIndex = IndexCoordinates.of(indexPrefix + "-that"); - template.save(randomEntity("test"), thisIndex) // - .then(template.save(randomEntity("test"), thatIndex)) // + operations.save(randomEntity("test"), thisIndex) // + .then(operations.save(randomEntity("test"), thatIndex)) // .then() // .as(StepVerifier::create)// .verifyComplete(); - template.indexOps(thisIndex).refresh().then(template.indexOps(thatIndex).refresh()).block(); + operations.indexOps(thisIndex).refresh().then(operations.indexOps(thatIndex).refresh()).block(); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // .withQuery(termQuery("message", "test")) // .build(); - template.delete(searchQuery, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) // + operations.delete(searchQuery, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) // .map(ByQueryResponse::getDeleted) // .as(StepVerifier::create) // .expectNext(2L) // .verifyComplete(); - template.indexOps(thisIndex).delete().then(template.indexOps(thatIndex).delete()).block(); + operations.indexOps(thisIndex).delete().then(operations.indexOps(thatIndex).delete()).block(); } @Test // DATAES-547 @@ -678,25 +666,25 @@ public void shouldDeleteAcrossIndexWhenNoMatchingDataPresent() { IndexCoordinates thisIndex = IndexCoordinates.of(indexPrefix + "-this"); IndexCoordinates thatIndex = IndexCoordinates.of(indexPrefix + "-that"); - template.save(randomEntity("positive"), thisIndex) // - .then(template.save(randomEntity("positive"), thatIndex)) // + operations.save(randomEntity("positive"), thisIndex) // + .then(operations.save(randomEntity("positive"), thatIndex)) // .then() // .as(StepVerifier::create)// .verifyComplete(); - template.indexOps(thisIndex).refresh().then(template.indexOps(thatIndex).refresh()).block(); + operations.indexOps(thisIndex).refresh().then(operations.indexOps(thatIndex).refresh()).block(); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() // .withQuery(termQuery("message", "negative")) // .build(); - template.delete(searchQuery, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) // + operations.delete(searchQuery, SampleEntity.class, IndexCoordinates.of(indexPrefix + '*')) // .map(ByQueryResponse::getDeleted) // .as(StepVerifier::create) // .expectNext(0L) // .verifyComplete(); - template.indexOps(thisIndex).delete().then(template.indexOps(thatIndex).delete()).block(); + operations.indexOps(thisIndex).delete().then(operations.indexOps(thatIndex).delete()).block(); } @Test // DATAES-504 @@ -706,7 +694,7 @@ public void deleteByQueryShouldReturnNumberOfDeletedDocuments() { CriteriaQuery query = new CriteriaQuery(new Criteria("message").contains("test")); - template.delete(query, SampleEntity.class) // + operations.delete(query, SampleEntity.class) // .map(ByQueryResponse::getDeleted) // .as(StepVerifier::create) // .expectNext(2L) // @@ -720,7 +708,7 @@ public void deleteByQueryShouldReturnZeroIfNothingDeleted() { CriteriaQuery query = new CriteriaQuery(new Criteria("message").contains("luke")); - template.delete(query, SampleEntity.class) // + operations.delete(query, SampleEntity.class) // .map(ByQueryResponse::getDeleted) // .as(StepVerifier::create) // .expectNext(0L) // @@ -744,7 +732,7 @@ public void shouldReturnDocumentWithCollapsedField() { .withPageable(PageRequest.of(0, 25)) // .build(); - template.search(query, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + operations.search(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .expectNextCount(2) // .verifyComplete(); @@ -761,7 +749,7 @@ void shouldReturnSortFields() { .withSort(new FieldSortBuilder("rate").order(SortOrder.DESC)) // .build(); - template.search(query, SampleEntity.class) // + operations.search(query, SampleEntity.class) // .as(StepVerifier::create) // .consumeNextWith(it -> { List sortValues = it.getSortValues(); @@ -784,7 +772,7 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGet() { .withIds(Arrays.asList(entity1.getId(), entity2.getId())) // .build(); - template.multiGet(query, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + operations.multiGet(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) // .map(MultiGetItem::getItem).as(StepVerifier::create) // .expectNext(entity1, entity2) // .verifyComplete(); @@ -804,7 +792,7 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { .withFields("message") // .build(); - template.multiGet(query, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + operations.multiGet(query, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .expectNextCount(2) // .verifyComplete(); @@ -834,12 +822,12 @@ public void shouldDoBulkUpdate() { .build(); List queries = Arrays.asList(updateQuery1, updateQuery2); - template.bulkUpdate(queries, IndexCoordinates.of(DEFAULT_INDEX)).block(); + operations.bulkUpdate(queries, IndexCoordinates.of(indexNameProvider.indexName())).block(); NativeSearchQuery getQuery = new NativeSearchQueryBuilder() // .withIds(Arrays.asList(entity1.getId(), entity2.getId())) // .build(); - template.multiGet(getQuery, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + operations.multiGet(getQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) // .map(MultiGetItem::getItem) // .as(StepVerifier::create) // .expectNextMatches(entity -> entity.getMessage().equals("updated 1")) // @@ -854,12 +842,11 @@ void shouldSaveAll() { SampleEntity entity2 = randomEntity("test message 2"); entity2.rate = 2; - template.saveAll(Mono.just(Arrays.asList(entity1, entity2)), IndexCoordinates.of(DEFAULT_INDEX)) // - .then(indexOperations.refresh()) // - .block(); + operations.saveAll(Mono.just(Arrays.asList(entity1, entity2)), IndexCoordinates.of(indexNameProvider.indexName())) // + .then().block(); NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build(); - template.search(searchQuery, SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + operations.search(searchQuery, SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .expectNextMatches(hit -> entity1.equals(hit.getContent()) || entity2.equals(hit.getContent())) // .expectNextMatches(hit -> entity1.equals(hit.getContent()) || entity2.equals(hit.getContent())) // @@ -868,7 +855,7 @@ void shouldSaveAll() { @Test // DATAES-753 void shouldReturnEmptyFluxOnSaveAllWithEmptyInput() { - template.saveAll(Collections.emptyList(), IndexCoordinates.of(DEFAULT_INDEX)) // + operations.saveAll(Collections.emptyList(), IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .verifyComplete(); } @@ -877,9 +864,9 @@ void shouldReturnEmptyFluxOnSaveAllWithEmptyInput() { void getShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); original.setMessage("It's fine"); - OptimisticEntity saved = template.save(original).block(); + OptimisticEntity saved = operations.save(original).block(); - template.get(saved.getId(), OptimisticEntity.class).as(StepVerifier::create) + operations.get(saved.getId(), OptimisticEntity.class).as(StepVerifier::create) .assertNext(this::assertThatSeqNoPrimaryTermIsFilled).verifyComplete(); } @@ -895,11 +882,11 @@ private void assertThatSeqNoPrimaryTermIsFilled(OptimisticEntity retrieved) { void multiGetShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); original.setMessage("It's fine"); - OptimisticEntity saved = template.save(original).block(); + OptimisticEntity saved = operations.save(original).block(); - template + operations .multiGet(multiGetQueryForOne(saved.getId()), OptimisticEntity.class, - template.getIndexCoordinatesFor(OptimisticEntity.class)) // + operations.getIndexCoordinatesFor(OptimisticEntity.class)) // .map(MultiGetItem::getItem) // .as(StepVerifier::create) // .assertNext(this::assertThatSeqNoPrimaryTermIsFilled).verifyComplete(); @@ -913,13 +900,13 @@ private Query multiGetQueryForOne(String id) { void searchShouldReturnSeqNoPrimaryTerm() { OptimisticEntity original = new OptimisticEntity(); original.setMessage("It's fine"); - OptimisticEntity saved = template.save(original).block(); + OptimisticEntity saved = operations.save(original).block(); - template.indexOps(OptimisticEntity.class).refresh().block(); + operations.indexOps(OptimisticEntity.class).refresh().block(); - template + operations .search(searchQueryForOne(saved.getId()), OptimisticEntity.class, - template.getIndexCoordinatesFor(OptimisticEntity.class)) + operations.getIndexCoordinatesFor(OptimisticEntity.class)) .map(SearchHit::getContent).as(StepVerifier::create).assertNext(this::assertThatSeqNoPrimaryTermIsFilled) .verifyComplete(); } @@ -932,16 +919,16 @@ private Query searchQueryForOne(String id) { void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnEntityWithSeqNoPrimaryTermProperty() { OptimisticEntity original = new OptimisticEntity(); original.setMessage("It's fine"); - OptimisticEntity saved = template.save(original).block(); + OptimisticEntity saved = operations.save(original).block(); - OptimisticEntity forEdit1 = template.get(saved.getId(), OptimisticEntity.class).block(); - OptimisticEntity forEdit2 = template.get(saved.getId(), OptimisticEntity.class).block(); + OptimisticEntity forEdit1 = operations.get(saved.getId(), OptimisticEntity.class).block(); + OptimisticEntity forEdit2 = operations.get(saved.getId(), OptimisticEntity.class).block(); forEdit1.setMessage("It'll be ok"); - template.save(forEdit1).block(); + operations.save(forEdit1).block(); forEdit2.setMessage("It'll be great"); - template.save(forEdit2) // + operations.save(forEdit2) // .as(StepVerifier::create) // .expectError(OptimisticLockingFailureException.class) // .verify(); @@ -951,28 +938,28 @@ void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnEnt void shouldThrowOptimisticLockingFailureExceptionWhenConcurrentUpdateOccursOnVersionedEntityWithSeqNoPrimaryTermProperty() { OptimisticAndVersionedEntity original = new OptimisticAndVersionedEntity(); original.setMessage("It's fine"); - OptimisticAndVersionedEntity saved = template.save(original).block(); + OptimisticAndVersionedEntity saved = operations.save(original).block(); - OptimisticAndVersionedEntity forEdit1 = template.get(saved.getId(), OptimisticAndVersionedEntity.class).block(); - OptimisticAndVersionedEntity forEdit2 = template.get(saved.getId(), OptimisticAndVersionedEntity.class).block(); + OptimisticAndVersionedEntity forEdit1 = operations.get(saved.getId(), OptimisticAndVersionedEntity.class).block(); + OptimisticAndVersionedEntity forEdit2 = operations.get(saved.getId(), OptimisticAndVersionedEntity.class).block(); forEdit1.setMessage("It'll be ok"); - template.save(forEdit1).block(); + operations.save(forEdit1).block(); forEdit2.setMessage("It'll be great"); - template.save(forEdit2).as(StepVerifier::create).expectError(OptimisticLockingFailureException.class).verify(); + operations.save(forEdit2).as(StepVerifier::create).expectError(OptimisticLockingFailureException.class).verify(); } @Test // DATAES-799 void shouldAllowFullReplaceOfEntityWithBothSeqNoPrimaryTermAndVersion() { OptimisticAndVersionedEntity original = new OptimisticAndVersionedEntity(); original.setMessage("It's fine"); - OptimisticAndVersionedEntity saved = template.save(original).block(); + OptimisticAndVersionedEntity saved = operations.save(original).block(); - OptimisticAndVersionedEntity forEdit = template.get(saved.getId(), OptimisticAndVersionedEntity.class).block(); + OptimisticAndVersionedEntity forEdit = operations.get(saved.getId(), OptimisticAndVersionedEntity.class).block(); forEdit.setMessage("It'll be ok"); - template.save(forEdit).as(StepVerifier::create).expectNextCount(1).verifyComplete(); + operations.save(forEdit).as(StepVerifier::create).expectNextCount(1).verifyComplete(); } @Test // DATAES-909 @@ -988,11 +975,12 @@ void shouldDoUpdate() { .withDocument(document) // .build(); - UpdateResponse updateResponse = template.update(updateQuery, IndexCoordinates.of(DEFAULT_INDEX)).block(); + UpdateResponse updateResponse = operations.update(updateQuery, IndexCoordinates.of(indexNameProvider.indexName())) + .block(); assertThat(updateResponse).isNotNull(); assertThat(updateResponse.getResult()).isEqualTo(UpdateResponse.Result.UPDATED); - template.get(entity.getId(), SampleEntity.class, IndexCoordinates.of(DEFAULT_INDEX)) // + operations.get(entity.getId(), SampleEntity.class, IndexCoordinates.of(indexNameProvider.indexName())) // .as(StepVerifier::create) // .expectNextMatches(foundEntity -> foundEntity.getMessage().equals("updated")) // .verifyComplete(); @@ -1000,28 +988,28 @@ void shouldDoUpdate() { @Test // DATAES-908 void shouldFillVersionOnSaveOne() { - VersionedEntity saved = template.save(new VersionedEntity()).block(); + VersionedEntity saved = operations.save(new VersionedEntity()).block(); assertThat(saved.getVersion()).isNotNull(); } @Test // DATAES-908 void shouldFillVersionOnSaveAll() { - VersionedEntity saved = template.saveAll(singletonList(new VersionedEntity()), VersionedEntity.class).blockLast(); + VersionedEntity saved = operations.saveAll(singletonList(new VersionedEntity()), VersionedEntity.class).blockLast(); assertThat(saved.getVersion()).isNotNull(); } @Test // DATAES-908 void shouldFillSeqNoPrimaryTermOnSaveOne() { - OptimisticEntity saved = template.save(new OptimisticEntity()).block(); + OptimisticEntity saved = operations.save(new OptimisticEntity()).block(); assertThatSeqNoPrimaryTermIsFilled(saved); } @Test // DATAES-908 void shouldFillSeqNoPrimaryTermOnSaveAll() { - OptimisticEntity saved = template.saveAll(singletonList(new OptimisticEntity()), OptimisticEntity.class) + OptimisticEntity saved = operations.saveAll(singletonList(new OptimisticEntity()), OptimisticEntity.class) .blockLast(); assertThatSeqNoPrimaryTermIsFilled(saved); @@ -1037,9 +1025,9 @@ void shouldReturnMonoOfSearchPage() { Query query = Query.findAll().setPageable(PageRequest.of(0, 5)); - template.saveAll(Mono.just(entities), SampleEntity.class).then(indexOperations.refresh()).block(); + operations.saveAll(Mono.just(entities), SampleEntity.class).then().block(); - Mono> searchPageMono = template.searchForPage(query, SampleEntity.class); + Mono> searchPageMono = operations.searchForPage(query, SampleEntity.class); searchPageMono.as(StepVerifier::create) // .consumeNextWith(searchPage -> { @@ -1057,19 +1045,19 @@ void shouldBeAbleToProcessDateMathIndexNames() { String indexName = "foo-" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM")); String dateMathIndexName = ""; - template.indexOps(IndexCoordinates.of(dateMathIndexName)) // + operations.indexOps(IndexCoordinates.of(dateMathIndexName)) // .create() // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); // - template.indexOps(IndexCoordinates.of(indexName)) // + operations.indexOps(IndexCoordinates.of(indexName)) // .exists() // .as(StepVerifier::create) // .expectNext(true) // .verifyComplete(); // - template.indexOps(IndexCoordinates.of(dateMathIndexName)) // + operations.indexOps(IndexCoordinates.of(dateMathIndexName)) // .delete() // .as(StepVerifier::create) // .expectNext(true) // @@ -1082,12 +1070,12 @@ void shouldNotReturnExplanationWhenNotRequested() { ElasticsearchTemplateTests.SampleEntity entity = ElasticsearchTemplateTests.SampleEntity.builder().id("42") .message("a message with text").build(); - template.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete(); + operations.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete(); Criteria criteria = new Criteria("message").contains("with"); CriteriaQuery query = new CriteriaQuery(criteria); - template.search(query, ElasticsearchTemplateTests.SampleEntity.class).as(StepVerifier::create) + operations.search(query, ElasticsearchTemplateTests.SampleEntity.class).as(StepVerifier::create) .consumeNextWith(searchHit -> { Explanation explanation = searchHit.getExplanation(); assertThat(explanation).isNull(); @@ -1100,13 +1088,13 @@ void shouldReturnExplanationWhenRequested() { ElasticsearchTemplateTests.SampleEntity entity = ElasticsearchTemplateTests.SampleEntity.builder().id("42") .message("a message with text").build(); - template.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete(); + operations.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete(); Criteria criteria = new Criteria("message").contains("with"); CriteriaQuery query = new CriteriaQuery(criteria); query.setExplain(true); - template.search(query, ElasticsearchTemplateTests.SampleEntity.class).as(StepVerifier::create) + operations.search(query, ElasticsearchTemplateTests.SampleEntity.class).as(StepVerifier::create) .consumeNextWith(searchHit -> { Explanation explanation = searchHit.getExplanation(); assertThat(explanation).isNotNull(); @@ -1116,11 +1104,12 @@ void shouldReturnExplanationWhenRequested() { @Test // #1646, #1718 @DisplayName("should return a list of info for specific index") void shouldReturnInformationListOfAllIndices() { - String indexName = "test-index-reactive-information-list"; - String aliasName = "testindexinformationindex"; - ReactiveIndexOperations indexOps = template.indexOps(EntityWithSettingsAndMappingsReactive.class); + String indexName = indexNameProvider.indexName(); + String aliasName = indexName + "-alias"; + ReactiveIndexOperations indexOps = operations.indexOps(EntityWithSettingsAndMappingsReactive.class); - indexOps.createWithMapping().block(); + // beforeEach uses SampleEntity, so recreate the index here + indexOps.delete().then(indexOps.createWithMapping()).block(); AliasActionParameters parameters = AliasActionParameters.builder().withAliases(aliasName).withIndices(indexName) .withIsHidden(false).withIsWriteIndex(false).withRouting("indexrouting").withSearchRouting("searchrouting") @@ -1158,7 +1147,7 @@ void shouldWorkWithImmutableClasses() { ImmutableEntity entity = new ImmutableEntity(null, "some text", null); AtomicReference savedEntity = new AtomicReference<>(); - template.save(entity).as(StepVerifier::create).consumeNextWith(saved -> { + operations.save(entity).as(StepVerifier::create).consumeNextWith(saved -> { assertThat(saved).isNotNull(); savedEntity.set(saved); assertThat(saved.getId()).isNotEmpty(); @@ -1166,7 +1155,7 @@ void shouldWorkWithImmutableClasses() { assertThat(seqNoPrimaryTerm).isNotNull(); }).verifyComplete(); - template.get(savedEntity.get().getId(), ImmutableEntity.class).as(StepVerifier::create) + operations.get(savedEntity.get().getId(), ImmutableEntity.class).as(StepVerifier::create) .consumeNextWith(retrieved -> { assertThat(retrieved).isEqualTo(savedEntity.get()); }).verifyComplete(); @@ -1195,58 +1184,18 @@ private List getIndexQueries(SampleEntity... sampleEntities) { private void index(SampleEntity... entities) { - IndexCoordinates indexCoordinates = IndexCoordinates.of(DEFAULT_INDEX); + IndexCoordinates indexCoordinates = IndexCoordinates.of(indexNameProvider.indexName()); if (entities.length == 1) { - template.save(entities[0], indexCoordinates).then(indexOperations.refresh()).block(); + operations.save(entities[0], indexCoordinates).block(); } else { - template.saveAll(Mono.just(Arrays.asList(entities)), indexCoordinates).then(indexOperations.refresh()).block(); + operations.saveAll(Mono.just(Arrays.asList(entities)), indexCoordinates).then().block(); } } // endregion // region Entities - @Document(indexName = "marvel") - static class Person { - @Nullable private @Id String id; - @Nullable private String name; - @Nullable private int age; - - public Person() {} - - public Person(String name, int age) { - this.name = name; - this.age = age; - } - - @Nullable - public String getId() { - return id; - } - - public void setId(@Nullable String id) { - this.id = id; - } - - @Nullable - public String getName() { - return name; - } - - public void setName(@Nullable String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - } - static class Message { @Nullable String message; @@ -1281,7 +1230,7 @@ public int hashCode() { } } - @Document(indexName = DEFAULT_INDEX) + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String message; @@ -1351,7 +1300,7 @@ public int hashCode() { } } - @Document(indexName = "test-index-reactive-optimistic-entity-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class OptimisticEntity { @Nullable @Id private String id; @Nullable private String message; @@ -1385,7 +1334,7 @@ public void setSeqNoPrimaryTerm(@Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) { } } - @Document(indexName = "test-index-reactive-optimistic-and-versioned-entity-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class OptimisticAndVersionedEntity { @Nullable @Id private String id; @Nullable private String message; @@ -1429,7 +1378,7 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = "test-index-reactive-versioned-entity-template") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class VersionedEntity { @Nullable @Id private String id; @Nullable @Version private Long version; @@ -1453,7 +1402,7 @@ public void setVersion(@Nullable java.lang.Long version) { } } - @Document(indexName = "test-index-reactive-information-list", createIndex = false) + @Document(indexName = "#{@indexNameProvider.indexName()}", createIndex = false) @Setting(settingPath = "settings/test-settings.json") @Mapping(mappingPath = "mappings/test-mappings.json") private static class EntityWithSettingsAndMappingsReactive { @@ -1469,7 +1418,7 @@ public void setId(@Nullable String id) { } } - @Document(indexName = "immutable-class") + @Document(indexName = "#{@indexNameProvider.indexName()}") private static final class ImmutableEntity { @Id private final String id; @Field(type = FieldType.Text) private final String text; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index 98f6a264b..67a9a6099 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -38,9 +38,8 @@ import org.assertj.core.data.Percentage; import org.elasticsearch.search.suggest.completion.context.ContextMapping; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; @@ -84,27 +83,18 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests { @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; - - @AfterEach - @BeforeEach - public void deleteIndices() { - indexOperations = operations.indexOps(SimpleRecursiveEntity.class); - indexOperations.delete(); - operations.indexOps(StockPrice.class).delete(); - operations.indexOps(SampleInheritedEntity.class).delete(); - operations.indexOps(User.class).delete(); - operations.indexOps(Group.class).delete(); - operations.indexOps(Book.class).delete(); - operations.indexOps(NormalizerEntity.class).delete(); - operations.indexOps(CopyToEntity.class).delete(); + + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } @Test public void shouldNotFailOnCircularReference() { - operations.indexOps(SimpleRecursiveEntity.class).create(); - indexOperations.putMapping(SimpleRecursiveEntity.class); + IndexOperations indexOperations = operations.indexOps(SimpleRecursiveEntity.class); + indexOperations.createWithMapping(); indexOperations.refresh(); } @@ -274,7 +264,6 @@ void shouldWriteDenseVectorFieldMapping() { IndexOperations indexOps = operations.indexOps(DenseVectorEntity.class); indexOps.create(); indexOps.putMapping(); - indexOps.delete(); } @Test // #1370 @@ -284,7 +273,7 @@ void shouldWriteMappingForDisabledEntity() { IndexOperations indexOps = operations.indexOps(DisabledMappingEntity.class); indexOps.create(); indexOps.putMapping(); - indexOps.delete(); + } @Test // #1370 @@ -294,7 +283,7 @@ void shouldWriteMappingForDisabledProperty() { IndexOperations indexOps = operations.indexOps(DisabledMappingProperty.class); indexOps.create(); indexOps.putMapping(); - indexOps.delete(); + } @Test // #1767 @@ -304,7 +293,7 @@ void shouldWriteDynamicMappingEntries() { IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class); indexOps.create(); indexOps.putMapping(); - indexOps.delete(); + } @Test // #638 @@ -314,7 +303,7 @@ void shouldWriteDynamicDetectionValues() { IndexOperations indexOps = operations.indexOps(DynamicDetectionMapping.class); indexOps.create(); indexOps.putMapping(); - indexOps.delete(); + } @Test // #1816 @@ -324,7 +313,7 @@ void shouldWriteRuntimeFields() { IndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class); indexOps.create(); indexOps.putMapping(); - indexOps.delete(); + } // region entities diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java index 18e2527a3..9c88b9fff 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryIntegrationTests.java @@ -24,10 +24,11 @@ import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; @@ -35,12 +36,12 @@ import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; 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.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; @@ -56,31 +57,31 @@ public class CriteriaQueryIntegrationTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) - static class Config {} - - private final IndexCoordinates index = IndexCoordinates.of("test-index-sample-core-query"); + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider(); + } + } @Autowired private ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(SampleEntity.class); - indexOperations.delete(); - indexOperations.create(); - indexOperations.putMapping(SampleEntity.class); - indexOperations.refresh(); + indexNameProvider.increment(); + operations.indexOps(SampleEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } - @Test // ,DATAES-706 + @Test // DATAES-706 public void shouldPerformAndOperationOnCriteriaEntries() { - // given SampleEntity sampleEntity1 = new SampleEntity(); sampleEntity1.setId(nextIdAsString()); sampleEntity1.setMessage("some test message"); @@ -89,22 +90,18 @@ public void shouldPerformAndOperationOnCriteriaEntries() { sampleEntity2.setId(nextIdAsString()); sampleEntity2.setMessage("some other message"); operations.save(sampleEntity2); - indexOperations.refresh(); - // when CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("test").and("message").contains("some")); - SearchHit searchHit = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit searchHit = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(searchHit).isNotNull(); assertThat(searchHit.getId()).isEqualTo(sampleEntity1.id); } - @Test // ,DATAES-706 + @Test // DATAES-706 public void shouldPerformOrOperationOnCriteriaEntries() { - // given SampleEntity sampleEntity1 = new SampleEntity(); sampleEntity1.setId(nextIdAsString()); sampleEntity1.setMessage("some test message"); @@ -113,23 +110,19 @@ public void shouldPerformOrOperationOnCriteriaEntries() { sampleEntity2.setId(nextIdAsString()); sampleEntity2.setMessage("some other message"); operations.save(sampleEntity2); - indexOperations.refresh(); - // when CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("test").or("message").contains("other")); - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getSearchHits().stream().map(SearchHit::getId)).containsExactlyInAnyOrder(sampleEntity1.id, sampleEntity2.id); } - @Test // ,DATAES-706 + @Test // DATAES-706 public void shouldPerformAndOperationWithinCriteria() { - // given SampleEntity sampleEntity1 = new SampleEntity(); sampleEntity1.setId(nextIdAsString()); sampleEntity1.setMessage("some test message"); @@ -138,22 +131,18 @@ public void shouldPerformAndOperationWithinCriteria() { sampleEntity2.setId(nextIdAsString()); sampleEntity2.setMessage("some other message"); operations.save(sampleEntity2); - indexOperations.refresh(); - // when CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("test").and(new Criteria("message").contains("some"))); - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } - @Test // ,DATAES-706 + @Test // DATAES-706 public void shouldPerformOrOperationWithinCriteria() { - // given SampleEntity sampleEntity1 = new SampleEntity(); sampleEntity1.setId(nextIdAsString()); sampleEntity1.setMessage("some test message"); @@ -162,14 +151,11 @@ public void shouldPerformOrOperationWithinCriteria() { sampleEntity2.setId(nextIdAsString()); sampleEntity2.setMessage("some other message"); operations.save(sampleEntity2); - indexOperations.refresh(); - // when CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("test").or(new Criteria("message").contains("other"))); - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getSearchHits().stream().map(SearchHit::getId)).containsExactlyInAnyOrder(sampleEntity1.id, sampleEntity2.id); @@ -178,9 +164,8 @@ public void shouldPerformOrOperationWithinCriteria() { @Test public void shouldPerformIsOperation() { - // given List indexQueries = new ArrayList<>(); - // first document + String documentId = nextIdAsString(); SampleEntity sampleEntity = new SampleEntity(); sampleEntity.setId(documentId); @@ -192,12 +177,12 @@ public void shouldPerformIsOperation() { indexQuery.setObject(sampleEntity); indexQueries.add(indexQuery); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").is("some message")); // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); @@ -207,7 +192,6 @@ public void shouldPerformIsOperation() { @Test public void shouldPerformMultipleIsOperations() { - // given List indexQueries = new ArrayList<>(); // first document @@ -234,14 +218,12 @@ public void shouldPerformMultipleIsOperations() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").is("some message")); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); assertThat(searchHits.getTotalHits()).isEqualTo(1); } @@ -249,7 +231,6 @@ public void shouldPerformMultipleIsOperations() { @Test public void shouldPerformEndsWithOperation() { - // given List indexQueries = new ArrayList<>(); // first document @@ -276,15 +257,13 @@ public void shouldPerformEndsWithOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + Criteria criteria = new Criteria("message").endsWith("end"); CriteriaQuery criteriaQuery = new CriteriaQuery(criteria); - // when - SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); assertThat(sampleEntity).isNotNull(); } @@ -292,7 +271,6 @@ public void shouldPerformEndsWithOperation() { @Test public void shouldPerformStartsWithOperation() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -318,15 +296,13 @@ public void shouldPerformStartsWithOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + Criteria criteria = new Criteria("message").startsWith("start"); CriteriaQuery criteriaQuery = new CriteriaQuery(criteria); - // when - SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); assertThat(sampleEntity).isNotNull(); } @@ -334,7 +310,6 @@ public void shouldPerformStartsWithOperation() { @Test public void shouldPerformContainsOperation() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -360,14 +335,12 @@ public void shouldPerformContainsOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("contains")); - // when - SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); assertThat(sampleEntity).isNotNull(); } @@ -401,12 +374,12 @@ public void shouldExecuteExpression() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").expression("+elasticsearch || test")); // when - SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class); // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); @@ -416,7 +389,6 @@ public void shouldExecuteExpression() { @Test public void shouldExecuteCriteriaChain() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -442,15 +414,13 @@ public void shouldExecuteCriteriaChain() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").startsWith("some").endsWith("search").contains("message").is("some message search")); - // when - SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(criteriaQuery.getCriteria().getField().getName()).isEqualTo("message"); assertThat(sampleEntity).isNotNull(); } @@ -458,7 +428,6 @@ public void shouldExecuteCriteriaChain() { @Test public void shouldPerformIsNotOperation() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -484,14 +453,12 @@ public void shouldPerformIsNotOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").is("foo").not()); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(criteriaQuery.getCriteria().isNegating()).isTrue(); assertThat(searchHits).isNotNull(); assertThat(searchHits.iterator().next().getContent().getMessage()).doesNotContain("foo"); @@ -500,7 +467,6 @@ public void shouldPerformIsNotOperation() { @Test public void shouldPerformBetweenOperation() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -528,21 +494,18 @@ public void shouldPerformBetweenOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("rate").between(100, 150)); - // when - SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(sampleEntity).isNotNull(); } @Test public void shouldPerformBetweenOperationWithoutUpperBound() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -570,14 +533,12 @@ public void shouldPerformBetweenOperationWithoutUpperBound() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("rate").between(350, null)); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } @@ -585,7 +546,6 @@ public void shouldPerformBetweenOperationWithoutUpperBound() { @Test public void shouldPerformBetweenOperationWithoutLowerBound() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -613,14 +573,12 @@ public void shouldPerformBetweenOperationWithoutLowerBound() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("rate").between(null, 550)); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } @@ -628,7 +586,6 @@ public void shouldPerformBetweenOperationWithoutLowerBound() { @Test public void shouldPerformLessThanEqualOperation() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -656,14 +613,12 @@ public void shouldPerformLessThanEqualOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("rate").lessThanEqual(750)); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } @@ -671,7 +626,6 @@ public void shouldPerformLessThanEqualOperation() { @Test public void shouldPerformGreaterThanEquals() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -699,14 +653,12 @@ public void shouldPerformGreaterThanEquals() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("rate").greaterThanEqual(950)); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits).isNotNull(); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } @@ -714,7 +666,6 @@ public void shouldPerformGreaterThanEquals() { @Test public void shouldPerformBoostOperation() { - // given List indexQueries = new ArrayList<>(); // first document String documentId = nextIdAsString(); @@ -742,14 +693,12 @@ public void shouldPerformBoostOperation() { indexQuery2.setObject(sampleEntity2); indexQueries.add(indexQuery2); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); + CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").contains("foo").boost(1)); - // when - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); - // then assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); } @@ -760,13 +709,12 @@ public void shouldReturnDocumentAboveMinimalScoreGivenCriteria() { indexQueries.add(buildIndex(new SampleEntity("1", "ab"))); indexQueries.add(buildIndex(new SampleEntity("2", "bc"))); indexQueries.add(buildIndex(new SampleEntity("3", "ac"))); - operations.bulkIndex(indexQueries, index); - indexOperations.refresh(); + operations.bulkIndex(indexQueries, SampleEntity.class); CriteriaQuery criteriaQuery = new CriteriaQuery( new Criteria("message").contains("a").or(new Criteria("message").contains("b"))); criteriaQuery.setMinScore(2.0F); - SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class, index); + SearchHits searchHits = operations.search(criteriaQuery, SampleEntity.class); assertThat(searchHits.getTotalHits()).isEqualTo(1); assertThat(searchHits.getSearchHit(0).getContent().getMessage()).isEqualTo("ab"); @@ -775,7 +723,6 @@ public void shouldReturnDocumentAboveMinimalScoreGivenCriteria() { @Test // DATAES-213 public void shouldEscapeValue() { - // given String documentId = nextIdAsString(); SampleEntity sampleEntity = new SampleEntity(); sampleEntity.setId(documentId); @@ -785,19 +732,16 @@ public void shouldEscapeValue() { IndexQuery indexQuery = new IndexQuery(); indexQuery.setId(documentId); indexQuery.setObject(sampleEntity); - operations.index(indexQuery, index); - indexOperations.refresh(); + operations.index(indexQuery, IndexCoordinates.of(indexNameProvider.indexName())); CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("message").is("Hello World!")); - // when - SearchHit sampleEntity1 = operations.searchOne(criteriaQuery, SampleEntity.class, index); + SearchHit sampleEntity1 = operations.searchOne(criteriaQuery, SampleEntity.class); - // then assertThat(sampleEntity1).isNotNull(); } - @Document(indexName = "test-index-sample-core-query") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryTransportIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryTransportIntegrationTests.java index d66f07a4c..561a2fe90 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryTransportIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/query/CriteriaQueryTransportIntegrationTests.java @@ -15,10 +15,12 @@ */ package org.springframework.data.elasticsearch.core.query; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -29,5 +31,11 @@ public class CriteriaQueryTransportIntegrationTests extends CriteriaQueryIntegra @Configuration @Import({ ElasticsearchTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("criteria-query"); + } + + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java index 111b91c44..4b6f8083b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java @@ -15,15 +15,11 @@ */ package org.springframework.data.elasticsearch.junit.jupiter; -import java.net.MalformedURLException; -import java.net.URL; - import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.elasticsearch.support.VersionInfo; import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; import org.testcontainers.elasticsearch.ElasticsearchContainer; /** @@ -47,12 +43,10 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour @Nullable private final ClusterConnectionInfo clusterConnectionInfo; /** - * creates the ClusterConnection, starting a container if necessary. - * - * @param clusterUrl if null or empty a local cluster is tarted + * creates the ClusterConnection, starting a container */ - public ClusterConnection(@Nullable String clusterUrl) { - clusterConnectionInfo = StringUtils.isEmpty(clusterUrl) ? startElasticsearchContainer() : parseUrl(clusterUrl); + public ClusterConnection() { + clusterConnectionInfo = startElasticsearchContainer(); if (clusterConnectionInfo != null) { LOGGER.debug(clusterConnectionInfo.toString()); @@ -75,28 +69,6 @@ public ClusterConnectionInfo getClusterConnectionInfo() { return clusterConnectionInfo; } - /** - * @param clusterUrl the URL to parse - * @return the connection information - */ - private ClusterConnectionInfo parseUrl(String clusterUrl) { - try { - URL url = new URL(clusterUrl); - - if (!url.getProtocol().startsWith("http") || url.getPort() <= 0) { - throw new ClusterConnectionException("invalid url " + clusterUrl); - } - - return ClusterConnectionInfo.builder() // - .withHostAndPort(url.getHost(), url.getPort()) // - .useSsl(url.getProtocol().equals("https")) // - .build(); - } catch (MalformedURLException e) { - throw new ClusterConnectionException(e); - } - - } - @Nullable private ClusterConnectionInfo startElasticsearchContainer() { diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringDataElasticsearchExtension.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringDataElasticsearchExtension.java index c966d32a5..82ba9b973 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringDataElasticsearchExtension.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringDataElasticsearchExtension.java @@ -46,7 +46,6 @@ public class SpringDataElasticsearchExtension implements BeforeAllCallback, ParameterResolver, ContextCustomizerFactory { - public static final String SPRING_DATA_ELASTICSEARCH_TEST_CLUSTER_URL = "SPRING_DATA_ELASTICSEARCH_TEST_CLUSTER_URL"; private static final Logger LOGGER = LoggerFactory.getLogger(SpringDataElasticsearchExtension.class); private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace @@ -77,7 +76,7 @@ private ExtensionContext.Store getStore(ExtensionContext extensionContext) { } private ClusterConnection createClusterConnection() { - return new ClusterConnection(System.getenv(SPRING_DATA_ELASTICSEARCH_TEST_CLUSTER_URL)); + return new ClusterConnection(); } @Override @@ -89,7 +88,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon /* * (non javadoc) - * no need to check the paramaterContext and extensionContext here, this was done before in supportsParameter. + * no need to check the parameterContext and extensionContext here, this was done before in supportsParameter. */ @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringIntegrationTest.java index 9c3a37493..3deecc7d7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringIntegrationTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/SpringIntegrationTest.java @@ -20,12 +20,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.test.context.junit.jupiter.SpringExtension; /** - * Combines the {@link SpringDataElasticsearchExtension} and the {@link SpringExtension}. + * Combines the {@link SpringDataElasticsearchExtension} and the {@link SpringExtension}. Tests are executed in + * accordance to the {@link org.junit.jupiter.api.Order} annotation, to be able to have an explicit last test method for + * cleanup (since 4.3) * * @author Peter-Josef Meisch */ @@ -34,5 +38,6 @@ @ExtendWith(SpringDataElasticsearchExtension.class) @ExtendWith(SpringExtension.class) @Tag(Tags.INTEGRATION_TEST) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public @interface SpringIntegrationTest { } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java index 321e32f1e..03c098230 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/cdi/CdiRepositoryTests.java @@ -153,10 +153,6 @@ public void returnOneFromCustomImpl() { assertThat(personRepository.returnOne()).isEqualTo(1); } - /** - * @author Mohsin Husen - * @author Artur Konczak - */ @Document(indexName = "test-index-product-cdi-repository") static class Product { @Nullable @Id private String id; @@ -372,7 +368,7 @@ public void setModel(@Nullable String model) { } } - static class Author { + private static class Author { @Nullable private String id; @Nullable private String name; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java index df74cf0dd..acb2fe57e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTests.java @@ -18,21 +18,22 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; 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.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.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; @@ -48,38 +49,37 @@ public class ComplexCustomMethodRepositoryTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("complex-custom-method"); + } + } @Autowired private ComplexElasticsearchRepository complexRepository; - @Autowired ElasticsearchOperations operations; - - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(SampleEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(SampleEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } @Test public void shouldExecuteComplexCustomMethod() { - // given - - // when String result = complexRepository.doSomethingSpecial(); - - // then assertThat(result).isEqualTo("2+2=4"); } - @Document(indexName = "test-index-sample-repositories-complex-custommethod-autowiring") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTransportTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTransportTests.java index 821f8e795..2ce12567e 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTransportTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/autowiring/ComplexCustomMethodRepositoryTransportTests.java @@ -15,10 +15,12 @@ */ package org.springframework.data.elasticsearch.repositories.complex.custommethod.autowiring; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -29,5 +31,10 @@ public class ComplexCustomMethodRepositoryTransportTests extends ComplexCustomMe @Configuration @Import({ ElasticsearchTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("complex-custom-method"); + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java index 4a535a291..7837d79d0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTests.java @@ -18,21 +18,22 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.data.elasticsearch.annotations.FieldType.*; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; 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.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.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; @@ -47,37 +48,37 @@ public class ComplexCustomMethodRepositoryManualWiringTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("complex-custom-method"); + } + } @Autowired private ComplexElasticsearchRepositoryManualWiring complexRepository; - @Autowired ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired private IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(SampleEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(SampleEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } @Test public void shouldExecuteComplexCustomMethod() { - // given - - // when String result = complexRepository.doSomethingSpecial(); - - // then assertThat(result).isEqualTo("3+3=6"); } - @Document(indexName = "test-index-sample-repository-manual-wiring") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTransportTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTransportTests.java index 6a5b5092d..7eb99bae6 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTransportTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/complex/custommethod/manualwiring/ComplexCustomMethodRepositoryManualWiringTransportTests.java @@ -15,10 +15,12 @@ */ package org.springframework.data.elasticsearch.repositories.complex.custommethod.manualwiring; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -30,5 +32,10 @@ public class ComplexCustomMethodRepositoryManualWiringTransportTests @Configuration @Import({ ElasticsearchTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("complex-custom-method"); + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java index 81665558f..196adb47b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryBaseTests.java @@ -28,7 +28,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -47,16 +46,16 @@ import org.springframework.data.elasticsearch.annotations.HighlightField; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; -import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.SearchPage; import org.springframework.data.elasticsearch.core.geo.GeoBox; import org.springframework.data.elasticsearch.core.geo.GeoPoint; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.GeoDistanceOrder; import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.data.geo.Box; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Metrics; @@ -76,22 +75,23 @@ @SpringIntegrationTest public abstract class CustomMethodRepositoryBaseTests { + @Autowired private IndexNameProvider indexNameProvider; @Autowired private SampleCustomMethodRepository repository; - @Autowired private SampleStreamingCustomMethodRepository streamingRepository; @Autowired ElasticsearchOperations operations; - private IndexOperations indexOperations; @BeforeEach public void before() { - indexOperations = operations.indexOps(SampleEntity.class); - IndexInitializer.init(indexOperations); + + indexNameProvider.increment(); + operations.indexOps(SampleEntity.class).createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @org.junit.jupiter.api.Order(java.lang.Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } @Test @@ -1431,13 +1431,15 @@ public void streamMethodsShouldWorkWithLargeResultSets() { // given List entities = createSampleEntities("abc", 10001); repository.saveAll(entities); + operations.indexOps(SampleEntity.class).refresh(); // when Stream stream = streamingRepository.findByType("abc"); // then assertThat(stream).isNotNull(); - assertThat(stream.count()).isEqualTo(10001L); + long count = stream.count(); + assertThat(count).isEqualTo(10001L); } @Test // DATAES-605 @@ -1647,7 +1649,7 @@ void shouldStreamSearchHitsWithQueryAnnotatedMethod() { assertThat(count).isEqualTo(20); } - @Document(indexName = "test-index-sample-repositories-custom-method") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class SampleEntity { @Nullable @Id private String id; @Nullable @Field(type = Text, store = true, fielddata = true) private String type; @@ -1734,6 +1736,7 @@ public void setVersion(@Nullable java.lang.Long version) { * @author Mohsin Husen * @author Kevin Leturc */ + @SuppressWarnings("SpringDataRepositoryMethodParametersInspection") public interface SampleCustomMethodRepository extends ElasticsearchRepository { Page findByType(String type, Pageable pageable); diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTests.java index 3bc287dd4..891579868 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryRestTests.java @@ -15,10 +15,12 @@ */ package org.springframework.data.elasticsearch.repositories.custommethod; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -34,5 +36,10 @@ public class CustomMethodRepositoryRestTests extends CustomMethodRepositoryBaseT @EnableElasticsearchRepositories( basePackages = { "org.springframework.data.elasticsearch.repositories.custommethod" }, considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("custom-method-repository"); + } + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryTests.java index bb4632ebe..30bdd1b54 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/custommethod/CustomMethodRepositoryTests.java @@ -15,10 +15,13 @@ */ package org.springframework.data.elasticsearch.repositories.custommethod; +import org.junit.jupiter.api.Disabled; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -34,6 +37,15 @@ public class CustomMethodRepositoryTests extends CustomMethodRepositoryBaseTests @EnableElasticsearchRepositories( basePackages = { "org.springframework.data.elasticsearch.repositories.custommethod" }, considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider("custom-method-repository"); + } + } + @Disabled("this test crashes the transport client connection in some dockerized test container setup") + @Override + public void streamMethodsShouldWorkWithLargeResultSets() { + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java index dc8d526f8..d234f1a81 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/doubleid/DoubleIDRepositoryTests.java @@ -21,22 +21,23 @@ import java.util.Arrays; import java.util.Optional; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; import org.springframework.data.elasticsearch.annotations.Document; 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.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -53,22 +54,28 @@ public class DoubleIDRepositoryTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider(); + } + } @Autowired private DoubleIDRepository repository; @Autowired ElasticsearchOperations operations; - private IndexOperations indexOperations; + @Autowired IndexNameProvider indexNameProvider; @BeforeEach public void before() { - indexOperations = operations.indexOps(DoubleIDEntity.class); - IndexInitializer.init(indexOperations); + indexNameProvider.increment(); + operations.indexOps(DoubleIDEntity.class).createWithMapping(); } - @AfterEach - public void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + public void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } @Test @@ -116,12 +123,7 @@ public void shouldSaveDocument() { assertThat(entityFromElasticSearch).isPresent(); } - /** - * @author Rizwan Idrees - * @author Mohsin Husen - */ - - @Document(indexName = "test-index-double-keyed-entity") + @Document(indexName = "#{@indexNameProvider.indexName()}") static class DoubleIDEntity { @Id private Double id; @@ -162,9 +164,5 @@ public void setVersion(Long version) { } } - /** - * @author Ryan Henszey - * @author Mohsin Husen - */ interface DoubleIDRepository extends ElasticsearchRepository {} } diff --git a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryTests.java index 757a3b6a9..c38a0da86 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repositories/setting/dynamic/DynamicSettingAndMappingEntityRepositoryTests.java @@ -22,10 +22,11 @@ import java.util.Map; import org.elasticsearch.index.query.QueryBuilders; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.annotation.Id; @@ -42,7 +43,7 @@ import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; -import org.springframework.data.elasticsearch.utils.IndexInitializer; +import org.springframework.data.elasticsearch.utils.IndexNameProvider; import org.springframework.test.context.ContextConfiguration; /** @@ -59,31 +60,34 @@ public class DynamicSettingAndMappingEntityRepositoryTests { @Configuration @Import({ ElasticsearchRestTemplateConfiguration.class }) @EnableElasticsearchRepositories(considerNestedRepositories = true) - static class Config {} + static class Config { + @Bean + IndexNameProvider indexNameProvider() { + return new IndexNameProvider(); + } + } @Autowired private ElasticsearchOperations operations; private IndexOperations indexOperations; - + @Autowired IndexNameProvider indexNameProvider; @Autowired private DynamicSettingAndMappingEntityRepository repository; @BeforeEach public void before() { + indexNameProvider.increment(); indexOperations = operations.indexOps(DynamicSettingAndMappingEntity.class); - IndexInitializer.init(indexOperations); + indexOperations.createWithMapping(); } - @AfterEach - void after() { - indexOperations.delete(); + @Test + @Order(Integer.MAX_VALUE) + void cleanup() { + operations.indexOps(IndexCoordinates.of("*")).delete(); } @Test // DATAES-64 public void shouldCreateGivenDynamicSettingsForGivenIndex() { - // given - // delete , create and apply mapping in before method - - // then assertThat(indexOperations.exists()).isTrue(); Map map = indexOperations.getSettings(); assertThat(map.containsKey("index.number_of_replicas")).isTrue(); @@ -97,7 +101,6 @@ public void shouldCreateGivenDynamicSettingsForGivenIndex() { @Test // DATAES-64 public void shouldSearchOnGivenTokenizerUsingGivenDynamicSettingsForGivenIndex() { - // given DynamicSettingAndMappingEntity dynamicSettingAndMappingEntity1 = new DynamicSettingAndMappingEntity(); dynamicSettingAndMappingEntity1.setId(nextIdAsString()); dynamicSettingAndMappingEntity1.setName("test-setting1"); @@ -112,16 +115,14 @@ public void shouldSearchOnGivenTokenizerUsingGivenDynamicSettingsForGivenIndex() repository.save(dynamicSettingAndMappingEntity2); - // when NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.termQuery("email", dynamicSettingAndMappingEntity1.getEmail())).build(); - IndexCoordinates index = IndexCoordinates.of("test-index-dynamic-setting-and-mapping"); + IndexCoordinates index = IndexCoordinates.of(indexNameProvider.indexName()); long count = operations.count(searchQuery, DynamicSettingAndMappingEntity.class, index); SearchHits entityList = operations.search(searchQuery, DynamicSettingAndMappingEntity.class, index); - // then assertThat(count).isEqualTo(1L); assertThat(entityList).isNotNull().hasSize(1); assertThat(entityList.getSearchHit(0).getContent().getEmail()) @@ -131,13 +132,8 @@ public void shouldSearchOnGivenTokenizerUsingGivenDynamicSettingsForGivenIndex() @Test public void shouldGetMappingForGivenIndexAndType() { - // given - // delete , create and apply mapping in before method - - // when Map mapping = indexOperations.getMapping(); - // then Map properties = (Map) mapping.get("properties"); assertThat(mapping).isNotNull(); assertThat(properties).isNotNull(); @@ -176,9 +172,6 @@ public void shouldCreateMappingWithSpecifiedMappings() { @Test // DATAES-86 public void shouldCreateMappingWithUsingMappingAnnotation() { - // given - - // then Map mapping = indexOperations.getMapping(); Map properties = (Map) mapping.get("properties"); assertThat(mapping).isNotNull(); @@ -188,10 +181,7 @@ public void shouldCreateMappingWithUsingMappingAnnotation() { assertThat(emailProperties.get("analyzer")).isEqualTo("emailAnalyzer"); } - /** - * @author Mohsin Husen - */ - @Document(indexName = "test-index-dynamic-setting-and-mapping") + @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(settingPath = "/settings/test-settings.json") @Mapping(mappingPath = "/mappings/test-mappings.json") static class DynamicSettingAndMappingEntity { @@ -225,9 +215,6 @@ public void setEmail(String email) { } } - /** - * @author Mohsin Husen - */ public interface DynamicSettingAndMappingEntityRepository extends ElasticsearchRepository {} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/IndexNameProvider.java b/src/test/java/org/springframework/data/elasticsearch/utils/IndexNameProvider.java new file mode 100644 index 000000000..442d2681f --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/utils/IndexNameProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.utils; + +/** + * Class providing an index name with a prefix and a index number + * + * @author Peter-Josef Meisch + */ +public class IndexNameProvider { + private final String prefix; + private int idx = -1; + private String indexName; + + public IndexNameProvider() { + this("index-default"); + } + + public IndexNameProvider(String prefix) { + this.prefix = prefix; + increment(); + } + + public void increment() { + indexName = prefix + "-" + ++idx; + } + + public String indexName() { + return indexName; + } +} From 44e7ab63b04b2eb8ff69baa06d087c61da7a12d1 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 24 May 2021 14:47:47 +0200 Subject: [PATCH 078/776] Dependency cleanup and minor updates. Original Pull Request #1829 Closes #1828 --- pom.xml | 21 +------- .../core/convert/DateTimeConverters.java | 50 ------------------- ...icsearchConfigurationSupportUnitTests.java | 5 +- .../core/convert/DateTimeConvertersTests.java | 49 ------------------ .../elasticsearch/utils/IndexBuilder.java | 8 ++- 5 files changed, 10 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java delete mode 100644 src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java diff --git a/pom.xml b/pom.xml index 14734d8f2..75ec427af 100644 --- a/pom.xml +++ b/pom.xml @@ -18,10 +18,9 @@ https://github.com/spring-projects/spring-data-elasticsearch - 2.6 7.12.1 - 2.13.3 - 4.1.52.Final + 2.14.1 + 4.1.65.Final 2.6.0-SNAPSHOT 1.15.3 1.0.6.RELEASE @@ -132,22 +131,6 @@ test - - - commons-lang - commons-lang - ${commonslang} - test - - - - - joda-time - joda-time - ${jodatime} - true - - org.elasticsearch.client diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java deleted file mode 100644 index 48cd5ade1..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/DateTimeConverters.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.convert; - -import java.util.Date; - -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; -import org.joda.time.ReadableInstant; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; -import org.springframework.core.convert.converter.Converter; - -/** - * DateTimeConverters - * - * @author Rizwan Idrees - * @author Mohsin Husen - */ -public final class DateTimeConverters { - - private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC); - - public enum JavaDateConverter implements Converter { - INSTANCE; - - @Override - public String convert(Date source) { - if (source == null) { - return null; - } - - return formatter.print(source.getTime()); - } - - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java index b45a1da2b..75cf5812c 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java @@ -18,19 +18,18 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import org.springframework.context.annotation.Bean; import reactor.core.publisher.Mono; import java.util.Collection; import java.util.Collections; -import org.apache.commons.lang.ClassUtils; import org.elasticsearch.Version; import org.elasticsearch.action.main.MainResponse; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.cluster.ClusterName; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.data.elasticsearch.annotations.Document; @@ -52,7 +51,7 @@ public class ElasticsearchConfigurationSupportUnitTests { public void usesConfigClassPackageAsBaseMappingPackage() throws ClassNotFoundException { ElasticsearchConfigurationSupport configuration = new StubConfig(); - assertThat(configuration.getMappingBasePackages()).contains(ClassUtils.getPackageName(StubConfig.class)); + assertThat(configuration.getMappingBasePackages()).contains(StubConfig.class.getPackage().getName()); assertThat(configuration.getInitialEntitySet()).contains(Entity.class); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java deleted file mode 100644 index d7eb9a4b9..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/DateTimeConvertersTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.elasticsearch.core.convert; - -import static org.assertj.core.api.Assertions.*; - -import java.util.Calendar; -import java.util.TimeZone; - -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; -import org.junit.jupiter.api.Test; - -/** - * @author Rizwan Idrees - * @author Mohsin Husen - */ -public class DateTimeConvertersTests { - - @Test - public void testJavaDateConverterWithNullValue() { - assertThat(DateTimeConverters.JavaDateConverter.INSTANCE.convert(null)).isNull(); - } - - @Test - public void testJavaDateConverter() { - DateTime dateTime = new DateTime(2013, 1, 24, 6, 35, 0, DateTimeZone.UTC); - Calendar calendar = Calendar.getInstance(); - calendar.setTimeZone(TimeZone.getTimeZone("UTC")); - calendar.setTimeInMillis(dateTime.getMillis()); - - assertThat(DateTimeConverters.JavaDateConverter.INSTANCE.convert(calendar.getTime())) - .isEqualTo("2013-01-24T06:35:00.000Z"); - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/utils/IndexBuilder.java b/src/test/java/org/springframework/data/elasticsearch/utils/IndexBuilder.java index f365581e6..2fabc53b2 100644 --- a/src/test/java/org/springframework/data/elasticsearch/utils/IndexBuilder.java +++ b/src/test/java/org/springframework/data/elasticsearch/utils/IndexBuilder.java @@ -2,17 +2,21 @@ import java.lang.reflect.Field; -import org.apache.commons.lang.ArrayUtils; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.core.query.IndexQuery; /** * Created by akonczak on 02/12/2015. + * + * @author Peter-Josef Meisch */ public class IndexBuilder { public static IndexQuery buildIndex(Object object) { for (Field f : object.getClass().getDeclaredFields()) { - if (ArrayUtils.isNotEmpty(f.getAnnotationsByType(org.springframework.data.annotation.Id.class))) { + + if (AnnotationUtils.findAnnotation(f, Id.class) != null) { try { f.setAccessible(true); IndexQuery indexQuery = new IndexQuery(); From 6b0c4281a4207383e1ad2669a432f986a9c5307e Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 26 May 2021 22:34:25 +0200 Subject: [PATCH 079/776] Upgrade to Elasticsearch 7.13.0. Original Pull Request #1832 Closes #1831 --- pom.xml | 2 +- src/main/asciidoc/preface.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 75ec427af..8b54b61b3 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ https://github.com/spring-projects/spring-data-elasticsearch - 7.12.1 + 7.13.0 2.14.1 4.1.65.Final 2.6.0-SNAPSHOT diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index e6987b0fb..76e6d9665 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -34,7 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat [cols="^,^,^,^,^",options="header"] |=== | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot -| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.12.1 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] +| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.0 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] | 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x | 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x | Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x From 38dc7fb0fb493f0944985bcfd809ebefc3d12d45 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 2 Jun 2021 22:08:22 +0200 Subject: [PATCH 080/776] Adapt XNamedContents used by ReactiveElasticsearchClient for missing entries (terms and aggregations). Original Pull Request #1837 Closes #1834 --- .../client/util/NamedXContents.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/client/util/NamedXContents.java b/src/main/java/org/springframework/data/elasticsearch/client/util/NamedXContents.java index e43a0fcea..a94bd320c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/util/NamedXContents.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/util/NamedXContents.java @@ -20,6 +20,12 @@ import java.util.Map; import java.util.stream.Collectors; +import org.elasticsearch.client.analytics.InferencePipelineAggregationBuilder; +import org.elasticsearch.client.analytics.ParsedInference; +import org.elasticsearch.client.analytics.ParsedStringStats; +import org.elasticsearch.client.analytics.ParsedTopMetrics; +import org.elasticsearch.client.analytics.StringStatsAggregationBuilder; +import org.elasticsearch.client.analytics.TopMetricsAggregationBuilder; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.xcontent.ContextParser; import org.elasticsearch.common.xcontent.NamedXContentRegistry; @@ -44,6 +50,8 @@ import org.elasticsearch.search.aggregations.bucket.histogram.ParsedAutoDateHistogram; import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram; import org.elasticsearch.search.aggregations.bucket.histogram.ParsedHistogram; +import org.elasticsearch.search.aggregations.bucket.histogram.ParsedVariableWidthHistogram; +import org.elasticsearch.search.aggregations.bucket.histogram.VariableWidthHistogramAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.missing.MissingAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.missing.ParsedMissing; import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder; @@ -60,12 +68,7 @@ import org.elasticsearch.search.aggregations.bucket.range.RangeAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.sampler.InternalSampler; import org.elasticsearch.search.aggregations.bucket.sampler.ParsedSampler; -import org.elasticsearch.search.aggregations.bucket.terms.DoubleTerms; -import org.elasticsearch.search.aggregations.bucket.terms.LongTerms; -import org.elasticsearch.search.aggregations.bucket.terms.ParsedDoubleTerms; -import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms; -import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; -import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; +import org.elasticsearch.search.aggregations.bucket.terms.*; import org.elasticsearch.search.aggregations.metrics.*; import org.elasticsearch.search.aggregations.pipeline.*; import org.elasticsearch.search.suggest.Suggest; @@ -81,7 +84,7 @@ *

* Original implementation source {@link org.elasticsearch.client.RestHighLevelClient#getDefaultNamedXContents()} by * {@literal Elasticsearch} (https://www.elastic.co) licensed under the Apache - * License, Version 2.0. + * License, Version 2.0. The latest version used from Elasticsearch is 7.10.2. *

* Modified for usage with {@link ReactiveElasticsearchClient}. *

@@ -126,9 +129,13 @@ public static List getDefaultNamedXContents() { map.put(HistogramAggregationBuilder.NAME, (p, c) -> ParsedHistogram.fromXContent(p, (String) c)); map.put(DateHistogramAggregationBuilder.NAME, (p, c) -> ParsedDateHistogram.fromXContent(p, (String) c)); map.put(AutoDateHistogramAggregationBuilder.NAME, (p, c) -> ParsedAutoDateHistogram.fromXContent(p, (String) c)); + map.put(VariableWidthHistogramAggregationBuilder.NAME, + (p, c) -> ParsedVariableWidthHistogram.fromXContent(p, (String) c)); map.put(StringTerms.NAME, (p, c) -> ParsedStringTerms.fromXContent(p, (String) c)); map.put(LongTerms.NAME, (p, c) -> ParsedLongTerms.fromXContent(p, (String) c)); map.put(DoubleTerms.NAME, (p, c) -> ParsedDoubleTerms.fromXContent(p, (String) c)); + map.put(LongRareTerms.NAME, (p, c) -> ParsedLongRareTerms.fromXContent(p, (String) c)); + map.put(StringRareTerms.NAME, (p, c) -> ParsedStringRareTerms.fromXContent(p, (String) c)); map.put(MissingAggregationBuilder.NAME, (p, c) -> ParsedMissing.fromXContent(p, (String) c)); map.put(NestedAggregationBuilder.NAME, (p, c) -> ParsedNested.fromXContent(p, (String) c)); map.put(ReverseNestedAggregationBuilder.NAME, (p, c) -> ParsedReverseNested.fromXContent(p, (String) c)); @@ -142,10 +149,15 @@ public static List getDefaultNamedXContents() { map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c)); map.put(FiltersAggregationBuilder.NAME, (p, c) -> ParsedFilters.fromXContent(p, (String) c)); map.put(AdjacencyMatrixAggregationBuilder.NAME, (p, c) -> ParsedAdjacencyMatrix.fromXContent(p, (String) c)); + map.put(SignificantLongTerms.NAME, (p, c) -> ParsedSignificantLongTerms.fromXContent(p, (String) c)); + map.put(SignificantStringTerms.NAME, (p, c) -> ParsedSignificantStringTerms.fromXContent(p, (String) c)); map.put(ScriptedMetricAggregationBuilder.NAME, (p, c) -> ParsedScriptedMetric.fromXContent(p, (String) c)); map.put(IpRangeAggregationBuilder.NAME, (p, c) -> ParsedBinaryRange.fromXContent(p, (String) c)); map.put(TopHitsAggregationBuilder.NAME, (p, c) -> ParsedTopHits.fromXContent(p, (String) c)); map.put(CompositeAggregationBuilder.NAME, (p, c) -> ParsedComposite.fromXContent(p, (String) c)); + map.put(StringStatsAggregationBuilder.NAME, (p, c) -> ParsedStringStats.PARSER.parse(p, (String) c)); + map.put(TopMetricsAggregationBuilder.NAME, (p, c) -> ParsedTopMetrics.PARSER.parse(p, (String) c)); + map.put(InferencePipelineAggregationBuilder.NAME, (p, c) -> ParsedInference.fromXContent(p, (String) (c))); List entries = map.entrySet().stream().map( entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) .collect(Collectors.toList()); From b515f18b33d11ea8401723b66b21aec1060ff625 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 6 Jun 2021 16:55:35 +0200 Subject: [PATCH 081/776] Upgrade to Elasticsearch 7.13.1. Original Pull Request #1840 Closes #1839 --- pom.xml | 2 +- src/main/asciidoc/preface.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8b54b61b3..8f68f1df7 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ https://github.com/spring-projects/spring-data-elasticsearch - 7.13.0 + 7.13.1 2.14.1 4.1.65.Final 2.6.0-SNAPSHOT diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index 76e6d9665..f3277ed40 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -34,7 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat [cols="^,^,^,^,^",options="header"] |=== | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot -| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.0 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] +| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.1 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] | 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x | 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x | Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x From bc4c913a978cd99f556dbe89c1b39f9aeec3654c Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 6 Jun 2021 21:14:20 +0200 Subject: [PATCH 082/776] Make CompletionField annotation composable. Original Pull Request #1841 Closes #1836 --- .../annotations/CompletionField.java | 6 ++- .../ComposableAnnotationsUnitTest.java | 38 +++++++++++++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java index bcb26a984..aa9b96202 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java @@ -23,13 +23,15 @@ import java.lang.annotation.Target; /** - * Based on the reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html + * Based on the reference doc - + * https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters-completion.html * * @author Mewes Kochheim * @author Robert Gruendler + * @author Peter-Josef Meisch */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Documented @Inherited public @interface CompletionField { diff --git a/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java b/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java index 7ce4e0f1c..6c23ac96d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/annotations/ComposableAnnotationsUnitTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.annotation.AliasFor; import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.core.completion.Completion; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.index.MappingBuilder; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; @@ -73,12 +74,17 @@ void fieldAnnotationShouldBeComposable() { assertThat(property.storeNullValue()).isTrue(); } - @Test // DATAES-362 + @Test // DATAES-362, #1836 @DisplayName("should use composed Field annotations in MappingBuilder") void shouldUseComposedFieldAnnotationsInMappingBuilder() throws JSONException { String expected = "{\n" + // - " \"properties\":{\n" + // + " \"properties\": {\n" + // + " \"_class\": {\n" + // + " \"type\": \"keyword\",\n" + // + " \"index\": false,\n" + // + " \"doc_values\": false\n" + // + " },\n" + // " \"null-value\": {\n" + // " \"null_value\": \"NULL\"\n" + // " },\n" + // @@ -93,13 +99,21 @@ void shouldUseComposedFieldAnnotationsInMappingBuilder() throws JSONException { " \"type\": \"keyword\"\n" + // " }\n" + // " }\n" + // + " },\n" + // + " \"suggest\": {\n" + // + " \"type\": \"completion\",\n" + // + " \"max_input_length\": 50,\n" + // + " \"preserve_position_increments\": true,\n" + // + " \"preserve_separators\": true,\n" + // + " \"search_analyzer\": \"myAnalyzer\",\n" + // + " \"analyzer\": \"myAnalyzer\"\n" + // " }\n" + // " }\n" + // "}\n"; // String mapping = mappingBuilder.buildPropertyMapping(ComposedAnnotationEntity.class); - assertEquals(expected, mapping, false); + assertEquals(expected, mapping, true); } @Inherited @@ -142,12 +156,21 @@ void shouldUseComposedFieldAnnotationsInMappingBuilder() throws JSONException { public @interface TextKeywordField { } + @Inherited + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @CompletionField(analyzer = "myAnalyzer", searchAnalyzer = "myAnalyzer") + public @interface MyAnalyzerCompletionField { + } + @DocumentNoCreate(indexName = "test-no-create") static class ComposedAnnotationEntity { @Nullable @Id private String id; @Nullable @NullValueField(name = "null-value") private String nullValue; @Nullable @LocalDateField private LocalDate theDate; @Nullable @TextKeywordField private String multiField; + @Nullable @MyAnalyzerCompletionField private Completion suggest; @Nullable public String getId() { @@ -184,5 +207,14 @@ public String getMultiField() { public void setMultiField(@Nullable String multiField) { this.multiField = multiField; } + + @Nullable + public Completion getSuggest() { + return suggest; + } + + public void setSuggest(@Nullable Completion suggest) { + this.suggest = suggest; + } } } From e80737f76db64562d0d704db5a19b9fb97ea20b1 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 22 Jun 2021 15:17:52 +0200 Subject: [PATCH 083/776] Updated changelog. See #1813 --- src/main/resources/changelog.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index dd4d9d8d4..ca5eea544 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,12 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.1.10 (2021-06-22) +-------------------------------------- +* #1843 - Pageable results and @Query annotation. +* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. + + Changes in version 4.2.1 (2021-05-14) ------------------------------------- * #1811 - StringQuery execution crashes on return type `SearchPage`. @@ -1624,5 +1630,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From 3871d2d073873b412d600f811036cadaf53c2f8a Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 22 Jun 2021 15:51:35 +0200 Subject: [PATCH 084/776] Updated changelog. See #1814 --- src/main/resources/changelog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index ca5eea544..1ba11aae9 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,11 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.2.2 (2021-06-22) +------------------------------------- +* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. + + Changes in version 4.1.10 (2021-06-22) -------------------------------------- * #1843 - Pageable results and @Query annotation. @@ -1631,5 +1636,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From a16a87f3fa42c96566fac06e89aa703767a4842e Mon Sep 17 00:00:00 2001 From: Sascha Woo Date: Tue, 22 Jun 2021 20:51:47 +0200 Subject: [PATCH 085/776] Add missing hashCode and equals methods to JoinField. Original Pull Request #1847 Closes #1846 --- .../elasticsearch/core/join/JoinField.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java b/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java index 034905be0..11a1a0e60 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/join/JoinField.java @@ -15,11 +15,14 @@ */ package org.springframework.data.elasticsearch.core.join; +import java.util.Objects; + import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.lang.Nullable; /** * @author Subhobrata Dey + * @author Sascha Woo * @since 4.1 */ public class JoinField { @@ -54,4 +57,21 @@ public ID getParent() { public String getName() { return name; } + + @Override + public int hashCode() { + return Objects.hash(name, parent); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof JoinField)) { + return false; + } + JoinField other = (JoinField) obj; + return Objects.equals(name, other.name) && Objects.equals(parent, other.parent); + } } From 4ce1137b4e6191e9c525a970eee3ced7fa7c801d Mon Sep 17 00:00:00 2001 From: Sascha Woo Date: Fri, 2 Jul 2021 15:47:49 +0200 Subject: [PATCH 086/776] Improve NativeSearchQueryBuilder by adding convenience methods and modifying existing ones. Original Pull Request #1855 Closes #1854 --- .../core/query/NativeSearchQueryBuilder.java | 119 ++++++++++++++++-- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java index 86673df38..92eb86334 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/NativeSearchQueryBuilder.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.elasticsearch.action.search.SearchType; @@ -58,16 +59,16 @@ public class NativeSearchQueryBuilder { private final List> aggregationBuilders = new ArrayList<>(); private final List pipelineAggregationBuilders = new ArrayList<>(); @Nullable private HighlightBuilder highlightBuilder; - @Nullable private HighlightBuilder.Field[] highlightFields; + @Nullable private List highlightFields = new ArrayList<>(); private Pageable pageable = Pageable.unpaged(); - @Nullable private String[] fields; + @Nullable private List fields = new ArrayList<>(); @Nullable private SourceFilter sourceFilter; @Nullable private CollapseBuilder collapseBuilder; - @Nullable private List indicesBoost; + @Nullable private List indicesBoost = new ArrayList<>(); @Nullable private SearchTemplateRequestBuilder searchTemplateBuilder; private float minScore; private boolean trackScores; - @Nullable private Collection ids; + @Nullable private List ids = new ArrayList<>(); @Nullable private String route; @Nullable private SearchType searchType; @Nullable private IndicesOptions indicesOptions; @@ -87,11 +88,31 @@ public NativeSearchQueryBuilder withFilter(QueryBuilder filterBuilder) { return this; } + /** + * @deprecated use {@link #withSorts(SortBuilder...)} instead. + */ + @Deprecated public NativeSearchQueryBuilder withSort(SortBuilder sortBuilder) { this.sortBuilders.add(sortBuilder); return this; } + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withSorts(Collection> sortBuilders) { + this.sortBuilders.addAll(sortBuilders); + return this; + } + + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withSorts(SortBuilder... sortBuilders) { + Collections.addAll(this.sortBuilders, sortBuilders); + return this; + } + public NativeSearchQueryBuilder withScriptField(ScriptField scriptField) { this.scriptFields.add(scriptField); return this; @@ -110,6 +131,10 @@ public NativeSearchQueryBuilder withCollapseBuilder(@Nullable CollapseBuilder co return this; } + /** + * @deprecated use {@link #withAggregations(AbstractAggregationBuilder...)} instead. + */ + @Deprecated public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder aggregationBuilder) { this.aggregationBuilders.add(aggregationBuilder); return this; @@ -118,23 +143,73 @@ public NativeSearchQueryBuilder addAggregation(AbstractAggregationBuilder agg /** * @since 4.3 */ + public NativeSearchQueryBuilder withAggregations(Collection> aggregationBuilders) { + this.aggregationBuilders.addAll(aggregationBuilders); + return this; + } + + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withAggregations(AbstractAggregationBuilder... aggregationBuilders) { + Collections.addAll(this.aggregationBuilders, aggregationBuilders); + return this; + } + + /** + * @deprecated use {@link #withPipelineAggregations(PipelineAggregationBuilder...)} instead. + */ + @Deprecated public NativeSearchQueryBuilder addAggregation(PipelineAggregationBuilder pipelineAggregationBuilder) { this.pipelineAggregationBuilders.add(pipelineAggregationBuilder); return this; } + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withPipelineAggregations( + Collection pipelineAggregationBuilders) { + this.pipelineAggregationBuilders.addAll(pipelineAggregationBuilders); + return this; + } + + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withPipelineAggregations(PipelineAggregationBuilder... pipelineAggregationBuilders) { + Collections.addAll(this.pipelineAggregationBuilders, pipelineAggregationBuilders); + return this; + } + public NativeSearchQueryBuilder withHighlightBuilder(HighlightBuilder highlightBuilder) { this.highlightBuilder = highlightBuilder; return this; } public NativeSearchQueryBuilder withHighlightFields(HighlightBuilder.Field... highlightFields) { - this.highlightFields = highlightFields; + Collections.addAll(this.highlightFields, highlightFields); + return this; + } + + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withHighlightFields(Collection highlightFields) { + this.highlightFields.addAll(highlightFields); return this; } - public NativeSearchQueryBuilder withIndicesBoost(List indicesBoost) { - this.indicesBoost = indicesBoost; + public NativeSearchQueryBuilder withIndicesBoost(Collection indicesBoost) { + this.indicesBoost.addAll(indicesBoost); + return this; + } + + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withIndicesBoost(IndexBoost... indicesBoost) { + Collections.addAll(this.indicesBoost, indicesBoost); return this; } @@ -148,8 +223,16 @@ public NativeSearchQueryBuilder withPageable(Pageable pageable) { return this; } + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withFields(Collection fields) { + this.fields.addAll(fields); + return this; + } + public NativeSearchQueryBuilder withFields(String... fields) { - this.fields = fields; + Collections.addAll(this.fields, fields); return this; } @@ -174,7 +257,15 @@ public NativeSearchQueryBuilder withTrackScores(boolean trackScores) { } public NativeSearchQueryBuilder withIds(Collection ids) { - this.ids = ids; + this.ids.addAll(ids); + return this; + } + + /** + * @since 4.3 + */ + public NativeSearchQueryBuilder withIds(String... ids) { + Collections.addAll(this.ids, ids); return this; } @@ -223,14 +314,18 @@ public NativeSearchQueryBuilder withRescorerQuery(RescorerQuery rescorerQuery) { public NativeSearchQuery build() { - NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryBuilder, filterBuilder, sortBuilders, - highlightBuilder, highlightFields); + NativeSearchQuery nativeSearchQuery = new NativeSearchQuery( // + queryBuilder, // + filterBuilder, // + sortBuilders, // + highlightBuilder, // + highlightFields.toArray(new HighlightBuilder.Field[highlightFields.size()])); nativeSearchQuery.setPageable(pageable); nativeSearchQuery.setTrackScores(trackScores); if (fields != null) { - nativeSearchQuery.addFields(fields); + nativeSearchQuery.setFields(fields); } if (sourceFilter != null) { From 112ca59c95ef1e32e8a9b0a44f53feebd0726734 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 2 Jul 2021 18:07:23 +0200 Subject: [PATCH 087/776] Adapt pull request template. --- .github/PULL_REQUEST_TEMPLATE.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4ac6144ab..bcd78be66 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,12 +1,18 @@ - [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc). -- [ ] There is a ticket in the bug tracker for the project in our [issue tracker](https://github.com/spring-projects/spring-data-elasticsearch/issues). +- [ ] **There is a ticket in the bug tracker for the project in our [issue tracker](https://github. + com/spring-projects/spring-data-elasticsearch/issues)**. Add the issue number to the _Closes #issue-number_ line below - [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Don’t submit any formatting related changes. - [ ] You submit test cases (unit or integration tests) that back your changes. - [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only). + +Closes #issue-number From 6f84a1c589998ffb47b8a0f97446073ecaa396b8 Mon Sep 17 00:00:00 2001 From: Niklas Herder Date: Sat, 3 Jul 2021 13:42:56 +0200 Subject: [PATCH 088/776] Support collection parameters in @Query methods. Original Pull Request #1856 Closes #1858 --- .../repository/support/StringQueryUtil.java | 32 ++++++++-- .../ElasticsearchStringQueryUnitTests.java | 60 ++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java index 27ad71b47..89e4cc921 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java @@ -15,8 +15,10 @@ */ package org.springframework.data.elasticsearch.repository.support; +import java.util.Collection; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.repository.query.ParameterAccessor; @@ -24,6 +26,7 @@ /** * @author Peter-Josef Meisch + * @author Niklas Herder */ final public class StringQueryUtil { @@ -53,6 +56,28 @@ private static String getParameterWithIndex(ParameterAccessor accessor, int inde // noinspection ConstantConditions if (parameter != null) { + parameterValue = convert(parameter); + } + + return parameterValue; + + } + + private static String convert(Object parameter) { + if (Collection.class.isAssignableFrom(parameter.getClass())) { + Collection collectionParam = (Collection) parameter; + StringBuilder sb = new StringBuilder("["); + sb.append(collectionParam.stream().map(o -> { + if (o instanceof String) { + return "\"" + convert(o) + "\""; + } else { + return convert(o); + } + }).collect(Collectors.joining(","))); + sb.append("]"); + return sb.toString(); + } else { + String parameterValue = "null"; if (conversionService.canConvert(parameter.getClass(), String.class)) { String converted = conversionService.convert(parameter, String.class); @@ -62,11 +87,10 @@ private static String getParameterWithIndex(ParameterAccessor accessor, int inde } else { parameterValue = parameter.toString(); } - } - - parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\"")); - return parameterValue; + parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\"")); + return parameterValue; + } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java index d1c221b9f..0c351db0a 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.*; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -51,6 +52,7 @@ /** * @author Christoph Strobl * @author Peter-Josef Meisch + * @author Niklas Herder */ @ExtendWith(MockitoExtension.class) public class ElasticsearchStringQueryUnitTests { @@ -95,7 +97,42 @@ void shouldEscapeStringsInQueryParameters() throws Exception { .isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}"); } - private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args) + @Test // #1858 + @DisplayName("should only quote String query parameters") + void shouldOnlyEscapeStringQueryParameters() throws Exception { + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAge", Integer.valueOf(30)); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()).isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'age' : 30 } } } }"); + + } + + @Test // #1858 + @DisplayName("should only quote String collection query parameters") + void shouldOnlyEscapeStringCollectionQueryParameters() throws Exception { + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAgeIn", + new ArrayList<>(Arrays.asList(30, 35, 40))); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()) + .isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'age' : [30,35,40] } } } }"); + + } + + @Test // #1858 + @DisplayName("should escape Strings in collection query parameters") + void shouldEscapeStringsInCollectionsQueryParameters() throws Exception { + + final List another_string = Arrays.asList("hello \"Stranger\"", "Another string"); + List params = new ArrayList<>(another_string); + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByNameIn", params); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()).isEqualTo( + "{ 'bool' : { 'must' : { 'terms' : { 'name' : [\"hello \\\"Stranger\\\"\",\"Another string\"] } } } }"); + } + + private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args) throws NoSuchMethodException { Class[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); @@ -103,6 +140,7 @@ private org.springframework.data.elasticsearch.core.query.Query createQuery(Stri ElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod); return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args)); } + private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) { return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery()); } @@ -116,9 +154,18 @@ private ElasticsearchQueryMethod getQueryMethod(String name, Class... paramet private interface SampleRepository extends Repository { + @Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }") + List findByAge(Integer age); + + @Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }") + List findByAgeIn(ArrayList age); + @Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }") Person findByName(String name); + @Query("{ 'bool' : { 'must' : { 'terms' : { 'name' : ?0 } } } }") + Person findByNameIn(ArrayList names); + @Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)") Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, String arg7, String arg8, String arg9, String arg10, String arg11); @@ -131,16 +178,27 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String * @author Rizwan Idrees * @author Mohsin Husen * @author Artur Konczak + * @author Niklas Herder */ @Document(indexName = "test-index-person-query-unittest") static class Person { + @Nullable public int age; @Nullable @Id private String id; @Nullable private String name; @Nullable @Field(type = FieldType.Nested) private List car; @Nullable @Field(type = FieldType.Nested, includeInParent = true) private List books; + @Nullable + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + @Nullable public String getId() { return id; From 66d13444aa5515e6f78270001673f3685510fe5f Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 5 Jul 2021 20:20:11 +0200 Subject: [PATCH 089/776] Add TestContainers configuration. Original Pull Request #1861 Closes #1860 --- .../junit/jupiter/ClusterConnection.java | 27 +++++++++++++++++++ .../testcontainers-elasticsearch.properties | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 src/test/resources/testcontainers-elasticsearch.properties diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java index 4b6f8083b..e0d2bc430 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java @@ -15,6 +15,12 @@ */ package org.springframework.data.elasticsearch.junit.jupiter; +import java.io.InputStream; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,10 +83,13 @@ private ClusterConnectionInfo startElasticsearchContainer() { try { String elasticsearchVersion = VersionInfo.versionProperties() .getProperty(VersionInfo.VERSION_ELASTICSEARCH_CLIENT); + Map elasticsearchProperties = elasticsearchProperties(); String dockerImageName = ELASTICSEARCH_DEFAULT_IMAGE + ':' + elasticsearchVersion; LOGGER.debug("Docker image: {}", dockerImageName); + ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(dockerImageName); + elasticsearchContainer.withEnv(elasticsearchProperties); elasticsearchContainer.start(); return ClusterConnectionInfo.builder() // .withHostAndPort(elasticsearchContainer.getHost(), @@ -95,6 +104,24 @@ private ClusterConnectionInfo startElasticsearchContainer() { return null; } + private Map elasticsearchProperties() { + + String propertiesFile = "testcontainers-elasticsearch.properties"; + try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propertiesFile)) { + Properties props = new Properties(); + + if (inputStream != null) { + props.load(inputStream); + } + Map elasticsearchProperties = new LinkedHashMap<>(); + props.forEach((key, value) -> elasticsearchProperties.put(key.toString(), value.toString())); + return elasticsearchProperties; + } catch (Exception e) { + LOGGER.error("Cannot load " + propertiesFile); + } + return Collections.emptyMap(); + } + @Override public void close() { diff --git a/src/test/resources/testcontainers-elasticsearch.properties b/src/test/resources/testcontainers-elasticsearch.properties new file mode 100644 index 000000000..5bef8c62b --- /dev/null +++ b/src/test/resources/testcontainers-elasticsearch.properties @@ -0,0 +1,2 @@ +# needed as we do a DELETE /* at the end of the tests, will be requited from 8.0 on, produces a warning since 7.13 +action.destructive_requires_name=false From 271e1eee0d0f85e04ee09d8366c51d7d07238e2a Mon Sep 17 00:00:00 2001 From: Sascha Woo Date: Mon, 12 Jul 2021 20:15:35 +0200 Subject: [PATCH 090/776] Add native support for range field types by using a range object Original Pull Request #1863 Closes #1862 --- .../data/elasticsearch/core/Range.java | 444 ++++++++++++++++++ .../AbstractPersistentPropertyConverter.java | 40 ++ ...tractRangePersistentPropertyConverter.java | 121 +++++ .../DatePersistentPropertyConverter.java | 70 +++ .../DateRangePersistentPropertyConverter.java | 62 +++ .../MappingElasticsearchConverter.java | 5 +- ...umberRangePersistentPropertyConverter.java | 53 +++ .../TemporalPersistentPropertyConverter.java | 72 +++ ...poralRangePersistentPropertyConverter.java | 67 +++ ...sticsearchPersistentPropertyConverter.java | 19 +- ...SimpleElasticsearchPersistentProperty.java | 188 +++++--- .../core/CriteriaQueryMappingUnitTests.java | 2 +- .../data/elasticsearch/core/RangeTests.java | 177 +++++++ ...appingElasticsearchConverterUnitTests.java | 204 +++++++- ...sticsearchPersistentPropertyUnitTests.java | 4 +- 15 files changed, 1433 insertions(+), 95 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/Range.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractPersistentPropertyConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePersistentPropertyConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/DatePersistentPropertyConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/DateRangePersistentPropertyConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/NumberRangePersistentPropertyConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPersistentPropertyConverter.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalRangePersistentPropertyConverter.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java diff --git a/src/main/java/org/springframework/data/elasticsearch/core/Range.java b/src/main/java/org/springframework/data/elasticsearch/core/Range.java new file mode 100644 index 000000000..762fa758b --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/Range.java @@ -0,0 +1,444 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import java.util.Optional; + +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Simple value object to work with ranges and boundaries. + * + * @author Sascha Woo + * @since 4.3 + */ +public class Range { + + private final static Range UNBOUNDED = Range.of(Bound.unbounded(), Bound.UNBOUNDED); + + /** + * The lower bound of the range. + */ + private Bound lowerBound; + + /** + * The upper bound of the range. + */ + private Bound upperBound; + + /** + * Creates a new {@link Range} with inclusive bounds for both values. + * + * @param + * @param from must not be {@literal null}. + * @param to must not be {@literal null}. + * @return + */ + public static Range closed(T from, T to) { + return new Range<>(Bound.inclusive(from), Bound.inclusive(to)); + } + + /** + * Creates a new Range with the given value as sole member. + * + * @param + * @param value must not be {@literal null}. + * @return + * @see Range#closed(T, T) + */ + public static Range just(T value) { + return Range.closed(value, value); + } + + /** + * Creates a new left-open {@link Range}, i.e. left exclusive, right inclusive. + * + * @param + * @param from must not be {@literal null}. + * @param to must not be {@literal null}. + * @return + */ + public static Range leftOpen(T from, T to) { + return new Range<>(Bound.exclusive(from), Bound.inclusive(to)); + } + + /** + * Creates a left-unbounded {@link Range} (the left bound set to {@link Bound#unbounded()}) with the given right + * bound. + * + * @param + * @param to the right {@link Bound}, must not be {@literal null}. + * @return + */ + public static Range leftUnbounded(Bound to) { + return new Range<>(Bound.unbounded(), to); + } + + /** + * Creates a new {@link Range} with the given lower and upper bound. Prefer {@link #from(Bound)} for a more builder + * style API. + * + * @param lowerBound must not be {@literal null}. + * @param upperBound must not be {@literal null}. + * @see #from(Bound) + */ + public static Range of(Bound lowerBound, Bound upperBound) { + return new Range<>(lowerBound, upperBound); + } + + /** + * Creates a new {@link Range} with exclusive bounds for both values. + * + * @param + * @param from must not be {@literal null}. + * @param to must not be {@literal null}. + * @return + */ + public static Range open(T from, T to) { + return new Range<>(Bound.exclusive(from), Bound.exclusive(to)); + } + + /** + * Creates a new right-open {@link Range}, i.e. left inclusive, right exclusive. + * + * @param + * @param from must not be {@literal null}. + * @param to must not be {@literal null}. + * @return + */ + public static Range rightOpen(T from, T to) { + return new Range<>(Bound.inclusive(from), Bound.exclusive(to)); + } + + /** + * Creates a right-unbounded {@link Range} (the right bound set to {@link Bound#unbounded()}) with the given left + * bound. + * + * @param + * @param from the left {@link Bound}, must not be {@literal null}. + * @return + */ + public static Range rightUnbounded(Bound from) { + return new Range<>(from, Bound.unbounded()); + } + + /** + * Returns an unbounded {@link Range}. + * + * @return + */ + @SuppressWarnings("unchecked") + public static Range unbounded() { + return (Range) UNBOUNDED; + } + + private Range() { + } + + private Range(Bound lowerBound, Bound upperBound) { + + Assert.notNull(lowerBound, "Lower bound must not be null!"); + Assert.notNull(upperBound, "Upper bound must not be null!"); + + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + /** + * Returns whether the {@link Range} contains the given value. + * + * @param value must not be {@literal null}. + * @return + */ + public boolean contains(T value) { + + Assert.notNull(value, "Reference value must not be null!"); + Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!"); + + boolean greaterThanLowerBound = lowerBound.getValue() // + .map(it -> lowerBound.isInclusive() ? ((Comparable) it).compareTo(value) <= 0 + : ((Comparable) it).compareTo(value) < 0) // + .orElse(true); + + boolean lessThanUpperBound = upperBound.getValue() // + .map(it -> upperBound.isInclusive() ? ((Comparable) it).compareTo(value) >= 0 + : ((Comparable) it).compareTo(value) > 0) // + .orElse(true); + + return greaterThanLowerBound && lessThanUpperBound; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + + if (!(o instanceof Range)) { + return false; + } + + Range range = (Range) o; + + if (!ObjectUtils.nullSafeEquals(lowerBound, range.lowerBound)) { + return false; + } + + return ObjectUtils.nullSafeEquals(upperBound, range.upperBound); + } + + public Range.Bound getLowerBound() { + return this.lowerBound; + } + + public Range.Bound getUpperBound() { + return this.upperBound; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int result = ObjectUtils.nullSafeHashCode(lowerBound); + result = 31 * result + ObjectUtils.nullSafeHashCode(upperBound); + return result; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("%s-%s", lowerBound.toPrefixString(), upperBound.toSuffixString()); + } + + /** + * Value object representing a boundary. A boundary can either be {@link #unbounded() unbounded}, {@link #inclusive(T) + * including its value} or {@link #exclusive(T) its value}. + */ + public static final class Bound { + + @SuppressWarnings({ "rawtypes", "unchecked" }) // + private static final Bound UNBOUNDED = new Bound(Optional.empty(), true); + + private final Optional value; + private final boolean inclusive; + + /** + * Creates a boundary excluding {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound exclusive(double value) { + return exclusive((Double) value); + } + + /** + * Creates a boundary excluding {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound exclusive(float value) { + return exclusive((Float) value); + } + + /** + * Creates a boundary excluding {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound exclusive(int value) { + return exclusive((Integer) value); + } + + /** + * Creates a boundary excluding {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound exclusive(long value) { + return exclusive((Long) value); + } + + /** + * Creates a boundary excluding {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound exclusive(T value) { + + Assert.notNull(value, "Value must not be null!"); + Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!"); + return new Bound<>(Optional.of(value), false); + } + + /** + * Creates a boundary including {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound inclusive(double value) { + return inclusive((Double) value); + } + + /** + * Creates a boundary including {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound inclusive(float value) { + return inclusive((Float) value); + } + + /** + * Creates a boundary including {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound inclusive(int value) { + return inclusive((Integer) value); + } + + /** + * Creates a boundary including {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound inclusive(long value) { + return inclusive((Long) value); + } + + /** + * Creates a boundary including {@code value}. + * + * @param value must not be {@literal null}. + * @return + */ + public static Bound inclusive(T value) { + + Assert.notNull(value, "Value must not be null!"); + Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!"); + return new Bound<>(Optional.of(value), true); + } + + /** + * Creates an unbounded {@link Bound}. + */ + @SuppressWarnings("unchecked") + public static Bound unbounded() { + return (Bound) UNBOUNDED; + } + + private Bound(Optional value, boolean inclusive) { + this.value = value; + this.inclusive = inclusive; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + + if (!(o instanceof Bound)) { + return false; + } + + Bound bound = (Bound) o; + + if (inclusive != bound.inclusive) + return false; + + return ObjectUtils.nullSafeEquals(value, bound.value); + } + + public Optional getValue() { + return this.value; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int result = ObjectUtils.nullSafeHashCode(value); + result = 31 * result + (inclusive ? 1 : 0); + return result; + } + + /** + * Returns whether this boundary is bounded. + * + * @return + */ + public boolean isBounded() { + return value.isPresent(); + } + + public boolean isInclusive() { + return this.inclusive; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return value.map(Object::toString).orElse("unbounded"); + } + + String toPrefixString() { + + return getValue() // + .map(Object::toString) // + .map(it -> isInclusive() ? "[".concat(it) : "(".concat(it)) // + .orElse("unbounded"); + } + + String toSuffixString() { + + return getValue() // + .map(Object::toString) // + .map(it -> isInclusive() ? it.concat("]") : it.concat(")")) // + .orElse("unbounded"); + } + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractPersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractPersistentPropertyConverter.java new file mode 100644 index 000000000..44a06beb5 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractPersistentPropertyConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.util.Assert; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public abstract class AbstractPersistentPropertyConverter implements ElasticsearchPersistentPropertyConverter { + + private final PersistentProperty property; + + public AbstractPersistentPropertyConverter(PersistentProperty property) { + + Assert.notNull(property, "property must not be null."); + this.property = property; + } + + protected PersistentProperty getProperty() { + return property; + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePersistentPropertyConverter.java new file mode 100644 index 000000000..223f82f4f --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/AbstractRangePersistentPropertyConverter.java @@ -0,0 +1,121 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.data.elasticsearch.core.Range; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.util.Assert; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public abstract class AbstractRangePersistentPropertyConverter extends AbstractPersistentPropertyConverter { + + protected static final String LT_FIELD = "lt"; + protected static final String LTE_FIELD = "lte"; + protected static final String GT_FIELD = "gt"; + protected static final String GTE_FIELD = "gte"; + + public AbstractRangePersistentPropertyConverter(PersistentProperty property) { + super(property); + } + + @Override + public Object read(Object value) { + + Assert.notNull(value, "value must not be null."); + Assert.isInstanceOf(Map.class, value, "value must be instance of Map."); + + try { + Map source = (Map) value; + Range.Bound lowerBound; + Range.Bound upperBound; + + if (source.containsKey(GTE_FIELD)) { + lowerBound = Range.Bound.inclusive(parse((String) source.get(GTE_FIELD))); + } else if (source.containsKey(GT_FIELD)) { + lowerBound = Range.Bound.exclusive(parse((String) source.get(GT_FIELD))); + } else { + lowerBound = Range.Bound.unbounded(); + } + + if (source.containsKey(LTE_FIELD)) { + upperBound = Range.Bound.inclusive(parse((String) source.get(LTE_FIELD))); + } else if (source.containsKey(LT_FIELD)) { + upperBound = Range.Bound.exclusive(parse((String) source.get(LT_FIELD))); + } else { + upperBound = Range.Bound.unbounded(); + } + + return Range.of(lowerBound, upperBound); + + } catch (Exception e) { + throw new ConversionException( + String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e); + } + } + + @Override + public Object write(Object value) { + + Assert.notNull(value, "value must not be null."); + Assert.isInstanceOf(Range.class, value, "value must be instance of Range."); + + try { + Range range = (Range) value; + Range.Bound lowerBound = range.getLowerBound(); + Range.Bound upperBound = range.getUpperBound(); + Map target = new LinkedHashMap<>(); + + if (lowerBound.isBounded()) { + String lowerBoundValue = format(lowerBound.getValue().get()); + if (lowerBound.isInclusive()) { + target.put(GTE_FIELD, lowerBoundValue); + } else { + target.put(GT_FIELD, lowerBoundValue); + } + } + + if (upperBound.isBounded()) { + String upperBoundValue = format(upperBound.getValue().get()); + if (upperBound.isInclusive()) { + target.put(LTE_FIELD, upperBoundValue); + } else { + target.put(LT_FIELD, upperBoundValue); + } + } + + return target; + + } catch (Exception e) { + throw new ConversionException( + String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e); + } + } + + protected abstract String format(T value); + + protected Class getGenericType() { + return getProperty().getTypeInformation().getTypeArguments().get(0).getType(); + } + + protected abstract T parse(String value); + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePersistentPropertyConverter.java new file mode 100644 index 000000000..27b0bc635 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/DatePersistentPropertyConverter.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mapping.PersistentProperty; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public class DatePersistentPropertyConverter extends AbstractPersistentPropertyConverter { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatePersistentPropertyConverter.class); + + private final List dateConverters; + + public DatePersistentPropertyConverter(PersistentProperty property, + List dateConverters) { + + super(property); + this.dateConverters = dateConverters; + } + + @Override + public Object read(Object value) { + + String s = value.toString(); + + for (ElasticsearchDateConverter dateConverter : dateConverters) { + try { + return dateConverter.parse(s); + } catch (Exception e) { + LOGGER.trace(e.getMessage(), e); + } + } + + throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s, + getProperty().getActualType().getTypeName(), getProperty().getName())); + } + + @Override + public Object write(Object value) { + + try { + return dateConverters.get(0).format((Date) value); + } catch (Exception e) { + throw new ConversionException( + String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e); + } + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/DateRangePersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/DateRangePersistentPropertyConverter.java new file mode 100644 index 000000000..b628a79f1 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/DateRangePersistentPropertyConverter.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mapping.PersistentProperty; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public class DateRangePersistentPropertyConverter extends AbstractRangePersistentPropertyConverter { + + private static final Logger LOGGER = LoggerFactory.getLogger(DateRangePersistentPropertyConverter.class); + + private final List dateConverters; + + public DateRangePersistentPropertyConverter(PersistentProperty property, + List dateConverters) { + + super(property); + this.dateConverters = dateConverters; + } + + @Override + protected String format(Date value) { + return dateConverters.get(0).format(value); + } + + @Override + protected Date parse(String value) { + + for (ElasticsearchDateConverter converters : dateConverters) { + try { + return converters.parse(value); + } catch (Exception e) { + LOGGER.trace(e.getMessage(), e); + } + } + + throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value, + getGenericType().getTypeName(), getProperty().getName())); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index 86643e544..f69d4563a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -482,10 +482,7 @@ private Object propertyConverterRead(ElasticsearchPersistentProperty property, O } private Object convertOnRead(ElasticsearchPersistentPropertyConverter propertyConverter, Object source) { - if (String.class.isAssignableFrom(source.getClass())) { - source = propertyConverter.read((String) source); - } - return source; + return propertyConverter.read(source); } /** diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/NumberRangePersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/NumberRangePersistentPropertyConverter.java new file mode 100644 index 000000000..76b819e69 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/NumberRangePersistentPropertyConverter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import org.springframework.data.mapping.PersistentProperty; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public class NumberRangePersistentPropertyConverter extends AbstractRangePersistentPropertyConverter { + + public NumberRangePersistentPropertyConverter(PersistentProperty property) { + super(property); + } + + @Override + protected String format(Number number) { + return String.valueOf(number); + } + + @Override + protected Number parse(String value) { + + Class type = getGenericType(); + if (Integer.class.isAssignableFrom(type)) { + return Integer.valueOf(value); + } else if (Float.class.isAssignableFrom(type)) { + return Float.valueOf(value); + } else if (Long.class.isAssignableFrom(type)) { + return Long.valueOf(value); + } else if (Double.class.isAssignableFrom(type)) { + return Double.valueOf(value); + } + + throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value, + type.getTypeName(), getProperty().getName())); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPersistentPropertyConverter.java new file mode 100644 index 000000000..2cdd167e9 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalPersistentPropertyConverter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import java.time.temporal.TemporalAccessor; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mapping.PersistentProperty; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public class TemporalPersistentPropertyConverter extends AbstractPersistentPropertyConverter { + + private static final Logger LOGGER = LoggerFactory.getLogger(TemporalPersistentPropertyConverter.class); + + private final List dateConverters; + + public TemporalPersistentPropertyConverter(PersistentProperty property, + List dateConverters) { + + super(property); + this.dateConverters = dateConverters; + } + + @SuppressWarnings("unchecked") + @Override + public Object read(Object value) { + + String s = value.toString(); + Class actualType = getProperty().getActualType(); + + for (ElasticsearchDateConverter dateConverter : dateConverters) { + try { + return dateConverter.parse(s, (Class) actualType); + } catch (Exception e) { + LOGGER.trace(e.getMessage(), e); + } + } + + throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", s, + getProperty().getActualType().getTypeName(), getProperty().getName())); + } + + @Override + public Object write(Object value) { + + try { + return dateConverters.get(0).format((TemporalAccessor) value); + } catch (Exception e) { + throw new ConversionException( + String.format("Unable to convert value '%s' of property '%s'", value, getProperty().getName()), e); + } + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalRangePersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalRangePersistentPropertyConverter.java new file mode 100644 index 000000000..6111846eb --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/TemporalRangePersistentPropertyConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.convert; + +import java.time.temporal.TemporalAccessor; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.util.Assert; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public class TemporalRangePersistentPropertyConverter + extends AbstractRangePersistentPropertyConverter { + + private static final Logger LOGGER = LoggerFactory.getLogger(TemporalRangePersistentPropertyConverter.class); + + private final List dateConverters; + + public TemporalRangePersistentPropertyConverter(PersistentProperty property, + List dateConverters) { + + super(property); + + Assert.notEmpty(dateConverters, "dateConverters must not be empty."); + this.dateConverters = dateConverters; + } + + @Override + protected String format(TemporalAccessor temporal) { + return dateConverters.get(0).format(temporal); + } + + @Override + protected TemporalAccessor parse(String value) { + + Class type = getGenericType(); + for (ElasticsearchDateConverter converters : dateConverters) { + try { + return converters.parse(value, (Class) type); + } catch (Exception e) { + LOGGER.trace(e.getMessage(), e); + } + } + + throw new ConversionException(String.format("Unable to convert value '%s' to %s for property '%s'", value, + type.getTypeName(), getProperty().getName())); + } + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentPropertyConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentPropertyConverter.java index 0f3183975..630f949af 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentPropertyConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentPropertyConverter.java @@ -16,25 +16,26 @@ package org.springframework.data.elasticsearch.core.mapping; /** - * Interface defining methods to convert a property value to a String and back. + * Interface defining methods to convert a persistent property value to an elasticsearch property value and back. * * @author Peter-Josef Meisch + * @author Sascha Woo */ public interface ElasticsearchPersistentPropertyConverter { /** - * converts the property value to a String. + * Converts a persistent property value to an elasticsearch property value. * - * @param property the property value to convert, must not be {@literal null} - * @return String representation. + * @param value the persistent property value to convert, must not be {@literal null} + * @return The elasticsearch property value. */ - String write(Object property); + Object write(Object value); /** - * converts a property value from a String. + * Converts an elasticsearch property value to a persistent property value. * - * @param s the property to convert, must not be {@literal null} - * @return property value + * @param value the elasticsearch property value to convert, must not be {@literal null} + * @return The persistent property value. */ - Object read(String s); + Object read(Object value); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java index 1ecb9dcb1..d13dfc3c1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentProperty.java @@ -29,9 +29,14 @@ import org.springframework.data.elasticsearch.annotations.GeoPointField; import org.springframework.data.elasticsearch.annotations.GeoShapeField; import org.springframework.data.elasticsearch.annotations.MultiField; +import org.springframework.data.elasticsearch.core.Range; import org.springframework.data.elasticsearch.core.completion.Completion; -import org.springframework.data.elasticsearch.core.convert.ConversionException; +import org.springframework.data.elasticsearch.core.convert.DatePersistentPropertyConverter; +import org.springframework.data.elasticsearch.core.convert.DateRangePersistentPropertyConverter; import org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter; +import org.springframework.data.elasticsearch.core.convert.NumberRangePersistentPropertyConverter; +import org.springframework.data.elasticsearch.core.convert.TemporalPersistentPropertyConverter; +import org.springframework.data.elasticsearch.core.convert.TemporalRangePersistentPropertyConverter; import org.springframework.data.elasticsearch.core.geo.GeoJson; import org.springframework.data.elasticsearch.core.geo.GeoPoint; import org.springframework.data.elasticsearch.core.join.JoinField; @@ -39,6 +44,7 @@ import org.springframework.data.mapping.Association; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.Property; @@ -92,7 +98,7 @@ public SimpleElasticsearchPersistentProperty(Property property, throw new MappingException("@Field annotation must not be used on a @MultiField property."); } - initDateConverter(); + initPropertyConverter(); storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue(); } @@ -128,102 +134,127 @@ protected boolean hasExplicitFieldName() { } /** - * Initializes an {@link ElasticsearchPersistentPropertyConverter} if this property is annotated as a Field with type - * {@link FieldType#Date}, has a {@link DateFormat} set and if the type of the property is one of the Java8 temporal - * classes or java.util.Date. + * Initializes the property converter for this {@link PersistentProperty}, if any. */ - private void initDateConverter() { - Field field = findAnnotation(Field.class); + private void initPropertyConverter() { Class actualType = getActualTypeOrNull(); - if (actualType == null) { return; } - boolean isTemporalAccessor = TemporalAccessor.class.isAssignableFrom(actualType); - boolean isDate = Date.class.isAssignableFrom(actualType); + Field field = findAnnotation(Field.class); + if (field == null) { + return; + } - if (field != null && (field.type() == FieldType.Date || field.type() == FieldType.Date_Nanos) - && (isTemporalAccessor || isDate)) { + switch (field.type()) { + case Date: + case Date_Nanos: { + List dateConverters = getDateConverters(field, actualType); + if (dateConverters.isEmpty()) { + LOGGER.warn("No date formatters configured for property '{}'.", getName()); + return; + } - DateFormat[] dateFormats = field.format(); - String[] dateFormatPatterns = field.pattern(); + if (TemporalAccessor.class.isAssignableFrom(actualType)) { + propertyConverter = new TemporalPersistentPropertyConverter(this, dateConverters); + } else if (Date.class.isAssignableFrom(actualType)) { + propertyConverter = new DatePersistentPropertyConverter(this, dateConverters); + } else { + LOGGER.warn("Unsupported type '{}' for date property '{}'.", actualType, getName()); + } + break; + } + case Date_Range: { + if (!Range.class.isAssignableFrom(actualType)) { + return; + } - String property = getOwner().getType().getSimpleName() + "." + getName(); + List dateConverters = getDateConverters(field, actualType); + if (dateConverters.isEmpty()) { + LOGGER.warn("No date formatters configured for property '{}'.", getName()); + return; + } - if (dateFormats.length == 0 && dateFormatPatterns.length == 0) { - LOGGER.warn( - "Property '{}' has @Field type '{}' but has no built-in format or custom date pattern defined. Make sure you have a converter registered for type {}.", - property, field.type().name(), actualType.getSimpleName()); - return; + Class genericType = getTypeInformation().getTypeArguments().get(0).getType(); + if (TemporalAccessor.class.isAssignableFrom(genericType)) { + propertyConverter = new TemporalRangePersistentPropertyConverter(this, dateConverters); + } else if (Date.class.isAssignableFrom(genericType)) { + propertyConverter = new DateRangePersistentPropertyConverter(this, dateConverters); + } else { + LOGGER.warn("Unsupported generic type '{}' for date range property '{}'.", genericType, getName()); + } + break; } + case Integer_Range: + case Float_Range: + case Long_Range: + case Double_Range: { + if (!Range.class.isAssignableFrom(actualType)) { + return; + } - List converters = new ArrayList<>(); - - // register converters for built-in formats - for (DateFormat dateFormat : dateFormats) { - switch (dateFormat) { - case none: - case custom: - break; - case weekyear: - case weekyear_week: - case weekyear_week_day: - LOGGER.warn("No default converter available for '{}' and date format '{}'. Use a custom converter instead.", - actualType.getName(), dateFormat.name()); - break; - default: - converters.add(ElasticsearchDateConverter.of(dateFormat)); - break; + Class genericType = getTypeInformation().getTypeArguments().get(0).getType(); + if ((field.type() == FieldType.Integer_Range && !Integer.class.isAssignableFrom(genericType)) + || (field.type() == FieldType.Float_Range && !Float.class.isAssignableFrom(genericType)) + || (field.type() == FieldType.Long_Range && !Long.class.isAssignableFrom(genericType)) + || (field.type() == FieldType.Double_Range && !Double.class.isAssignableFrom(genericType))) { + LOGGER.warn("Unsupported generic type '{}' for range field type '{}' of property '{}'.", genericType, + field.type(), getName()); + return; } + + propertyConverter = new NumberRangePersistentPropertyConverter(this); + break; + } + case Ip_Range: { + // TODO currently unsupported, needs a library like https://seancfoley.github.io/IPAddress/ } + default: + break; + } + } - // register converters for custom formats - for (String dateFormatPattern : dateFormatPatterns) { - if (!StringUtils.hasText(dateFormatPattern)) { - throw new MappingException(String.format("Date pattern of property '%s' must not be empty", property)); - } - converters.add(ElasticsearchDateConverter.of(dateFormatPattern)); + private List getDateConverters(Field field, Class actualType) { + + DateFormat[] dateFormats = field.format(); + String[] dateFormatPatterns = field.pattern(); + List converters = new ArrayList<>(); + + if (dateFormats.length == 0 && dateFormatPatterns.length == 0) { + LOGGER.warn( + "Property '{}' has @Field type '{}' but has no built-in format or custom date pattern defined. Make sure you have a converter registered for type {}.", + getName(), field.type().name(), actualType.getSimpleName()); + return converters; + } + + // register converters for built-in formats + for (DateFormat dateFormat : dateFormats) { + switch (dateFormat) { + case none: + case custom: + break; + case weekyear: + case weekyear_week: + case weekyear_week_day: + LOGGER.warn("No default converter available for '{}' and date format '{}'. Use a custom converter instead.", + actualType.getName(), dateFormat.name()); + break; + default: + converters.add(ElasticsearchDateConverter.of(dateFormat)); + break; } + } - if (!converters.isEmpty()) { - propertyConverter = new ElasticsearchPersistentPropertyConverter() { - final List dateConverters = converters; - - @SuppressWarnings("unchecked") - @Override - public Object read(String s) { - for (ElasticsearchDateConverter dateConverter : dateConverters) { - try { - if (isTemporalAccessor) { - return dateConverter.parse(s, (Class) actualType); - } else { // must be date - return dateConverter.parse(s); - } - } catch (Exception e) { - LOGGER.trace(e.getMessage(), e); - } - } - - throw new ConversionException(String - .format("Unable to parse date value '%s' of property '%s' with configured converters", s, property)); - } - - @Override - public String write(Object property) { - ElasticsearchDateConverter dateConverter = dateConverters.get(0); - if (isTemporalAccessor && TemporalAccessor.class.isAssignableFrom(property.getClass())) { - return dateConverter.format((TemporalAccessor) property); - } else if (isDate && Date.class.isAssignableFrom(property.getClass())) { - return dateConverter.format((Date) property); - } else { - return property.toString(); - } - } - }; + for (String dateFormatPattern : dateFormatPatterns) { + if (!StringUtils.hasText(dateFormatPattern)) { + throw new MappingException(String.format("Date pattern of property '%s' must not be empty", getName())); } + converters.add(ElasticsearchDateConverter.of(dateFormatPattern)); } + + return converters; } @SuppressWarnings("ConstantConditions") @@ -303,4 +334,5 @@ public boolean isJoinFieldProperty() { public boolean isCompletionProperty() { return getActualType() == Completion.class; } + } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java index 294e99866..cb63c81ee 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/CriteriaQueryMappingUnitTests.java @@ -196,7 +196,7 @@ void shouldMapNamesAndConvertValuesInCriteriaQueryForSubCriteriaWithDate() throw CriteriaQuery criteriaQuery = new CriteriaQuery( // Criteria.or().subCriteria(Criteria.where("birthDate") // .between(LocalDate.of(1989, 11, 9), LocalDate.of(1990, 11, 9))) // - .subCriteria(Criteria.where("createdDate").is(383745721653L)) // + .subCriteria(Criteria.where("createdDate").is(new Date(383745721653L))) // ); // mapped field name and converted parameter diff --git a/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java b/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java new file mode 100644 index 000000000..8d631f08b --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java @@ -0,0 +1,177 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +/** + * @author Sascha Woo + * @since 4.3 + */ +public class RangeTests { + + @Test + public void shouldContainsLocalDate() { + + // given + // when + // then + assertThat(Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1)).contains(LocalDate.of(2021, 1, 10))) + .isTrue(); + } + + @Test + public void shouldEqualToSameRange() { + + // given + Range range1 = Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1)); + Range range2 = Range.open(LocalDate.of(2021, 1, 1), LocalDate.of(2021, 2, 1)); + // when + // then + assertThat(range1).isEqualTo(range2); + } + + @Test + public void shouldHaveClosedBoundaries() { + + // given + Range range = Range.closed(1, 3); + // when + // then + assertThat(range.contains(1)).isTrue(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isTrue(); + } + + @Test + public void shouldHaveJustOneValue() { + + // given + Range range = Range.just(2); + // when + // then + assertThat(range.contains(1)).isFalse(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isFalse(); + } + + @Test + public void shouldHaveLeftOpenBoundary() { + + // given + Range range = Range.leftOpen(1, 3); + // when + // then + assertThat(range.contains(1)).isFalse(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isTrue(); + } + + @Test + public void shouldHaveLeftUnboundedAndRightExclusive() { + + // given + Range range = Range.leftUnbounded(Range.Bound.exclusive(3)); + // when + // then + assertThat(range.contains(0)).isTrue(); + assertThat(range.contains(1)).isTrue(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isFalse(); + } + + @Test + public void shouldHaveLeftUnboundedAndRightInclusive() { + + // given + Range range = Range.leftUnbounded(Range.Bound.inclusive(3)); + // when + // then + assertThat(range.contains(0)).isTrue(); + assertThat(range.contains(1)).isTrue(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isTrue(); + } + + @Test + public void shouldHaveOpenBoundaries() { + + // given + Range range = Range.open(1, 3); + // when + // then + assertThat(range.contains(1)).isFalse(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isFalse(); + } + + @Test + public void shouldHaveRightOpenBoundary() { + + // given + Range range = Range.rightOpen(1, 3); + // when + // then + assertThat(range.contains(1)).isTrue(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isFalse(); + } + + @Test + public void shouldHaveRightUnboundedAndLeftExclusive() { + + // given + Range range = Range.rightUnbounded(Range.Bound.exclusive(1)); + // when + // then + assertThat(range.contains(1)).isFalse(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isTrue(); + assertThat(range.contains(4)).isTrue(); + } + + @Test + public void shouldHaveRightUnboundedAndLeftInclusive() { + + // given + Range range = Range.rightUnbounded(Range.Bound.inclusive(1)); + // when + // then + assertThat(range.contains(1)).isTrue(); + assertThat(range.contains(2)).isTrue(); + assertThat(range.contains(3)).isTrue(); + assertThat(range.contains(4)).isTrue(); + } + + @Test + public void shouldThrowExceptionIfNotComparable() { + + // given + // when + Throwable thrown = catchThrowable(() -> { + Range.just(Arrays.asList("test")); + }); + // then + assertThat(thrown).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("value must implements Comparable!"); + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 1d5e456eb..4e0a469d4 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -20,9 +20,15 @@ import static org.skyscreamer.jsonassert.JSONAssert.*; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -46,6 +52,7 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.GeoPointField; +import org.springframework.data.elasticsearch.core.Range; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.geo.GeoJsonEntity; import org.springframework.data.elasticsearch.core.geo.GeoJsonGeometryCollection; @@ -882,6 +889,200 @@ void shouldWriteNullValueIfConfigured() throws JSONException { assertEquals(expected, document.toJson(), false); } + @Nested + class RangeTests { + + static final String JSON = "{" + + "\"_class\":\"org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$RangeTests$RangeEntity\"," + + "\"integerRange\":{\"gt\":\"1\",\"lt\":\"10\"}," // + + "\"floatRange\":{\"gte\":\"1.2\",\"lte\":\"2.5\"}," // + + "\"longRange\":{\"gt\":\"2\",\"lte\":\"5\"}," // + + "\"doubleRange\":{\"gte\":\"3.2\",\"lt\":\"7.4\"}," // + + "\"dateRange\":{\"gte\":\"1970-01-01T00:00:00.000Z\",\"lte\":\"1970-01-01T01:00:00.000Z\"}," // + + "\"localDateRange\":{\"gte\":\"2021-07-06\"}," // + + "\"localTimeRange\":{\"gte\":\"00:30:00.000\",\"lt\":\"02:30:00.000\"}," // + + "\"localDateTimeRange\":{\"gt\":\"2021-01-01T00:30:00.000\",\"lt\":\"2021-01-01T02:30:00.000\"}," // + + "\"offsetTimeRange\":{\"gte\":\"00:30:00.000+02:00\",\"lt\":\"02:30:00.000+02:00\"}," // + + "\"zonedDateTimeRange\":{\"gte\":\"2021-01-01T00:30:00.000+02:00\",\"lte\":\"2021-01-01T00:30:00.000+02:00\"}," // + + "\"nullRange\":null}"; + + @Test + public void shouldReadRanges() throws JSONException { + + // given + Document source = Document.parse(JSON); + + // when + RangeEntity entity = mappingElasticsearchConverter.read(RangeEntity.class, source); + + // then + assertThat(entity) // + .isNotNull() // + .satisfies(e -> { + assertThat(e.getIntegerRange()).isEqualTo(Range.open(1, 10)); + assertThat(e.getFloatRange()).isEqualTo(Range.closed(1.2f, 2.5f)); + assertThat(e.getLongRange()).isEqualTo(Range.leftOpen(2l, 5l)); + assertThat(e.getDoubleRange()).isEqualTo(Range.rightOpen(3.2d, 7.4d)); + assertThat(e.getDateRange()).isEqualTo(Range.closed(new Date(0), new Date(60 * 60 * 1000))); + assertThat(e.getLocalDateRange()) + .isEqualTo(Range.rightUnbounded(Range.Bound.inclusive(LocalDate.of(2021, 7, 6)))); + assertThat(e.getLocalTimeRange()).isEqualTo(Range.rightOpen(LocalTime.of(0, 30), LocalTime.of(2, 30))); + assertThat(e.getLocalDateTimeRange()) + .isEqualTo(Range.open(LocalDateTime.of(2021, 1, 1, 0, 30), LocalDateTime.of(2021, 1, 1, 2, 30))); + assertThat(e.getOffsetTimeRange()) + .isEqualTo(Range.rightOpen(OffsetTime.of(LocalTime.of(0, 30), ZoneOffset.ofHours(2)), + OffsetTime.of(LocalTime.of(2, 30), ZoneOffset.ofHours(2)))); + assertThat(e.getZonedDateTimeRange()).isEqualTo( + Range.just(ZonedDateTime.of(LocalDate.of(2021, 1, 1), LocalTime.of(0, 30), ZoneOffset.ofHours(2)))); + assertThat(e.getNullRange()).isNull(); + }); + } + + @Test + public void shouldWriteRanges() throws JSONException { + + // given + Document source = Document.parse(JSON); + RangeEntity entity = new RangeEntity(); + entity.setIntegerRange(Range.open(1, 10)); + entity.setFloatRange(Range.closed(1.2f, 2.5f)); + entity.setLongRange(Range.leftOpen(2l, 5l)); + entity.setDoubleRange(Range.rightOpen(3.2d, 7.4d)); + entity.setDateRange(Range.closed(new Date(0), new Date(60 * 60 * 1000))); + entity.setLocalDateRange(Range.rightUnbounded(Range.Bound.inclusive(LocalDate.of(2021, 7, 6)))); + entity.setLocalTimeRange(Range.rightOpen(LocalTime.of(0, 30), LocalTime.of(2, 30))); + entity + .setLocalDateTimeRange(Range.open(LocalDateTime.of(2021, 1, 1, 0, 30), LocalDateTime.of(2021, 1, 1, 2, 30))); + entity.setOffsetTimeRange(Range.rightOpen(OffsetTime.of(LocalTime.of(0, 30), ZoneOffset.ofHours(2)), + OffsetTime.of(LocalTime.of(2, 30), ZoneOffset.ofHours(2)))); + entity.setZonedDateTimeRange( + Range.just(ZonedDateTime.of(LocalDate.of(2021, 1, 1), LocalTime.of(0, 30), ZoneOffset.ofHours(2)))); + entity.setNullRange(null); + + // when + Document document = mappingElasticsearchConverter.mapObject(entity); + + // then + assertThat(document).isEqualTo(source); + } + + @org.springframework.data.elasticsearch.annotations.Document(indexName = "test-index-range-entity-mapper") + class RangeEntity { + + @Id private String id; + @Field(type = FieldType.Integer_Range) private Range integerRange; + @Field(type = FieldType.Float_Range) private Range floatRange; + @Field(type = FieldType.Long_Range) private Range longRange; + @Field(type = FieldType.Double_Range) private Range doubleRange; + @Field(type = FieldType.Date_Range) private Range dateRange; + @Field(type = FieldType.Date_Range, format = DateFormat.year_month_day) private Range localDateRange; + @Field(type = FieldType.Date_Range, + format = DateFormat.hour_minute_second_millis) private Range localTimeRange; + @Field(type = FieldType.Date_Range, + format = DateFormat.date_hour_minute_second_millis) private Range localDateTimeRange; + @Field(type = FieldType.Date_Range, format = DateFormat.time) private Range offsetTimeRange; + @Field(type = FieldType.Date_Range) private Range zonedDateTimeRange; + @Field(type = FieldType.Date_Range, storeNullValue = true) private Range nullRange; + + public String getId() { + return id; + } + + public Range getIntegerRange() { + return integerRange; + } + + public Range getFloatRange() { + return floatRange; + } + + public Range getLongRange() { + return longRange; + } + + public Range getDoubleRange() { + return doubleRange; + } + + public Range getDateRange() { + return dateRange; + } + + public Range getLocalDateRange() { + return localDateRange; + } + + public Range getLocalTimeRange() { + return localTimeRange; + } + + public Range getLocalDateTimeRange() { + return localDateTimeRange; + } + + public Range getOffsetTimeRange() { + return offsetTimeRange; + } + + public Range getZonedDateTimeRange() { + return zonedDateTimeRange; + } + + public Range getNullRange() { + return nullRange; + } + + public void setId(String id) { + this.id = id; + } + + public void setIntegerRange(Range integerRange) { + this.integerRange = integerRange; + } + + public void setFloatRange(Range floatRange) { + this.floatRange = floatRange; + } + + public void setLongRange(Range longRange) { + this.longRange = longRange; + } + + public void setDoubleRange(Range doubleRange) { + this.doubleRange = doubleRange; + } + + public void setDateRange(Range dateRange) { + this.dateRange = dateRange; + } + + public void setLocalDateRange(Range localDateRange) { + this.localDateRange = localDateRange; + } + + public void setLocalTimeRange(Range localTimeRange) { + this.localTimeRange = localTimeRange; + } + + public void setLocalDateTimeRange(Range localDateTimeRange) { + this.localDateTimeRange = localDateTimeRange; + } + + public void setOffsetTimeRange(Range offsetTimeRange) { + this.offsetTimeRange = offsetTimeRange; + } + + public void setZonedDateTimeRange(Range zonedDateTimeRange) { + this.zonedDateTimeRange = zonedDateTimeRange; + } + + public void setNullRange(Range nullRange) { + this.nullRange = nullRange; + } + + } + } + @Nested class GeoJsonUnitTests { private GeoJsonEntity entity; @@ -2074,7 +2275,8 @@ public void setSaved(@Nullable String saved) { } } - private static class ElectricCar extends Car {} + private static class ElectricCar extends Car { + } private static class PersonWithCars { @Id @Nullable String id; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java index 8ac934880..fcf02f7c5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentPropertyUnitTests.java @@ -114,7 +114,7 @@ void shouldConvertFromLocalDate() { ElasticsearchPersistentProperty persistentProperty = persistentEntity.getRequiredPersistentProperty("localDate"); LocalDate localDate = LocalDate.of(2019, 12, 27); - String converted = persistentProperty.getPropertyConverter().write(localDate); + String converted = persistentProperty.getPropertyConverter().write(localDate).toString(); assertThat(converted).isEqualTo("27.12.2019"); } @@ -138,7 +138,7 @@ void shouldConvertFromLegacyDate() { .from(ZonedDateTime.of(LocalDateTime.of(2020, 4, 19, 19, 44), ZoneId.of("UTC"))); Date legacyDate = calendar.getTime(); - String converted = persistentProperty.getPropertyConverter().write(legacyDate); + String converted = persistentProperty.getPropertyConverter().write(legacyDate).toString(); assertThat(converted).isEqualTo("20200419T194400.000Z"); } From 7c340bc9a25a9532a369356d390eb6a8aa23d869 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 12 Jul 2021 21:22:01 +0200 Subject: [PATCH 091/776] Polishing and documentation. --- ...elasticsearch-migration-guide-4.2-4.3.adoc | 3 +- .../elasticsearch-object-mapping.adoc | 81 +++++++++++++++---- .../data/elasticsearch/core/Range.java | 24 +++--- .../data/elasticsearch/core/RangeTests.java | 6 +- ...appingElasticsearchConverterUnitTests.java | 3 +- 5 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc index ff3467ac5..4012e4604 100644 --- a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc @@ -11,5 +11,4 @@ This section describes breaking changes from version 4.2.x to 4.3.x and how remo === Handling of field and sourceFilter properties of Query -Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`. This was not correct, as these are different things for Elasticsearch. This has been corrected. As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's -`_source' and should be changed to use the `sourceFilter`. +Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`. This was not correct, as these are different things for Elasticsearch. This has been corrected. As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`. diff --git a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc index 2a90e4883..5c249ad97 100644 --- a/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc +++ b/src/main/asciidoc/reference/elasticsearch-object-mapping.adoc @@ -47,8 +47,9 @@ Constructor arguments are mapped by name to the key values in the retrieved Docu * `@Field`: Applied at the field level and defines properties of the field, most of the attributes map to the respective https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html[Elasticsearch Mapping] definitions (the following list is not complete, check the annotation Javadoc for a complete reference): ** `name`: The name of the field as it will be represented in the Elasticsearch document, if not set, the Java field name is used. ** `type`: The field type, can be one of _Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type_. -See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]. If the field type is not specified, it defaults to `FieldType.Auto`. This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored -(check the Elasticsearch documentation for dynamic mapping rules). +See https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html[Elasticsearch Mapping Types]. +If the field type is not specified, it defaults to `FieldType.Auto`. +This means, that no mapping entry is written for the property and that Elasticsearch will add a mapping entry dynamically when the first data for this property is stored (check the Elasticsearch documentation for dynamic mapping rules). ** `format`: One or more built-in date formats, see the next section <>. ** `pattern`: One or more custom date formats, see the next section <>. ** `store`: Flag whether the original field value should be store in Elasticsearch, default value is _false_. @@ -61,21 +62,20 @@ The mapping metadata infrastructure is defined in a separate spring-data-commons [[elasticsearch.mapping.meta-model.date-formats]] ==== Date format mapping -Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation -of type `FieldType.Date` or a custom converter must be registered for this type. This paragraph describes the use of +Properties that derive from `TemporalAccessor` or are of type `java.util.Date` must either have a `@Field` annotation of type `FieldType.Date` or a custom converter must be registered for this type. +This paragraph describes the use of `FieldType.Date`. -There are two attributes of the `@Field` annotation that define which date format information is written to the -mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats]) +There are two attributes of the `@Field` annotation that define which date format information is written to the mapping (also see https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats[Elasticsearch Built In Formats] and https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#custom-date-formats[Elasticsearch Custom Date Formats]) -The `format` attributes is used to define at least one of the predefined formats. If it is not defined, then a -default value of __date_optional_time_ and _epoch_millis_ is used. +The `format` attributes is used to define at least one of the predefined formats. +If it is not defined, then a default value of __date_optional_time_ and _epoch_millis_ is used. -The `pattern` attribute can be used to add additional custom format strings. If you want to use only custom date formats, you must set the `format` property to empty `{}`. +The `pattern` attribute can be used to add additional custom format strings. +If you want to use only custom date formats, you must set the `format` property to empty `{}`. The following table shows the different attributes and the mapping created from their values: - [cols=2*,options=header] |=== | annotation @@ -101,12 +101,59 @@ The following table shows the different attributes and the mapping created from NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_. This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7]. +==== Range types + +When a field is annotated with a type of one of _Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range,_ or _Ip_Range_ the field must be an instance of a class that will be mapped to an Elasticsearch range, for example: + +==== +[source,java] +---- +class SomePersonData { + + @Field(type = FieldType.Integer_Range) + private ValidAge validAge; + + // getter and setter +} + +class ValidAge { + @Field(name="gte") + private Integer from; + + @Field(name="lte") + private Integer to; + + // getter and setter +} +---- +==== + +As an alternative Spring Data Elasticsearch provides a `Range` class so that the previous example can be written as: + +==== +[source,java] +---- +class SomePersonData { + + @Field(type = FieldType.Integer_Range) + private Range validAge; + + // getter and setter +} +---- +==== + +Supported classes for the type `` are `Integer`, `Long`, `Float`, `Double`, `Date` and classes that implement the +`TemporalAccessor` interface. + ==== Mapped field names -Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch. This can be changed for individual field by using the `@Field` annotation on that property. +Without further configuration, Spring Data Elasticsearch will use the property name of an object as field name in Elasticsearch. +This can be changed for individual field by using the `@Field` annotation on that property. -It is also possible to define a `FieldNamingStrategy` in the configuration of the client (<>). If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch. A `FieldNamingStrategy` applies to all entities; it can be overwritten by -setting a specific name with `@Field` on a property. +It is also possible to define a `FieldNamingStrategy` in the configuration of the client (<>). +If for example a `SnakeCaseFieldNamingStrategy` is configured, the property _sampleProperty_ of the object would be mapped to _sample_property_ in Elasticsearch. +A `FieldNamingStrategy` applies to all entities; it can be overwritten by setting a specific name with `@Field` on a property. [[elasticsearch.mapping.meta-model.rules]] === Mapping Rules @@ -171,11 +218,13 @@ NOTE: Type hints will not be written for nested Objects unless the properties ty ===== Disabling Type Hints -It may be necessary to disable writing of type hints when the index that should be used already exists without having the type hints defined in its mapping and with the mapping mode set to strict. In this case, writing the type hint will produce an error, as the field cannot be added automatically. +It may be necessary to disable writing of type hints when the index that should be used already exists without having the type hints defined in its mapping and with the mapping mode set to strict. +In this case, writing the type hint will produce an error, as the field cannot be added automatically. Type hints can be disabled for the whole application by overriding the method `writeTypeHints()` in a configuration class derived from `AbstractElasticsearchConfiguration` (see <>). As an alternativ they can be disabled for a single index with the `@Document` annotation: + ==== [source,java] ---- @@ -183,7 +232,9 @@ As an alternativ they can be disabled for a single index with the `@Document` an ---- ==== -WARNING: We strongly advise against disabling Type Hints. Only do this if you are forced to. Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely. +WARNING: We strongly advise against disabling Type Hints. +Only do this if you are forced to. +Disabling type hints can lead to documents not being retrieved correctly from Elasticsearch in case of polymorphic data or document retrieval may fail completely. ==== Geospatial Types diff --git a/src/main/java/org/springframework/data/elasticsearch/core/Range.java b/src/main/java/org/springframework/data/elasticsearch/core/Range.java index 762fa758b..62b264f61 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/Range.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/Range.java @@ -22,7 +22,7 @@ /** * Simple value object to work with ranges and boundaries. - * + * * @author Sascha Woo * @since 4.3 */ @@ -33,12 +33,12 @@ public class Range { /** * The lower bound of the range. */ - private Bound lowerBound; + private final Bound lowerBound; /** * The upper bound of the range. */ - private Bound upperBound; + private final Bound upperBound; /** * Creates a new {@link Range} with inclusive bounds for both values. @@ -89,12 +89,10 @@ public static Range leftUnbounded(Bound to) { } /** - * Creates a new {@link Range} with the given lower and upper bound. Prefer {@link #from(Bound)} for a more builder - * style API. + * Creates a new {@link Range} with the given lower and upper bound. * * @param lowerBound must not be {@literal null}. * @param upperBound must not be {@literal null}. - * @see #from(Bound) */ public static Range of(Bound lowerBound, Bound upperBound) { return new Range<>(lowerBound, upperBound); @@ -146,10 +144,7 @@ public static Range unbounded() { return (Range) UNBOUNDED; } - private Range() { - } - - private Range(Bound lowerBound, Bound upperBound) { + private Range(Bound lowerBound, Bound upperBound) { Assert.notNull(lowerBound, "Lower bound must not be null!"); Assert.notNull(upperBound, "Upper bound must not be null!"); @@ -164,7 +159,8 @@ private Range(Bound lowerBound, Bound upperBound) { * @param value must not be {@literal null}. * @return */ - public boolean contains(T value) { + @SuppressWarnings("unchecked") + public boolean contains(T value) { Assert.notNull(value, "Reference value must not be null!"); Assert.isInstanceOf(Comparable.class, value, "value must implements Comparable!"); @@ -243,7 +239,8 @@ public static final class Bound { @SuppressWarnings({ "rawtypes", "unchecked" }) // private static final Bound UNBOUNDED = new Bound(Optional.empty(), true); - private final Optional value; + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private final Optional value; private final boolean inclusive; /** @@ -360,7 +357,8 @@ public static Bound unbounded() { return (Bound) UNBOUNDED; } - private Bound(Optional value, boolean inclusive) { + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Bound(Optional value, boolean inclusive) { this.value = value; this.inclusive = inclusive; } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java b/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java index 8d631f08b..d4299a6b1 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/RangeTests.java @@ -18,7 +18,7 @@ import static org.assertj.core.api.Assertions.*; import java.time.LocalDate; -import java.util.Arrays; +import java.util.Collections; import org.junit.jupiter.api.Test; @@ -166,9 +166,7 @@ public void shouldThrowExceptionIfNotComparable() { // given // when - Throwable thrown = catchThrowable(() -> { - Range.just(Arrays.asList("test")); - }); + Throwable thrown = catchThrowable(() -> Range.just(Collections.singletonList("test"))); // then assertThat(thrown).isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("value must implements Comparable!"); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 4e0a469d4..aab41993b 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -2275,8 +2275,7 @@ public void setSaved(@Nullable String saved) { } } - private static class ElectricCar extends Car { - } + private static class ElectricCar extends Car {} private static class PersonWithCars { @Id @Nullable String id; From 567bdf21faf9924392626efc4e24d2ce10e2946a Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 12 Jul 2021 22:12:11 +0200 Subject: [PATCH 092/776] Upgrade to Elasticsearch 7.13.3. Original Pull Request #1865 Closes #1864 --- pom.xml | 2 +- src/main/asciidoc/preface.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8f68f1df7..124e702b1 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ https://github.com/spring-projects/spring-data-elasticsearch - 7.13.1 + 7.13.3 2.14.1 4.1.65.Final 2.6.0-SNAPSHOT diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index f3277ed40..30e4f1a36 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -34,7 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat [cols="^,^,^,^,^",options="header"] |=== | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot -| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.1 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] +| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.3 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] | 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x | 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x | Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x From 27094724dcf1a9f3932c86adff6cb58341ec2016 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 14 Jul 2021 19:31:30 +0200 Subject: [PATCH 093/776] Use registered converters for parameters of @Query annotated methods. Original Pull Request #1867 Closes #1866 --- .../query/ElasticsearchStringQuery.java | 3 +- .../ReactiveElasticsearchStringQuery.java | 4 +- .../repository/support/StringQueryUtil.java | 15 ++-- .../ElasticsearchStringQueryUnitTestBase.java | 78 +++++++++++++++++++ .../ElasticsearchStringQueryUnitTests.java | 55 ++++++------- ...tiveElasticsearchStringQueryUnitTests.java | 54 ++++++------- 6 files changed, 138 insertions(+), 71 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTestBase.java diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java index 3f5e9ed83..1b359db3f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java @@ -102,7 +102,8 @@ public Object execute(Object[] parameters) { } protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) { - String queryString = StringQueryUtil.replacePlaceholders(this.query, parameterAccessor); + String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService()) + .replacePlaceholders(this.query, parameterAccessor); return new StringQuery(queryString); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java index 110a2d8ba..6fe4f4793 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java @@ -47,7 +47,9 @@ public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQuery @Override protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor) { - String queryString = StringQueryUtil.replacePlaceholders(this.query, parameterAccessor); + String queryString = new StringQueryUtil( + getElasticsearchOperations().getElasticsearchConverter().getConversionService()).replacePlaceholders(this.query, + parameterAccessor); return new StringQuery(queryString); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java index 89e4cc921..6f1354cd7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java @@ -20,7 +20,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.core.convert.ConversionService; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.util.NumberUtils; @@ -31,11 +31,14 @@ final public class StringQueryUtil { private static final Pattern PARAMETER_PLACEHOLDER = Pattern.compile("\\?(\\d+)"); - private static final GenericConversionService conversionService = new GenericConversionService(); - private StringQueryUtil() {} + private final ConversionService conversionService; - public static String replacePlaceholders(String input, ParameterAccessor accessor) { + public StringQueryUtil(ConversionService conversionService) { + this.conversionService = conversionService; + } + + public String replacePlaceholders(String input, ParameterAccessor accessor) { Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input); String result = input; @@ -48,7 +51,7 @@ public static String replacePlaceholders(String input, ParameterAccessor accesso return result; } - private static String getParameterWithIndex(ParameterAccessor accessor, int index) { + private String getParameterWithIndex(ParameterAccessor accessor, int index) { Object parameter = accessor.getBindableValue(index); String parameterValue = "null"; @@ -63,7 +66,7 @@ private static String getParameterWithIndex(ParameterAccessor accessor, int inde } - private static String convert(Object parameter) { + private String convert(Object parameter) { if (Collection.class.isAssignableFrom(parameter.getClass())) { Collection collectionParam = (Collection) parameter; StringBuilder sb = new StringBuilder("["); diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTestBase.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTestBase.java new file mode 100644 index 000000000..429d97a56 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTestBase.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.repository.query; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions; +import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; +import org.springframework.lang.Nullable; + +/** + * @author Peter-Josef Meisch + */ +public class ElasticsearchStringQueryUnitTestBase { + + protected ElasticsearchConverter setupConverter() { + MappingElasticsearchConverter converter = new MappingElasticsearchConverter( + new SimpleElasticsearchMappingContext()); + Collection> converters = new ArrayList<>(); + converters.add(ElasticsearchStringQueryUnitTests.CarConverter.INSTANCE); + CustomConversions customConversions = new ElasticsearchCustomConversions(converters); + converter.setConversions(customConversions); + converter.afterPropertiesSet(); + return converter; + } + + static class Car { + @Nullable private String name; + @Nullable private String model; + + @Nullable + public String getName() { + return name; + } + + public void setName(@Nullable String name) { + this.name = name; + } + + @Nullable + public String getModel() { + return model; + } + + public void setModel(@Nullable String model) { + this.model = model; + } + } + + enum CarConverter implements Converter { + INSTANCE; + + @Override + public String convert(ElasticsearchStringQueryUnitTests.Car car) { + return (car.getName() != null ? car.getName() : "null") + '-' + + (car.getModel() != null ? car.getModel() : "null"); + } + } + +} diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java index 0c351db0a..d37c5707f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.repository.query; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.lang.reflect.Method; import java.util.ArrayList; @@ -40,9 +41,6 @@ import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHits; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; -import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.StringQuery; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; @@ -55,14 +53,13 @@ * @author Niklas Herder */ @ExtendWith(MockitoExtension.class) -public class ElasticsearchStringQueryUnitTests { +public class ElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase { @Mock ElasticsearchOperations operations; - ElasticsearchConverter converter; @BeforeEach public void setUp() { - converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()); + when(operations.getElasticsearchConverter()).thenReturn(setupConverter()); } @Test // DATAES-552 @@ -141,6 +138,21 @@ private org.springframework.data.elasticsearch.core.query.Query createQuery(Stri return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args)); } + @Test // #1866 + @DisplayName("should use converter on parameters") + void shouldUseConverterOnParameters() throws NoSuchMethodException { + + Car car = new Car(); + car.setName("Toyota"); + car.setModel("Prius"); + + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByCar", car); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()) + .isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }"); + } + private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) { return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery()); } @@ -149,7 +161,7 @@ private ElasticsearchQueryMethod getQueryMethod(String name, Class... paramet Method method = SampleRepository.class.getMethod(name, parameters); return new ElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - new SpelAwareProxyProjectionFactory(), converter.getMappingContext()); + new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext()); } private interface SampleRepository extends Repository { @@ -172,13 +184,16 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String @Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}") SearchHits findByPrefix(String prefix); + + @Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }") + Person findByCar(Car car); } /** * @author Rizwan Idrees * @author Mohsin Husen * @author Artur Konczak - * @author Niklas Herder + * @author Niklas Herder */ @Document(indexName = "test-index-person-query-unittest") @@ -292,29 +307,6 @@ public void setDescription(@Nullable String description) { } } - static class Car { - @Nullable private String name; - @Nullable private String model; - - @Nullable - public String getName() { - return name; - } - - public void setName(@Nullable String name) { - this.name = name; - } - - @Nullable - public String getModel() { - return model; - } - - public void setModel(@Nullable String model) { - this.model = model; - } - } - static class Author { @Nullable private String id; @@ -338,5 +330,4 @@ public void setName(String name) { this.name = name; } } - } diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java index 9a24e0af3..aabbd80f7 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.repository.query; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -43,9 +44,6 @@ import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations; import org.springframework.data.elasticsearch.core.SearchHit; -import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; -import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.StringQuery; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; @@ -59,16 +57,15 @@ * @author Peter-Josef Meisch */ @ExtendWith(MockitoExtension.class) -public class ReactiveElasticsearchStringQueryUnitTests { +public class ReactiveElasticsearchStringQueryUnitTests extends ElasticsearchStringQueryUnitTestBase { SpelExpressionParser PARSER = new SpelExpressionParser(); - ElasticsearchConverter converter; @Mock ReactiveElasticsearchOperations operations; @BeforeEach public void setUp() { - converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()); + when(operations.getElasticsearchConverter()).thenReturn(setupConverter()); } @Test // DATAES-519 @@ -132,7 +129,22 @@ void shouldEscapeStringsInQueryParameters() throws Exception { .isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}"); } - private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args) + @Test // #1866 + @DisplayName("should use converter on parameters") + void shouldUseConverterOnParameters() throws Exception { + + Car car = new Car(); + car.setName("Toyota"); + car.setModel("Prius"); + + org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByCar", car); + + assertThat(query).isInstanceOf(StringQuery.class); + assertThat(((StringQuery) query).getSource()) + .isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }"); + } + + private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args) throws NoSuchMethodException { Class[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); @@ -152,7 +164,7 @@ private ReactiveElasticsearchQueryMethod getQueryMethod(String name, Class... Method method = SampleRepository.class.getMethod(name, parameters); return new ReactiveElasticsearchQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - new SpelAwareProxyProjectionFactory(), converter.getMappingContext()); + new SpelAwareProxyProjectionFactory(), operations.getElasticsearchConverter().getMappingContext()); } private ReactiveElasticsearchStringQuery createQueryForMethod(String name, Class... parameters) throws Exception { @@ -180,6 +192,9 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String @Query("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"?0\"}}]}}") Flux> findByPrefix(String prefix); + @Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }") + Mono findByCar(Car car); + } /** @@ -292,29 +307,6 @@ public void setDescription(@Nullable String description) { } } - static class Car { - @Nullable private String name; - @Nullable private String model; - - @Nullable - public String getName() { - return name; - } - - public void setName(@Nullable String name) { - this.name = name; - } - - @Nullable - public String getModel() { - return model; - } - - public void setModel(@Nullable String model) { - this.model = model; - } - } - static class Author { @Nullable private String id; From 039e59d3c2ea2408f91e7260321a50672cb56775 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 14 Jul 2021 22:19:39 +0200 Subject: [PATCH 094/776] rename a couple of package private classes to a consistent naming scheme. Original Pull Request #1869 Closes #1868 --- ...dexOperations.java => AbstractIndexTemplate.java} | 8 ++++---- .../core/ElasticsearchRestTemplate.java | 4 ++-- .../elasticsearch/core/ElasticsearchTemplate.java | 4 ++-- .../core/ReactiveElasticsearchTemplate.java | 4 ++-- ...dexOperations.java => ReactiveIndexTemplate.java} | 8 ++++---- ...ltIndexOperations.java => RestIndexTemplate.java} | 8 ++++---- ...exOperations.java => TransportIndexTemplate.java} | 12 ++++++------ 7 files changed, 24 insertions(+), 24 deletions(-) rename src/main/java/org/springframework/data/elasticsearch/core/{AbstractDefaultIndexOperations.java => AbstractIndexTemplate.java} (95%) rename src/main/java/org/springframework/data/elasticsearch/core/{DefaultReactiveIndexOperations.java => ReactiveIndexTemplate.java} (97%) rename src/main/java/org/springframework/data/elasticsearch/core/{DefaultIndexOperations.java => RestIndexTemplate.java} (96%) rename src/main/java/org/springframework/data/elasticsearch/core/{DefaultTransportIndexOperations.java => TransportIndexTemplate.java} (96%) diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java similarity index 95% rename from src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java rename to src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java index 0ac274c70..95d761e6e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractDefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java @@ -46,9 +46,9 @@ * @author Sascha Woo * @since 4.0 */ -abstract class AbstractDefaultIndexOperations implements IndexOperations { +abstract class AbstractIndexTemplate implements IndexOperations { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDefaultIndexOperations.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIndexTemplate.class); protected final ElasticsearchConverter elasticsearchConverter; protected final RequestFactory requestFactory; @@ -56,7 +56,7 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations { @Nullable protected final Class boundClass; @Nullable private final IndexCoordinates boundIndex; - public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, Class boundClass) { + public AbstractIndexTemplate(ElasticsearchConverter elasticsearchConverter, Class boundClass) { Assert.notNull(boundClass, "boundClass may not be null"); @@ -66,7 +66,7 @@ public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConver this.boundIndex = null; } - public AbstractDefaultIndexOperations(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) { + public AbstractIndexTemplate(ElasticsearchConverter elasticsearchConverter, IndexCoordinates boundIndex) { Assert.notNull(boundIndex, "boundIndex may not be null"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index d65cb1045..6e9d2eb78 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -131,7 +131,7 @@ public IndexOperations indexOps(Class clazz) { Assert.notNull(clazz, "clazz must not be null"); - return new DefaultIndexOperations(this, clazz); + return new RestIndexTemplate(this, clazz); } @Override @@ -139,7 +139,7 @@ public IndexOperations indexOps(IndexCoordinates index) { Assert.notNull(index, "index must not be null"); - return new DefaultIndexOperations(this, index); + return new RestIndexTemplate(this, index); } // endregion diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index c0960ff1d..0ae4b14da 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -134,7 +134,7 @@ public IndexOperations indexOps(Class clazz) { Assert.notNull(clazz, "clazz must not be null"); - return new DefaultTransportIndexOperations(client, elasticsearchConverter, clazz); + return new TransportIndexTemplate(client, elasticsearchConverter, clazz); } @Override @@ -142,7 +142,7 @@ public IndexOperations indexOps(IndexCoordinates index) { Assert.notNull(index, "index must not be null"); - return new DefaultTransportIndexOperations(client, elasticsearchConverter, index); + return new TransportIndexTemplate(client, elasticsearchConverter, index); } // endregion diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index be256ee3e..02516564a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -963,12 +963,12 @@ public ElasticsearchConverter getElasticsearchConverter() { @Override public ReactiveIndexOperations indexOps(IndexCoordinates index) { - return new DefaultReactiveIndexOperations(this, index); + return new ReactiveIndexTemplate(this, index); } @Override public ReactiveIndexOperations indexOps(Class clazz) { - return new DefaultReactiveIndexOperations(this, clazz); + return new ReactiveIndexTemplate(this, clazz); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java similarity index 97% rename from src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java rename to src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java index d2558113b..2133ab578 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultReactiveIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveIndexTemplate.java @@ -63,9 +63,9 @@ * @author George Popides * @since 4.1 */ -class DefaultReactiveIndexOperations implements ReactiveIndexOperations { +class ReactiveIndexTemplate implements ReactiveIndexOperations { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReactiveIndexOperations.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveIndexTemplate.class); @Nullable private final Class boundClass; private final IndexCoordinates boundIndex; @@ -73,7 +73,7 @@ class DefaultReactiveIndexOperations implements ReactiveIndexOperations { private final ReactiveElasticsearchOperations operations; private final ElasticsearchConverter converter; - public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, IndexCoordinates index) { + public ReactiveIndexTemplate(ReactiveElasticsearchOperations operations, IndexCoordinates index) { Assert.notNull(operations, "operations must not be null"); Assert.notNull(index, "index must not be null"); @@ -85,7 +85,7 @@ public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations this.boundIndex = index; } - public DefaultReactiveIndexOperations(ReactiveElasticsearchOperations operations, Class clazz) { + public ReactiveIndexTemplate(ReactiveElasticsearchOperations operations, Class clazz) { Assert.notNull(operations, "operations must not be null"); Assert.notNull(clazz, "clazz must not be null"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java similarity index 96% rename from src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java rename to src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java index fdc8159ed..04a4382dd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java @@ -64,18 +64,18 @@ * @author George Popides * @since 4.0 */ -class DefaultIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations { +class RestIndexTemplate extends AbstractIndexTemplate implements IndexOperations { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIndexOperations.class); + private static final Logger LOGGER = LoggerFactory.getLogger(RestIndexTemplate.class); private final ElasticsearchRestTemplate restTemplate; - public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, Class boundClass) { + public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, Class boundClass) { super(restTemplate.getElasticsearchConverter(), boundClass); this.restTemplate = restTemplate; } - public DefaultIndexOperations(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) { + public RestIndexTemplate(ElasticsearchRestTemplate restTemplate, IndexCoordinates boundIndex) { super(restTemplate.getElasticsearchConverter(), boundIndex); this.restTemplate = restTemplate; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java similarity index 96% rename from src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java rename to src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java index acb244601..753221bf7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultTransportIndexOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java @@ -69,20 +69,20 @@ * @author George Popides * @since 4.0 */ -class DefaultTransportIndexOperations extends AbstractDefaultIndexOperations implements IndexOperations { +class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOperations { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTransportIndexOperations.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TransportIndexTemplate.class); private final Client client; - public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter, - Class boundClass) { + public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, + Class boundClass) { super(elasticsearchConverter, boundClass); this.client = client; } - public DefaultTransportIndexOperations(Client client, ElasticsearchConverter elasticsearchConverter, - IndexCoordinates boundIndex) { + public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, + IndexCoordinates boundIndex) { super(elasticsearchConverter, boundIndex); this.client = client; } From 8e3d8669ea74c02471cb0dddcd5584416de9e0a6 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 16 Jul 2021 10:18:56 +0200 Subject: [PATCH 095/776] Updated changelog. See #1849 --- src/main/resources/changelog.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 1ba11aae9..4afdc938c 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,13 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.1.11 (2021-07-16) +-------------------------------------- +* #1866 - Queries defined with `@Query` are not using registered converters for parameter conversion. +* #1858 - Collection parameters for @Query-annotated methods get escaped wrongly. +* #1846 - Missing hashCode and equals methods in JoinField. + + Changes in version 4.2.2 (2021-06-22) ------------------------------------- * #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. @@ -1637,5 +1644,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From bf248d78dec33e302904713f1472d65a654a82bb Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 16 Jul 2021 14:08:52 +0200 Subject: [PATCH 096/776] Updated changelog. See #1777 --- src/main/resources/changelog.txt | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 4afdc938c..57105de1f 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -1,6 +1,45 @@ Spring Data Elasticsearch Changelog =================================== +Changes in version 4.3.0-M1 (2021-07-16) +---------------------------------------- +* #1868 - Internal refactoring. +* #1866 - Queries defined with `@Query` are not using registered converters for parameter conversion. +* #1864 - Upgrade to Elasticsearch 7.13.3. +* #1862 - Add native support for range field types by using a range object. +* #1860 - Make the TestContainers Elasticsearch container configurable. +* #1858 - Collection parameters for @Query-annotated methods get escaped wrongly. +* #1854 - Improve NativeSearchQueryBuilder by adding convenience methods and modifying existing ones. +* #1846 - Missing hashCode and equals methods in JoinField. +* #1839 - Upgrade to Elasticsearch 7.13.1. +* #1836 - Make CompletionField annotation composable. +* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. +* #1831 - Upgrade to Elasticsearch 7.13.0. +* #1828 - Dependency cleanup. +* #1826 - Improve integration test time. +* #1824 - Fix reactive blocking calls. +* #1822 - Add Blockhound to test setup. +* #1821 - Fix reactive mapping creation. +* #1817 - Fix fields and source_filter setup. +* #1816 - Allow runtime_fields to be defined in the index mapping. +* #1811 - StringQuery execution crashes on return type `SearchPage`. +* #1803 - Default FieldType.Auto on Arrays of Objects. +* #1800 - Improve handling of immutable classes. +* #1794 - Refactor `DefaultReactiveElasticsearchClient` to do request customization with the `WebClient`. +* #1792 - Upgrade to Elasticsearch 7.12.1. +* #1790 - Custom Query with string parameter which contains double quotes. +* #1788 - Allow disabling TypeHints. +* #1787 - Search with MoreLikeThisQuery should use Pageable. +* #1785 - Fix documentation about auditing. +* #1781 - Remove deprecated code. +* #1778 - Custom property names must be used in SourceFilter and source fields. +* #1767 - DynamicMapping annotation should be applicable to any object field. +* #1564 - (Reactive)ElasticsearchOperations does not have option to include request_cache path param in search request [DATAES-992]. +* #1488 - @ScriptedFields & Kotlin data classes [DATAES-915]. +* #1255 - Add pipeline aggregations to NativeSearchQuery. +* #638 - datatype detection support in mapping ( i.e. numeric_detection, date_detection etc..) [DATAES-62]. + + Changes in version 4.1.11 (2021-07-16) -------------------------------------- * #1866 - Queries defined with `@Query` are not using registered converters for parameter conversion. @@ -1645,5 +1684,6 @@ Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) + From c79b6d158a853d841037f45c197e638a6a8bb0f1 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 16 Jul 2021 14:08:59 +0200 Subject: [PATCH 097/776] Prepare 4.3 M1 (2021.1.0). See #1777 --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 124e702b1..96b983a2c 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.6.0-SNAPSHOT + 2.6.0-M1 Spring Data Elasticsearch @@ -21,7 +21,7 @@ 7.13.3 2.14.1 4.1.65.Final - 2.6.0-SNAPSHOT + 2.6.0-M1 1.15.3 1.0.6.RELEASE spring.data.elasticsearch @@ -487,8 +487,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-milestone + https://repo.spring.io/libs-milestone diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 2be9fae82..347f28d81 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Elasticsearch 4.2 GA (2021.0.0) +Spring Data Elasticsearch 4.3 M1 (2021.1.0) Copyright (c) [2013-2021] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -27,3 +27,4 @@ conditions of the subcomponent's license, as noted in the LICENSE file. + From c470a3d4eb09248bbff16111c2d2bfa238cee6cb Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 16 Jul 2021 14:09:26 +0200 Subject: [PATCH 098/776] Release version 4.3 M1 (2021.1.0). See #1777 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96b983a2c..18b8b869a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.3.0-SNAPSHOT + 4.3.0-M1 org.springframework.data.build From 4f7e7526e3bb557e58a5fae4c2ab597c16256ef0 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 16 Jul 2021 14:19:56 +0200 Subject: [PATCH 099/776] Prepare next development iteration. See #1777 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 18b8b869a..96b983a2c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.3.0-M1 + 4.3.0-SNAPSHOT org.springframework.data.build From 7c35756923a848350e921dd44adaab97b32e4ac7 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Fri, 16 Jul 2021 14:19:58 +0200 Subject: [PATCH 100/776] After release cleanups. See #1777 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 96b983a2c..124e702b1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.6.0-M1 + 2.6.0-SNAPSHOT Spring Data Elasticsearch @@ -21,7 +21,7 @@ 7.13.3 2.14.1 4.1.65.Final - 2.6.0-M1 + 2.6.0-SNAPSHOT 1.15.3 1.0.6.RELEASE spring.data.elasticsearch @@ -487,8 +487,8 @@ - spring-libs-milestone - https://repo.spring.io/libs-milestone + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From d88fb037dadc6798641d51876d81ea64c43bf36b Mon Sep 17 00:00:00 2001 From: Frnandu Martinski Date: Sat, 17 Jul 2021 19:09:35 +0200 Subject: [PATCH 101/776] Fix uri encode bug when url path start with '/'. Original Pull Request #1873 Closes #1870 --- .../data/elasticsearch/client/util/RequestConverters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a8c6045ec..796433c0e 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 @@ -1435,7 +1435,7 @@ private static String encodePart(String pathPart) { // encode each part (e.g. index, type and id) separately before merging them into the path // we prepend "/" to the path part to make this path absolute, otherwise there can be issues with // paths that start with `-` or contain `:` - URI uri = new URI(null, null, null, -1, '/' + pathPart, null, null); + URI uri = new URI((String)null, "", "/" + pathPart, (String)null, (String)null); // manually encode any slash that each part may contain return uri.getRawPath().substring(1).replaceAll("/", "%2F"); } catch (URISyntaxException e) { From d3e8c9fce5f788cc1d5a6289eda6ce34325234fe Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 17 Jul 2021 19:15:30 +0200 Subject: [PATCH 102/776] Polishing. --- .../data/elasticsearch/client/util/RequestConverters.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 796433c0e..856ecb4a6 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 @@ -1435,7 +1435,8 @@ private static String encodePart(String pathPart) { // encode each part (e.g. index, type and id) separately before merging them into the path // we prepend "/" to the path part to make this path absolute, otherwise there can be issues with // paths that start with `-` or contain `:` - URI uri = new URI((String)null, "", "/" + pathPart, (String)null, (String)null); + // the authority must be an empty string and not null, else paths that being with slashes could have them + URI uri = new URI((String) null, "", "/" + pathPart, (String) null, (String) null); // manually encode any slash that each part may contain return uri.getRawPath().substring(1).replaceAll("/", "%2F"); } catch (URISyntaxException e) { From fa6f63690647153eee8475519279e1430a26705b Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 18 Jul 2021 14:02:07 +0200 Subject: [PATCH 103/776] Build jdk11 and jdk16 sequentially --- Jenkinsfile | 68 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index b247df884..ffe984892 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -49,50 +49,48 @@ pipeline { not { triggeredBy 'UpstreamCause' } } } - parallel { - stage("test: baseline (jdk11)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES') } + stage("test: baseline (jdk11)") { + agent { + label 'data' + } + options { timeout(time: 30, unit: 'MINUTES') } - environment { - DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') - ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') - } + environment { + DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') + } - steps { - script { - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { - docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { - sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" - sh 'PROFILE=java11 ci/verify.sh' - sh "ci/clean.sh" - } + steps { + script { + docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { + sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" + sh 'PROFILE=java11 ci/verify.sh' + sh "ci/clean.sh" } } } } + } - stage("test: baseline (jdk16)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES') } + stage("test: baseline (jdk16)") { + agent { + label 'data' + } + options { timeout(time: 30, unit: 'MINUTES') } - environment { - DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') - ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') - } + environment { + DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') + } - steps { - script { - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { - docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { - sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" - sh 'PROFILE=java11 ci/verify.sh' - sh "ci/clean.sh" - } + steps { + script { + docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { + sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" + sh 'PROFILE=java11 ci/verify.sh' + sh "ci/clean.sh" } } } From 3b921b7454e9a6ca6c4e49d33e6a20788e45142a Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 18 Jul 2021 14:03:42 +0200 Subject: [PATCH 104/776] Revert build jdk11 and jdk16 sequentially --- Jenkinsfile | 68 +++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index ffe984892..b247df884 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -49,48 +49,50 @@ pipeline { not { triggeredBy 'UpstreamCause' } } } - stage("test: baseline (jdk11)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES') } + parallel { + stage("test: baseline (jdk11)") { + agent { + label 'data' + } + options { timeout(time: 30, unit: 'MINUTES') } - environment { - DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') - ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') - } + environment { + DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') + } - steps { - script { - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { - docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { - sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" - sh 'PROFILE=java11 ci/verify.sh' - sh "ci/clean.sh" + steps { + script { + docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + docker.image('adoptopenjdk/openjdk11:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { + sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" + sh 'PROFILE=java11 ci/verify.sh' + sh "ci/clean.sh" + } } } } } - } - stage("test: baseline (jdk16)") { - agent { - label 'data' - } - options { timeout(time: 30, unit: 'MINUTES') } + stage("test: baseline (jdk16)") { + agent { + label 'data' + } + options { timeout(time: 30, unit: 'MINUTES') } - environment { - DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') - ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') - } + environment { + DOCKER_HUB = credentials('hub.docker.com-springbuildmaster') + ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c') + } - steps { - script { - docker.withRegistry('', 'hub.docker.com-springbuildmaster') { - docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { - sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" - sh 'PROFILE=java11 ci/verify.sh' - sh "ci/clean.sh" + steps { + script { + docker.withRegistry('', 'hub.docker.com-springbuildmaster') { + docker.image('adoptopenjdk/openjdk16:latest').inside('-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home') { + sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}" + sh 'PROFILE=java11 ci/verify.sh' + sh "ci/clean.sh" + } } } } From f74dd879dfb22a9f226d69a2976a9b3d33813f0f Mon Sep 17 00:00:00 2001 From: Sascha Woo Date: Tue, 20 Jul 2021 07:51:39 +0200 Subject: [PATCH 105/776] Move dynamic mapping parameter configuration to @Document and @Field. Original Pull Request #1872 Closes #1871 --- .../elasticsearch/annotations/Document.java | 9 ++ .../elasticsearch/annotations/Dynamic.java | 50 ++++++++++ .../annotations/DynamicMapping.java | 3 + .../annotations/DynamicMappingValue.java | 3 + .../data/elasticsearch/annotations/Field.java | 8 ++ .../core/index/MappingBuilder.java | 20 +++- .../ElasticsearchPersistentEntity.java | 7 ++ .../SimpleElasticsearchPersistentEntity.java | 11 ++- .../index/MappingBuilderIntegrationTests.java | 43 ++++++++- .../core/index/MappingBuilderUnitTests.java | 93 ++++++++++++++++++- 10 files changed, 233 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/Dynamic.java diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index c1a7b8ff1..4378f0bdf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -33,6 +33,7 @@ * @author Ivan Greene * @author Mark Paluch * @author Peter-Josef Meisch + * @author Sascha Woo */ @Persistent @Inherited @@ -112,4 +113,12 @@ * @since 4.3 */ WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT; + + /** + * Controls how Elasticsearch dynamically adds fields to the document. + * + * @since 4.3 + */ + Dynamic dynamic() default Dynamic.INHERIT; + } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Dynamic.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Dynamic.java new file mode 100644 index 000000000..a0bd128f9 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Dynamic.java @@ -0,0 +1,50 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.annotations; + +/** + * Values for the {@code dynamic} mapping parameter. + * + * @author Sascha Woo + * @since 4.3 + */ +public enum Dynamic { + /** + * New fields are added to the mapping. + */ + TRUE, + /** + * New fields are added to the mapping as + * runtime fields. These + * fields are not indexed, and are loaded from {@code _source} at query time. + */ + RUNTIME, + /** + * New fields are ignored. These fields will not be indexed or searchable, but will still appear in the + * {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added + * explicitly. + */ + FALSE, + /** + * If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly + * added to the mapping. + */ + STRICT, + /** + * Inherit the dynamic setting from their parent object or from the mapping type. + */ + INHERIT +} diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java index d06aed12b..513faf7a5 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java @@ -26,11 +26,14 @@ * {@see elasticsearch doc} * * @author Peter-Josef Meisch + * @author Sascha Woo * @since 4.0 + * @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead. */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.FIELD }) @Documented +@Deprecated public @interface DynamicMapping { DynamicMappingValue value() default DynamicMappingValue.True; diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMappingValue.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMappingValue.java index b2110637e..85fe3b8e8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMappingValue.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMappingValue.java @@ -19,8 +19,11 @@ * values for the {@link DynamicMapping annotation} * * @author Peter-Josef Meisch + * @author Sascha Woo * @since 4.0 + * @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead. */ +@Deprecated public enum DynamicMappingValue { True, False, Strict } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java index be5ae13f5..75d7bdedf 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -195,4 +195,12 @@ * @since 4.2 */ int dims() default -1; + + /** + * Controls how Elasticsearch dynamically adds fields to the inner object within the document.
+ * To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested} + * + * @since 4.3 + */ + Dynamic dynamic() default Dynamic.INHERIT; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index a5153bb63..981b7ec52 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -197,7 +197,9 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten } } - if (dynamicMapping != null) { + if (entity != null && entity.dynamic() != Dynamic.INHERIT) { + builder.field(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase()); + } else if (dynamicMapping != null) { builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); } @@ -440,8 +442,12 @@ private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersist builder.startObject(property.getFieldName()); - if (nestedOrObjectField && dynamicMapping != null) { - builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + if (nestedOrObjectField) { + if (annotation.dynamic() != Dynamic.INHERIT) { + builder.field(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase()); + } else if (dynamicMapping != null) { + builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + } } addFieldMappingParameters(builder, annotation, nestedOrObjectField); @@ -489,8 +495,12 @@ private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersiste // main field builder.startObject(property.getFieldName()); - if (nestedOrObjectField && dynamicMapping != null) { - builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + if (nestedOrObjectField) { + if (annotation.mainField().dynamic() != Dynamic.INHERIT) { + builder.field(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase()); + } else if (dynamicMapping != null) { + builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + } } addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 3a53b4b1c..2bb134cbb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -16,6 +16,7 @@ package org.springframework.data.elasticsearch.core.mapping; import org.elasticsearch.index.VersionType; +import org.springframework.data.elasticsearch.annotations.Dynamic; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.join.JoinField; @@ -160,4 +161,10 @@ default ElasticsearchPersistentProperty getRequiredSeqNoPrimaryTermProperty() { * @since 4.3 */ boolean writeTypeHints(); + + /** + * @return the {@code dynamic} mapping parameter value. + * @since 4.3 + */ + Dynamic dynamic(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java index 9b4822a6e..0cec9b4d7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java @@ -25,6 +25,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Dynamic; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.Routing; @@ -73,6 +74,7 @@ public class SimpleElasticsearchPersistentEntity extends BasicPersistentEntit private @Nullable ElasticsearchPersistentProperty joinFieldProperty; private @Nullable VersionType versionType; private boolean createIndexAndMapping; + private final Dynamic dynamic; private final Map fieldNamePropertyCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap routingExpressions = new ConcurrentHashMap<>(); private @Nullable String routing; @@ -102,8 +104,10 @@ public SimpleElasticsearchPersistentEntity(TypeInformation typeInformation, this.indexName = document.indexName(); this.versionType = document.versionType(); this.createIndexAndMapping = document.createIndex(); + this.dynamic = document.dynamic(); + } else { + this.dynamic = Dynamic.INHERIT; } - Routing routingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Routing.class); if (routingAnnotation != null) { @@ -559,4 +563,9 @@ public FieldNamingStrategy getFieldNamingStrategy() { return fieldNamingStrategy; } } + + @Override + public Dynamic dynamic() { + return dynamic; + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java index 67a9a6099..d5e3d6da3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java @@ -287,8 +287,18 @@ void shouldWriteMappingForDisabledProperty() { } @Test // #1767 - @DisplayName("should write dynamic mapping entries") - void shouldWriteDynamicMappingEntries() { + @DisplayName("should write dynamic mapping annotations") + void shouldWriteDynamicMappingAnnotations() { + + IndexOperations indexOps = operations.indexOps(DynamicMappingAnnotationEntity.class); + indexOps.create(); + indexOps.putMapping(); + + } + + @Test // #1871 + @DisplayName("should write dynamic mapping") + void shouldWriteDynamicMapping() { IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class); indexOps.create(); @@ -1104,9 +1114,9 @@ public void setDense_vector(@Nullable float[] dense_vector) { } } - @Document(indexName = "dynamic-mapping") + @Document(indexName = "dynamic-mapping-annotation") @DynamicMapping(DynamicMappingValue.False) - static class DynamicMappingEntity { + static class DynamicMappingAnnotationEntity { @Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author; @Nullable @DynamicMapping(DynamicMappingValue.False) @Field( @@ -1124,6 +1134,31 @@ public void setAuthor(Author author) { } } + @Document(indexName = "dynamic-mapping", dynamic = Dynamic.FALSE) + static class DynamicMappingEntity { + + @Nullable @Field(type = FieldType.Object) // + private Map objectInherit; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) // + private Map objectFalse; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) // + private Map objectTrue; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) // + private Map objectStrict; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) // + private Map objectRuntime; + @Nullable @Field(type = FieldType.Nested) // + private List> nestedObjectInherit; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) // + private List> nestedObjectFalse; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) // + private List> nestedObjectTrue; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) // + private List> nestedObjectStrict; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) // + private List> nestedObjectRuntime; + } + @Document(indexName = "dynamic-detection-mapping-true") @Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE, dynamicDateFormats = { "MM/dd/yyyy" }) diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java index 7ed2405c0..bb27ea215 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java @@ -419,7 +419,7 @@ public void shouldSetFieldMappingProperties() throws JSONException { } @Test // DATAES-148, #1767 - void shouldWriteDynamicMappingSettings() throws JSONException { + void shouldWriteDynamicMappingFromAnnotation() throws JSONException { String expected = "{\n" + // " \"dynamic\": \"false\",\n" + // @@ -451,7 +451,65 @@ void shouldWriteDynamicMappingSettings() throws JSONException { " }\n" + // "}"; // - String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class); + String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingAnnotationEntity.class); + + assertEquals(expected, mapping, true); + } + + @Test // #1871 + void shouldWriteDynamicMapping() throws JSONException { + + String expected = "{\n" // + + " \"dynamic\": \"false\",\n" // + + " \"properties\": {\n" // + + " \"_class\": {\n" // + + " \"type\": \"keyword\",\n" // + + " \"index\": false,\n" // + + " \"doc_values\": false\n" // + + " },\n" // + + " \"objectInherit\": {\n" // + + " \"type\": \"object\"\n" // + + " },\n" // + + " \"objectFalse\": {\n" // + + " \"dynamic\": \"false\",\n" // + + " \"type\": \"object\"\n" // + + " },\n" // + + " \"objectTrue\": {\n" // + + " \"dynamic\": \"true\",\n" // + + " \"type\": \"object\"\n" // + + " },\n" // + + " \"objectStrict\": {\n" // + + " \"dynamic\": \"strict\",\n" // + + " \"type\": \"object\"\n" // + + " },\n" // + + " \"objectRuntime\": {\n" // + + " \"dynamic\": \"runtime\",\n" // + + " \"type\": \"object\"\n" // + + " },\n" // + + " \"nestedObjectInherit\": {\n" // + + " \"type\": \"nested\"\n" // + + " },\n" // + + " \"nestedObjectFalse\": {\n" // + + " \"dynamic\": \"false\",\n" // + + " \"type\": \"nested\"\n" // + + " },\n" // + + " \"nestedObjectTrue\": {\n" // + + " \"dynamic\": \"true\",\n" // + + " \"type\": \"nested\"\n" // + + " },\n" // + + " \"nestedObjectStrict\": {\n" // + + " \"dynamic\": \"strict\",\n" // + + " \"type\": \"nested\"\n" // + + " },\n" // + + " \"nestedObjectRuntime\": {\n" // + + " \"dynamic\": \"runtime\",\n" // + + " \"type\": \"nested\"\n" // + + " }\n" // + + " }\n" // + + "}\n" // + + ""; + + String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingEntity.class); assertEquals(expected, mapping, true); } @@ -865,7 +923,8 @@ void shouldWriteRuntimeFields() throws JSONException { " \"day_of_week\": {\n" + // " \"type\": \"keyword\",\n" + // " \"script\": {\n" + // - " \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + // + " \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + + // " }\n" + // " }\n" + // " },\n" + // @@ -1441,7 +1500,7 @@ static class FieldMappingParameters { @Document(indexName = "test-index-configure-dynamic-mapping") @DynamicMapping(DynamicMappingValue.False) - static class ConfigureDynamicMappingEntity { + static class DynamicMappingAnnotationEntity { @Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author; @Nullable @DynamicMapping(DynamicMappingValue.False) @Field( @@ -1459,6 +1518,32 @@ public void setAuthor(Author author) { } } + @Document(indexName = "test-index-configure-dynamic-mapping", dynamic = Dynamic.FALSE) + static class DynamicMappingEntity { + + @Nullable @Field(type = FieldType.Object) // + private Map objectInherit; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) // + private Map objectFalse; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) // + private Map objectTrue; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) // + private Map objectStrict; + @Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) // + private Map objectRuntime; + @Nullable @Field(type = FieldType.Nested) // + private List> nestedObjectInherit; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) // + private List> nestedObjectFalse; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) // + private List> nestedObjectTrue; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) // + private List> nestedObjectStrict; + @Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) // + private List> nestedObjectRuntime; + + } + static class ValueObject { private final String value; From d2e3ea26b80aaf661363a920254350abe915c36c Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 21 Jul 2021 07:33:58 +0200 Subject: [PATCH 106/776] Upgrade maven wrapper to use maven 3.8.1. Original Pull Request #1878 Closes #1877 --- .mvn/wrapper/MavenWrapperDownloader.java | 117 +++++++++++++++++++++++ .mvn/wrapper/maven-wrapper.jar | Bin 48337 -> 50710 bytes .mvn/wrapper/maven-wrapper.properties | 3 +- mvnw | 36 +++++-- mvnw.cmd | 45 ++++++--- 5 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java mode change 100755 => 100644 .mvn/wrapper/maven-wrapper.jar mode change 100755 => 100644 .mvn/wrapper/maven-wrapper.properties mode change 100755 => 100644 mvnw.cmd diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..b901097f2 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar old mode 100755 new mode 100644 index 01e67997377a393fd672c7dcde9dccbedf0cb1e9..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch delta 12928 zcmY+KV|ZOrx3*)mu^ZcV(l)kj+s1Be?$~yAY&C3bCyi~p$=CLM&vm}D_K&rndpu*T zdChD78EcK1XNZ(&$apV68J1Xk1BebTaIiYqH1g{jg{;Zo+M2p*8NEmUN59Wokh`WR z&OTn**GslKMR*G_0({F}F`_WV6kLMgZ9=C{MIeJM)pTWK$OZOYpa}#Ef^4G*G#?lk7%v!*6r8Z3 z4I&E$oDIlgKnQ$6LQW67A=7nsX>gemm@+kgIIM9H22;J7 z@5Sv;oYl)k_3WU26B0(76`uEHL0hef@)JUm(<2&HypElIjsyfze7%4a-s;92szH%c zki=+gQ_3JU^A=4qpqd;Sl+vKN^!g0hFuH}q1k_wNmt0hpko~@05k@cBCTTbu6l8-G z5eO~I2|>FU_FFK2^Qudc#T36{qTdb#=|c<+Y<3O3p}f1rqVp@EAE$~d_*%?Q-HuK( z!eT(f_1=8qI}L~oBJ4i{DDJinvaGD@Z+d++H#Ilu+kx6zV>(bSSc-P{;>SRne38*d zKCNM|^JqHh>Z|g;rXIzfTpY1&!qS-w>NXZ8l@~jtHz)@mYLVN-#6|&0 z(G~ZtAnOS!S}mH3MO{;+S{E8N*K^F6h6s#o-D=qg!<~3PlpwI_vjh)64;AK z2vmbN1CY^a_GrKEGtpJ*ryI(`#YDib4$(y%FJOvmHjKxwQyG(W-rt>zu-Y&I}fD!LT1VL>lt5$z^(<+<@LgC*cJEC7acgC{4Mydcf8lWD`-w=!6Mybtwc%E3~vt1B`;K$_b0$p5xBGLkFI21;??xGGONW z%S(FuXUA8JQh6i{=)$n4Q8Fxk={0!dd)4hSdqt<-v##$g;8V#bilbI(CmISqUkxcp zg_mIjd1w+o7F)|deW8W3$uW5Cs5urjgH44Wv-no*6jD3A*sgP;9qz%Z!34_R`m8XF z0??3$pDIfK(ux z%oBvkq+WMncuw&=o!Se-h1{01z@>s!oJOkP^{&4lhMiO0ra&V*(y7t3v>W5m3;l4) z1m^EK{1-H8i3rzQo#KxTv0$6)#%=qtO_@W;tcq$&mYqK zEKSLvTweLA<@O4R8*_a1Ke(T|~h7=}a(0HN@~(+CUL{8D#W|M!DQ!#*LQN6a7k{2>0hy zj1d?hzymuG)d8BYh6D?E?>UUmlRGT2Sv*Q#BH-Y!+7(D-yv8H64j*79j1tn_^L^4iBoNdOypSxtM_FivB0l?@aNx@_lh zqp^i8jIyrrERI52c0v4KoUZA~cO0tlSCMkz8)9slnS;VetgKDZ9_!*;4EIK{*j2MJ;F?XRc?{a!Q!Ck#av&)u=Ho{wc@Qu$n?wi$YiO zE4$BG<3cJCN&ULN3r+F3YofXJw5uI2Gs8uLJlGzdLQc>%LTLp11Z&4 zA`1ZedRLI2&jac}hq2$9cZX}^6yx~@cHHHk&CB1m6~E@iox>J^olNlO(fItL`<7=| zjf!&sVIz}767Gg`^TWM>|2(NU98wQ9aWQznOuQc@p#7N##{*(H;+8Xu+At}HdpZU& z&2tf)f7eX);VijG5XO5(f>}~Eor#F5JOa=iIuvq4d2J7gL(8|=#Y-BJecIYb)p zmc|@+ctqP_Htw5pIvJ0R)&y~O(x1wi3YrI)#oFM0(p00Xbma!a=x2*<^w;V)J_Nwl zk`y|7M7x>$w3LXw3lcVCvR8ctg!Sfk^1o)Z6VakF3lxm zn-9q|f#hP$&>ND{Rb_v$#}87R;#6v|WwHs%U}xDC?$OkC=F2WjJAKassxn40e8<@> z#@AQQ03uT0dE%ugLAbD)>m``Q9|Gu-1zwyXYktrl!x~rf>)jDlI>0YWbMd{!13bZx zTQgEsY`|&;oA5zKAVT&D@2nZ6sKISFp5Y4HQVGHUto=v|zZ>sP(f)gmZe1SaPIPWG z2Nlpc`X}xF?#T;#uM;`Xs8H@Hv&}Eb$emc+N=!e;Erq8z+)rDzz;4#7VF32PmKfo5 zXSS3i06qK6$n3}cKB<^`XFBeQ+Rw;pk&ubsSY||$7h2p+G~hn1y1;D2uJ--(*{^g) z!*&MLF>)3^O-6$;JlSJL88$9V&CJ=6a4bCm<@X)Z)^R^zk=NtC%H~7UoT@u%w!qbx zkv`=JxA%kmag6uGe5rZe|OQ}9!2 zT`Z^}K9p$v{S37r!K7r0>g`{&W8-Bic$u8SS&XAm^gP1$TjU7kDu9yA#cNh|&le*$;Eq*b3zMP^quP#VWh}U=}lL04Zd=H1%)ja zn4G>tFKKg`3~+ldb=2^{A1EU`Q83*UCIvq7{y-{Gc@Ju>``d& zH#p&$E0ey0Gge69!6`Tnyota9g7-L^n~KvXXd;pO8+rF)+8(wmn>Q-dHT|gVqCx}3 z(6w&Lb?N7hMaf9{m;+wL=}UwqjdHzHL}RzN0R8&;q8>C{WWXcLmBpJ{zrg`hH(NCF z8!~{J?Ch?{g;%CGh0Us=MwW_EZ}wV&>QdxmGNr*Z<9+Q1;A+$NjK5Scim96UaMmxI zZw#T&`B3CE;9B)GKAYJ7myve~x1dqoGAq26k8AI{PY)GB8-m? zWP$S0xS-O+`*}Kl#yjg$dLmX^`O@V2zI(d;Xo@PmbXg>^skc4m7;@d0lq5PjQ$R5_o?+cATIIL!FI4-K^){wT z{K_yi&|$UFa1WLW5g5;=Hp}m__vSS`BWN~eT?sK$Z+d-E6HN0NjVD6)@a=Y^PGN3! zIRkVGxCcgIt-MWFpLYXKMBHW+%Q&imySGtt9D^f`(*Et?ES0>p_RR;}a@@Qnhz|lb zT&4vCY=G@hrBO>v!1uYUvP;wjN3tDW}Gdgz->8qm6<2 zgVkoYI=2(ez>&wp1^E(JCjv&&1|FrX#+#b=7fLzlN>j;8w}$Qxgmku;rQaXeDAer5 z^v_eMB3)K_Iv7l26rxT@lLQx7*^J%`QX0LS4$V2lC7wnU1&>rry3p6j*4S*HX z6M%y42|~437f?X3SK9QZ+?Ap&Bo{4gPD!Z4s&qu*y`V{`>;4?HgxH+W4m2ULSWD)J zI!qc&AZ)h}y+rH5IjFT!P;oJZsq+HAk5@Rq#Vx+I8iu~}LtXNY+inEUNbp!C?o{Nh z29!HqtK10W(1(fTT0U|g##`3#1^$j82e4oyXk^d{*opBFQ zKBSSb8oNfx#l|I% zvPc6|Ho4s%FI;r!Xkx75Hb|rZI3TXF;|_EnHyQ?DdJpIJGcro#H|2U_3p~MWj$+*IzLEQ(iv_YQN4eC+U9|-ZJ{I5Y`73va_na}a z24eymbOD^=j>zIGP|@4s#&1-*u{v$9C`C@xOPAX(e!9W~_poj6XoOgCr)r^uOhdbl zti)ka*J*?2dn!^HM(Y6{Y;+!MfYY18Fv(1k=aEPG&Zx~@N3hEwJ=t6ux30`j949@= zeCsj-chk*}S2{7Nx2pd0a%DX@m*h(bD@7^aK7^pVGF3O&Su-*$t^&GKs3W?!!omY# zozvQ&y{{6q1hC=#@8EJIq2eaDFyt87pHxwDC$f4SIC!W~ks<)20e8u*aLVR%#8e@aw!Chz^T$Q>5hH-9O8rYOp2;U@7KFE4= ziGQB`_95#n^{ST-pDpxb1QZZDZK5lsmtnl8;N=U7L%Wfkk(nz+|GGwF3LSR&mfmY^{^? zG-;im_T$(5InxG=M+49L*WZ_vI~)BzM_rTwc=VbF63SWS5GGs3by8aDVc>OITFo$B zI}EYDMdzP%cy548>NAr{SiCv3e5&>8;SI&a#+5u0dWBd=BJbD=P+zj5etdxa?Nz^W z2FE!ffq|Lh{_Ry8A~86@0bYiL`)(hCR>fM?L#ffWQT+@s&+%@C1g7yZ*Q9vgD6}+F z238H(7i0GZJNU= z3}$elXGKK6s5LuXtw$L$Uq-|D<_R?Q$7g=A&wLjTK$mV5GagY|+Fe7|mCn|kEz7n& z`L$|qNU10Md<7U%r5Q(~e;t$6jWuUmEwySlb4;{PzsuEc!D{_d=to&7kj?9-Kb<^V z6s(c18CH1=cJH6`yy2*hGJMiD6onkJ(t9Dn>>$gzLk7>6~5?PL) zDce{O6O!u61_5=*kiFPUrMnF+P6$xy{5Z#|?mI7x>p|v!FW&=m94b}EYXY%nT=?R^UH7%Y$(^Q-RKPZd z%DK<=mI{Eu@8)7|-@q+R@c81$?oLpf=4b)SVSV-8@bYKPMZ+#20`Y4|caa@eC{isA z=&49R_DnQ9ypP2Wbr914fh_4OA;|-o@#if1jWZt%B{$tR!GhmkB8;i*)!f;KL2Hrh zzmw!WK2arRtipJ-SHTgW7?o79nhyfo1q+g;lUf0uR&{&FC+f!z>`oo@{FY%_;QH%Q zzYtW)KIIMx!dy^|HCR%uLZj3&rC?i_*D>LZgvqW)P1TB95uGm|z#Ygcd#|`7kNZ(w zXc(xq-_SC$7K&P^~jBs&Uf$}%ctraPO834l>;ww8QSkg ztrvu}1BKYPXP}&^xr55*-bdn=_zq5;(fCEF2a9NefUq7T=Dj&V6XtvQrXdaf-5U9# z2%Er(0qb^}__^sO+co~I?20Hgaoy1xed#eP|73lOABKkIxI%^}(XK7XwYj`%Ioa@@1c>`H_cYc0j{9;Qd+f6!@g$5P+;3HX z;759q;$(zn;ikEZk)Zt5Qzi?*ZZ?M*hh5HU8olVbsgp#M*H>cCEL%`QhtV-%jO&ht7Pf#)i_eU#CV%l24{;ZRAcA>HS;XXO%ba! zB>Msope$z1*E+}Mv~zql%Y0g#b`#!Yq?3Ea5WAZZvbKTvD3_NqMx7hG!6a?L0v9!E za#Q5%=}*OnoHpCRXAd+X11hXvcD_j@@`?VUqQ*jLucp36gcxblKasO?lz1?rmxqxH z#QJ5lCcS)S=-*XAhh6FkAR@XCgZW)3KPo(4*;C*c$-!i6rPUf`lMOfa%XKL;3l6ou zQMTfEprHg#+PGsyt+d3-{)Ec6o_i{rD$Yxtc@mMYWJ=&STD&Aob`o}9BYJ*<(a`r# zX97}nmrHwf=g~s?yUF(qxYRRcUkM$C>SvPVykSA}Gn^OGVdRdx}E(ENS*$nk!$HQ8P`b$D(u{Lru;l z$s!nz$j~#g7I=OhKm@0m4p6~8vN+E}S_85Kic=n@27(V9O)b)Vw%L--*do)t7)fe4 zUQjFx5l*&EjCxKwTXwClS>GNR{*a|gF^&s?;2wT0^!;e!n{mndi9RlwPm@7~J-ir; zcX2rjSqR~xEUIbf)rs@_-rcW6=>sVwypj6^TMYX3`;l5rfRNOP9fE61rOb^p!twgU zXXjE{kC9T|86=*%8L}d~EMl>#0omU^akQ}W$PGL;9(+#xrxHsfVX|!+Ju%71!9)}D z${yVhPBr=EtMxrjSC^3CJ*C=VBiL4bW8H+6Mh?JR3COM(uIEm0g zKWPN9CEbo}sla!Z&Bi6^Y8+Ebzdt%VP?=wQaOFZ20cKnpPKH=Y?VoLUN>K&p!HONYAHl4LrwOnxn^QcS63F7ySWnJ}0#_?Ti4H+*vFZ^EY!p0SybKbyFeDWBb z*sV~fGvuRHCGqxf3pdIA0n`@NdDWu2o=q$_i3FN@;nMDs9g2&BNBX;`UVnuK4Ku&y z;sQ{KF;cG0wVF>{UPb#Eoca8d{0?ca5#hoD0LK%VB`!aINC2K{cWrr1`53_~@ryOx zoF|=wy`J1YZqB*JC=Qf_JYcGrzrxo* zQ+>%BU_jizT;7gva4eNYqa-$T6{_4b!RN^*^L{S12R&cYdR)qT`#GrgS{5wzoZdxJ z?=5|oi30eTuI>_c9=lZoHO4Q+?Mei&zO@3ae8-Hc_IiZj8{SGAEobHL}B#zAic{2F-g$$-yc{6em2df zX#9pmtWaqmx>m}2lbeT@u6%C;P;ZsLEaiycjh$@z3|$#8=HM&ff2=91tE8BNb|EJ2 z+mutvJ4+EhZ3cVvo+{e9qmmG>!~wnq)a7?zY~`>EqKBJrpCw~KgQ}Q zqB8keb9-35U)5iz0@@@KjY-sv($rY`sz@lGE`G+e#$dL%xKUpiVhId>1L#lv=m6Gk72C?Hsc1JDNjf8P}J%}9XdUcuvQk7)CaWNA}tJ@G)`OTX2u+w zBi4_^iFg1YUB53}opjY!ZJ;2g6KNTb5k#8XPR*B{qSza>jvxKL{=uulC)(`H{p246*F%CqInIF5ZJ4Su;=-oaLGFKES*Itx4F3If-a?(+A03;2po}j{-Q)_`2Dj_D?lwQZYaC2GlpQZrqi@fh0 zO%{ae39!^JuhQIM-Qo+~qLc>47oD>v}@doAuqbMoHj-$HytVM=WVLJ}Gb6w1P%b4yhKVz6@~Oa;4wpnjV}&#O=k znY!-$#teMn)=pAJ15*jW&7B0&OTtT|0IShAnE8!7=~6gu2lkB8G|5Xx$!9L6-VSJl zr2{NbGWGr#sITcJNuatm4sjpPD+NL02mKUl32_cbL**wg{rwR{=cJGOp!k@~74y9h z@V_qv_VeL%VAoN>z`{xXeZ4*-dePSPgiXnLef2Y}X=?KLMtCR9^m~zRPyUl9u@&9;&-$Ku^ zq+#J8Ki2a7@e9)E{#h}2F<~z#AU=i2D<>Xb8i_s3cewg{SWeR#Kc;WC}S$!K?~!uwzA@r`iPe{goVtGGtiN{^X*P< zo@Gq&;Tu^WX}|TDkc3nbiTCNb)36B9634in>bNx($Rx+{nss0FZ4{B5mdlF)_axQo zQsakX{{Yk|(p)o1WOm~w#tne+5_(>xfjku!CAGVKs`o~mt0#aATaH0Q14#13+MoD4 zH+(7LB#fj0(CS2oxlj?&VX4%vB)z>P!696)k3mgZvgL=67xapM!8)$5%ykV$f42dH zfbb>p+ z3?`Hg{_?hJmBfYDctflguJY4DD&^+Qhf>u6v|lFYD@<2M=ZSMF1Sb~mSc%9ReibF) zOj#YZ!4z)Fu#)^e6iBde!PNA-Z{T2HcK;b^;G^OK_!4N~ee^8XneNx`@kOxXQ%=;H z>5N*=a!7^Q(hpo$oiDr1F6!su6qF>g0+J9+M);d}kdKi{|*YQ6>MkaJM ziTSb5o^`^Iksab!Vp+MeVJ(Ec0LlFg!?Z>CQ`CvlsdGT&OQ>kTsJe-&ORvP5jNfV^ zAaY2Yb1o(ZVu6?1s}^2IcUc#+{+(S)CW?MJ(tDK+q}5f@d!%gv)jU6iq~1Tyi27#f zoL`q#{aa3y9RdObWOEcWp=2OxOtNSrUc_XoXVoQ2 zRrYe;;{T z)Ml}84dKg_BqU_8j2)#RSvBR>j-x3wBk7|t0X8laUGsAV%^CQm?$l1JAu14m*2F~+ za%IJsSdZi^7({T?%i2#!HyE76G_6x09{~K~t~{!nS9n10l#9p>Z=012=+xUlu1hm- zzhVIYsB*PiP0NVXvKyimG%s^^^KBk42fomv+7DH;vFFm)Hp%wVq}nqH#O>RvTXj`% zqDxaX@x(MaeLz6Bj1}qF&Jmf*30bD@tzD7P@^1IWct-g8l%MylObj3QIJg&(9XHoA zo6u$@cZM9NH@3!ylx!*kKFDuK=y%75CQEKi|;|fe@05hED0@L!9By?(Tj-}z+iFCqS z5engFm;5l4_8~TaEw>dTZH&D5;?wfO2j6l?T>UenEH^i?xj(O;r_i}TS|53Rw4dx& z!HPqmF5dkX##0hvZjp3@wW{@@XazQ~<#(ZHH15v6(5DFz%RQaS|LWhCs6=mD$j(rP$DQtIN$7=@QhttCP-q6yr5Dsr&oCD1h zRkv7Ax;9yfcq60|yVTWL`X-hNh%u+HnphySFP@f3k5Z0^9#T^yLZV-dT#hcxR@G!BPhe~yIyTx0r< z_%<41Q11aqixKawxhAlHOZe50q%oa(l7Y21E+*d7Pon$un}e|772!QxnDv=)*U{iL z+TP)P;fDIw%Ssc-ZFY>x8_r;!svVLk4H$15~tB)t?4znTQbsc1*o>UUK>5FH=V;-h78DC`xs@pHne^1~HMEF1k%R`R3|4 zc5e^?&Ja+8GuAp#O~<~>!)ZQbZB=EKj(zJ3$JyV+Fb*M8k5rQvzq67eFwHNTdqdzD z7s9W%ZalReZSbqtsiJakN6uxOPQ~eqFS?uges<#}As_A**{WD&aby;j@>2<(AEnwj zC@V61Mj~fp2RqY}PRx|suU$~?B@v$S6H$~kps>5_f}iTAYHqn$qh;UDTpi68y3V&Q z8Qbifr^rFt0#W%aL~>U0@;2z7cXHL@k-m{eT*y^bzN@bK9PA`ehaMm6(3LD-)4UFl z6qZul`tJ;!5XRi?z}Ey&c*Y{4u5BcV11!yUrpvetII&! z=EF4TXG~wFI|Z^NVRbVHP)i&Q3b^(Rz}$y28nVb6W|RJTKX2lI*7|6NJ*78)atNut z<7sXwyLY=Qxj92arMF#Lq8BOFl%?Lo=+_qjOajMHCVM#duQ&|6q6hT_wdYdD-78Fc z;)e}o4c>@D+3n_Sq#WT09QmC=l~xnaPs|FtSYCFJ(Xh2KFSe!r{J1eu$NPHAfJm9? zU_sxtD49AE#@6VD+x=!WSR%h`{LaN@Q;2L&qVsZbsG7qy2PB*hUgugb(S4t6CzG&Dk%NyW0oZ|M$y=5tXMN41LSbg=X59Vk5lCwK;={gBsEI)m zv2QEG#FW%{tuQa%2r9Ed8(~yVOhIvT`zvyQUiFP!8C%hFOnDczL&n8j@y%3Xu;Egy`pe1Z z&5pBDlLRP2mQkeX+O%-}!K|xo9xir^jC9&H^wMq^D}}4h-Z^-6Kmf)-m=u!~%t}4u z<6Me$V?^Gv`D?zGt;G=BO&JEkQ4@5L>QNAp`@H>>*hF6F?ATh@LX{QKyF~sndi=#B z9bTPFYoVh|_j=se*;XD6jm47>fZpnVI>YY)ZfAN2A?WRaHvM`G)N$;0ReCd5+6ti zLL>a&nPf09wm-A&|8q5h^TYo${uynDpkM`F*JDHfXHu0Fgz=|LHRAt7fvzB4!vFnT z3h%t>VG5& zFtYw{h0y`dffpw0XCXS7vQ7Ptt>i%j^i(5v zJgFmpf9noor!kS)Yw}hFbya!>6n@$CbH4imn9-Jcsz&Zd#RIWNuiSifTX3{goI9zh_3hpLtcQXCR`ntcas3dMr7f^!qg3jqPa0|6$3 z1lBe~rNe;-krvno7Nm^Y-$n8+}T-E^eJ zcV4ugUjpKOJ@b;OC(2)thc}~`v&d?d1qXBIRhNqO=|=4?S&G?70x8mH25rP`HDU4CV`uNSoi+C-)T` znLOA%o?tvWcQ#lrK(C4l3PS)1XKe;O;HVv88h$N*@wGE8B&` z`2ls=e(L8RQQw6lf^oH*scO+x*L7 z-0UFz?d9fqGNtqF3ET9WR5ynSB{uViz7C$ zXe#X!W3?YDRR+RN!;KmJ5io^0%ho4CUu{_#1t>JS8u&^SCG!daG~K~%y$^Gkl5#Xq&l99nX!@(RTZC$iB*|?0_iY{ zRrgA@s#d~y#Zj)g5(|2x?U??EsPkz7#p=k!?@++O*a#%jlIa|bJrKy9W%3qMkYCx9 zNqfQ_hCiB)M?#S=eHjq|y^2%l!yqjl8=&ezx*7=hp)OS5$hA0Np0|DkcDvAwi+CaR z+6=aHXOH4WD}IOya2Fzy^}I%9<0a{_00il8tLs-4&Rg4J-fd^^m$1?<>C$i~%4l4F zS-EDd{Q&aazB)3|V3a~>qDNzBvS=^(q=1hIR2G3b{r$)r6VQYjEvTQ#Dw5if~o$nPn#V`{I?bjKXtl{ny# zD@3rOw~O(XvWYQM1y_+z=vpAnS?dsWc;&zqVa%zCXJuKX`zk1Xz|@OVy8B{@lOo?Z zlNR({<)uRlA5Tf?T2H`;>tR}1j6ndSiq5IJuc9ovTCLuVaf=+9hC5z^NjX#B?Wo8U z1(potd1Fm`tww^d%T`gOMcH)89l7f+$jr?%xhu6ho+7mc%UX zdjZFAZ8fFdGQf6~t_AG`oLln)+hA)xyr z-E5r2@XkD|RpKg7MQhKWOFIPvZ3f1K3hoBI4qh|o9=lKLe$_7*ac_(IjVQYw4}lSz z4sD{(6DJQCpf8_Y8dN#7mIoWGUge03Z*V-B}4(o0N9g17Sd( zeM5>qrB+ae#%zM;JGtW}zX5+c4SP1);*&B!*7+0VXrUBB%F|S65&YK9HN7$Gft|5- zu6={*8!_oVM0TECYmj=Lo8nK8zBQY7QCeimta+t5ld)5o*9-T0^OSdDKL$c4S|V-+ z0npbZZkb#y5&*v{OpMkIYaMPeWGp=_{j2E$w4 zct##01GWtLlI}pU*b(kb3P?A3Wa?=dke(J6GUCaJY2;Rm_=zxVFV~vDoI@!j5ut&^ z_D^lnR4QEW;`Abky^Z%gK1yS#M=u>``|poB{lJt+Qi#U4_f5|2-LAmPW*yv;2$S^>~%=!tVV;%GfJ<6>%+Sn*|*wtn4j>W)#3 zHqi%74K7+;ZCI;&AJBu~CZFT?4$xN&cJ4sZnZS{U*d56z2~)Ur&$LTwMwQYULTV2G z9bLP)Mr%OtTMmH^=Rh**u#=Z-=bM&Oxha2QSND}^3hS@`v`?Ar`1>#T0 zgzV9C)`}KT?Z45FV@hURf)b6YB#)UDM47c@!#z zX`ojk`O%CrcP$^yWYzcS&5(BpeD~0I(UQD}Ne47Ik3xiuG8m89G$CcscTFn->OKXf z=>=O}8z;PAeKP8gH3fhI+O~0UWx8n)-thiT$_BXop%vjFAjVO_%F@7EQld|gAS)xi zdAHXXjv1opOs?S?CKhvu(WLs7fg};Off}koC~e#%GuEZ0s85PFOYFBH+Ki*ZpSvhA zuzHf}+-Fp`C>SFqzr;7kK=iz!?|CcEVDJWT3Bp;d!1Ncg;j6&di*BF zh)-~iF}7^YFezmcE0j>TRu@Cm*s{k(I^6S^E3J;C>7B(>rIZ(62OSovp zbrV2P$_(~*?d2(R-rmAZbhi{swe-{*J0}34Rc)g54na@%{Oi65U5VV7czsr#|9mDA zg0-BdQgLoSwcHry*eR++S;v=$EWqkH4Rr7%$zAB(gQe9A?r5Q1U(sXG3do$2J2mPLwg}%$HN9PD)_MyQeocb@eYG(|iH#Gqfr#5i zM>7DuV!#=C5g~OG%z^EuC4w(e9#levOd@x}kx?O-E7>Pghjwvttc3WRS|j^gWVfe< z<(Lr3k@Z^om9;M25}kBV_MAfnix-i8+U^pqh5OLqsJHw_CbDhEhc zrZf?>e{oa;_fKFFg33EQtg%j#znjrKavZp`Eu0iqR<7(0Fa^{k6$vFN?LcC;vJQzd zv|zR<-^#TX$eW>^4iT|Am1ZtvRLw>rb=#Hn8FYX8-9I&%7eBLUZ_)|v--V=&creIk znO^1s9xIJUE2-&Kbxj9EwZ-5{pknkfSo|(A@Nrese~p7W1R+UCoXG$shPp6Gwy|n* z3+h>5=kP*pua{?O>gC0pXts0srn%f#17KB!XM_zxo5F*54d)Z)rT}?h#F$k4Ue+5# zr)Zb4n2loJnaVYQ^*NQgBvs6=J+ucaA5LX*ljJm4!1aYA2Wy2ZDVr0spxkSBQ2$he zwm$AWp7Yr-XbgP%;@Q=OSi^@-AHX`XQtedP z^SdfMw$#Ha;EW%?QFJ&cpGTO?v#*Z^FcMKWivgp)&x#$(7s|62d#_wVq{v2wA`t_h ztGs_XP*@zxW$cqU$ehX@u8I<#hqWiKbynqtP54j-+NyabaHpaOH(DB*hvf3tb~Flo z4ChiZ`gA$c@*v~T)v&V>mT`r)`aMVp|N4<)w;39~+%vNANh^U_L-;olq6Cz|X)=f# zlkmP`7bVnX!+tJ6N%)7wQU(Qhmdy*f{VsIetY!v*AGg2)Ld{+#>-XBOCTU7aqQkCL54^*`^*9CxP`&757OQTroz%Cn}z zxc%7mD!o&vymv@>&>u%i?Do`LV4tG^ZT69Fp?j%;KCtAQn?r|~yGF;wdcVSAgUW0{ z^a0mok7)w99=nQ&2i-<_pL{t?wkGibUbru;K1&gU?D<2{m#ot$q)4lfT(9wLTxEup zE$yZ|m7fO3Etc$lOP)!^c@~1#Rv(DU^Mt1MT0IVS{&4zR?p$6`)R@BqU7#9S?AduS zfT_0tUbH0Igspwi4u*0db)s}BUXGcp$LCFt_&v<@Gg&#i{*{Dxd0vN9#gu35o z3EyO+PAw>v`8WC%6OYLCX%rK1OaJKR4HY)MXo+D+R)W7Zq;@dMJja&>?Sqt~_PyX3PmEPm}Y13FFLyVUSdb@{|17 zI3D2?s!K?~;@&3S${ougTS~^%U;gaLZyYSI&1948@e2vFHr&s|SB6grWby zTXex}xj;4F+;lX7b6J=c4&Ip*h@|n`o@_B~<|Dt1S zV_@N$J%?GXt92|sqSoLq1-4z-z{V73zKTW?bIxP7eBB0~uYFd=6jK#k8lOD*;0^l6gbW`)#WnNN>!RoxNE*gQRElfFq87loh4 zQ1)sRjA)Kzp%By*hPgm~qieKg&@C>v^%z0|=VyQNpXLx~p|f415Nykj{U~nINe7>f ztnd()q&q)82}2tdu<$t&$N4Ck2c59TQHOW_$%7i32TN3<#*9`K8icGV@arjqgdpjI zaeOb5w1NUz23TJFb3ssWQVN%?-^I^|Is91ld z&K^s%x|0s}p;Vk;8~{6w{^X=NH5=sLr_{QM~j0&FAOdRM@K*~ zs(huunH(2of+;S1z6>-9rS%Fp)e zBm~a&1oZR^7xqPNiXWB@lugq=t4E*W9L#B?!~0!ODx8K@%o}*Yjn^k0I{n-tvUIrT z{!Ee=r=y5ODtnq0U@d_C&=;p7NYCwd%@lfpn9ra%s}AyuKv%DGRBcvx{;Uz3{)-Cy z5LJ2dL$5M>PIHCB5lvBk>(d#R>wpvVe(6nM3~*??yDJ1=(<-BEx%P_X}qHSa0R}2 zV}IN-_l*VZlA44jAAy5yC6aOsHMVe%yS}aki4gRW@I>hnZ}Mc`eJ#6m{DsvagHC7a z??q5KWxRFn2)n`zobDej4)+eb4ApI5++1MV`X~ccGre zjA^iE=%XLl{|VK5yOH~_yHX`fTKl-3t4Skg)8qm4{Auv8^E{|u7H9=wm)V!rf%qK4 zd-9v6ItjFSUH_wVLqlWbfH!c|X4FQ{<|$S6Ap=}wYb_YN$!Cl?JgQuF7;ZoH%!|5G^b&(n>w4-Nr0sPko zP|p3MtJWy2iK<8hGRo;?X3%Mwgre#EPZlY6sZGj_yTs0r^NyJkA=0bnl2?;1%>ku9 zrQZ;JJJ;1uWwsq=l^^a4tLCmZNect%E~_mZpC~te%kR(#Qa#@zKHtNHt`+T4>6Gee^Vwc-&13d)lP*5fMSyT;S(o%;e9&ZP9960ornv|Lu zSLWq`w{J(}OGSOj&=1BDlv*0YyH2&I{YpS#rIgC4NxgHIA|; zKGcQUMG0y>hKRj-quk8w(f15tpWG#B`-(n8aG5{rmwZ3@bfup@)XJP8Xj6df{66(9 zux=A+1kF#M>pH{AE6FsIX;W*?RGr(-wL1gWxgs>0bt<+WxiyBIi*p-1awE1?6f%UO zBgT#h5=Lc;4>$1Ar|Dy4ej%j|uRj5Ep=C?+x6?V=DQ#4p^&L#>ZRLgw;w_X8yeob+ z(}h3h%8jGnypUPHRk@^!Tal#9g2uY}mv`o3v0em!e0NP{H+$ZkI`Z_EHI-?&pj~%` z8zlq|TDO=m2}F8Tj^{7emi=nR&UY4{JykUH4duJrPi#sZgd!mFO!y!e0_&OXqG&|5 z>jc>X1Xc?nrN`+_i@TzCTr1x;jED-9Ph zACA3%XHIR><2vfne$VbgjUSqE-8Me}K|k7Qs3*Glj|7V z{}@2Og02u8R@DsbI>l|eD$y0KE^e+1A>3SFN{@f?wX2vkcxH`$HByx!86hRlm^CPn z!5-s0v>Zp|ouTvz)YKL=U{GrMSm>DHD@WpvQvr@0r5P#)YK~Cce3YiwgKXrNNo#4E z9gC9LV@;%q9mRfLoa8>m7gYTGO8Pf%9uBY6gF%OYz##dXH-ohp89=M{v%(kxX?YF1 zP4=l+A0c&gWTc_y^e7E<$i&ns^XkN5@>L9{1{fWJU|E{v7U;x z$C2AE^YOVHJd9PpoZiR&gdo`tkAdp_%qS3w-qgyR9)~Wz2b2-SM>uvhnZ#C+4A1uN z3SC3=+~eVUf!;1a@di2Ejz?x(P(!bpvMj83#XkQc5UpN5VNKxNx$hAU&QUYRk!RP^ zZ9iZ4*cHTPPo7s6klc7l$Rn>)>IDWKgsX{(h9sIFqEIIK@0Hh6t5TmdOtTwcX@sMWALPgglC``IxcjHL}1)8yDvM|nqbu!0X1j5IF|aG_>T3XpZG4a8B&voN3`RI=W+gA&jUqo zVjXq2k{48DP@|<+M2pkF}#?O!t5O&c2UW&qx zoFGpk3j+VQrR(7lvtc(Zi;{Be)f=VRnJH>3LiA>LfomlD&+C!C4b0y!)@FwlvdU`c zT9vYFkrFcMV3+6W=t_gxMmBtc+Ovhf^*n73$1{!&@GIuDK3zU_Ui)9(uirmy4_>yD zb{Qxa8YK!xo5hCKOEd9ihwiQ<*oPF_*@2u@8hiC`j>GP!@(Bt8OunJg)X^~kXUDD` zC$F)+=P;Mnu&nHWXF&X8YhZ+HEXtkaqr{4L3pv!ci89wtQ=)?h$ZzLfCKT;{0`1>B zzHNVx8LH}9F&~xVPCZ7ks?NIzG&E{I)f8pRUPZ;$&?_AFCH{Uv*1R5Ck7KwOx&rz% z=lA6c?abobEa|mz#@9EaXzc_2& zwevY=5yCFk&biix>^I~R|HTZ2o$U@O8Va`Zq%n*41R=oO`vc zN0OL#!&#g&#uhzN2y^Zg$s>8jPU)K849P{U?)0NV?^+9M~_r19e|gi9!@MJv5jTn5$k00d~%Lmsk z{4*?DWk9gN1c-eG(1=H^@-kbNQ@;cQo!o1D_|+NNdJ~QIx2|pJk-7l|$9PYIYgj@2U%F1=LZg5GS3DXD#ec z)^m`mU}AB;|pPvl`zQqMBRq343F5&dXYuIWNeh>a*PVWhtyB;PMzbF|Rn z9C$WtXD=%lY#}w8>krcMOltFKK;E7){_-qTRW}87T2VV`%%G>z7QUb2`0#;9zB6|@ z=GPzdGcb%-M+DwhFr&%3JOaBdj>+rH#22i(C^<6pwM>n&=6(&0E^?ot&jn7#Y_U;9 zSdk-Us@pRb*gp-V_~WhiDfc}ZHu1SW(OAu%)iWHS!fW=_ihcpTmBJGK_!Y=5!re*5 zp?lqA9y8*AMFXZ=mpv?BD%`4vLLEIBSzcv$BIVkk_Htyf*Sv;n`|c#c@PbOxy_T-xcB_|(OrW5=IS=EZ&8r1h5 zX)TS~QhN@`H%J1|W2@R)xJNBa4oNx`(;LNAzqd6mx**cVsDIU09FjJJk%vF}8$&WT z3MTeZ2fb8|!*9q{0kuFGm(lUiR&B$L^fV?up@}WgnrJ_*t)2W%m=w;FK@`Cpqoj{a z$IAZccCJj+op<*pF~FTcziUY+&Fy2#!(Lyw`{JfeyJ2S#|CH^zYhID>?bnFdVX}OV zTZsz)&;2y}B|Y=@ObRe0d+*Cey@=x#zqV(AKk*={vC&H(7zHKQZ2i~@#D8n_D#Y^C=1)Rl7r|yWV^!$o}6pCXiP_MNy=Q;g61~wY3K|OTk zHVirG3~`_ZJ@8tz?o+AzVsG9!f-U0w8LT9=9ar&EhC1?ZF1LJ;I!2d`=?DOugXnFd zVdPa~W4ghBSZ|1R>?CT`%5(MfGPKA=7KV1tY_!9J_PMt4Y`fbIeoO)ELY~ z7r5QqhkVM5%01uYz?ZHdJ9!^=xF5@lCgqRnl$+qQ{FYL!d1?|ia9+F3X~B?%UX^bc-8+t5lSLm}Wa6xw zbt0#mcAKw)>X*^{g_wFE$2~eAcKauX$BxlFMp|X4Gk*EeXkjzI*hiWqCz;!OsH!zs2K8HJG^QfQ2(M-%w zCnrw%1q}WgF=vn#K(^vkEUWVya5c~y^2Xrr3{7&-Y6kR-)Z&rp8(@U|>5SiNIKKgc z>a|I(7->q>l_}oDlza3}4saAJ4Q8(n+HRhAodf3rk$F7vAZaCn`HOBVppYslsV_CN z6DDn(Gt~tvN4*Ttg)TXbC^Vt;npfuGY|})S9kYCK(=4h@;qc%~REngtUrEl|N7p?G zKrd|pA8o|}nOhYB*6tPw!Crv23bb+1*3DT1mIv#Ry|Mr#Kc?oDP6R{sPZjvJFc`EJ zYm(ehq{?b-;7M7AxGEkV*nGmCd8o>(b=J9alt~DdLP)7-DffN5Jm~H<{i$-plT3k2E>zy(;|t72^d|oDXsn ztAsQ|DM#}8<4X6@0;p+1`o2ajEkTxRef!)Wd1b;Cj|d!KiYiPfT>Eil52)8uEY=Nk zCqUmN?#oMFLEFvuB$&3$Y5%bc(EB}_%e!xQ{CG=`7fb0e%&0!TtECxaC+Ke zeX8Oq@YZ=OWv5RY+?ang0e=O`tW8nkRQ`le5!+?orA|9Yc5q?6xSv(Xc3y+tdL`KQ z^P@wTxi1819$Y7XP9eKbLh6?t>#*Oz2`Lz8kMXTc?K}i8Z`Op`gnpL{o1&BfU8QI# zG-PW%G0O6lrVD}=e);TAQHQx3owocW-&dT~%ACF>(d)3$6V5uP>Hky#aSy(cqWjWc zDIxt#={~%jcRMIrO;`3vw&r*9U}=m$I*0t;gQxOfjkKw-9ZKwB!i4SQ-{cP08At6; z{2+=1Y9ZF$GufKnc~aug!#VlcziDw*>0)L0DnQ}5@Tr~zl58f%)(yEi*5)N|5Td~} zOo`ZdAcVbHCzf29GU87ii1_z$D_cUud=J9~nqIv7gR+Y3!3*zlQ(^+`If6it+e|52 z^}_A`(p8k!rZ$mGIBLDwPC&xaocfRN%Zz3xcX+pU;^NoK_Fm;ei`(sM1gV(aF287` zNa|b4a|!hKG#Nnq^N~g2{7`g;5twEDs7nF6Ypf#%*|{X9jQLI@Ai_DP_$Sm0!EZL6 zo89CmpMmQv>qBM)Njc(rjP&}zlYV<#6vfvfjD0aX0F}B))u8-}d`h17FQuF|E~SyA z1x#;@k~By}kZ=Y-y_dLI_U6F;%ncj}D=mGsn0-Roo!NsXXNs<1fv?&_;sj=(5tF*W zsegn0n<8Na{~CkaM}mM@!Ta}b(U^D33+CS@F!4G(NSMEyziN%@Eg@kZAd{iNWJE;Z zNoYcFWIqP*-}WCU2na_Q2nf~p{bB!mJV5dxz8isH2Q&;3!y z*^B&NBQ99DiT1q-N9=dN@GdZb|ChT#K+yibC=Z-}{n%(Ch50*|2SYaFyoXLx{G%Mr zK;Ykd`p+#vKrsKm&=1uAOrgz!?-3@f|AbNtDmaoCAB@=Yw+G?u|Al<-f}HQ45ELT@ z5B{sQ7ybtvTH(Qf*1rx5vHt=gC^dPoNdE`$R4dIp&z1+X8j^wu+hpDavhqK{pu-5} zZ^Hohw&A}+7L9i(0{+ncx7BOScR>&K?I#5%w*Rfo(FRwy8@v|)hG0w=R&z*jxo?_xdpUnA(T|FQe`{u67Rw*@?Efi#rr /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi - wget "$jarUrl" -O "$wrapperJarPath" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi - curl -o "$wrapperJarPath" "$jarUrl" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then @@ -277,6 +296,11 @@ if $cygwin; then MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ diff --git a/mvnw.cmd b/mvnw.cmd old mode 100755 new mode 100644 index fef5a8f7f..86115719e --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -26,7 +26,7 @@ @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -37,7 +37,7 @@ @echo off @REM set title of command window title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -120,23 +120,44 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) ) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) ) @REM End of extension +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end From e6869bcdfdfc5dd26477bea76b4920ddf73d1883 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Wed, 21 Jul 2021 08:06:03 +0200 Subject: [PATCH 107/776] Fix http URL in license header --- .mvn/wrapper/MavenWrapperDownloader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java index b901097f2..e76d1f324 100644 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, From d80d920a575b97b74d634cad3ed1b8b6034cd1ad Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 23 Jul 2021 20:11:17 +0200 Subject: [PATCH 108/776] Remove changelog shipped with the binaries. Original Pull Request #1881 Closes #1879 --- src/main/resources/changelog.txt | 1689 ------------------------------ 1 file changed, 1689 deletions(-) delete mode 100644 src/main/resources/changelog.txt diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt deleted file mode 100644 index 57105de1f..000000000 --- a/src/main/resources/changelog.txt +++ /dev/null @@ -1,1689 +0,0 @@ -Spring Data Elasticsearch Changelog -=================================== - -Changes in version 4.3.0-M1 (2021-07-16) ----------------------------------------- -* #1868 - Internal refactoring. -* #1866 - Queries defined with `@Query` are not using registered converters for parameter conversion. -* #1864 - Upgrade to Elasticsearch 7.13.3. -* #1862 - Add native support for range field types by using a range object. -* #1860 - Make the TestContainers Elasticsearch container configurable. -* #1858 - Collection parameters for @Query-annotated methods get escaped wrongly. -* #1854 - Improve NativeSearchQueryBuilder by adding convenience methods and modifying existing ones. -* #1846 - Missing hashCode and equals methods in JoinField. -* #1839 - Upgrade to Elasticsearch 7.13.1. -* #1836 - Make CompletionField annotation composable. -* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. -* #1831 - Upgrade to Elasticsearch 7.13.0. -* #1828 - Dependency cleanup. -* #1826 - Improve integration test time. -* #1824 - Fix reactive blocking calls. -* #1822 - Add Blockhound to test setup. -* #1821 - Fix reactive mapping creation. -* #1817 - Fix fields and source_filter setup. -* #1816 - Allow runtime_fields to be defined in the index mapping. -* #1811 - StringQuery execution crashes on return type `SearchPage`. -* #1803 - Default FieldType.Auto on Arrays of Objects. -* #1800 - Improve handling of immutable classes. -* #1794 - Refactor `DefaultReactiveElasticsearchClient` to do request customization with the `WebClient`. -* #1792 - Upgrade to Elasticsearch 7.12.1. -* #1790 - Custom Query with string parameter which contains double quotes. -* #1788 - Allow disabling TypeHints. -* #1787 - Search with MoreLikeThisQuery should use Pageable. -* #1785 - Fix documentation about auditing. -* #1781 - Remove deprecated code. -* #1778 - Custom property names must be used in SourceFilter and source fields. -* #1767 - DynamicMapping annotation should be applicable to any object field. -* #1564 - (Reactive)ElasticsearchOperations does not have option to include request_cache path param in search request [DATAES-992]. -* #1488 - @ScriptedFields & Kotlin data classes [DATAES-915]. -* #1255 - Add pipeline aggregations to NativeSearchQuery. -* #638 - datatype detection support in mapping ( i.e. numeric_detection, date_detection etc..) [DATAES-62]. - - -Changes in version 4.1.11 (2021-07-16) --------------------------------------- -* #1866 - Queries defined with `@Query` are not using registered converters for parameter conversion. -* #1858 - Collection parameters for @Query-annotated methods get escaped wrongly. -* #1846 - Missing hashCode and equals methods in JoinField. - - -Changes in version 4.2.2 (2021-06-22) -------------------------------------- -* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. - - -Changes in version 4.1.10 (2021-06-22) --------------------------------------- -* #1843 - Pageable results and @Query annotation. -* #1834 - TopMetricsAggregation NamedObjectNotFoundException: unknown field [top_metrics]. - - -Changes in version 4.2.1 (2021-05-14) -------------------------------------- -* #1811 - StringQuery execution crashes on return type `SearchPage`. -* #1805 - Upgrade to Elasticsearch 7.12.1. -* #1794 - Refactor `DefaultReactiveElasticsearchClient` to do request customization with the `WebClient`. -* #1790 - Custom Query with string parameter which contains double quotes. -* #1787 - Search with MoreLikeThisQuery should use Pageable. -* #1785 - Fix documentation about auditing. -* #1778 - Custom property names must be used in SourceFilter and source fields. -* #1767 - DynamicMapping annotation should be applicable to any object field. - - -Changes in version 4.1.9 (2021-05-14) -------------------------------------- -* #1811 - StringQuery execution crashes on return type `SearchPage`. -* #1790 - Custom Query with string parameter which contains double quotes. -* #1787 - Search with MoreLikeThisQuery should use Pageable. -* #1785 - Fix documentation about auditing. -* #1767 - DynamicMapping annotation should be applicable to any object field. - - -Changes in version 4.2.0 (2021-04-14) -------------------------------------- -* #1771 - Remove `@Persistent` from entity-scan include filters. -* #1761 - CriteriaQuery must use nested query only with properties of type nested. -* #1759 - health check with DefaultReactiveElasticsearchClient. -* #1758 - Nested Criteria queries must consider sub-fields. -* #1755 - Documentation fix to not show deprecated calls. -* #1754 - Types are in the process of being removed. -* #1753 - CriteriaQuery must support nested queries. -* #1390 - Introduce ClusterOperations [DATAES-818]. - - -Changes in version 4.1.8 (2021-04-14) -------------------------------------- -* #1759 - health check with DefaultReactiveElasticsearchClient. - - -Changes in version 4.0.9.RELEASE (2021-04-14) ---------------------------------------------- -* #1759 - health check with DefaultReactiveElasticsearchClient. - - -Changes in version 4.1.7 (2021-03-31) -------------------------------------- - - -Changes in version 4.2.0-RC1 (2021-03-31) ------------------------------------------ -* #1745 - Automatically close scroll context when returning streamed results. -* #1741 - Upgrade to Elasticsearch 7.12. -* #1738 - Readme lists artifacts with .RELEASE and .BUILD-SNAPSHOT suffixes. -* #1736 - Upgrade to OpenWebBeans 2.0. -* #1734 - Remove lombok. -* #1733 - Update CI to Java 16. -* #1727 - Allow multiple date formats for date fields. -* #1719 - Configure index settings with @Setting annotation. - - -Changes in version 4.2.0-M5 (2021-03-17) ----------------------------------------- -* #1725 - Add support for SearchTemplate for reactive client. -* #1721 - IndexOps.getMapping raises exception if mapping contains "dynamic_templates". -* #1718 - Create index with mapping in one step. -* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. -* #1711 - Add the type hint _class attribute to the index mapping. -* #1704 - Add SearchFailure field in ByQueryResponse. -* #1700 - Add missing "Document ranking types". -* #1687 - Upgrade to Elasticsearch 7.11. -* #1686 - Add rescore functionality. -* #1678 - Errors are silent in multiGet. -* #1658 - ReactiveElasticsearchClient should use the same request parameters as non-reactive code. -* #1646 - Add function to list all indexes. -* #1514 - Add `matched_queries` field in SearchHit [DATAES-979]. - - -Changes in version 4.1.6 (2021-03-17) -------------------------------------- -* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. - - -Changes in version 4.0.8.RELEASE (2021-03-17) ---------------------------------------------- -* #1712 - Requests with ReactiveElasticsearchRepository methods doesn't fail if it can't connect with Elasticsearch. - - -Changes in version 4.2.0-M4 (2021-02-18) ----------------------------------------- - - -Changes in version 4.1.5 (2021-02-18) -------------------------------------- - - -Changes in version 4.2.0-M3 (2021-02-17) ----------------------------------------- -* #1689 - Missing anchor links in documentation. -* #1680 - After upgrade to 4.x can't read property id from _source named (different value from _id). -* #1679 - Errors are silent in delete by query in ReactiveElasticsearchTemplate. -* #1676 - Align MappingElasticsearchConverter with other Spring Data converters. -* #1675 - Consider Document as simple type. -* #1669 - Cleanup Deprecations from 4.0. -* #1668 - Writing a more complex CriteriaQuery. -* #1667 - Couldn't find PersistentEntity for type class com.example.demo.dto.Address. -* #1665 - ReactiveElasticsearchOperations indexName twice endcoding. -* #1662 - Documentation fix. -* #1659 - Fix source filter setup in multiget requests. -* #1655 - GeoJson types can be lowercase in Elasticsearch. -* #1649 - Upgrade to Elasticsearch 7.10.2. -* #1647 - Use own implementation of date formatters. -* #1644 - Implement update by query. -* #1565 - Allow using FieldNamingStrategy for property to fieldname matching [DATAES-993]. -* #1370 - Add enabled mapping parameter to FieldType configuration [DATAES-798]. -* #1218 - Add routing parameter to ElasticsearchOperations [DATAES-644]. -* #1156 - Add @CountQuery annotation [DATAES-584]. -* #1143 - Support for search_after [DATAES-571]. -* #803 - Don't update indexed object if it is no persistent entity [DATAES-229]. -* #725 - Add query Explain Support [DATAES-149]. - - -Changes in version 4.1.4 (2021-02-17) -------------------------------------- -* #1667 - Couldn't find PersistentEntity for type class com.example.demo.dto.Address. -* #1665 - ReactiveElasticsearchOperations indexName twice endcoding. -* #1662 - Documentation fix. -* #1659 - Fix source filter setup in multiget requests. -* #1655 - GeoJson types can be lowercase in Elasticsearch. - - -Changes in version 4.0.7.RELEASE (2021-02-17) ---------------------------------------------- -* DATAES-996 - Update CI jobs with Docker Login. -* #1667 - Couldn't find PersistentEntity for type class com.example.demo.dto.Address. -* #1665 - ReactiveElasticsearchOperations indexName twice endcoding. -* #1662 - Documentation fix. -* #1659 - Fix source filter setup in multiget requests. - - -Changes in version 3.2.13.RELEASE (2021-02-17) ----------------------------------------------- -* #1694 - Upgrade to Elasticsearch 6.8.14. -* #1662 - Documentation fix. - - -Changes in version 4.2.0-M2 (2021-01-13) ----------------------------------------- -* DATAES-1003 - add timeout to search query. -* DATAES-996 - Update CI jobs with Docker Login. -* DATAES-982 - Improve refresh handling. -* DATAES-946 - Support 'wildcard' field type. -* #1640 - Add support for GetFieldMapping request in ReactiveElasticsearchClient. -* #1638 - Upgrade to Elasticsearch 7.10.1. -* #1634 - Update Testcontainers dependency. -* #1632 - Update copyright notice to 2021. -* #1629 - Update repository after GitHub issues migration. -* #1576 - Add version of Spring dependency to docs [DATAES-1004]. -* #1056 - Repository initialization should throw an Exception when index cannot be created [DATAES-481]. - - -Changes in version 4.1.3 (2021-01-13) -------------------------------------- -* DATAES-996 - Update CI jobs with Docker Login. -* #1634 - Update Testcontainers dependency. - - -Changes in version 4.1.2 (2020-12-09) -------------------------------------- -* DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads). -* DATAES-990 - Index creation fails with Authentication object cannot be null on startup. -* DATAES-987 - IndexOperations getMapping fail when using index alias. -* DATAES-978 - Accept DateFormat.none for a date property to enable custom Converters. -* DATAES-977 - Fix versions in reference documentation for 4.1. -* DATAES-973 - Release 4.1.2 (2020.0.2). -* DATAES-972 - BeforeConvertCallback should be called before index query is built. -* DATAES-543 - Adjust configuration support classes so they do not require proxying. - - -Changes in version 4.2.0-M1 (2020-12-09) ----------------------------------------- -* DATAES-995 - Code Cleanup after DATACMNS-1838. -* DATAES-994 - Add setup for mutation testing. -* DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads). -* DATAES-990 - Index creation fails with Authentication object cannot be null on startup. -* DATAES-989 - Improve deprecation warning for id properties without annotation. -* DATAES-988 - Allow specifying max results in NativeSearchQueryBuilder. -* DATAES-987 - IndexOperations getMapping fail when using index alias. -* DATAES-986 - Fix Javadoc. -* DATAES-985 - Add builder method for track_total_hits to NativeSearchQueryBuilder. -* DATAES-983 - Test dependency hoverfly-java-junit5 leaks into compile scope. -* DATAES-978 - Accept DateFormat.none for a date property to enable custom Converters. -* DATAES-976 - Implement CrudRepository.delete(Iterable ids). -* DATAES-975 - Upgrade to Elasticsearch 7.10. -* DATAES-974 - remove usage of deprecated WebClient exchange() method. -* DATAES-972 - BeforeConvertCallback should be called before index query is built. -* DATAES-971 - Fix tests for using a proxy with reactive client. -* DATAES-970 - Take Testcontainers version from the Spring Data Build pom. -* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree. -* DATAES-968 - Enable Maven caching for Jenkins jobs. -* DATAES-966 - Release 4.2 M1 (2021.0.0). -* DATAES-882 - HLRC Configuration - add ability to set max connections for the underlying HttpClient. -* DATAES-588 - Add support for custom callbacks in High Level/Low Level REST Client builder. -* DATAES-543 - Adjust configuration support classes so they do not require proxying. -* DATAES-362 - Add support for composable meta annotations. -* DATAES-247 - Support OpType in IndexQuery. - - -Changes in version 4.0.6.RELEASE (2020-12-09) ---------------------------------------------- -* DATAES-991 - Wrong value for TermVector(with_positions_offets_payloads). -* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree. -* DATAES-968 - Enable Maven caching for Jenkins jobs. -* DATAES-964 - Release 4.0.6 (Neumann SR6). - - -Changes in version 3.2.12.RELEASE (2020-12-09) ----------------------------------------------- -* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree. -* DATAES-963 - Release 3.2.12 (Moore SR12). - - -Changes in version 4.1.1 (2020-11-11) -------------------------------------- -* DATAES-969 - Use ResultProcessor in ElasticsearchPartQuery to build PartTree. -* DATAES-968 - Enable Maven caching for Jenkins jobs. -* DATAES-965 - Release 4.1.1 (2020.0.1). - - -Changes in version 4.1.0 (2020-10-28) -------------------------------------- -* DATAES-962 - Deprecate Joda support. -* DATAES-960 - Upgrade to Elasticsearch 7.9.3. -* DATAES-956 - Prevent double converter registration. -* DATAES-953 - DateTimeException occurred "yyyy-MM-dd HH: mm: ss" string is converted to Date. -* DATAES-952 - Optimize SearchPage implementation. -* DATAES-951 - Revert DATAES-934. -* DATAES-950 - Release 4.1 GA (2020.0.0). -* DATAES-931 - Add query support for geo shape queries. -* DATAES-796 - Provide new method to return SearchHits in ReactiveElasticsearchClient. - - -Changes in version 4.0.5.RELEASE (2020-10-28) ---------------------------------------------- -* DATAES-953 - DateTimeException occurred "yyyy-MM-dd HH: mm: ss" string is converted to Date. -* DATAES-937 - Repository queries with IN filters fail with empty input list. -* DATAES-936 - Take id property from the source when deserializing an entity. -* DATAES-926 - Release 4.0.5 (Neumann SR5). - - -Changes in version 3.2.11.RELEASE (2020-10-28) ----------------------------------------------- -* DATAES-961 - Upgrade to Elasticsearch 6.8.13. -* DATAES-937 - Repository queries with IN filters fail with empty input list. -* DATAES-925 - Release 3.2.11 (Moore SR11). - - -Changes in version 3.1.21.RELEASE (2020-10-28) ----------------------------------------------- -* DATAES-958 - Release 3.1.21 (Lovelace SR21). - - -Changes in version 4.1.0-RC2 (2020-10-14) ------------------------------------------ -* DATAES-949 - dependency cleanup. -* DATAES-947 - Adopt to API changes in Project Reactor. -* DATAES-945 - Compilation error on JDK11+. -* DATAES-944 - Simplify logging setup in test environment. -* DATAES-943 - Add missing mapping parameters. -* DATAES-940 - Update to Elasticsearch 7.9.2. -* DATAES-937 - Repository queries with IN filters fail with empty input list. -* DATAES-936 - Take id property from the source when deserializing an entity. -* DATAES-935 - Setup integration tests separate from unit tests. -* DATAES-934 - Add a Query taking method to ElasticsearchRepository. -* DATAES-933 - Fix typo in javaDoc. -* DATAES-932 - GeoPoint - Point conversion is wrong. -* DATAES-930 - Add support for geo_shape type entity properties. -* DATAES-929 - Support geo_shape field type field type. -* DATAES-927 - Release 4.1 RC2 (2020.0.0). -* DATAES-921 - Investigate WebClient.retrieve() instead of using WebClient.exchange(). - - -Changes in version 4.1.0-RC1 (2020-09-16) ------------------------------------------ -* DATAES-924 - Conversion of properties of collections of Temporal values fails. -* DATAES-923 - Upgrade to Elasticsearch 7.9.1. -* DATAES-922 - Move off Sink.emitXXX methods. -* DATAES-920 - Add parameter to @Field annotation to store null values. -* DATAES-919 - Fix error messages in test output. -* DATAES-914 - Use Testcontainers. -* DATAES-913 - Minor optimization on collection-returning derived queries. -* DATAES-912 - Derived Query with "In" Keyword does not work on Text field. -* DATAES-911 - Add documentation for automatic index creation. -* DATAES-910 - Upgrade to Elasticsearch 7.9.0. -* DATAES-909 - Add singular update() methods to ReactiveDocumentOperations. -* DATAES-908 - Fill version on an indexed entity. -* DATAES-907 - Track Total Hits not working when set to false. -* DATAES-904 - Release 4.1 RC1 (2020.0.0). -* DATAES-902 - Update to Elasticsearch 7.8.1. -* DATAES-898 - Add join-type relevant parts to reactive calls. -* DATAES-895 - Criteria.OperationKey.NEAR is not used anywhere. -* DATAES-854 - Add support for rank_feature datatype. -* DATAES-706 - CriteriaQueryProcessor must handle nested Criteria definitions. - - -Changes in version 4.0.4.RELEASE (2020-09-16) ---------------------------------------------- -* DATAES-924 - Conversion of properties of collections of Temporal values fails. -* DATAES-912 - Derived Query with "In" Keyword does not work on Text field. -* DATAES-905 - Release 4.0.4 (Neumann SR4). - - -Changes in version 3.2.10.RELEASE (2020-09-16) ----------------------------------------------- -* DATAES-903 - Update to Elasticsearch 6.8.12. -* DATAES-892 - Fix ElasticsearchEntityMapper recursive descent when reading Map objects. -* DATAES-888 - Release 3.2.10 (Moore SR10). - - -Changes in version 3.1.20.RELEASE (2020-09-16) ----------------------------------------------- -* DATAES-887 - Release 3.1.20 (Lovelace SR20). - - -Changes in version 4.0.3.RELEASE (2020-08-12) ---------------------------------------------- -* DATAES-897 - Add documentation for Highlight annotation. -* DATAES-896 - Use mainField property of @MultiField annotation instead of additional @Field annotation. -* DATAES-891 - Returning a Stream from a Query annotated repository method crashes. -* DATAES-890 - Release 4.0.3 (Neumann SR3). - - -Changes in version 4.1.0-M2 (2020-08-12) ----------------------------------------- -* DATAES-901 - Operations deleting an entity should use a routing deducted from the entity. -* DATAES-899 - Add documentation for join-type. -* DATAES-897 - Add documentation for Highlight annotation. -* DATAES-896 - Use mainField property of @MultiField annotation instead of additional @Field annotation. -* DATAES-894 - Adapt to changes in Reactor. -* DATAES-893 - Adopt to changed module layout of Reactor Netty. -* DATAES-891 - Returning a Stream from a Query annotated repository method crashes. -* DATAES-886 - Complete reactive auditing. -* DATAES-883 - Fix log level on resource load error. -* DATAES-878 - Wrong value for TermVector(woth_positions_offsets). -* DATAES-877 - Update test logging dependency. -* DATAES-876 - Add seqno and primary term to entity on initial save. -* DATAES-875 - MappingElasticsearchConverter.updateQuery not called at all places. -* DATAES-874 - Deprecate parent-id related methods and fields. -* DATAES-872 - Release 4.1 M2 (2020.0.0). -* DATAES-869 - Update to Elasticsearch 7.8. -* DATAES-864 - Rework alias management. -* DATAES-842 - Documentation fixes. -* DATAES-612 - Add support for index templates. -* DATAES-433 - Replace parent-child mappings to join field. -* DATAES-321 - Support time base rolling indices. -* DATAES-244 - Support alias renaming. -* DATAES-233 - Support for rolling index strategy. -* DATAES-207 - Allow fetching indices by alias. -* DATAES-192 - Define alias for document. -* DATAES-150 - mapping are not created when entity is saved in new dynamic name index (spel). - - -Changes in version 4.0.2.RELEASE (2020-07-22) ---------------------------------------------- -* DATAES-883 - Fix log level on resource load error. -* DATAES-878 - Wrong value for TermVector(woth_positions_offsets). -* DATAES-865 - Fix MappingElasticsearchConverter writing an Object property containing a Map. -* DATAES-863 - Improve server error response handling. -* DATAES-862 - Release 4.0.2 (Neumann SR2). - - -Changes in version 3.2.9.RELEASE (2020-07-22) ---------------------------------------------- -* DATAES-861 - Release 3.2.9 (Moore SR9). - - -Changes in version 3.1.19.RELEASE (2020-07-22) ----------------------------------------------- -* DATAES-860 - Release 3.1.19 (Lovelace SR19). - - -Changes in version 4.1.0-M1 (2020-06-25) ----------------------------------------- -* DATAES-870 - Workaround for reactor-netty error. -* DATAES-868 - Upgrade to Netty 4.1.50.Final. -* DATAES-867 - Adopt to changes in Reactor Netty 1.0. -* DATAES-866 - Implement suggest search in reactive client. -* DATAES-865 - Fix MappingElasticsearchConverter writing an Object property containing a Map. -* DATAES-863 - Improve server error response handling. -* DATAES-859 - Don't use randomNumeric() in tests. -* DATAES-858 - Use standard Spring code of conduct. -* DATAES-857 - Registered simple types are not read from list. -* DATAES-853 - Cleanup tests that do not delete test indices. -* DATAES-852 - Upgrade to Elasticsearch 7.7.1. -* DATAES-850 - Add warning and documentation for missing TemporalAccessor configuration. -* DATAES-848 - Add the name of the index to SearchHit. -* DATAES-847 - Add missing DateFormat values. -* DATAES-845 - MappingElasticsearchConverter crashes when writing lists containing null values. -* DATAES-844 - Improve TOC formatting for migration guides. -* DATAES-841 - Remove deprecated type mappings code. -* DATAES-840 - Consolidate index name SpEL resolution. -* DATAES-839 - ReactiveElasticsearchTemplate should use RequestFactory. -* DATAES-838 - Update to Elasticsearch 7.7.0. -* DATAES-836 - Fix typo in Javadocs. -* DATAES-835 - Fix code sample in documentation for scroll API. -* DATAES-832 - findAllById repository method returns iterable with null elements for not found ids. -* DATAES-831 - SearchOperations.searchForStream does not use requested maxResults. -* DATAES-829 - Deprecate AbstractElasticsearchRepository and cleanup SimpleElasticsearchRepository. -* DATAES-828 - Fields of type date need to have a format defined. -* DATAES-827 - Repositories should not try to create an index when it already exists. -* DATAES-826 - Add method to IndexOperations to write an index mapping from a entity class. -* DATAES-825 - Update readme to use latest spring.io docs. -* DATAES-824 - Release 4.1 M1 (2020.0.0). -* DATAES-678 - Introduce ReactiveIndexOperations. -* DATAES-263 - Inner Hits support. - - -Changes in version 4.0.1.RELEASE (2020-06-10) ---------------------------------------------- -* DATAES-857 - Registered simple types are not read from list. -* DATAES-850 - Add warning and documentation for missing TemporalAccessor configuration. -* DATAES-845 - MappingElasticsearchConverter crashes when writing lists containing null values. -* DATAES-844 - Improve TOC formatting for migration guides. -* DATAES-839 - ReactiveElasticsearchTemplate should use RequestFactory. -* DATAES-835 - Fix code sample in documentation for scroll API. -* DATAES-832 - findAllById repository method returns iterable with null elements for not found ids. -* DATAES-831 - SearchOperations.searchForStream does not use requested maxResults. -* DATAES-828 - Fields of type date need to have a format defined. -* DATAES-827 - Repositories should not try to create an index when it already exists. -* DATAES-823 - Release 4.0.1 (Neumann SR1). - - -Changes in version 3.2.8.RELEASE (2020-06-10) ---------------------------------------------- -* DATAES-851 - Upgrade to Elasticsearch 6.8.10. -* DATAES-837 - Update to Elasticsearch 6.8.9. -* DATAES-821 - Fix code for adding an alias. -* DATAES-811 - Remove Travis CI. -* DATAES-807 - Release 3.2.8 (Moore SR8). -* DATAES-776 - Adapt RestClients class to change in InetSocketAddress class in JDK14. -* DATAES-767 - Fix ReactiveElasticsearch handling of 4xx HTTP responses. - - -Changes in version 3.1.18.RELEASE (2020-06-10) ----------------------------------------------- -* DATAES-811 - Remove Travis CI. -* DATAES-806 - Release 3.1.18 (Lovelace SR18). - - -Changes in version 4.0.0.RELEASE (2020-05-12) ---------------------------------------------- -* DATAES-822 - ElasticsearchRestTemplate should not use `spring-web`. -* DATAES-821 - Fix code for adding an alias. -* DATAES-819 - Test refactorings. -* DATAES-817 - StreamQueries does only delete the last scrollid. -* DATAES-814 - Fix documentation. -* DATAES-812 - IndexOperations should use SpEL index name from entity. -* DATAES-811 - Remove Travis CI. -* DATAES-809 - Creation of test data may lead to duplicate values. -* DATAES-808 - Release 4.0 GA (Neumann). -* DATAES-799 - Support optimistic locking for full update scenario using seq_no + primary_term. -* DATAES-767 - Fix ReactiveElasticsearch handling of 4xx HTTP responses. -* DATAES-750 - Migration guide and documentation update. -* DATAES-602 - add support for refreshAsync into ElasticsearchRestTemplate. -* DATAES-587 - Add lifecycle events. -* DATAES-267 - ElasticSearch should allow to create nested not analyzed fields. - - -Changes in version 4.0.0.RC2 (2020-04-28) ------------------------------------------ -* DATAES-803 - Move count request setup from reactive template to reactive client. -* DATAES-802 - Update documentation for using scroll API with repository methods. -* DATAES-801 - Implement callback to enable adding custom headers in the REST HTTP request. -* DATAES-800 - De-Lombok production code. -* DATAES-797 - Fix MappingElasticsearchConverter recursive descent when reading Map objetcsa. -* DATAES-795 - Fix MappingElasticsearchConverter conversion from Document into Map. -* DATAES-794 - MappingBuilder must not write empty mapping properties. -* DATAES-792 - Add java.util.Date to the supported types for Field annonation date times. -* DATAES-791 - DocumentOperations.multiGet() implementations must return null values for not found entities. -* DATAES-790 - Deprecate noRefresh repository method. -* DATAES-789 - Make ElasticsearchRestTemplate.ClientCallback public. -* DATAES-788 - Add missing path mapping to completion context. -* DATAES-787 - Use JDK 14 for Java.NEXT CI testing. -* DATAES-786 - Move the creation of SearchHit(s) from ElasticsearchConverter closer to ElasticsearchTemplate. -* DATAES-785 - Various entity callbacks implementation improvements. -* DATAES-784 - MappingBuilder should use @Field annotation with custom value objects. -* DATAES-782 - Make underlying TransportClient accessible. -* DATAES-781 - Upgrade to Elasticsearch 7.6.2. -* DATAES-778 - Fix SSL setup in the reactive client. -* DATAES-777 - SearchHitsSupport must preserve pageable when unwrapping to AggregatedPage. -* DATAES-776 - Adapt RestClients class to change in InetSocketAddress class in JDK14. -* DATAES-775 - Fix test runner setup. -* DATAES-774 - Release 4.0 RC2 (Neumann). -* DATAES-773 - Add search-as-you-type field support to index mappings. -* DATAES-772 - Add after-convert entity callbacks support. -* DATAES-567 - Unable to read aggregations via Reactive*Operations. - - -Changes in version 3.2.7.RELEASE (2020-04-28) ---------------------------------------------- -* DATAES-780 - Upgrade 3.2.x to Elasticsearch 6.8.8. -* DATAES-778 - Fix SSL setup in the reactive client. -* DATAES-770 - Release 3.2.7 (Moore SR7). - - -Changes in version 3.1.17.RELEASE (2020-04-28) ----------------------------------------------- -* DATAES-793 - Upgrade to Elasticsearch 6.2.4. -* DATAES-755 - Release 3.1.17 (Lovelace SR17). - - -Changes in version 4.0.0.RC1 (2020-03-31) ------------------------------------------ -* DATAES-771 - Add after-save entity callbacks support. -* DATAES-768 - Add missing query parameters for an UpdateQuery. -* DATAES-766 - Replace CloseableIterator with SearchHitsIterator in stream operations. -* DATAES-765 - Pageable.unpaged() is not used to build a query returning all documents. -* DATAES-763 - Allow map properties in entity with null values. -* DATAES-762 - Release 4.0 RC1 (Neumann). -* DATAES-751 - Introduce ClientCallbackfor the rest client. -* DATAES-653 - Make it easier to use a custom request converter when extending DefaultReactiveElasticsearchClient. -* DATAES-435 - Report version mismatch if used with older ElasticSearch version. -* DATAES-382 - Add Exception translation for Elasticsearch errors. -* DATAES-68 - Add support for auditing annotations. - - -Changes in version 3.2.6.RELEASE (2020-03-25) ---------------------------------------------- -* DATAES-769 - Upgrade to Elasticsearch 6.8.7. -* DATAES-765 - Pageable.unpaged() is not used to build a query returning all documents. -* DATAES-764 - StreamQueries#streamResults does not clear scroll context when finished. -* DATAES-763 - Allow map properties in entity with null values. -* DATAES-758 - fix documentation for @Query annotation. -* DATAES-756 - Release 3.2.6 (Moore SR6). - - -Changes in version 4.0.0.M4 (2020-03-11) ----------------------------------------- -* DATAES-759 - Update to Elasticsearch 7.6.1. -* DATAES-758 - fix documentation for @Query annotation. -* DATAES-754 - Completion field deserialization is failing due to class cast error. -* DATAES-753 - Reactive Elasticsearch repository: Bulk update fails on empty entity list. -* DATAES-749 - Introduce SearchPage as return type for repository methods. -* DATAES-747 - ElasticsearchConfigurationSupport does not set customConversions into the MappingElasticsearchConverter. -* DATAES-746 - Add store converters to convert binary data to base64 encoded strings. -* DATAES-745 - Consolidate Operations API. -* DATAES-744 - Release 4.0 M4 (Neumann). -* DATAES-741 - Tests fail due to Elasticsearch cluster 'blocks' on nearly-full file-systems. -* DATAES-282 - Remove all low-level reflection based field inspection from MappingBuilder in favor of PersistentProperty inspections. - - -Changes in version 3.2.5.RELEASE (2020-02-26) ---------------------------------------------- -* DATAES-752 - Upgrade to Elasticsearch 6.8.6. -* DATAES-741 - Tests fail due to Elasticsearch cluster 'blocks' on nearly-full file-systems. -* DATAES-730 - Release 3.2.5 (Moore SR5). -* DATAES-214 - ElasticsearchTemplate's prepareSearch(Query query) method should use getOffset(). - - -Changes in version 3.1.16.RELEASE (2020-02-26) ----------------------------------------------- -* DATAES-729 - Release 3.1.16 (Lovelace SR16). - - -Changes in version 4.0.0.M3 (2020-02-12) ----------------------------------------- -* DATAES-743 - Revert geo converters to back to store converters. -* DATAES-740 - Adapt to spring-data-commons changes. -* DATAES-739 - Introduce nullable annotations for API validation. -* DATAES-738 - Add entity related save methods to DocumentOperations. -* DATAES-735 - Update to Elasticsearch 7.5.2. -* DATAES-734 - Add Sort implementation that allows geo distance sorts. -* DATAES-732 - Release 4.0 M3 (Neumann). -* DATAES-449 - Pass route parameter to created search request. - - -Changes in version 4.0.0.M2 (2020-01-17) ----------------------------------------- -* DATAES-731 - Release 4.0 M2 (Neumann). - - -Changes in version 4.0.0.M1 (2020-01-16) ----------------------------------------- -* DATAES-727 - Use track_total_hits parameter for count queries. -* DATAES-725 - Update copyright years to 2020. -* DATAES-724 - Provide IndexOperations bean. -* DATAES-723 - Cleanup ElasticsearchRepository interface. -* DATAES-722 - Return total count relation in the SearchHits object. -* DATAES-721 - Deprecation and Warnings cleanup. -* DATAES-720 - SimpleReactiveElasticsearchRepository findAll() returns only 10 elements. -* DATAES-719 - Add customization hook for reactive WebClient. -* DATAES-718 - Deprecate @Score and scoreProperty. -* DATAES-717 - Enable Repositories to return a SearchHits instance instead of a list. -* DATAES-716 - Add Value mapping to the ElasticsearchMappingConverter. -* DATAES-715 - Highlight results should be returned in the SearchHits. -* DATAES-714 - Sort results should be returned in the SearchHits. -* DATAES-713 - Transfer returned aggregations from the AggregatedPage to the SearchHits. -* DATAES-711 - Update to Elasticsearch 7.5. -* DATAES-709 - Add parameter to include default settings on setting request. -* DATAES-702 - Travis CI builds currently broken. -* DATAES-701 - Enable proxy support for the reactive rest template. -* DATAES-700 - Enable proxy support for RestClient. -* DATAES-697 - Query refactoring cleanup. -* DATAES-693 - Support for source fetching in update operations. -* DATAES-690 - Enable builds on JDK 11+. -* DATAES-688 - Remove unneeded SearchQuery subinterface. -* DATAES-684 - Implement Bulk Request for Reactive. -* DATAES-680 - ReactiveElasticsearchTemplate should use the count API. -* DATAES-677 - Update to Elasticsearch 7.4.1. -* DATAES-676 - fix documentation to reflect the changes in API restructuring. -* DATAES-675 - migrate tests to JUnit 5. -* DATAES-673 - Create a Ssl Rest Client using SslContext and HostnameVerifier. -* DATAES-672 - Introduce SearchHit to enrich an Entity type. -* DATAES-671 - Missing indicesOptions support for scrolling queries. -* DATAES-670 - fix version compatibility matrix in documentation. -* DATAES-666 - Rebase branch 4.0.x onto master and merge it. -* DATAES-665 - Javadoc not deployed. -* DATAES-663 - Release 4.0 M1 (Neumann). -* DATAES-661 - Support track_total_hits request parameter. -* DATAES-659 - Move MappingElasticsearchConverter to correct package. -* DATAES-658 - Update 4.0.x to ES 7.3.2. -* DATAES-654 - Add Junit 5 support. -* DATAES-651 - Fix regression from escaping. -* DATAES-650 - Add support for pathPrefix to ClientConfiguration. -* DATAES-647 - In and NotIn uses should instead of terms-query. -* DATAES-639 - Add ignore_above mapping parameter support. -* DATAES-638 - Remove redundant public modifiers in @MultiField. -* DATAES-637 - Change branch 4.0. to use Elasticsearch 7.3. -* DATAES-635 - Create branch 4.0.x and integrate the changes from the existing 4.x branch. -* DATAES-634 - Rearrange methods in Template API. -* DATAES-633 - Introduce value object to capture index type/index name. -* DATAES-632 - Use single Query type in Template API methods. -* DATAES-631 - Consolidate query objects. - - -Changes in version 3.2.4.RELEASE (2020-01-15) ---------------------------------------------- -* DATAES-725 - Update copyright years to 2020. -* DATAES-720 - SimpleReactiveElasticsearchRepository findAll() returns only 10 elements. -* DATAES-719 - Add customization hook for reactive WebClient. -* DATAES-705 - Add support for PathPrefix to clients in 3.2.x. -* DATAES-704 - Release 3.2.4 (Moore SR4). - - -Changes in version 3.1.15.RELEASE (2020-01-15) ----------------------------------------------- -* DATAES-725 - Update copyright years to 2020. -* DATAES-703 - Release 3.1.15 (Lovelace SR15). - - -Changes in version 3.2.3.RELEASE (2019-12-04) ---------------------------------------------- -* DATAES-700 - Enable proxy support for RestClient. -* DATAES-699 - ElasticsearchRestTemplate.count(..) returns all documents instead of just total hits number. -* DATAES-692 - Release 3.2.3 (Moore SR3). - - -Changes in version 3.1.14.RELEASE (2019-12-04) ----------------------------------------------- -* DATAES-691 - Release 3.1.14 (Lovelace SR14). - - -Changes in version 3.2.2.RELEASE (2019-11-18) ---------------------------------------------- -* DATAES-685 - Release 3.2.2 (Moore SR2). -* DATAES-684 - Implement Bulk Request for Reactive. -* DATAES-680 - ReactiveElasticsearchTemplate should use the count API. - - -Changes in version 3.1.13.RELEASE (2019-11-18) ----------------------------------------------- -* DATAES-683 - Release 3.1.13 (Lovelace SR13). - - -Changes in version 3.2.1.RELEASE (2019-11-04) ---------------------------------------------- -* DATAES-679 - Upgrade to Elasticsearch 6.8.4. -* DATAES-673 - Create a Ssl Rest Client using SslContext and HostnameVerifier. -* DATAES-671 - Missing indicesOptions support for scrolling queries. -* DATAES-670 - fix version compatibility matrix in documentation. -* DATAES-665 - Javadoc not deployed. -* DATAES-662 - Release 3.2.1 (Moore SR1). - - -Changes in version 3.1.12.RELEASE (2019-11-04) ----------------------------------------------- -* DATAES-660 - Release 3.1.12 (Lovelace SR12). - - -Changes in version 3.2.0.RELEASE (2019-09-30) ---------------------------------------------- -* DATAES-657 - Sort by _score not supported by ElasticsearchRestTemplate. -* DATAES-652 - Send if_seq_no and if_primary_term parameters when indexing via ReactiveElasticsearchClient. -* DATAES-648 - Unify documentation of version compatibilities. -* DATAES-625 - Release 3.2 GA (Moore). -* DATAES-541 - IllegalArgumentException on ElasticsearchRestTemplate#removeAlias. -* DATAES-305 - let users create mappings with dynamical indexName. -* DATAES-227 - ElasticsearchTemplate.prepareUpdate should call setUpsert. - - -Changes in version 3.1.11.RELEASE (2019-09-30) ----------------------------------------------- -* DATAES-627 - Add HTTPS entries into spring.schemas. -* DATAES-624 - Release 3.1.11 (Lovelace SR11). - - -Changes in version 3.2.0.RC3 (2019-09-06) ------------------------------------------ -* DATAES-646 - Adapt to Spring Framework 5.2 RC2. -* DATAES-645 - Missing highlight support for scrolling queries. -* DATAES-643 - RestTemplate ignores search_type. -* DATAES-627 - Add HTTPS entries into spring.schemas. -* DATAES-626 - Release 3.2 RC3 (Moore). - - -Changes in version 3.2.0.RC2 (2019-08-05) ------------------------------------------ -* DATAES-619 - Migrate remaining tests to AssertJ. -* DATAES-616 - Fix implementations of ParameterAccessor interface. -* DATAES-615 - OrderBy clause on repository queries does not honour @Field name annotation. -* DATAES-611 - Adapt to API changes in Spring Webflux. -* DATAES-610 - Clarify inclusion of rc and snapshot builds in readme.adoc. -* DATAES-609 - Fix TransportClientFactoryBean.isSingleton(). -* DATAES-607 - Add client security configuration options for Rest clients. -* DATAES-606 - update 3.2.x to use ES 6.8.1. -* DATAES-604 - Revise readme for a consistent structure. -* DATAES-603 - Add support for bulk options in Elasticsearch-Template classes. -* DATAES-601 - Fix NoHTTP errors. -* DATAES-598 - Add Elasticsearch authentication code example to readme. -* DATAES-597 - Fix code formatting in readme. -* DATAES-595 - Support for setting preference parameter in query. -* DATAES-594 - Support for Index without refresh. -* DATAES-593 - Add support for field collapse function. -* DATAES-592 - Integrate nohttp tooling into CI build profile. -* DATAES-591 - Release 3.2 RC2 (Moore). -* DATAES-583 - Introduce Jenkins CI. -* DATAES-497 - Revise reference documentation. -* DATAES-456 - AbstractElasticsearchRepository.save(S entity) does not evaluate @Document(indexName) expression. -* DATAES-415 - More Field Types Support. -* DATAES-405 - use @Nullable from org.springframework.lang. -* DATAES-387 - Index Not Found Exception When Using Spring EL in Index Name/Type. - - -Changes in version 3.1.10.RELEASE (2019-08-05) ----------------------------------------------- -* DATAES-592 - Integrate nohttp tooling into CI build profile. -* DATAES-590 - Release 3.1.10 (Lovelace SR10). -* DATAES-583 - Introduce Jenkins CI. - - -Changes in version 2.1.23.RELEASE (2019-08-05) ----------------------------------------------- -* DATAES-583 - Introduce Jenkins CI. -* DATAES-581 - Release 2.1.23 (Ingalls SR23). - - -Changes in version 3.2.0.RC1 (2019-06-14) ------------------------------------------ -* DATAES-589 - improve readme file. -* DATAES-586 - Create security policy readme. -* DATAES-579 - Test Code Cleanup. -* DATAES-560 - Release 3.2 RC1 (Moore). - - -Changes in version 3.1.9.RELEASE (2019-06-14) ---------------------------------------------- -* DATAES-580 - Release 3.1.9 (Lovelace SR9). - - -Changes in version 2.1.22.RELEASE (2019-05-13) ----------------------------------------------- -* DATAES-578 - Release 2.1.22 (Ingalls SR22). - - -Changes in version 3.1.8.RELEASE (2019-05-13) ---------------------------------------------- -* DATAES-577 - Release 3.1.8 (Lovelace SR8). - - -Changes in version 3.2.0.M4 (2019-05-13) ----------------------------------------- -* DATAES-576 - ElasticsearchTransportTemplateTests.shouldDeleteAcrossIndex(…) consistently fails. -* DATAES-575 - Upgrade to Elasticsearch 6.7.2. -* DATAES-572 - Indices created in ElasticsearchTemplateTests are retained after tests. -* DATAES-570 - Use Delete By Query API for delete by query operations. -* DATAES-569 - Add index operations to reactive Elasticsearch client. -* DATAES-568 - MappingBuilder must use the @Field annotation's name attribute. -* DATAES-566 - Double SearchHit source to string conversion in DefaultResultMapper. -* DATAES-565 - ElasticsearchTemplate.prepareScroll() doesn't respect SourceFilter from the Query. -* DATAES-564 - Release 3.2 M4 (Moore). -* DATAES-563 - Add "elasticsearchTemplate" bean alias for ElasticsearchTemplate. -* DATAES-562 - Custom name attribute for @Field mapping in ElasticsearchEntityMapper. -* DATAES-561 - Reuse ObjectMapper in ElasticsearchEntityMapper. -* DATAES-546 - Documentation code snippet for "using @Query Annotation" does not compile. -* DATAES-459 - ElasticsearchTemplate does not provide the scroll ID with startScroll. -* DATAES-457 - ElasticsearchTemplate.prepareScroll() does not add sorting. - - -Changes in version 2.1.21.RELEASE (2019-05-10) ----------------------------------------------- -* DATAES-555 - Release 2.1.21 (Ingalls SR21). - - -Changes in version 3.1.7.RELEASE (2019-05-10) ---------------------------------------------- -* DATAES-557 - Release 3.1.7 (Lovelace SR7). -* DATAES-552 - @Query annotation fail when passing in over 10 parameters. -* DATAES-547 - ElasticSearchTemplate.delete(DeleteQuery, Class) does not delete documents. - - -Changes in version 3.2.0.M3 (2019-04-11) ----------------------------------------- -* DATAES-558 - Upgrade Elastic Search version to 6.6.2. -* DATAES-552 - @Query annotation fail when passing in over 10 parameters. -* DATAES-547 - ElasticSearchTemplate.delete(DeleteQuery, Class) does not delete documents. -* DATAES-545 - Move off deprecations in Spring Data Commons. -* DATAES-542 - Release 3.2 M3 (Moore). -* DATAES-536 - Add support for context suggestion. -* DATAES-535 - Add mapping annotation @DynamicTemplates. -* DATAES-530 - Add alternative to Jackson ObjectMapper for result mapping. - - -Changes in version 3.1.6.RELEASE (2019-04-01) ---------------------------------------------- -* DATAES-538 - Release 3.1.6 (Lovelace SR6). -* DATAES-536 - Add support for context suggestion. -* DATAES-535 - Add mapping annotation @DynamicTemplates. -* DATAES-523 - Allow specifying version type for index API. - - -Changes in version 3.0.14.RELEASE (2019-04-01) ----------------------------------------------- -* DATAES-528 - Release 3.0.14 (Kay SR14). -* DATAES-523 - Allow specifying version type for index API. -* DATAES-500 - queryForList(CriteriaQuery query, Class clazz) can't query all data. - - -Changes in version 2.1.20.RELEASE (2019-04-01) ----------------------------------------------- -* DATAES-554 - Release 2.1.20 (Ingalls SR20). - - -Changes in version 2.1.19.RELEASE (2019-04-01) ----------------------------------------------- -* DATAES-527 - Release 2.1.19 (Ingalls SR19). - - -Changes in version 3.2.0.M2 (2019-03-07) ----------------------------------------- -* DATAES-537 - Upgrade to Elasticsearch 6.6. -* DATAES-531 - ElasticsearchRestTemplate.getMapping always returns null. -* DATAES-526 - Introduce Concourse CI. -* DATAES-525 - ElasticsearchRestRemplate bulk delete by ID removes all objects from index. -* DATAES-524 - Update copyright years to 2019. -* DATAES-523 - Allow specifying version type for index API. -* DATAES-521 - Remove specific repository implementations for UUID und Numbers as id type. -* DATAES-519 - Add support for reactive repositories. -* DATAES-518 - Use scroll for unpaged find operations in ReactiveElasticsearchTemplate. -* DATAES-517 - Release 3.2 M2 (Moore). -* DATAES-510 - Add support for reactive scrolling. -* DATAES-500 - queryForList(CriteriaQuery query, Class clazz) can't query all data. - - -Changes in version 3.1.5.RELEASE (2019-02-13) ---------------------------------------------- -* DATAES-529 - Release 3.1.5 (Lovelace SR5). -* DATAES-500 - queryForList(CriteriaQuery query, Class clazz) can't query all data. - - -Changes in version 3.1.4.RELEASE (2019-01-10) ---------------------------------------------- -* DATAES-524 - Update copyright years to 2019. -* DATAES-507 - Release 3.1.4 (Lovelace SR4). - - -Changes in version 3.0.13.RELEASE (2019-01-10) ----------------------------------------------- -* DATAES-524 - Update copyright years to 2019. -* DATAES-506 - Release 3.0.13 (Kay SR13). - - -Changes in version 2.1.18.RELEASE (2019-01-10) ----------------------------------------------- -* DATAES-524 - Update copyright years to 2019. -* DATAES-505 - Release 2.1.18 (Ingalls SR18). - - -Changes in version 3.2.0.M1 (2018-12-11) ----------------------------------------- -* DATAES-515 - Override Elasticsearch's JarHell for tests. -* DATAES-514 - Simplify reference documentation setup. -* DATAES-513 - Release 3.2 M1 (Moore). -* DATAES-508 - Set up Travis-ci build. -* DATAES-503 - Missing copy_to property in @Field Annotation. -* DATAES-492 - Missing normalizer property in @Field and @InnerField Annotation. -* DATAES-445 - Usage example of scan and scroll is misleading. -* DATAES-407 - Add support for Java High Level REST Client. - - -Changes in version 3.1.3.RELEASE (2018-11-27) ---------------------------------------------- -* DATAES-503 - Missing copy_to property in @Field Annotation. -* DATAES-496 - Release 3.1.3 (Lovelace SR3). -* DATAES-492 - Missing normalizer property in @Field and @InnerField Annotation. -* DATAES-445 - Usage example of scan and scroll is misleading. - - -Changes in version 3.0.12.RELEASE (2018-11-27) ----------------------------------------------- -* DATAES-503 - Missing copy_to property in @Field Annotation. -* DATAES-492 - Missing normalizer property in @Field and @InnerField Annotation. -* DATAES-490 - Release 3.0.12 (Kay SR12). -* DATAES-445 - Usage example of scan and scroll is misleading. - - -Changes in version 2.1.17.RELEASE (2018-11-27) ----------------------------------------------- -* DATAES-491 - Release 2.1.17 (Ingalls SR17). - - -Changes in version 3.1.2.RELEASE (2018-10-29) ---------------------------------------------- -* DATAES-489 - Release 3.1.2 (Lovelace SR2). - - -Changes in version 2.1.16.RELEASE (2018-10-15) ----------------------------------------------- -* DATAES-484 - Release 2.1.16 (Ingalls SR16). - - -Changes in version 3.0.11.RELEASE (2018-10-15) ----------------------------------------------- -* DATAES-485 - Release 3.0.11 (Kay SR11). - - -Changes in version 3.1.1.RELEASE (2018-10-15) ---------------------------------------------- -* DATAES-486 - Release 3.1.1 (Lovelace SR1). - - -Changes in version 3.1.0.RELEASE (2018-09-21) ---------------------------------------------- -* DATAES-480 - Release 3.1 GA (Lovelace). -* DATAES-479 - NativeSearchQueryBuilder should have a withHighlighter() method. - - -Changes in version 3.0.10.RELEASE (2018-09-10) ----------------------------------------------- -* DATAES-473 - Release 3.0.10 (Kay SR10). - - -Changes in version 2.1.15.RELEASE (2018-09-10) ----------------------------------------------- -* DATAES-474 - Release 2.1.15 (Ingalls SR15). - - -Changes in version 3.1.0.RC2 (2018-08-20) ------------------------------------------ -* DATAES-472 - Release 3.1 RC2 (Lovelace). - - -Changes in version 2.1.14.RELEASE (2018-07-27) ----------------------------------------------- -* DATAES-463 - Release 2.1.14 (Ingalls SR14). -* DATAES-283 - Remove dependency to Commons Lang. - - -Changes in version 3.0.9.RELEASE (2018-07-26) ---------------------------------------------- -* DATAES-470 - TransportClientFactoryBean does not parse clusterNodes correctly. -* DATAES-469 - Is org.elasticsearch:elasticsearch a necessary dependency?. -* DATAES-467 - No mapping found for [_score] in order to sort on. -* DATAES-465 - Release 3.0.9 (Kay SR9). -* DATAES-317 - Add query logging to ElasticsearchTemplate. -* DATAES-283 - Remove dependency to Commons Lang. - - -Changes in version 3.1.0.RC1 (2018-07-26) ------------------------------------------ -* DATAES-471 - Adapt to changes in immutable entities support. -* DATAES-470 - TransportClientFactoryBean does not parse clusterNodes correctly. -* DATAES-469 - Is org.elasticsearch:elasticsearch a necessary dependency?. -* DATAES-467 - No mapping found for [_score] in order to sort on. -* DATAES-464 - Add support for @ReadOnlyProperty to prevent properties from being written. -* DATAES-462 - Add support for mapping document scores to entities. -* DATAES-452 - Release 3.1 RC1 (Lovelace). -* DATAES-317 - Add query logging to ElasticsearchTemplate. - - -Changes in version 3.0.8.RELEASE (2018-06-13) ---------------------------------------------- -* DATAES-460 - Avoid pulling in Netty 3 as dependency. -* DATAES-448 - Release 3.0.8 (Kay SR8). -* DATAES-420 - Analyzer of main field ignored when using @MultiField annotation. -* DATAES-312 - NullHandling.NULLS_LAST not working in query.sort. - - -Changes in version 2.1.13.RELEASE (2018-06-13) ----------------------------------------------- -* DATAES-447 - Release 2.1.13 (Ingalls SR13). - - -Changes in version 3.1.0.M3 (2018-05-17) ----------------------------------------- -* DATAES-451 - Adapt to SpEL extension API changes in Spring Data Commons. -* DATAES-440 - Release 3.1 M3 (Lovelace). -* DATAES-422 - Add support for IndicesOptions in search queries. -* DATAES-420 - Analyzer of main field ignored when using @MultiField annotation. -* DATAES-412 - Highlighted fields is not getting passed to the elastic search query. -* DATAES-363 - AbstractElasticsearchRepository.existsById(..) always returns true. -* DATAES-312 - NullHandling.NULLS_LAST not working in query.sort. -* DATAES-198 - @Version has no effect, so therefore is not useful with spring data elasticsearch. - - -Changes in version 3.0.7.RELEASE (2018-05-08) ---------------------------------------------- -* DATAES-438 - Expected a boolean [true/false] for property [index] but got [not_analyzed]. -* DATAES-437 - Release 3.0.7 (Kay SR7). -* DATAES-412 - Highlighted fields is not getting passed to the elastic search query. -* DATAES-402 - Paging not working correctly. -* DATAES-363 - AbstractElasticsearchRepository.existsById(..) always returns true. -* DATAES-198 - @Version has no effect, so therefore is not useful with spring data elasticsearch. - - -Changes in version 2.1.12.RELEASE (2018-05-08) ----------------------------------------------- -* DATAES-436 - Release 2.1.12 (Ingalls SR12). - - -Changes in version 3.1.0.M2 (2018-04-13) ----------------------------------------- -* DATAES-439 - Adapt to API changes in Spring Data Commons. -* DATAES-434 - Remove explicit declaration of Jackson library versions. -* DATAES-432 - Export composable repositories via CDI. -* DATAES-427 - Release 3.1 M2 (Lovelace). - - -Changes in version 3.0.6.RELEASE (2018-04-04) ---------------------------------------------- -* DATAES-434 - Remove explicit declaration of Jackson library versions. -* DATAES-430 - Release 3.0.6 (Kay SR6). - - -Changes in version 2.1.11.RELEASE (2018-04-04) ----------------------------------------------- -* DATAES-434 - Remove explicit declaration of Jackson library versions. -* DATAES-424 - Fix line endings. -* DATAES-423 - Release 2.1.11 (Ingalls SR11). - - -Changes in version 3.0.5.RELEASE (2018-02-28) ---------------------------------------------- -* DATAES-429 - Release 3.0.5 (Kay SR5). - - -Changes in version 3.0.4.RELEASE (2018-02-19) ---------------------------------------------- -* DATAES-425 - Release 3.0.4 (Kay SR4). - - -Changes in version 3.1.0.M1 (2018-02-06) ----------------------------------------- -* DATAES-424 - Fix line endings. -* DATAES-414 - Reduce scope of Lo4j dependencies to test. -* DATAES-410 - Adapt API changes in Property in test cases. -* DATAES-401 - Release 3.1 M1 (Lovelace). -* DATAES-361 - log4j2.xml in classpath root of the library prevents Spring Boot's logging auto-configuration to work. - - -Changes in version 3.0.3.RELEASE (2018-01-24) ---------------------------------------------- -* DATAES-424 - Fix line endings. -* DATAES-417 - Release 3.0.3 (Kay SR3). - - -Changes in version 2.1.10.RELEASE (2018-01-24) ----------------------------------------------- -* DATAES-416 - Release 2.1.10 (Ingalls SR10). - - -Changes in version 3.0.2.RELEASE (2017-11-27) ---------------------------------------------- -* DATAES-414 - Reduce scope of Lo4j dependencies to test. -* DATAES-411 - Release 3.0.2 (Kay SR2). - - -Changes in version 2.1.9.RELEASE (2017-11-27) ---------------------------------------------- -* DATAES-408 - Ensure Spring 5 compatibility in Ingalls. -* DATAES-404 - Release 2.1.9 (Ingalls SR9). - - -Changes in version 3.0.1.RELEASE (2017-10-27) ---------------------------------------------- -* DATAES-410 - Adapt API changes in Property in test cases. -* DATAES-400 - Release 3.0.1 (Kay SR1). -* DATAES-361 - log4j2.xml in classpath root of the library prevents Spring Boot's logging auto-configuration to work. - - -Changes in version 2.1.8.RELEASE (2017-10-11) ---------------------------------------------- -* DATAES-391 - Release 2.1.8 (Ingalls SR8). - - -Changes in version 3.0.0.RELEASE (2017-10-02) ---------------------------------------------- -* DATAES-397 - Add explicit automatic module name for Java 9. -* DATAES-395 - Upgrade to OpenWebBeans 2.0.1. -* DATAES-392 - Release 3.0 GA (Kay). - - -Changes in version 3.0.0.RC3 (2017-09-11) ------------------------------------------ -* DATAES-376 - Release 3.0 RC3 (Kay). - - -Changes in version 2.1.7.RELEASE (2017-09-11) ---------------------------------------------- -* DATAES-378 - Release 2.1.7 (Ingalls SR7). - - -Changes in version 2.1.6.RELEASE (2017-07-26) ---------------------------------------------- -* DATAES-374 - Release 2.1.6 (Ingalls SR6). - - -Changes in version 3.0.0.RC2 (2017-07-25) ------------------------------------------ -* DATAES-375 - Release 3.0 RC2 (Kay). - - -Changes in version 3.0.0.RC1 (2017-07-25) ------------------------------------------ -* DATAES-373 - Update ES to 5.5.0. -* DATAES-369 - Adapt to API changes in mapping subsystem. -* DATAES-359 - Release 3.0 RC1 (Kay). -* DATAES-274 - Jackson throws exception when deserializing Page. - - -Changes in version 2.1.5.RELEASE (2017-07-24) ---------------------------------------------- -* DATAES-358 - Release 2.1.5 (Ingalls SR5). - - -Changes in version 3.0.0.M4 (2017-06-14) ----------------------------------------- -* DATAES-353 - Release 3.0 M4 (Kay). -* DATAES-334 - Readme has broken jira link. -* DATAES-285 - Upgrade to Elasticsearch 5.0. - - -Changes in version 2.1.4.RELEASE (2017-06-08) ---------------------------------------------- -* DATAES-349 - Release 2.1.4 (Ingalls SR4). - - -Changes in version 2.0.11.RELEASE (2017-06-07) ----------------------------------------------- -* DATAES-348 - Release 2.0.11 (Hopper SR11). - - -Changes in version 3.0.0.M3 (2017-05-09) ----------------------------------------- -* DATAES-352 - Adapt to API changes in CrudRepository. -* DATAES-350 - Adapt to moved CustomConversions to Spring Data Commons. -* DATAES-344 - Release 3.0 M3 (Kay). - - -Changes in version 2.0.10.RELEASE (2017-04-19) ----------------------------------------------- -* DATAES-347 - Release 2.0.10 (Hopper SR10). - - -Changes in version 2.1.3.RELEASE (2017-04-19) ---------------------------------------------- -* DATAES-346 - Release 2.1.3 (Ingalls SR3). - - -Changes in version 2.0.9.RELEASE (2017-04-19) ---------------------------------------------- -* DATAES-336 - Release 2.0.9 (Hopper SR9). - - -Changes in version 2.1.2.RELEASE (2017-04-19) ---------------------------------------------- -* DATAES-335 - Release 2.1.2 (Ingalls SR2). - - -Changes in version 3.0.0.M2 (2017-04-04) ----------------------------------------- -* DATAES-342 - Adapt to API changes in RepositoryConfigurationExtensionSupport. -* DATAES-329 - Remove references to single-argument assertion methods of Spring. -* DATAES-328 - Integrate Data Commons Java 8 upgrade branch. -* DATAES-325 - Remove references to GenericCollectionTypeResolver in favor of ResolvableType. -* DATAES-322 - Update project documentation with the CLA tool integration. -* DATAES-315 - Adapt API in RepositoryFactoryBeanSupport implementation. -* DATAES-313 - Register repository factory in spring.factories for multi-store support. -* DATAES-311 - Release 3.0 M2 (Kay). - - -Changes in version 2.0.8.RELEASE (2017-03-02) ---------------------------------------------- -* DATAES-329 - Remove references to single-argument assertion methods of Spring. -* DATAES-326 - Release 2.0.8 (Hopper SR8). - - -Changes in version 2.1.1.RELEASE (2017-03-02) ---------------------------------------------- -* DATAES-329 - Remove references to single-argument assertion methods of Spring. -* DATAES-327 - Release 2.1.1 (Ingalls SR1). -* DATAES-325 - Remove references to GenericCollectionTypeResolver in favor of ResolvableType. - - -Changes in version 2.0.7.RELEASE (2017-01-26) ---------------------------------------------- -* DATAES-319 - Release 2.0.7 (Hopper SR7). - - -Changes in version 2.1.0.RELEASE (2017-01-26) ---------------------------------------------- -* DATAES-322 - Update project documentation with the CLA tool integration. -* DATAES-320 - Release 2.1 GA (Ingalls). - - -Changes in version 2.0.6.RELEASE (2016-12-21) ---------------------------------------------- -* DATAES-304 - Release 2.0.6 (Hopper SR6). - - -Changes in version 2.1.0.RC1 (2016-12-21) ------------------------------------------ -* DATAES-315 - Adapt API in RepositoryFactoryBeanSupport implementation. -* DATAES-313 - Register repository factory in spring.factories for multi-store support. -* DATAES-289 - Upgrade to Elasticsearch 2.4. -* DATAES-284 - Downgrade to Jackson 2.7.5 until Elasticsearch is compatible with 2.8. -* DATAES-281 - Can't save entity without id setter. -* DATAES-275 - Release 2.1 RC1 (Ingalls). - - -Changes in version 3.0.0.M1 (2016-11-23) ----------------------------------------- -* DATAES-307 - Set up 3.0 development. -* DATAES-306 - Release 3.0 M1 (Kay). - - -Changes in version 2.0.5.RELEASE (2016-11-03) ---------------------------------------------- -* DATAES-300 - Release 2.0.5 (Hopper SR5). - - -Changes in version 2.0.4.RELEASE (2016-09-29) ---------------------------------------------- -* DATAES-297 - Release 2.0.4 (Hopper SR4). - - -Changes in version 1.3.6.RELEASE (2016-09-29) ---------------------------------------------- -* DATAES-299 - Release 1.3.6 (Gosling SR6). - - -Changes in version 1.3.5.RELEASE (2016-09-20) ---------------------------------------------- -* DATAES-296 - Release 1.3.5 (Gosling SR5). - - -Changes in version 2.0.3.RELEASE (2016-09-20) ---------------------------------------------- -* DATAES-281 - Can't save entity without id setter. -* DATAES-268 - Release 2.0.3 (Hopper SR3). - - -Changes in version 2.1.0.M1 (2016-07-27) ----------------------------------------- -* DATAES-262 - Upgrade Elasticsearch to 2.3.3. -* DATAES-250 - Release 2.1 M1 (Ingalls). - - -Changes in version 2.0.2.RELEASE (2016-06-15) ---------------------------------------------- -* DATAES-251 - Release 2.0.2 (Hopper SR2). - - -Changes in version 2.0.1.RELEASE (2016-04-06) ---------------------------------------------- -* DATAES-249 - Release 2.0.1 (Hopper SR1). - - -Changes in version 2.0.0.RELEASE (2016-04-06) ---------------------------------------------- -* DATAES-245 - Release 2.0 GA (Hopper). - - -Changes in version 2.0.0.RC1 (2016-03-18) ------------------------------------------ -* DATAES-241 - remove commons-collections dependency and use CollectionUtils from Spring Utils. -* DATAES-240 - Release 2.0 RC1 (Hopper). -* DATAES-238 - ElasticsearchTemplate.prepareUpdate() does not preserve routing parameter which is required for updating child documents. -* DATAES-237 - path-configuration fails to load configs from inside jars. -* DATAES-236 - Clear the search contexts associated with specified scroll id. -* DATAES-234 - CDI support can fail due to Set.toString() used as Map key. -* DATAES-211 - Upgrade to Elasticsearch 2.0. -* DATAES-188 - Source filtering feature Implementation. -* DATAES-124 - ElasticSearchTemplate should expose client. - - -Changes in version 1.4.0.M1 (2016-02-12) ----------------------------------------- -* DATAES-232 - Add code of conduct. -* DATAES-231 - Release 1.4 M1 (Hopper). -* DATAES-230 - Remove unnecessary type check in AbstractElasticsearchRepository. -* DATAES-224 - ElasticsearchTemplate discards newlines in mappings and settings files. -* DATAES-221 - Adapt to changes in Spring Data Commons. -* DATAES-216 - Add support to Indices Boost. -* DATAES-210 - Typo in error message. -* DATAES-209 - Handle dynamic mapping annotation at field level. -* DATAES-194 - Tests should clean up "data" directory. -* DATAES-171 - findByIdNotIn not work. -* DATAES-94 - Bump to support ES 1.2.0. - - -Changes in version 1.3.2.RELEASE (2015-12-18) ---------------------------------------------- -* DATAES-223 - Release 1.3.2 (Gosling). - - -Changes in version 1.3.1.RELEASE (2015-11-15) ---------------------------------------------- -* DATAES-212 - Release 1.3.1 (Gosling). - - -Changes in version 1.1.4.RELEASE (2015-10-14) ---------------------------------------------- -* DATAES-202 - Release 1.1.4 (Evans). - - -Changes in version 1.3.0.RELEASE (2015-09-01) ---------------------------------------------- -* DATAES-193 - Release 1.3 GA (Gosling). -* DATAES-137 - Should work out of the box with Spring Data REST. - - -Changes in version 1.3.0.RC1 (2015-08-04) ------------------------------------------ -* DATAES-186 - Release 1.3 RC1 (Gosling). -* DATAES-182 - Switch from BeanWrapper to PersistentPropertyAccessor. - - -Changes in version 1.2.2.RELEASE (2015-07-28) ---------------------------------------------- -* DATAES-184 - Release 1.2.2 (Fowler). - - -Changes in version 1.0.6.RELEASE (2015-07-01) ---------------------------------------------- -* DATAES-173 - Release 1.0.6 (Dijkstra). - - -Changes in version 1.1.3.RELEASE (2015-07-01) ---------------------------------------------- -* DATAES-174 - Release 1.1.3 (Evans). - - -Changes in version 1.2.1.RELEASE (2015-06-30) ---------------------------------------------- -* DATAES-175 - Release 1.2.1 (Fowler). -* DATAES-164 - CriteriaQuery equals method has to use AND operator instead of OR. -* DATAES-155 - findAll(Iterable ids) doesn't set persistent entity id. - - -Changes in version 1.3.0.M1 (2015-06-02) ----------------------------------------- -* DATAES-168 - Release 1.3 M1 (Gosling). -* DATAES-164 - CriteriaQuery equals method has to use AND operator instead of OR. -* DATAES-162 - Adapt API changes in Spring Data Commons to simplify custom repository base class registration. -* DATAES-159 - Bulk update with ElasticsearchTemplate. -* DATAES-158 - UpdateRequest.script not working. -* DATAES-157 - support deleteBy operation. -* DATAES-155 - findAll(Iterable ids) doesn't set persistent entity id. - - -Changes in version 1.2.0.RELEASE (2015-03-23) ---------------------------------------------- -* DATAES-154 - Release 1.2 GA. - - -Changes in version 1.2.0.RC1 (2015-03-05) ------------------------------------------ -* DATAES-152 - Release 1.2 RC1. -* DATAES-151 - findAll(Iterable ids) uses id representation in source instead of ES id. -* DATAES-140 - Document fields should not be indexed for search. -* DATAES-135 - Add necessary implementation for improved multi-store behavior. -* DATAES-132 - support include_in_parent for nested fieldtype. -* DATAES-130 - Allow Spring EL usage in type attribute of @Document. -* DATAES-129 - Custom Repository Method for simple geo request does not work. -* DATAES-115 - FindBy projections for list returns only 10 results. -* DATAES-100 - Allow configurable searchTimeout. -* DATAES-94 - Bump to support ES 1.2.0. -* DATAES-91 - Support for 'suggest' operations. - - -Changes in version 1.1.2.RELEASE (2015-01-28) ---------------------------------------------- -* DATAES-146 - Release 1.1.2. - - -Changes in version 1.0.5.RELEASE (2015-01-27) ---------------------------------------------- -* DATAES-145 - Release 1.0.5. - - -Changes in version 1.2.0.M1 (2014-12-01) ----------------------------------------- -* DATAES-134 - Release 1.2 M1. -* DATAES-94 - Support for Elasticsearch 1.2.0. -* DATAES-76 - Add support for mapping generation of inherited fields. - - -Changes in version 1.1.1.RELEASE (2014-10-30) ---------------------------------------------- -* DATAES-133 - Release 1.1.1. - - -Changes in version 1.1.0.RELEASE (2014-09-05) ---------------------------------------------- -* DATAES-128 - Release 1.1 GA. -* DATAES-125 - the name of the isAnnotated method doesn't correspond to method body. -* DATAES-123 - Custom repository implementations are not picked up when using CDI. -* DATAES-120 - Polish reference documentation. -* DATAES-106 - Add support for countBy projections. - - -Changes in version 1.0.4.RELEASE (2014-08-27) ---------------------------------------------- -* DATAES-122 - Release 1.0.4. - - -Changes in version 1.1.0.RC1 (2014-08-13) ------------------------------------------ -* DATAES-119 - Release 1.1 RC1. -* DATAES-117 - Displaying proper error message for entities that don't have an Id. -* DATAES-116 - Fix url typo in readme.md. -* DATAES-114 - Move to Asciidoctor for reference documentation. -* DATAES-113 - Add support for custom implementations in CDI repositories. -* DATAES-97 - UpdateQuery and UpdateBuilder should use UpdateRequest instead of the IndexRequest. -* DATAES-93 - Allow Spring EL usage in index name attribute of @Document. -* DATAES-89 - Support geolocation queries. - - -Changes in version 1.0.2.RELEASE (2014-07-28) ---------------------------------------------- -* DATAES-107 - Release 1.0.2. - - -Changes in version 1.1.0.M1 (2014-07-10) ----------------------------------------- -* DATAES-102 - Release 1.1 M1. -* DATAES-96 - Add support for aggregation in ElasticsearchTemplate. -* DATAES-94 - Bump to support ES 1.2.0. - - -Changes in version 1.0.1.RELEASE (2014-06-30) ---------------------------------------------- -* DATAES-99 - Release 1.0.1. - - -Changes in version 1.0.0.RELEASE (2014-05-20) ---------------------------------------------- -* DATAES-90 - Release 1.0 GA. - - -Changes in version 1.0.0.RC1 (2014-05-02) ------------------------------------------ -* DATAES-79 - Upgrade to elasticsearch 1.1.0. -* DATAES-78 - Release 1.0 M2. -* DATAES-74 - NPE in MappingElasticsearchEntityInformation. -* DATAES-73 - NullPointerException while persist a Map as part of persistent entity. -* DATAES-72 - Enhance Delete Index in ElasticsearchTemplate. -* DATAES-71 - Enhance Create Index in ElasticsearchTemplate. -* DATAES-69 - Sorting on String field not working. -* DATAES-67 - ElasticsearchTemplate#count does not respect specified index. -* DATAES-65 - Overhaul README.md. -* DATAES-64 - Add dynamic settings using @Setting annotation. -* DATAES-61 - Upgrade to elasticsearch 1.0.1. -* DATAES-60 - Java config support - default values in TransportClientFactoryBean. -* DATAES-56 - while specifying index and type at runtime indexing is failing. -* DATAES-55 - polish test cases and remove unwanted files. -* DATAES-54 - Upgrade to elasticsearch 1.0. -* DATAES-53 - @Field annotations (perhaps others?) ignored in included objects on mapping creation. -* DATAES-52 - Support Get & Multi Get. -* DATAES-51 - Allow for multiple sort builders when using NativeSearchQuery. -* DATAES-48 - Mapping for multiple level nested document is getting created wrong. -* DATAES-47 - Spring Application(Context) is not getting started up if elasticsearch nodes are not available. -* DATAES-46 - Remove unused imports & class. -* DATAES-35 - Investigate why build failed. -* DATAES-14 - Dynamic Search Type Support. -* DATAES-2 - Apply Spring Data formatting to sources. - - -Release Notes - Spring Data Elasticsearch - Version 1.0 M2 (2014-03-27) ------------------------------------------------------------------------ -* DATAES-74 - NPE in MappingElasticsearchEntityInformation -* DATAES-73 - NullPointerException while persist a Map as part of persistent entity -* DATAES-69 - Sorting on String field not working -* DATAES-67 - ElasticsearchTemplate#count does not respect specified index -* DATAES-56 - while specifying index and type at runtime indexing is failing -* DATAES-53 - @Field annotations (perhaps others?) ignored in included objects on mapping creation -* DATAES-48 - Mapping for multiple level nested document is getting created wrong -* DATAES-35 - Investigate why build failed -* DATAES-72 - Enhance Delete Index in ElasticsearchTemplate -* DATAES-71 - Enhance Create Index in ElasticsearchTemplate -* DATAES-61 - Upgrade to elasticsearch 1.0.1 -* DATAES-60 - Java config support - default values in TransportClientFactoryBean -* DATAES-54 - Upgrade to elasticsearch 1.0 -* DATAES-47 - Spring Application(Context) is not getting started up if elasticsearch nodes are not available -* DATAES-14 - Dynamic Search Type Support -* DATAES-79 - Upgrade to elasticsearch 1.1.0 -* DATAES-64 - Add dynamic settings using @Setting annotation -* DATAES-52 - Support Get & Multi Get -* DATAES-51 - Allow for multiple sort builders when using NativeSearchQuery -* DATAES-78 - Release 1.0 M2 -* DATAES-65 - Overhaul README.md -* DATAES-55 - polish test cases and remove unwanted files -* DATAES-46 - Remove unused imports & class -* DATAES-2 - Apply Spring Data formatting to sources - -Release Notes - Spring Data Elasticsearch - Version 1.0 M1 (2014-02-07) ------------------------------------------------------------------------ -* DATAES-9 - multiple elasticsearch cluster nodes not getting parsed if using property file -* DATAES-7 - Migrate to the latest version of elasticsearch 0.90.0 -* DATAES-11 - Missing core types in org.springframework.data.elasticsearch.annotations.FieldType -* DATAES-15 - Upgrade to latest version of elasticsearch( 0.90.2 ) -* DATAES-19 - Delete specific type in an index, Type exists check -* DATAES-22 - Add support for TransportClient additional parameters such as client.transport.ignore_cluster_name, client.transport.ping_timeout, client.transport.nodes_sampler_interval -* DATAES-8 - Add support for Index level configuration -* DATAES-17 - Add support to retrieve highlighted text in search result -* DATAES-1 - Migrate sources to SpringSource GitHub repository -* DATAES-3 - Move to Spring Data build system -* DATAES-4 - Initial round of JavaDoc polish -* DATAES-5 - Add Apache license headers where necessary -* DATAES-6 - Add ability to let NodeClient clean up working directory -* DATAES-45 - Release 1.0 M1. -* #42 - org.springframework.data.elasticsearch.client.NodeClientFactoryBean' is not a valid value for 'anyURI' -* #40 - how to create index for json objects? -* #38 - Configure with native Client with embedded elasticsearch -* #37 - embedded elasticsearch client - how to recover data after process is restarted? -* #36 - FieldType.Date annotation -* #35 - @Transient property still inserted into elastic -* #33 - timestamp mapping -* #32 - BigDecimal @Field(type = FieldType.Double) -* #31 - DATAES-33: Mapping of parent-child relationships -* #29 - Added DefaultMapper support to map only partial fields from result instead of whole source field. -* #28 - Documentation (README.md) for Geo Location / Filter Feature -* #27 - Adding support for letting Elasticsearch generate Id for document -* #25 - Aliases -* #24 - DATAES-31 Adding ability to use ES Java API setSource directly -* #22 - spring-elasticsearch-1.0.xsd not found on www.springframework.com -* #21 - Autogenerated elastic search id isn't returned -* #20 - Exception while using CustomRepository -* #19 - Compilation issue with 0.90.5. (ElasticsearchTemplate.refresh API) -* #18 - problem with ES 0.9.5 -* #17 - FacetResult extension is not possible -* #16 - Configuration of ObjectMapper in the ElasticsearchTemplate -* #15 - Added support for @Transient annotation to skip fields from mapping -* #14 - added format and pattern support for date fieldtype + fixed nullpointer... -* #12 - Where is the maven repository for this artifact? -* #11 - Build fails -* #10 - Elasticsearch + elasticsearch-river-rabbitmq -* #9 - MappingBuilder circular reference issue -* #8 - java.lang.NoSuchMethodError: org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty.isVersionProperty()Z -* #7 - Missing core types in org.springframework.data.elasticsearch.annotations.FieldType -* #6 - spirng-data-elasticsearch with elasticsearch-river-mongodb - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 1c8e0e03d363283bda288a31a5761de3ab43a830 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sun, 1 Aug 2021 19:36:57 +0200 Subject: [PATCH 109/776] Make integration tests configurable to use different containers. Original Pull Request: #1888 Closes #1882 --- pom.xml | 56 ++++++++++--------- .../data/elasticsearch/Foobar.java | 19 +++++++ .../elasticsearch/FoobarIntegrationTest.java | 28 ++++++++++ .../TransportFoobarIntegrationTest.java | 17 ++++++ .../config/AuditingIntegrationTest.java | 2 +- .../junit/jupiter/ClusterConnection.java | 55 ++++++++++++++---- .../junit/jupiter/ClusterConnectionInfo.java | 24 ++++++-- .../jupiter/IntegrationtestEnvironment.java | 39 +++++++++++++ .../testcontainers-elasticsearch.properties | 10 +++- .../testcontainers-opensearch.properties | 10 ++++ 10 files changed, 214 insertions(+), 46 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/Foobar.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/FoobarIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/TransportFoobarIntegrationTest.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/junit/jupiter/IntegrationtestEnvironment.java create mode 100644 src/test/resources/testcontainers-opensearch.properties diff --git a/pom.xml b/pom.xml index 124e702b1..63925dcd4 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,15 @@ 1.15.3 1.0.6.RELEASE spring.data.elasticsearch + + + test + integration-test + none
@@ -264,25 +273,6 @@ test - - org.skyscreamer jsonassert @@ -365,9 +355,6 @@ - org.apache.maven.plugins maven-surefire-plugin @@ -386,7 +373,7 @@ default-test - test + ${mvn.unit-test.goal} test @@ -394,15 +381,32 @@ integration-test - + + + integration-test-elasticsearch + ${mvn.integration-test-elasticsearch.goal} + + test + + + integration-test + + elasticsearch + + + + - integration-test - integration-test + integration-test-opensearch + ${mvn.integration-test-opensearch.goal} test integration-test + + opensearch + diff --git a/src/test/java/org/springframework/data/elasticsearch/Foobar.java b/src/test/java/org/springframework/data/elasticsearch/Foobar.java new file mode 100644 index 000000000..3f9d28f02 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/Foobar.java @@ -0,0 +1,19 @@ +package org.springframework.data.elasticsearch; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +/** + * @author Peter-Josef Meisch + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Tag("foobar") +public @interface Foobar { +} diff --git a/src/test/java/org/springframework/data/elasticsearch/FoobarIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/FoobarIntegrationTest.java new file mode 100644 index 000000000..409bf4349 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/FoobarIntegrationTest.java @@ -0,0 +1,28 @@ +package org.springframework.data.elasticsearch; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest; + +/** + * @author Peter-Josef Meisch + */ +@SpringIntegrationTest +@Foobar +public abstract class FoobarIntegrationTest { + + private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + @Test + @DisplayName("should run test") + void shouldRunTest() { + + int answer = 42; + + assertThat(answer).isEqualTo(42); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/TransportFoobarIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/TransportFoobarIntegrationTest.java new file mode 100644 index 000000000..bdc39c75c --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/TransportFoobarIntegrationTest.java @@ -0,0 +1,17 @@ +package org.springframework.data.elasticsearch; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; +import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration; +import org.springframework.data.elasticsearch.junit.jupiter.IntegrationtestEnvironment; +import org.springframework.test.context.ContextConfiguration; + +/** + * This class should only run when the cluster is an Elasticsearch cluster. + * + * @author Peter-Josef Meisch + */ +@EnabledIfSystemProperty(named = IntegrationtestEnvironment.SYSTEM_PROPERTY, matches = "(?i)elasticsearch") +@ContextConfiguration(classes = { ElasticsearchTemplateConfiguration.class }) +@DisplayName("foobar integration with transport client") +public class TransportFoobarIntegrationTest extends FoobarIntegrationTest {} diff --git a/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTest.java b/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTest.java index e14647448..e0bc69f89 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTest.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/AuditingIntegrationTest.java @@ -73,7 +73,7 @@ void shouldEnableAuditingAndSetAuditingDates() throws InterruptedException { assertThat(entity.getCreatedBy()).isEqualTo("Auditor 1"); assertThat(entity.getModifiedBy()).isEqualTo("Auditor 1"); - Thread.sleep(10); + Thread.sleep(50); entity = callbacks.callback(BeforeConvertCallback.class, entity, IndexCoordinates.of("index")); diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java index e0d2bc430..5f16814a3 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnection.java @@ -24,9 +24,9 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.elasticsearch.support.VersionInfo; import org.springframework.lang.Nullable; import org.testcontainers.elasticsearch.ElasticsearchContainer; +import org.testcontainers.utility.DockerImageName; /** * This class manages the connection to an Elasticsearch Cluster, starting a containerized one if necessary. The @@ -40,9 +40,10 @@ public class ClusterConnection implements ExtensionContext.Store.CloseableResour private static final Logger LOGGER = LoggerFactory.getLogger(ClusterConnection.class); + private static final String SDE_TESTCONTAINER_IMAGE_NAME = "sde.testcontainers.image-name"; + private static final String SDE_TESTCONTAINER_IMAGE_VERSION = "sde.testcontainers.image-version"; private static final int ELASTICSEARCH_DEFAULT_PORT = 9200; private static final int ELASTICSEARCH_DEFAULT_TRANSPORT_PORT = 9300; - private static final String ELASTICSEARCH_DEFAULT_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch"; private static final ThreadLocal clusterConnectionInfoThreadLocal = new ThreadLocal<>(); @@ -78,20 +79,27 @@ public ClusterConnectionInfo getClusterConnectionInfo() { @Nullable private ClusterConnectionInfo startElasticsearchContainer() { - LOGGER.debug("Starting Elasticsearch Container"); + LOGGER.info("Starting Elasticsearch Container..."); try { - String elasticsearchVersion = VersionInfo.versionProperties() - .getProperty(VersionInfo.VERSION_ELASTICSEARCH_CLIENT); - Map elasticsearchProperties = elasticsearchProperties(); + IntegrationtestEnvironment integrationtestEnvironment = IntegrationtestEnvironment.get(); + LOGGER.info("Integration test environment: {}", integrationtestEnvironment); + if (integrationtestEnvironment == IntegrationtestEnvironment.UNDEFINED) { + throw new IllegalArgumentException(IntegrationtestEnvironment.SYSTEM_PROPERTY + " property not set"); + } + + String testcontainersConfiguration = integrationtestEnvironment.name().toLowerCase(); + Map testcontainersProperties = testcontainersProperties( + "testcontainers-" + testcontainersConfiguration + ".properties"); - String dockerImageName = ELASTICSEARCH_DEFAULT_IMAGE + ':' + elasticsearchVersion; - LOGGER.debug("Docker image: {}", dockerImageName); + DockerImageName dockerImageName = getDockerImageName(testcontainersProperties); - ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(dockerImageName); - elasticsearchContainer.withEnv(elasticsearchProperties); + ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer(dockerImageName) + .withEnv(testcontainersProperties); elasticsearchContainer.start(); + return ClusterConnectionInfo.builder() // + .withIntegrationtestEnvironment(integrationtestEnvironment) .withHostAndPort(elasticsearchContainer.getHost(), elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_PORT)) // .withTransportPort(elasticsearchContainer.getMappedPort(ELASTICSEARCH_DEFAULT_TRANSPORT_PORT)) // @@ -104,9 +112,32 @@ private ClusterConnectionInfo startElasticsearchContainer() { return null; } - private Map elasticsearchProperties() { + private DockerImageName getDockerImageName(Map testcontainersProperties) { + + String imageName = testcontainersProperties.get(SDE_TESTCONTAINER_IMAGE_NAME); + String imageVersion = testcontainersProperties.get(SDE_TESTCONTAINER_IMAGE_VERSION); + + if (imageName == null) { + throw new IllegalArgumentException("property " + SDE_TESTCONTAINER_IMAGE_NAME + " not configured"); + } + testcontainersProperties.remove(SDE_TESTCONTAINER_IMAGE_NAME); + + if (imageVersion == null) { + throw new IllegalArgumentException("property " + SDE_TESTCONTAINER_IMAGE_VERSION + " not configured"); + } + testcontainersProperties.remove(SDE_TESTCONTAINER_IMAGE_VERSION); + + String configuredImageName = imageName + ':' + imageVersion; + DockerImageName dockerImageName = DockerImageName.parse(configuredImageName) + .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"); + LOGGER.info("Docker image: {}", dockerImageName); + return dockerImageName; + } + + private Map testcontainersProperties(String propertiesFile) { + + LOGGER.info("load configuration from {}", propertiesFile); - String propertiesFile = "testcontainers-elasticsearch.properties"; try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propertiesFile)) { Properties props = new Properties(); diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnectionInfo.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnectionInfo.java index ee4217809..307e5e033 100644 --- a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnectionInfo.java +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/ClusterConnectionInfo.java @@ -25,10 +25,11 @@ * with a rest client for both a local started cluster and for one defined by the cluster URL when creating the * {@link ClusterConnection}.
* The object must be created by using a {@link ClusterConnectionInfo.Builder}. - * + * * @author Peter-Josef Meisch */ public final class ClusterConnectionInfo { + private final IntegrationtestEnvironment integrationtestEnvironment; private final boolean useSsl; private final String host; private final int httpPort; @@ -40,8 +41,9 @@ public static Builder builder() { return new Builder(); } - private ClusterConnectionInfo(String host, int httpPort, boolean useSsl, int transportPort, - @Nullable ElasticsearchContainer elasticsearchContainer) { + private ClusterConnectionInfo(IntegrationtestEnvironment integrationtestEnvironment, String host, int httpPort, + boolean useSsl, int transportPort, @Nullable ElasticsearchContainer elasticsearchContainer) { + this.integrationtestEnvironment = integrationtestEnvironment; this.host = host; this.httpPort = httpPort; this.useSsl = useSsl; @@ -53,7 +55,8 @@ private ClusterConnectionInfo(String host, int httpPort, boolean useSsl, int tra @Override public String toString() { return "ClusterConnectionInfo{" + // - "useSsl=" + useSsl + // + "configuration: " + integrationtestEnvironment + // + ", useSsl=" + useSsl + // ", host='" + host + '\'' + // ", httpPort=" + httpPort + // ", transportPort=" + transportPort + // @@ -86,14 +89,22 @@ public ElasticsearchContainer getElasticsearchContainer() { } public static class Builder { - boolean useSsl = false; + private IntegrationtestEnvironment integrationtestEnvironment; + private boolean useSsl = false; private String host; private int httpPort; private int transportPort; @Nullable private ElasticsearchContainer elasticsearchContainer; + public Builder withIntegrationtestEnvironment(IntegrationtestEnvironment integrationtestEnvironment) { + this.integrationtestEnvironment = integrationtestEnvironment; + return this; + } + public Builder withHostAndPort(String host, int httpPort) { + Assert.hasLength(host, "host must not be empty"); + this.host = host; this.httpPort = httpPort; return this; @@ -115,7 +126,8 @@ public Builder withElasticsearchContainer(ElasticsearchContainer elasticsearchCo } public ClusterConnectionInfo build() { - return new ClusterConnectionInfo(host, httpPort, useSsl, transportPort, elasticsearchContainer); + return new ClusterConnectionInfo(integrationtestEnvironment, host, httpPort, useSsl, transportPort, + elasticsearchContainer); } } } diff --git a/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/IntegrationtestEnvironment.java b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/IntegrationtestEnvironment.java new file mode 100644 index 000000000..5db8bc7fc --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/junit/jupiter/IntegrationtestEnvironment.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.junit.jupiter; + +/** + * @author Peter-Josef Meisch + */ +public enum IntegrationtestEnvironment { + + ELASTICSEARCH, OPENSEARCH, UNDEFINED; + + public static final String SYSTEM_PROPERTY = "sde.integration-test.environment"; + + public static IntegrationtestEnvironment get() { + + String property = System.getProperty(SYSTEM_PROPERTY, "elasticsearch"); + switch (property.toUpperCase()) { + case "ELASTICSEARCH": + return ELASTICSEARCH; + case "OPENSEARCH": + return OPENSEARCH; + default: + return UNDEFINED; + } + } +} diff --git a/src/test/resources/testcontainers-elasticsearch.properties b/src/test/resources/testcontainers-elasticsearch.properties index 5bef8c62b..2ca3d43e3 100644 --- a/src/test/resources/testcontainers-elasticsearch.properties +++ b/src/test/resources/testcontainers-elasticsearch.properties @@ -1,2 +1,10 @@ -# needed as we do a DELETE /* at the end of the tests, will be requited from 8.0 on, produces a warning since 7.13 +# +# properties defining the image, these are not passed to the container on startup +# +sde.testcontainers.image-name=docker.elastic.co/elasticsearch/elasticsearch +sde.testcontainers.image-version=7.13.3 +# +# +# needed as we do a DELETE /* at the end of the tests, will be required from 8.0 on, produces a warning since 7.13 +# action.destructive_requires_name=false diff --git a/src/test/resources/testcontainers-opensearch.properties b/src/test/resources/testcontainers-opensearch.properties new file mode 100644 index 000000000..147af3ce5 --- /dev/null +++ b/src/test/resources/testcontainers-opensearch.properties @@ -0,0 +1,10 @@ +# +# properties defining the image, these are not passed to the container on startup +# +sde.testcontainers.image-name=opensearchproject/opensearch +sde.testcontainers.image-version=1.0.0 +# +# +# Opensearch as default has TLS and Basic auth enabled, we do not want this here, Testcontainers cannot ignore self signed certificates +# +plugins.security.disabled=true From 36b449c3852fee3b4cdeb0760058c4265d8a176c Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Fri, 6 Aug 2021 20:01:02 +0200 Subject: [PATCH 110/776] Fix NPE on IndexQuery with source and version. Original Pull Request #1894 Closes #1893 --- .../elasticsearch/core/RequestFactory.java | 26 ++++++++++++------- .../core/ElasticsearchTemplateTests.java | 16 ++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) 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 4451ae9c0..6981d5905 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -700,15 +700,17 @@ public IndexRequest indexRequest(IndexQuery query, IndexCoordinates index) { String indexName = index.getIndexName(); IndexRequest indexRequest; - if (query.getObject() != null) { - String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId(); + Object queryObject = query.getObject(); + + if (queryObject != null) { + String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId(); // If we have a query id and a document id, do not ask ES to generate one. if (id != null) { indexRequest = new IndexRequest(indexName).id(id); } else { indexRequest = new IndexRequest(indexName); } - indexRequest.source(elasticsearchConverter.mapObject(query.getObject()).toJson(), Requests.INDEX_CONTENT_TYPE); + indexRequest.source(elasticsearchConverter.mapObject(queryObject).toJson(), Requests.INDEX_CONTENT_TYPE); } else if (query.getSource() != null) { indexRequest = new IndexRequest(indexName).id(query.getId()).source(query.getSource(), Requests.INDEX_CONTENT_TYPE); @@ -719,7 +721,8 @@ public IndexRequest indexRequest(IndexQuery query, IndexCoordinates index) { if (query.getVersion() != null) { indexRequest.version(query.getVersion()); - VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass()); + VersionType versionType = retrieveVersionTypeFromPersistentEntity( + queryObject != null ? queryObject.getClass() : null); indexRequest.versionType(versionType); } @@ -754,15 +757,16 @@ public IndexRequestBuilder indexRequestBuilder(Client client, IndexQuery query, IndexRequestBuilder indexRequestBuilder; - if (query.getObject() != null) { - String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId(); + Object queryObject = query.getObject(); + if (queryObject != null) { + String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(queryObject) : query.getId(); // If we have a query id and a document id, do not ask ES to generate one. if (id != null) { indexRequestBuilder = client.prepareIndex(indexName, type, id); } else { indexRequestBuilder = client.prepareIndex(indexName, type); } - indexRequestBuilder.setSource(elasticsearchConverter.mapObject(query.getObject()).toJson(), + indexRequestBuilder.setSource(elasticsearchConverter.mapObject(queryObject).toJson(), Requests.INDEX_CONTENT_TYPE); } else if (query.getSource() != null) { indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource(), @@ -774,7 +778,8 @@ public IndexRequestBuilder indexRequestBuilder(Client client, IndexQuery query, if (query.getVersion() != null) { indexRequestBuilder.setVersion(query.getVersion()); - VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass()); + VersionType versionType = retrieveVersionTypeFromPersistentEntity( + queryObject != null ? queryObject.getClass() : null); indexRequestBuilder.setVersionType(versionType); } @@ -1640,12 +1645,13 @@ private String getPersistentEntityId(Object entity) { return null; } - private VersionType retrieveVersionTypeFromPersistentEntity(Class clazz) { + private VersionType retrieveVersionTypeFromPersistentEntity(@Nullable Class clazz) { MappingContext, ElasticsearchPersistentProperty> mappingContext = elasticsearchConverter .getMappingContext(); - ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(clazz); + ElasticsearchPersistentEntity persistentEntity = clazz != null ? mappingContext.getPersistentEntity(clazz) + : null; VersionType versionType = null; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 831de08e8..cdcc2197d 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -3576,6 +3576,22 @@ void shouldSetScriptedFieldsOnImmutableObjects() { assertThat(foundEntity.getScriptedRate()).isEqualTo(84.0); } + @Test // #1893 + @DisplayName("should index document from source with version") + void shouldIndexDocumentFromSourceWithVersion() { + + String source = "{\n" + // + " \"answer\": 42\n" + // + "}"; + IndexQuery query = new IndexQueryBuilder() // + .withId("42") // + .withSource(source) // + .withVersion(42L) // + .build(); + + operations.index(query, IndexCoordinates.of(indexNameProvider.indexName())); + } + // region entities @Document(indexName = "#{@indexNameProvider.indexName()}") @Setting(shards = 1, replicas = 0, refreshInterval = "-1") From 3eac1bb173ed867a45369a54a4f6654eeab30ce1 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 12 Aug 2021 15:02:57 +0200 Subject: [PATCH 111/776] Prepare 4.3 M2 (2021.1.0). See #1876 --- pom.xml | 8 ++++---- src/main/resources/notice.txt | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 63925dcd4..3b2659d02 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.6.0-SNAPSHOT + 2.6.0-M2 Spring Data Elasticsearch @@ -21,7 +21,7 @@ 7.13.3 2.14.1 4.1.65.Final - 2.6.0-SNAPSHOT + 2.6.0-M2 1.15.3 1.0.6.RELEASE spring.data.elasticsearch @@ -491,8 +491,8 @@ - spring-libs-snapshot - https://repo.spring.io/libs-snapshot + spring-libs-milestone + https://repo.spring.io/libs-milestone diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt index 347f28d81..873aa2953 100644 --- a/src/main/resources/notice.txt +++ b/src/main/resources/notice.txt @@ -1,4 +1,4 @@ -Spring Data Elasticsearch 4.3 M1 (2021.1.0) +Spring Data Elasticsearch 4.3 M2 (2021.1.0) Copyright (c) [2013-2021] Pivotal Software, Inc. This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -27,4 +27,5 @@ conditions of the subcomponent's license, as noted in the LICENSE file. + From 655db1e3626a4b4d6a9f2b3e367fa6936005b8b7 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 12 Aug 2021 15:03:18 +0200 Subject: [PATCH 112/776] Release version 4.3 M2 (2021.1.0). See #1876 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3b2659d02..53b8595ae 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.3.0-SNAPSHOT + 4.3.0-M2 org.springframework.data.build From 325d3f7a098be4102d620a8c3657dda1ea4eac9e Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 12 Aug 2021 15:16:22 +0200 Subject: [PATCH 113/776] Prepare next development iteration. See #1876 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 53b8595ae..3b2659d02 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-elasticsearch - 4.3.0-M2 + 4.3.0-SNAPSHOT org.springframework.data.build From fd3410bb75aad546adc5888f2f1596eaffea1f06 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Thu, 12 Aug 2021 15:16:23 +0200 Subject: [PATCH 114/776] After release cleanups. See #1876 --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 3b2659d02..63925dcd4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.springframework.data.build spring-data-parent - 2.6.0-M2 + 2.6.0-SNAPSHOT Spring Data Elasticsearch @@ -21,7 +21,7 @@ 7.13.3 2.14.1 4.1.65.Final - 2.6.0-M2 + 2.6.0-SNAPSHOT 1.15.3 1.0.6.RELEASE spring.data.elasticsearch @@ -491,8 +491,8 @@ - spring-libs-milestone - https://repo.spring.io/libs-milestone + spring-libs-snapshot + https://repo.spring.io/libs-snapshot From 50f2d8344219a41f9333818e7139283878c45202 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Thu, 12 Aug 2021 20:38:27 +0200 Subject: [PATCH 115/776] Upgrade to Elasticsearch 7.13.4. Original Pull Request #1900 Closes #1896 --- pom.xml | 2 +- src/main/asciidoc/preface.adoc | 2 +- .../testcontainers-elasticsearch.properties | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 63925dcd4..8a024510a 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ https://github.com/spring-projects/spring-data-elasticsearch - 7.13.3 + 7.13.4 2.14.1 4.1.65.Final 2.6.0-SNAPSHOT diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc index 30e4f1a36..4a598ab9f 100644 --- a/src/main/asciidoc/preface.adoc +++ b/src/main/asciidoc/preface.adoc @@ -34,7 +34,7 @@ The following table shows the Elasticsearch versions that are used by Spring Dat [cols="^,^,^,^,^",options="header"] |=== | Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot -| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.3 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] +| 2021.1 (Q)footnote:cdv[Currently in development] | 4.3.xfootnote:cdv[] | 7.13.4 | 5.3.xfootnote:cdv[] | 2.5.xfootnote:cdv[] | 2021.0 (Pascal) | 4.2.x | 7.12.0 | 5.3.x | 2.5.x | 2020.0 (Ockham) | 4.1.x | 7.9.3 | 5.3.2 | 2.4.x | Neumann | 4.0.x | 7.6.2 | 5.2.12 |2.3.x diff --git a/src/test/resources/testcontainers-elasticsearch.properties b/src/test/resources/testcontainers-elasticsearch.properties index 2ca3d43e3..67b11aa78 100644 --- a/src/test/resources/testcontainers-elasticsearch.properties +++ b/src/test/resources/testcontainers-elasticsearch.properties @@ -1,8 +1,23 @@ # +# Copyright 2021 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# # properties defining the image, these are not passed to the container on startup # sde.testcontainers.image-name=docker.elastic.co/elasticsearch/elasticsearch -sde.testcontainers.image-version=7.13.3 +sde.testcontainers.image-version=7.13.4 # # # needed as we do a DELETE /* at the end of the tests, will be required from 8.0 on, produces a warning since 7.13 From e688fc70e08588766d585b5f780e0de59ba385d5 Mon Sep 17 00:00:00 2001 From: Daniel Franco Date: Thu, 26 Aug 2021 19:13:19 +0100 Subject: [PATCH 116/776] Update maven wrapper version to 3.8.2 Original Pull Request #1905 --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index ffdc10e59..abd303b67 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,2 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar From 305d930870cfa6fa20e22d81c04f3800233c6a70 Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Mon, 30 Aug 2021 21:37:04 +0200 Subject: [PATCH 117/776] Remove org.elasticsearch dependencies from API classes. Original Pull Request #1913 Closes #1884 Closes #1885 --- .../reference/elasticsearch-clients.adoc | 42 +-- ...elasticsearch-migration-guide-4.2-4.3.adoc | 43 ++- .../annotations/CompletionContext.java | 27 +- .../elasticsearch/annotations/Document.java | 9 +- .../client/ClientConfiguration.java | 57 +++- .../client/ClientConfigurationBuilder.java | 24 +- .../elasticsearch/client/ClusterNodes.java | 10 +- .../client/DefaultClientConfiguration.java | 10 +- .../elasticsearch/client/RestClients.java | 16 +- .../DefaultReactiveElasticsearchClient.java | 5 +- .../client/reactive/ReactiveRestClients.java | 9 + .../client/util/ScrollState.java | 4 +- ...actElasticsearchRestTransportTemplate.java | 208 +++++++++++++ .../core/AbstractElasticsearchTemplate.java | 202 ++----------- .../core/AbstractIndexTemplate.java | 8 - .../elasticsearch/core/ActiveShardCount.java | 41 +++ .../core/ElasticsearchRestTemplate.java | 26 +- .../core/ElasticsearchTemplate.java | 27 +- .../core/ReactiveElasticsearchTemplate.java | 40 ++- .../core/ReactiveSearchOperations.java | 27 +- .../elasticsearch/core/RequestFactory.java | 65 ++-- .../elasticsearch/core/ResponseConverter.java | 71 +++++ .../elasticsearch/core/RestIndexTemplate.java | 16 - .../elasticsearch/core/SearchOperations.java | 19 ++ .../core/TransportIndexTemplate.java | 12 +- .../core/index/GeoShapeMappingParameters.java | 36 ++- .../core/index/MappingBuilder.java | 249 ++++++++-------- .../core/index/MappingParameters.java | 102 ++++--- .../core/index/ReactiveMappingBuilder.java | 2 +- .../ElasticsearchPersistentEntity.java | 4 +- .../SimpleElasticsearchPersistentEntity.java | 5 +- .../core/query/AbstractQuery.java | 11 +- .../elasticsearch/core/query/BulkOptions.java | 30 +- .../core/query/ByQueryResponse.java | 277 +++++++----------- .../core/query/HighlightQuery.java | 30 +- .../core/query/HighlightQueryBuilder.java | 116 ++++---- .../core/query/IndicesOptions.java | 89 ++++++ .../core/query/NativeSearchQueryBuilder.java | 9 +- .../data/elasticsearch/core/query/Query.java | 16 +- .../core/query/highlight/Highlight.java | 87 ++++++ .../highlight/HighlightCommonParameters.java | 219 ++++++++++++++ .../core/query/highlight/HighlightField.java | 70 +++++ .../highlight/HighlightFieldParameters.java | 64 ++++ .../query/highlight/HighlightParameters.java | 64 ++++ .../core/query/highlight/package-info.java | 22 ++ .../query/ElasticsearchQueryMethod.java | 11 +- .../ElasticsearchEntityInformation.java | 4 +- ...MappingElasticsearchEntityInformation.java | 4 +- .../SimpleElasticsearchRepository.java | 12 +- ...SimpleReactiveElasticsearchRepository.java | 10 +- .../data/elasticsearch/support/Version.java | 84 ++++++ .../elasticsearch/support/VersionInfo.java | 33 ++- .../ComposableAnnotationsUnitTest.java | 6 +- .../client/ClientConfigurationUnitTests.java | 66 ++++- .../elasticsearch/client/RestClientsTest.java | 45 ++- .../core/ElasticsearchRestTemplateTests.java | 6 +- .../core/ElasticsearchTemplateTests.java | 11 +- .../ElasticsearchTransportTemplateTests.java | 6 +- .../core/RequestFactoryTests.java | 13 +- ...chTemplateCompletionWithContextsTests.java | 5 +- .../index/MappingBuilderIntegrationTests.java | 5 +- .../core/index/MappingBuilderUnitTests.java | 12 +- .../query/HighlightQueryBuilderTests.java | 14 +- .../support/VersionUnitTest.java | 108 +++++++ 64 files changed, 2120 insertions(+), 855 deletions(-) create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchRestTransportTemplate.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/ActiveShardCount.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/IndicesOptions.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/highlight/Highlight.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightCommonParameters.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightField.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightFieldParameters.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/highlight/HighlightParameters.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/core/query/highlight/package-info.java create mode 100644 src/main/java/org/springframework/data/elasticsearch/support/Version.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/support/VersionUnitTest.java diff --git a/src/main/asciidoc/reference/elasticsearch-clients.adoc b/src/main/asciidoc/reference/elasticsearch-clients.adoc index 935c601bb..1a4376f4a 100644 --- a/src/main/asciidoc/reference/elasticsearch-clients.adoc +++ b/src/main/asciidoc/reference/elasticsearch-clients.adoc @@ -3,13 +3,14 @@ This chapter illustrates configuration and usage of supported Elasticsearch client implementations. -Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <> and <>. +Spring Data Elasticsearch operates upon an Elasticsearch client that is connected to a single Elasticsearch node or a cluster. +Although the Elasticsearch Client can be used to work with the cluster, applications using Spring Data Elasticsearch normally use the higher level abstractions of <> and <>. [[elasticsearch.clients.transport]] == Transport Client -WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used -Elasticsearch <> but has deprecated the classes using it since version 4.0. +WARNING: The `TransportClient` is deprecated as of Elasticsearch 7 and will be removed in Elasticsearch 8. (https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html[see the Elasticsearch documentation]). +Spring Data Elasticsearch will support the `TransportClient` as long as it is available in the used Elasticsearch <> but has deprecated the classes using it since version 4.0. We strongly recommend to use the <> instead of the `TransportClient`. @@ -46,6 +47,7 @@ IndexRequest request = new IndexRequest("spring-data") IndexResponse response = client.index(request); ---- + <.> The `TransportClient` must be configured with the cluster name. <.> The host and port to connect the client to. <.> the RefreshPolicy must be set in the `ElasticsearchTemplate` (override `refreshPolicy()` to not use the default) @@ -54,8 +56,7 @@ IndexResponse response = client.index(request); [[elasticsearch.clients.rest]] == High Level REST Client -The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns -the very same request/response objects and therefore depends on the Elasticsearch core project. +The Java High Level REST Client is the default client of Elasticsearch, it provides a straight forward replacement for the `TransportClient` as it accepts and returns the very same request/response objects and therefore depends on the Elasticsearch core project. Asynchronous calls are operated upon a client managed thread pool and require a callback to be notified when the request is done. .High Level REST Client @@ -93,6 +94,7 @@ IndexRequest request = new IndexRequest("spring-data") IndexResponse response = highLevelClient.index(request,RequestOptions.DEFAULT); ---- + <1> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL. <2> Create the RestHighLevelClient. <3> It is also possible to obtain the `lowLevelRest()` client. @@ -131,6 +133,7 @@ Mono response = client.index(request -> .source(singletonMap("feature", "reactive-client")); ); ---- + <.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL. ==== @@ -162,25 +165,30 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder() headers.add("currentTime", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); return headers; }) - .withWebClientConfigurer(webClient -> { <.> - //... - return webClient; - }) - .withHttpClientConfigurer(clientBuilder -> { <.> - //... + .withClientConfigurer( <.> + (ReactiveRestClients.WebClientConfigurationCallback) webClient -> { + // ... + return webClient; + }) + .withClientConfigurer( <.> + (RestClients.RestClientConfigurationCallback) clientBuilder -> { + // ... return clientBuilder; - }) + }) . // ... other options .build(); ---- + <.> Define default headers, if they need to be customized <.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL. <.> Optionally enable SSL. <.> Optionally set a proxy. <.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy. -<.> Set the connection timeout. Default is 10 sec. -<.> Set the socket timeout. Default is 5 sec. +<.> Set the connection timeout. +Default is 10 sec. +<.> Set the socket timeout. +Default is 5 sec. <.> Optionally set headers. <.> Add basic authentication. <.> A `Supplier

` function can be specified which is called every time before a request is sent to Elasticsearch - here, as an example, the current time is written in a header. @@ -188,13 +196,13 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder() <.> for non-reactive setup a function configuring the REST client ==== -IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. If this is used in the reactive setup, the supplier function *must not* block! +IMPORTANT: Adding a Header supplier as shown in above example allows to inject headers that may change over the time, like authentication JWT tokens. +If this is used in the reactive setup, the supplier function *must not* block! [[elasticsearch.clients.logging]] == Client Logging -To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs -to be turned on as outlined in the snippet below. +To see what is actually sent to and received from the server `Request` / `Response` logging on the transport level needs to be turned on as outlined in the snippet below. .Enable transport layer logging [source,xml] diff --git a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc index 4012e4604..a32cc0133 100644 --- a/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc +++ b/src/main/asciidoc/reference/elasticsearch-migration-guide-4.2-4.3.adoc @@ -3,12 +3,53 @@ This section describes breaking changes from version 4.2.x to 4.3.x and how removed features can be replaced by new introduced features. +[NOTE] +==== +Elasticsearch is working on a new Client that will replace the `RestHighLevelClient` because the `RestHighLevelClient` uses code from Elasticsearch core libraries which are not Apache 2 licensed anymore. +Spring Data Elasticsearch is preparing for this change as well. +This means that internally the implementations for the `*Operations` interfaces need to change - which should be no problem if users program against the interfaces like `ElasticsearchOperations` or `ReactiveElasticsearchOperations`. +If you are using the implementation classes like `ElasticsearchRestTemplate` directly, you will need to adapt to these changes. + +Spring Data Elasticsearch also removes or replaces the use of classes from the `org.elasticsearch` packages in it's API classes and methods, only using them in the implementation where the access to Elasticsearch is implemented. +For the user that means, that some enum classes that were used are replaced by enums that live in `org.springframework.data.elasticsearch` with the same values, these are internally mapped onto the Elasticsearch ones. + +Places where classes are used that cannot easily be replaced, this usage is marked as deprecated, we are working on replacements. + +Check the sections on <> and <> for further details. +==== + [[elasticsearch-migration-guide-4.2-4.3.deprecations]] == Deprecations [[elasticsearch-migration-guide-4.2-4.3.breaking-changes]] == Breaking Changes +=== Removal of `org.elasticsearch` classes from the API. + +* In the `org.springframework.data.elasticsearch.annotations.CompletionContext` annotation the property `type()` has changed from `org.elasticsearch.search.suggest.completion.context.ContextMapping.Type` to `org.springframework.data.elasticsearch.annotations.CompletionContext.ContextMappingType`, the available enum values are the same. +* In the `org.springframework.data.elasticsearch.annotations.Document` annotation the `versionType()` property has changed to `org.springframework.data.elasticsearch.annotations.Document.VersionType`, the available enum values are the same. +* In the `org.springframework.data.elasticsearch.core.query.Query` interface the `searchType()` property has changed to `org.springframework.data.elasticsearch.core.query.Query.SearchType`, the available enum values are the same. +* In the `org.springframework.data.elasticsearch.core.query.Query` interface the return value of `timeout()` was changed to `java.time.Duration`. + === Handling of field and sourceFilter properties of Query -Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`. This was not correct, as these are different things for Elasticsearch. This has been corrected. As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`. +Up to version 4.2 the `fields` property of a `Query` was interpreted and added to the include list of the `sourceFilter`. +This was not correct, as these are different things for Elasticsearch. +This has been corrected. +As a consequence code might not work anymore that relies on using `fields` to specify which fields should be returned from the document's `_source' and should be changed to use the `sourceFilter`. + +=== search_type default value + +The default value for the `search_type` in Elasticsearch is `query_then_fetch`. +This now is also set as default value in the `Query` implementations, it was previously set to `dfs_query_then_fetch`. + +=== BulkOptions changes + +Some properties of the `org.springframework.data.elasticsearch.core.query.BulkOptions` class have changed their type: + +* the type of the `timeout` property has been changed to `java.time.Duration`. +* the type of the`refreshPolicy` property has been changed to `org.springframework.data.elasticsearch.core.RefreshPolicy`. + +=== IndicesOptions change + +Spring Data Elasticsearch now uses `org.springframework.data.elasticsearch.core.query.IndicesOptions` instead of `org.elasticsearch.action.support.IndicesOptions`. diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionContext.java b/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionContext.java index fff649dea..f510a3d9b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionContext.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionContext.java @@ -1,3 +1,18 @@ +/* + * Copyright 2019-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.data.elasticsearch.annotations; import java.lang.annotation.Documented; @@ -7,12 +22,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.elasticsearch.search.suggest.completion.context.ContextMapping; - /** * Based on reference doc - https://www.elastic.co/guide/en/elasticsearch/reference/current/suggester-context.html * * @author Robert Gruendler + * @author Peter-Josef Meisch */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @@ -22,9 +36,16 @@ String name(); - ContextMapping.Type type(); + ContextMappingType type(); String precision() default ""; String path() default ""; + + /** + * @since 4.3 + */ + enum ContextMappingType { + CATEGORY, GEO + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java index 4378f0bdf..154b1acd0 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Document.java @@ -21,7 +21,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.elasticsearch.index.VersionType; import org.springframework.data.annotation.Persistent; /** @@ -116,9 +115,15 @@ /** * Controls how Elasticsearch dynamically adds fields to the document. - * + * * @since 4.3 */ Dynamic dynamic() default Dynamic.INHERIT; + /** + * @since 4.3 + */ + enum VersionType { + INTERNAL, EXTERNAL, EXTERNAL_GTE + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java index 7c6779dff..184c3dcf8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfiguration.java @@ -27,6 +27,7 @@ import javax.net.ssl.SSLContext; import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback; +import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.web.reactive.function.client.WebClient; @@ -120,16 +121,16 @@ static ClientConfiguration create(InetSocketAddress socketAddress) { boolean useSsl(); /** - * Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured. + * Returns the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured. * - * @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if unconfigured. + * @return the {@link SSLContext} to use. Can be {@link Optional#empty()} if not configured. */ Optional getSslContext(); /** - * Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured. + * Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured. * - * @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if unconfigured. + * @return the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured. */ Optional getHostNameVerifier(); @@ -152,7 +153,7 @@ static ClientConfiguration create(InetSocketAddress socketAddress) { /** * Returns the path prefix that should be prepended to HTTP(s) requests for Elasticsearch behind a proxy. - * + * * @return the path prefix. * @since 4.0 */ @@ -161,7 +162,7 @@ static ClientConfiguration create(InetSocketAddress socketAddress) { /** * returns an optionally set proxy in the form host:port - * + * * @return the optional proxy * @since 4.0 */ @@ -173,11 +174,19 @@ static ClientConfiguration create(InetSocketAddress socketAddress) { Function getWebClientConfigurer(); /** - * @return the client configuration callback. + * @return the Rest Client configuration callback. * @since 4.2 + * @deprecated since 4.3 use {@link #getClientConfigurer()} */ + @Deprecated HttpClientConfigCallback getHttpClientConfigurer(); + /** + * @return the client configuration callback + * @since 4.3 + */ + ClientConfigurationCallback getClientConfigurer(); + /** * @return the supplier for custom headers. */ @@ -274,7 +283,7 @@ interface TerminalClientConfigurationBuilder { TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders); /** - * Configure the {@literal milliseconds} for the connect timeout. + * Configure the {@literal milliseconds} for the connect-timeout. * * @param millis the timeout to use. * @return the {@link TerminalClientConfigurationBuilder} @@ -327,7 +336,7 @@ default TerminalClientConfigurationBuilder withSocketTimeout(long millis) { /** * Configure the path prefix that will be prepended to any HTTP(s) requests - * + * * @param pathPrefix the pathPrefix. * @return the {@link TerminalClientConfigurationBuilder} * @since 4.0 @@ -342,21 +351,36 @@ default TerminalClientConfigurationBuilder withSocketTimeout(long millis) { /** * set customization hook in case of a reactive configuration - * + * * @param webClientConfigurer function to configure the WebClient * @return the {@link TerminalClientConfigurationBuilder}. + * @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with + * {@link ReactiveRestClients.WebClientConfigurationCallback} */ + @Deprecated TerminalClientConfigurationBuilder withWebClientConfigurer(Function webClientConfigurer); /** * Register a {HttpClientConfigCallback} to configure the non-reactive REST client. - * + * * @param httpClientConfigurer configuration callback, must not be null. * @return the {@link TerminalClientConfigurationBuilder}. * @since 4.2 + * @deprecated since 4.3, use {@link #withClientConfigurer(ClientConfigurationCallback)} with + * {@link RestClients.RestClientConfigurationCallback} */ + @Deprecated TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientConfigCallback httpClientConfigurer); + /** + * Register a {@link ClientConfigurationCallback} to configure the client. + * + * @param clientConfigurer configuration callback, must not be {@literal null}. + * @return the {@link TerminalClientConfigurationBuilder}. + * @since 4.3 + */ + TerminalClientConfigurationBuilder withClientConfigurer(ClientConfigurationCallback clientConfigurer); + /** * set a supplier for custom headers. This is invoked for every HTTP request to Elasticsearch to retrieve headers * that should be sent with the request. A common use case is passing in authentication headers that may change. @@ -377,4 +401,15 @@ default TerminalClientConfigurationBuilder withSocketTimeout(long millis) { */ ClientConfiguration build(); } + + /** + * Callback to be executed to configure a client. + * + * @param the type of the client configuration class. + * @since 4.3 + */ + @FunctionalInterface + interface ClientConfigurationCallback { + T configure(T clientConfigurer); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java index 2f8be5b39..dfbd24afe 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClientConfigurationBuilder.java @@ -31,6 +31,7 @@ import org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationBuilderWithRequiredEndpoint; import org.springframework.data.elasticsearch.client.ClientConfiguration.MaybeSecureClientConfigurationBuilder; import org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder; +import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -49,7 +50,7 @@ class ClientConfigurationBuilder implements ClientConfigurationBuilderWithRequiredEndpoint, MaybeSecureClientConfigurationBuilder { - private List hosts = new ArrayList<>(); + private final List hosts = new ArrayList<>(); private HttpHeaders headers = HttpHeaders.EMPTY; private boolean useSsl; private @Nullable SSLContext sslContext; @@ -62,7 +63,8 @@ class ClientConfigurationBuilder private @Nullable String proxy; private Function webClientConfigurer = Function.identity(); private Supplier headersSupplier = () -> HttpHeaders.EMPTY; - private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder; + @Deprecated private HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder; + private ClientConfiguration.ClientConfigurationCallback clientConfigurer = t -> t; /* * (non-Javadoc) @@ -206,6 +208,9 @@ public TerminalClientConfigurationBuilder withWebClientConfigurer( Assert.notNull(webClientConfigurer, "webClientConfigurer must not be null"); this.webClientConfigurer = webClientConfigurer; + // noinspection NullableProblems + this.clientConfigurer = (ReactiveRestClients.WebClientConfigurationCallback) webClientConfigurer::apply; + return this; } @@ -215,6 +220,19 @@ public TerminalClientConfigurationBuilder withHttpClientConfigurer(HttpClientCon Assert.notNull(httpClientConfigurer, "httpClientConfigurer must not be null"); this.httpClientConfigurer = httpClientConfigurer; + // noinspection NullableProblems + this.clientConfigurer = (RestClients.RestClientConfigurationCallback) httpClientConfigurer::customizeHttpClient; + + return this; + } + + @Override + public TerminalClientConfigurationBuilder withClientConfigurer( + ClientConfiguration.ClientConfigurationCallback clientConfigurer) { + + Assert.notNull(clientConfigurer, "clientConfigurer must not be null"); + + this.clientConfigurer = clientConfigurer; return this; } @@ -242,7 +260,7 @@ public ClientConfiguration build() { } return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix, - hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, headersSupplier); + hostnameVerifier, proxy, webClientConfigurer, httpClientConfigurer, clientConfigurer, headersSupplier); } private static InetSocketAddress parse(String hostAndPort) { diff --git a/src/main/java/org/springframework/data/elasticsearch/client/ClusterNodes.java b/src/main/java/org/springframework/data/elasticsearch/client/ClusterNodes.java index fdafbf96e..bc556ad34 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/ClusterNodes.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/ClusterNodes.java @@ -32,7 +32,10 @@ * * @author Oliver Gierke * @since 3.1 + * @deprecated only used in {@link TransportClientFactoryBean}. */ +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated class ClusterNodes implements Streamable { public static ClusterNodes DEFAULT = ClusterNodes.of("127.0.0.1:9300"); @@ -44,7 +47,7 @@ class ClusterNodes implements Streamable { /** * Creates a new {@link ClusterNodes} by parsing the given source. - * + * * @param source must not be {@literal null} or empty. */ private ClusterNodes(String source) { @@ -74,15 +77,14 @@ private ClusterNodes(String source) { /** * Creates a new {@link ClusterNodes} by parsing the given source. The expected format is a comma separated list of * host-port-combinations separated by a colon: {@code host:port,host:port,…}. - * + * * @param source must not be {@literal null} or empty. - * @return */ public static ClusterNodes of(String source) { return new ClusterNodes(source); } - /* + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ diff --git a/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java index 9a598739f..d64df7a5d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/DefaultClientConfiguration.java @@ -55,12 +55,13 @@ class DefaultClientConfiguration implements ClientConfiguration { private final Function webClientConfigurer; private final HttpClientConfigCallback httpClientConfigurer; private final Supplier headersSupplier; + private final ClientConfigurationCallback clientConfigurer; DefaultClientConfiguration(List hosts, HttpHeaders headers, boolean useSsl, @Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix, @Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy, Function webClientConfigurer, HttpClientConfigCallback httpClientConfigurer, - Supplier headersSupplier) { + ClientConfigurationCallback clientConfigurer, Supplier headersSupplier) { this.hosts = Collections.unmodifiableList(new ArrayList<>(hosts)); this.headers = new HttpHeaders(headers); @@ -73,6 +74,7 @@ class DefaultClientConfiguration implements ClientConfiguration { this.proxy = proxy; this.webClientConfigurer = webClientConfigurer; this.httpClientConfigurer = httpClientConfigurer; + this.clientConfigurer = clientConfigurer; this.headersSupplier = headersSupplier; } @@ -132,6 +134,12 @@ public HttpClientConfigCallback getHttpClientConfigurer() { return httpClientConfigurer; } + @SuppressWarnings("unchecked") + @Override + public ClientConfigurationCallback getClientConfigurer() { + return (ClientConfigurationCallback) clientConfigurer; + } + @Override public Supplier getHeadersSupplier() { return headersSupplier; diff --git a/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java b/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java index a4500f15b..85fa8ecae 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/RestClients.java @@ -36,6 +36,7 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.apache.http.message.BasicHeader; import org.apache.http.protocol.HttpContext; import org.elasticsearch.client.RestClient; @@ -119,7 +120,7 @@ public static ElasticsearchRestClient create(ClientConfiguration clientConfigura clientConfiguration.getProxy().map(HttpHost::create).ifPresent(clientBuilder::setProxy); - clientBuilder = clientConfiguration.getHttpClientConfigurer().customizeHttpClient(clientBuilder); + clientBuilder = clientConfiguration. getClientConfigurer().configure(clientBuilder); return clientBuilder; }); @@ -198,7 +199,7 @@ public void process(HttpRequest request, HttpContext context) throws IOException } ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), "", - () -> new String(buffer.toByteArray())); + buffer::toString); } else { ClientLogger.logRequest(logId, request.getRequestLine().getMethod(), request.getRequestLine().getUri(), ""); } @@ -213,7 +214,7 @@ public void process(HttpResponse response, HttpContext context) { /** * Interceptor to inject custom supplied headers. - * + * * @since 4.0 */ private static class CustomHeaderInjector implements HttpRequestInterceptor { @@ -233,4 +234,13 @@ public void process(HttpRequest request, HttpContext context) { } } } + + /** + * {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure + * the RestClient with a {@link HttpAsyncClientBuilder} + * + * @since 4.3 + */ + public interface RestClientConfigurationCallback + extends ClientConfiguration.ClientConfigurationCallback {} } 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 e91b4443e..6e14bd3e7 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 @@ -111,6 +111,7 @@ import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient.Indices; import org.springframework.data.elasticsearch.client.util.NamedXContents; import org.springframework.data.elasticsearch.client.util.ScrollState; +import org.springframework.data.elasticsearch.core.ResponseConverter; import org.springframework.data.elasticsearch.core.query.ByQueryResponse; import org.springframework.data.util.Lazy; import org.springframework.http.HttpHeaders; @@ -289,7 +290,7 @@ private static WebClientProvider getWebClientProvider(ClientConfiguration client provider = provider // .withDefaultHeaders(clientConfiguration.getDefaultHeaders()) // - .withWebClientConfigurer(clientConfiguration.getWebClientConfigurer()) // + .withWebClientConfigurer(clientConfiguration. getClientConfigurer()::configure) // .withRequestConfigurer(requestHeadersSpec -> requestHeadersSpec.headers(httpHeaders -> { HttpHeaders suppliedHeaders = clientConfiguration.getHeadersSupplier().get(); @@ -485,7 +486,7 @@ public Mono deleteBy(HttpHeaders headers, DeleteByQueryReq public Mono updateBy(HttpHeaders headers, UpdateByQueryRequest updateRequest) { return sendRequest(updateRequest, requestCreator.updateByQuery(), BulkByScrollResponse.class, headers) // .next() // - .map(ByQueryResponse::of); + .map(ResponseConverter::byQueryResponseOf); } @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveRestClients.java b/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveRestClients.java index 00a5cd6b9..d35112a1c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveRestClients.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/reactive/ReactiveRestClients.java @@ -17,6 +17,7 @@ import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.util.Assert; +import org.springframework.web.reactive.function.client.WebClient; /** * Utility class for common access to reactive Elasticsearch clients. {@link ReactiveRestClients} consolidates set up @@ -61,4 +62,12 @@ public static ReactiveElasticsearchClient create(ClientConfiguration clientConfi return DefaultReactiveElasticsearchClient.create(clientConfiguration, requestCreator); } + + /** + * {@link org.springframework.data.elasticsearch.client.ClientConfiguration.ClientConfigurationCallback} to configure + * the ReactiveElasticsearchClient with a {@link WebClient} + * + * @since 4.3 + */ + public interface WebClientConfigurationCallback extends ClientConfiguration.ClientConfigurationCallback {} } diff --git a/src/main/java/org/springframework/data/elasticsearch/client/util/ScrollState.java b/src/main/java/org/springframework/data/elasticsearch/client/util/ScrollState.java index ab7201a77..bf6e09763 100644 --- a/src/main/java/org/springframework/data/elasticsearch/client/util/ScrollState.java +++ b/src/main/java/org/springframework/data/elasticsearch/client/util/ScrollState.java @@ -21,13 +21,11 @@ import java.util.List; import java.util.Set; -import org.elasticsearch.action.search.SearchScrollRequest; -import org.elasticsearch.search.Scroll; import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** - * Mutable state object holding scrollId to be used for {@link SearchScrollRequest#scroll(Scroll)} + * Mutable state object holding scrollId to be used for scroll requests. * * @author Christoph Strobl * @author Peter-Josef Meisch diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchRestTransportTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchRestTransportTemplate.java new file mode 100644 index 000000000..6796706e9 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchRestTransportTemplate.java @@ -0,0 +1,208 @@ +/* + * Copyright 2021-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.elasticsearch.Version; +import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.search.MultiSearchRequest; +import org.elasticsearch.action.search.MultiSearchResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.springframework.data.elasticsearch.BulkFailureException; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; +import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; +import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; +import org.springframework.data.elasticsearch.core.query.Query; +import org.springframework.util.Assert; + +/** + * This class contains methods that are common the implementations derived from {@link AbstractElasticsearchTemplate} + * using either the {@link org.elasticsearch.client.transport.TransportClient} or the + * {@link org.elasticsearch.client.RestHighLevelClient} and that use Elasticsearch specific libraries. + *

+ * Note: Although this class is public, it is not considered to be part of the official Spring Data + * Elasticsearch API and so might change at any time. + * + * @author Peter-Josef Meisch + */ +public abstract class AbstractElasticsearchRestTransportTemplate extends AbstractElasticsearchTemplate { + + // region DocumentOperations + /** + * @param bulkResponse + * @return the list of the item id's + */ + protected List checkForBulkOperationFailure(BulkResponse bulkResponse) { + + if (bulkResponse.hasFailures()) { + Map failedDocuments = new HashMap<>(); + for (BulkItemResponse item : bulkResponse.getItems()) { + + if (item.isFailed()) + failedDocuments.put(item.getId(), item.getFailureMessage()); + } + throw new BulkFailureException( + "Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages [" + + failedDocuments + ']', + failedDocuments); + } + + return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> { + DocWriteResponse response = bulkItemResponse.getResponse(); + if (response != null) { + return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(), + response.getVersion()); + } else { + return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null); + } + + }).collect(Collectors.toList()); + } + + // endregion + + // region SearchOperations + protected SearchHits doSearch(MoreLikeThisQuery query, Class clazz, IndexCoordinates index) { + MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index); + return search( + new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), + clazz, index); + } + + @Override + public List> multiSearch(List queries, Class clazz, IndexCoordinates index) { + MultiSearchRequest request = new MultiSearchRequest(); + for (Query query : queries) { + request.add(requestFactory.searchRequest(query, clazz, index)); + } + + MultiSearchResponse.Item[] items = getMultiSearchResult(request); + + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); + List> res = new ArrayList<>(queries.size()); + int c = 0; + for (Query query : queries) { + res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse()))); + } + return res; + } + + @Override + public List> multiSearch(List queries, List> classes) { + + Assert.notNull(queries, "queries must not be null"); + Assert.notNull(classes, "classes must not be null"); + Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size"); + + MultiSearchRequest request = new MultiSearchRequest(); + Iterator> it = classes.iterator(); + for (Query query : queries) { + Class clazz = it.next(); + request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz))); + } + + MultiSearchResponse.Item[] items = getMultiSearchResult(request); + + List> res = new ArrayList<>(queries.size()); + int c = 0; + Iterator> it1 = classes.iterator(); + for (Query query : queries) { + Class entityClass = it1.next(); + + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(entityClass, + getIndexCoordinatesFor(entityClass)); + + SearchResponse response = items[c++].getResponse(); + res.add(callback.doWith(SearchDocumentResponse.from(response))); + } + return res; + } + + @Override + public List> multiSearch(List queries, List> classes, + IndexCoordinates index) { + + Assert.notNull(queries, "queries must not be null"); + Assert.notNull(classes, "classes must not be null"); + Assert.notNull(index, "index must not be null"); + Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size"); + + MultiSearchRequest request = new MultiSearchRequest(); + Iterator> it = classes.iterator(); + for (Query query : queries) { + request.add(requestFactory.searchRequest(query, it.next(), index)); + } + + MultiSearchResponse.Item[] items = getMultiSearchResult(request); + + List> res = new ArrayList<>(queries.size()); + int c = 0; + Iterator> it1 = classes.iterator(); + for (Query query : queries) { + Class entityClass = it1.next(); + + SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(entityClass, + index); + + SearchResponse response = items[c++].getResponse(); + res.add(callback.doWith(SearchDocumentResponse.from(response))); + } + return res; + } + + abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request); + + // endregion + + // region helper + @Override + public Query matchAllQuery() { + return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build(); + } + + @Override + public Query idsQuery(List ids) { + + Assert.notNull(ids, "ids must not be null"); + + return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {}))) + .build(); + } + + @Override + protected String getVendor() { + return "Elasticsearch"; + } + + @Override + protected String getRuntimeLibraryVersion() { + return Version.CURRENT.toString(); + } + + // endregion +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index f42cd0fec..d3381980c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -15,32 +15,19 @@ */ package org.springframework.data.elasticsearch.core; -import java.util.ArrayList; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.elasticsearch.action.DocWriteResponse; -import org.elasticsearch.action.bulk.BulkItemResponse; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.action.search.MultiSearchRequest; -import org.elasticsearch.action.search.MultiSearchResponse; import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.support.WriteRequest; -import org.elasticsearch.action.support.WriteRequestBuilder; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.index.query.MoreLikeThisQueryBuilder; import org.elasticsearch.search.suggest.SuggestBuilder; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.data.convert.EntityReader; -import org.springframework.data.elasticsearch.BulkFailureException; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.document.Document; @@ -57,7 +44,6 @@ import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import org.springframework.data.elasticsearch.core.query.MoreLikeThisQuery; -import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.core.query.UpdateQuery; @@ -73,7 +59,13 @@ import org.springframework.util.Assert; /** - * AbstractElasticsearchTemplate + * This class contains methods that are common to different implementations of the {@link ElasticsearchOperations} + * interface that use different clients, like TransportClient, RestHighLevelClient and the next Java client from + * Elasticsearch or some future implementation that might use an Opensearch client. This class must not contain imports + * or use classes that are specific to one of these implementations. + *

+ * Note: Although this class is public, it is not considered to be part of the official Spring Data + * Elasticsearch API and so might change at any time. * * @author Sascha Woo * @author Peter-Josef Meisch @@ -84,9 +76,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper @Nullable protected ElasticsearchConverter elasticsearchConverter; @Nullable protected RequestFactory requestFactory; - @Nullable private EntityOperations entityOperations; - @Nullable private EntityCallbacks entityCallbacks; - @Nullable private RefreshPolicy refreshPolicy; + @Nullable protected EntityOperations entityOperations; + @Nullable protected EntityCallbacks entityCallbacks; + @Nullable protected RefreshPolicy refreshPolicy; @Nullable protected RoutingResolver routingResolver; // region Initialization @@ -176,7 +168,7 @@ public RefreshPolicy getRefreshPolicy() { * @since 4.3 */ public void logVersions() { - VersionInfo.logVersions(getClusterVersion()); + VersionInfo.logVersions(getVendor(), getRuntimeLibraryVersion(), getClusterVersion()); } // endregion @@ -364,40 +356,6 @@ public List bulkOperation(List queries, BulkOptions public abstract List doBulkOperation(List queries, BulkOptions bulkOptions, IndexCoordinates index); - /** - * Pre process the write request before it is sent to the server, eg. by setting the - * {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable. - * - * @param request must not be {@literal null}. - * @param - * @return the processed {@link WriteRequest}. - */ - protected > R prepareWriteRequest(R request) { - - if (refreshPolicy == null) { - return request; - } - - return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy)); - } - - /** - * Pre process the write request before it is sent to the server, eg. by setting the - * {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable. - * - * @param requestBuilder must not be {@literal null}. - * @param - * @return the processed {@link WriteRequest}. - */ - protected > R prepareWriteRequestBuilder(R requestBuilder) { - - if (refreshPolicy == null) { - return requestBuilder; - } - - return requestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy)); - } - // endregion // region SearchOperations @@ -414,8 +372,7 @@ public SearchHitsIterator searchForStream(Query query, Class clazz) { @Override public SearchHitsIterator searchForStream(Query query, Class clazz, IndexCoordinates index) { - long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis(); - + long scrollTimeInMillis = Duration.ofMinutes(1).toMillis(); // noinspection ConstantConditions int maxCount = query.isLimiting() ? query.getMaxResults() : 0; @@ -436,98 +393,16 @@ public SearchHits search(MoreLikeThisQuery query, Class clazz, IndexCo Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery"); - MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index); - return search( - new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), - clazz, index); + return doSearch(query, clazz, index); } + protected abstract SearchHits doSearch(MoreLikeThisQuery query, Class clazz, IndexCoordinates index); + @Override public List> multiSearch(List queries, Class clazz) { return multiSearch(queries, clazz, getIndexCoordinatesFor(clazz)); } - @Override - public List> multiSearch(List queries, Class clazz, IndexCoordinates index) { - MultiSearchRequest request = new MultiSearchRequest(); - for (Query query : queries) { - request.add(requestFactory.searchRequest(query, clazz, index)); - } - - MultiSearchResponse.Item[] items = getMultiSearchResult(request); - - SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(clazz, index); - List> res = new ArrayList<>(queries.size()); - int c = 0; - for (Query query : queries) { - res.add(callback.doWith(SearchDocumentResponse.from(items[c++].getResponse()))); - } - return res; - } - - @Override - public List> multiSearch(List queries, List> classes) { - - Assert.notNull(queries, "queries must not be null"); - Assert.notNull(classes, "classes must not be null"); - Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size"); - - MultiSearchRequest request = new MultiSearchRequest(); - Iterator> it = classes.iterator(); - for (Query query : queries) { - Class clazz = it.next(); - request.add(requestFactory.searchRequest(query, clazz, getIndexCoordinatesFor(clazz))); - } - - MultiSearchResponse.Item[] items = getMultiSearchResult(request); - - List> res = new ArrayList<>(queries.size()); - int c = 0; - Iterator> it1 = classes.iterator(); - for (Query query : queries) { - Class entityClass = it1.next(); - - SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(entityClass, - getIndexCoordinatesFor(entityClass)); - - SearchResponse response = items[c++].getResponse(); - res.add(callback.doWith(SearchDocumentResponse.from(response))); - } - return res; - } - - @Override - public List> multiSearch(List queries, List> classes, - IndexCoordinates index) { - - Assert.notNull(queries, "queries must not be null"); - Assert.notNull(classes, "classes must not be null"); - Assert.notNull(index, "index must not be null"); - Assert.isTrue(queries.size() == classes.size(), "queries and classes must have the same size"); - - MultiSearchRequest request = new MultiSearchRequest(); - Iterator> it = classes.iterator(); - for (Query query : queries) { - request.add(requestFactory.searchRequest(query, it.next(), index)); - } - - MultiSearchResponse.Item[] items = getMultiSearchResult(request); - - List> res = new ArrayList<>(queries.size()); - int c = 0; - Iterator> it1 = classes.iterator(); - for (Query query : queries) { - Class entityClass = it1.next(); - - SearchDocumentResponseCallback> callback = new ReadSearchDocumentResponseCallback<>(entityClass, - index); - - SearchResponse response = items[c++].getResponse(); - res.add(callback.doWith(SearchDocumentResponse.from(response))); - } - return res; - } - @Override public SearchHits search(Query query, Class clazz) { return search(query, clazz, getIndexCoordinatesFor(clazz)); @@ -557,8 +432,6 @@ protected void searchScrollClear(String scrollId) { */ abstract protected void searchScrollClear(List scrollIds); - abstract protected MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest request); - @Override public SearchResponse suggest(SuggestBuilder suggestion, Class clazz) { return suggest(suggestion, getIndexCoordinatesFor(clazz)); @@ -600,37 +473,6 @@ public IndexCoordinates getIndexCoordinatesFor(Class clazz) { return getRequiredPersistentEntity(clazz).getIndexCoordinates(); } - /** - * @param bulkResponse - * @return the list of the item id's - */ - protected List checkForBulkOperationFailure(BulkResponse bulkResponse) { - - if (bulkResponse.hasFailures()) { - Map failedDocuments = new HashMap<>(); - for (BulkItemResponse item : bulkResponse.getItems()) { - - if (item.isFailed()) - failedDocuments.put(item.getId(), item.getFailureMessage()); - } - throw new BulkFailureException( - "Bulk operation has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages [" - + failedDocuments + ']', - failedDocuments); - } - - return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> { - DocWriteResponse response = bulkItemResponse.getResponse(); - if (response != null) { - return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(), - response.getVersion()); - } else { - return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null); - } - - }).collect(Collectors.toList()); - } - protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() @@ -749,6 +591,18 @@ private IndexQuery getIndexQuery(T entity) { @Nullable abstract protected String getClusterVersion(); + /** + * @return the vendor name of the used cluster and client library + * @since 4.3 + */ + abstract protected String getVendor(); + + /** + * @return the version of the used client runtime library. + * @since 4.3 + */ + abstract protected String getRuntimeLibraryVersion(); + // endregion // region Entity callbacks diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java index 95d761e6e..6706cf1b4 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractIndexTemplate.java @@ -17,14 +17,10 @@ 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.cluster.metadata.AliasMetadata; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.elasticsearch.UncategorizedElasticsearchException; @@ -48,8 +44,6 @@ */ abstract class AbstractIndexTemplate implements IndexOperations { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIndexTemplate.class); - protected final ElasticsearchConverter elasticsearchConverter; protected final RequestFactory requestFactory; @@ -175,8 +169,6 @@ public void refresh() { protected abstract void doRefresh(IndexCoordinates indexCoordinates); - protected abstract List doQueryForAlias(IndexCoordinates index); - @Override public Map> getAliases(String... aliasNames) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ActiveShardCount.java b/src/main/java/org/springframework/data/elasticsearch/core/ActiveShardCount.java new file mode 100644 index 000000000..60cf7b666 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/ActiveShardCount.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core; + +/** + * Class corresponding to the Elasticsearch class, but in the org.springframework.data.elasticsearch package + * + * @author Peter-Josef Meisch + */ +public class ActiveShardCount { + private static final int ACTIVE_SHARD_COUNT_DEFAULT = -2; + private static final int ALL_ACTIVE_SHARDS = -1; + + public static final ActiveShardCount DEFAULT = new ActiveShardCount(ACTIVE_SHARD_COUNT_DEFAULT); + public static final ActiveShardCount ALL = new ActiveShardCount(ALL_ACTIVE_SHARDS); + public static final ActiveShardCount NONE = new ActiveShardCount(0); + public static final ActiveShardCount ONE = new ActiveShardCount(1); + + private final int value; + + public ActiveShardCount(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 6e9d2eb78..bac58dc81 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -33,6 +33,7 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; @@ -92,7 +93,7 @@ * @author Massimiliano Poggi * @author Farid Faoudi */ -public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate { +public class ElasticsearchRestTemplate extends AbstractElasticsearchRestTransportTemplate { private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchRestTemplate.class); @@ -223,7 +224,8 @@ protected String doDelete(String id, @Nullable String routing, IndexCoordinates @Override public ByQueryResponse delete(Query query, Class clazz, IndexCoordinates index) { DeleteByQueryRequest deleteByQueryRequest = requestFactory.deleteByQueryRequest(query, clazz, index); - return ByQueryResponse.of(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT))); + return ResponseConverter + .byQueryResponseOf(execute(client -> client.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT))); } @Override @@ -261,7 +263,7 @@ public ByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) final BulkByScrollResponse bulkByScrollResponse = execute( client -> client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT)); - return ByQueryResponse.of(bulkByScrollResponse); + return ResponseConverter.byQueryResponseOf(bulkByScrollResponse); } public List doBulkOperation(List queries, BulkOptions bulkOptions, @@ -272,6 +274,24 @@ public List doBulkOperation(List queries, BulkOptio updateIndexedObjectsWithQueries(queries, indexedObjectInformationList); return indexedObjectInformationList; } + + /** + * Pre process the write request before it is sent to the server, eg. by setting the + * {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable. + * + * @param request must not be {@literal null}. + * @param + * @return the processed {@link WriteRequest}. + */ + protected > R prepareWriteRequest(R request) { + + if (refreshPolicy == null) { + return request; + } + + return request.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy)); + } + // endregion // region SearchOperations diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 0ae4b14da..64a5bd5ab 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -36,6 +36,8 @@ import org.elasticsearch.action.search.MultiSearchResponse; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.action.support.WriteRequestBuilder; import org.elasticsearch.action.update.UpdateRequestBuilder; import org.elasticsearch.client.Client; import org.elasticsearch.common.unit.TimeValue; @@ -90,7 +92,7 @@ * @deprecated as of 4.0 */ @Deprecated -public class ElasticsearchTemplate extends AbstractElasticsearchTemplate { +public class ElasticsearchTemplate extends AbstractElasticsearchRestTransportTemplate { private static final Logger QUERY_LOGGER = LoggerFactory .getLogger("org.springframework.data.elasticsearch.core.QUERY"); private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class); @@ -246,7 +248,8 @@ protected String doDelete(String id, @Nullable String routing, IndexCoordinates @Override public ByQueryResponse delete(Query query, Class clazz, IndexCoordinates index) { - return ByQueryResponse.of(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get()); + return ResponseConverter + .byQueryResponseOf(requestFactory.deleteByQueryRequestBuilder(client, query, clazz, index).get()); } @Override @@ -288,7 +291,7 @@ public ByQueryResponse updateByQuery(UpdateQuery query, IndexCoordinates index) // UpdateByQueryRequestBuilder has not parameters to set a routing value final BulkByScrollResponse bulkByScrollResponse = updateByQueryRequestBuilder.execute().actionGet(); - return ByQueryResponse.of(bulkByScrollResponse); + return ResponseConverter.byQueryResponseOf(bulkByScrollResponse); } public List doBulkOperation(List queries, BulkOptions bulkOptions, @@ -309,6 +312,24 @@ public List doBulkOperation(List queries, BulkOptio return allIndexedObjectInformations; } + + /** + * Pre process the write request before it is sent to the server, eg. by setting the + * {@link WriteRequest#setRefreshPolicy(String) refresh policy} if applicable. + * + * @param requestBuilder must not be {@literal null}. + * @param + * @return the processed {@link WriteRequest}. + */ + protected > R prepareWriteRequestBuilder(R requestBuilder) { + + if (refreshPolicy == null) { + return requestBuilder; + } + + return requestBuilder.setRefreshPolicy(RequestFactory.toElasticsearchRefreshPolicy(refreshPolicy)); + } + // endregion // region SearchOperations diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index 02516564a..2f872b7a1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; +import org.elasticsearch.Version; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkRequest; @@ -39,6 +40,7 @@ import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.index.get.GetResult; +import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.reindex.BulkByScrollResponse; import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.UpdateByQueryRequest; @@ -75,6 +77,7 @@ import org.springframework.data.elasticsearch.core.query.BulkOptions; import org.springframework.data.elasticsearch.core.query.ByQueryResponse; import org.springframework.data.elasticsearch.core.query.IndexQuery; +import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.core.query.UpdateQuery; @@ -164,7 +167,11 @@ private ReactiveElasticsearchTemplate copy() { * @since 4.3 */ public Mono logVersions() { - return getClusterVersion().doOnNext(VersionInfo::logVersions).then(); + return getVendor() // + .doOnNext(vendor -> getRuntimeLibraryVersion() // + .doOnNext(runtimeLibraryVersion -> getClusterVersion() // + .doOnNext(clusterVersion -> VersionInfo.logVersions(vendor, runtimeLibraryVersion, clusterVersion)))) // + .then(); // } @Override @@ -557,7 +564,7 @@ public Mono delete(Query query, Class entityType, IndexCoord Assert.notNull(query, "Query must not be null!"); - return doDeleteBy(query, entityType, index).map(ByQueryResponse::of); + return doDeleteBy(query, entityType, index).map(ResponseConverter::byQueryResponseOf); } @Override @@ -939,6 +946,35 @@ protected Mono getClusterVersion() { return Mono.empty(); } + /** + * @return the vendor name of the used cluster and client library + * @since 4.3 + */ + protected Mono getVendor() { + return Mono.just("Elasticsearch"); + } + + /** + * @return the version of the used client runtime library. + * @since 4.3 + */ + protected Mono getRuntimeLibraryVersion() { + return Mono.just(Version.CURRENT.toString()); + } + + @Override + public Query matchAllQuery() { + return new NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery()).build(); + } + + @Override + public Query idsQuery(List ids) { + + Assert.notNull(ids, "ids must not be null"); + + return new NativeSearchQueryBuilder().withQuery(QueryBuilders.idsQuery().addIds(ids.toArray(new String[] {}))) + .build(); + } // endregion @Override diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveSearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveSearchOperations.java index f3398f79c..ed4a0c507 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveSearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveSearchOperations.java @@ -18,14 +18,14 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import org.elasticsearch.index.query.QueryBuilders; +import java.util.List; + import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.suggest.Suggest; import org.elasticsearch.search.suggest.SuggestBuilder; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.Query; -import org.springframework.data.elasticsearch.core.query.StringQuery; /** * The reactive operations for the @@ -45,7 +45,7 @@ public interface ReactiveSearchOperations { * @return a {@link Mono} emitting the nr of matching documents. */ default Mono count(Class entityType) { - return count(new StringQuery(QueryBuilders.matchAllQuery().toString()), entityType); + return count(matchAllQuery(), entityType); } /** @@ -215,4 +215,25 @@ default Mono> searchForPage(Query query, Class entityType, * @return the suggest response */ Flux suggest(SuggestBuilder suggestion, IndexCoordinates index); + + // region helper + /** + * Creates a {@link Query} to find all documents. Must be implemented by the concrete implementations to provide an + * appropriate query using the respective client. + * + * @return a query to find all documents + * @since 4.3 + */ + Query matchAllQuery(); + + /** + * Creates a {@link Query} to find get all documents with given ids. Must be implemented by the concrete + * implementations to provide an appropriate query using the respective client. + * + * @param ids the list of ids must not be {@literal null} + * @return query returning the documents with the given ids + * @since 4.3 + */ + Query idsQuery(List ids); + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java index 6981d5905..b2cd02595 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RequestFactory.java @@ -18,13 +18,17 @@ import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.util.CollectionUtils.*; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.admin.indices.alias.Alias; @@ -50,6 +54,7 @@ import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequestBuilder; +import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.update.UpdateRequest; @@ -223,15 +228,15 @@ public BulkRequest bulkRequest(List queries, BulkOptions bulkOptions, IndexCo BulkRequest bulkRequest = new BulkRequest(); if (bulkOptions.getTimeout() != null) { - bulkRequest.timeout(bulkOptions.getTimeout()); + bulkRequest.timeout(TimeValue.timeValueMillis(bulkOptions.getTimeout().toMillis())); } if (bulkOptions.getRefreshPolicy() != null) { - bulkRequest.setRefreshPolicy(bulkOptions.getRefreshPolicy()); + bulkRequest.setRefreshPolicy(toElasticsearchRefreshPolicy(bulkOptions.getRefreshPolicy())); } if (bulkOptions.getWaitForActiveShards() != null) { - bulkRequest.waitForActiveShards(bulkOptions.getWaitForActiveShards()); + bulkRequest.waitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue())); } if (bulkOptions.getPipeline() != null) { @@ -258,15 +263,15 @@ public BulkRequestBuilder bulkRequestBuilder(Client client, List queries, Bul BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); if (bulkOptions.getTimeout() != null) { - bulkRequestBuilder.setTimeout(bulkOptions.getTimeout()); + bulkRequestBuilder.setTimeout(TimeValue.timeValueMillis(bulkOptions.getTimeout().toMillis())); } if (bulkOptions.getRefreshPolicy() != null) { - bulkRequestBuilder.setRefreshPolicy(bulkOptions.getRefreshPolicy()); + bulkRequestBuilder.setRefreshPolicy(toElasticsearchRefreshPolicy(bulkOptions.getRefreshPolicy())); } if (bulkOptions.getWaitForActiveShards() != null) { - bulkRequestBuilder.setWaitForActiveShards(bulkOptions.getWaitForActiveShards()); + bulkRequestBuilder.setWaitForActiveShards(ActiveShardCount.from(bulkOptions.getWaitForActiveShards().getValue())); } if (bulkOptions.getPipeline() != null) { @@ -802,7 +807,10 @@ public IndexRequestBuilder indexRequestBuilder(Client client, IndexQuery query, // region search @Nullable public HighlightBuilder highlightBuilder(Query query) { - HighlightBuilder highlightBuilder = query.getHighlightQuery().map(HighlightQuery::getHighlightBuilder).orElse(null); + HighlightBuilder highlightBuilder = query.getHighlightQuery() + .map(highlightQuery -> new HighlightQueryBuilder(elasticsearchConverter.getMappingContext()) + .getHighlightBuilder(highlightQuery.getHighlight(), highlightQuery.getType())) + .orElse(null); if (highlightBuilder == null) { @@ -954,7 +962,7 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz } if (query.getIndicesOptions() != null) { - request.indicesOptions(query.getIndicesOptions()); + request.indicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions())); } if (query.isLimiting()) { @@ -970,7 +978,7 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz request.preference(query.getPreference()); } - request.searchType(query.getSearchType()); + request.searchType(SearchType.fromString(query.getSearchType().name().toLowerCase())); prepareSort(query, sourceBuilder, getPersistentEntity(clazz)); @@ -994,9 +1002,9 @@ private SearchRequest prepareSearchRequest(Query query, @Nullable Class clazz request.routing(query.getRoute()); } - TimeValue timeout = query.getTimeout(); + Duration timeout = query.getTimeout(); if (timeout != null) { - sourceBuilder.timeout(timeout); + sourceBuilder.timeout(new TimeValue(timeout.toMillis())); } sourceBuilder.explain(query.getExplain()); @@ -1023,7 +1031,7 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli Assert.notEmpty(indexNames, "No index defined for Query"); SearchRequestBuilder searchRequestBuilder = client.prepareSearch(indexNames) // - .setSearchType(query.getSearchType()) // + .setSearchType(SearchType.fromString(query.getSearchType().name().toLowerCase())) // .setVersion(true) // .setTrackScores(query.getTrackScores()); if (hasSeqNoPrimaryTermProperty(clazz)) { @@ -1048,7 +1056,7 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli } if (query.getIndicesOptions() != null) { - searchRequestBuilder.setIndicesOptions(query.getIndicesOptions()); + searchRequestBuilder.setIndicesOptions(toElasticsearchIndicesOptions(query.getIndicesOptions())); } if (query.isLimiting()) { @@ -1086,9 +1094,9 @@ private SearchRequestBuilder prepareSearchRequestBuilder(Query query, Client cli searchRequestBuilder.setRouting(query.getRoute()); } - TimeValue timeout = query.getTimeout(); + Duration timeout = query.getTimeout(); if (timeout != null) { - searchRequestBuilder.setTimeout(timeout); + searchRequestBuilder.setTimeout(new TimeValue(timeout.toMillis())); } searchRequestBuilder.setExplain(query.getExplain()); @@ -1429,7 +1437,7 @@ public UpdateByQueryRequest updateByQueryRequest(UpdateQuery query, IndexCoordin updateByQueryRequest.setQuery(getQuery(queryQuery)); if (queryQuery.getIndicesOptions() != null) { - updateByQueryRequest.setIndicesOptions(queryQuery.getIndicesOptions()); + updateByQueryRequest.setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions())); } if (queryQuery.getScrollTime() != null) { @@ -1504,7 +1512,8 @@ public UpdateByQueryRequestBuilder updateByQueryRequestBuilder(Client client, Up updateByQueryRequestBuilder.filter(getQuery(queryQuery)); if (queryQuery.getIndicesOptions() != null) { - updateByQueryRequestBuilder.source().setIndicesOptions(queryQuery.getIndicesOptions()); + updateByQueryRequestBuilder.source() + .setIndicesOptions(toElasticsearchIndicesOptions(queryQuery.getIndicesOptions())); } if (queryQuery.getScrollTime() != null) { @@ -1618,6 +1627,21 @@ private FetchSourceContext getFetchSourceContext(Query searchQuery) { return null; } + public org.elasticsearch.action.support.IndicesOptions toElasticsearchIndicesOptions(IndicesOptions indicesOptions) { + + Assert.notNull(indicesOptions, "indicesOptions must not be null"); + + Set options = indicesOptions.getOptions().stream() + .map(it -> org.elasticsearch.action.support.IndicesOptions.Option.valueOf(it.name().toUpperCase())) + .collect(Collectors.toSet()); + + Set wildcardStates = indicesOptions + .getExpandWildcards().stream() + .map(it -> org.elasticsearch.action.support.IndicesOptions.WildcardStates.valueOf(it.name().toUpperCase())) + .collect(Collectors.toSet()); + + return new org.elasticsearch.action.support.IndicesOptions(EnumSet.copyOf(options), EnumSet.copyOf(wildcardStates)); + } // endregion @Nullable @@ -1656,7 +1680,12 @@ private VersionType retrieveVersionTypeFromPersistentEntity(@Nullable Class c VersionType versionType = null; if (persistentEntity != null) { - versionType = persistentEntity.getVersionType(); + org.springframework.data.elasticsearch.annotations.Document.VersionType entityVersionType = persistentEntity + .getVersionType(); + + if (entityVersionType != null) { + versionType = VersionType.fromString(entityVersionType.name().toLowerCase()); + } } return versionType != null ? versionType : VersionType.EXTERNAL; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java index 1b8dfb73b..7d8da4053 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ResponseConverter.java @@ -28,6 +28,7 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.get.MultiGetItemResponse; import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.client.indices.GetIndexResponse; @@ -37,11 +38,14 @@ import org.elasticsearch.cluster.metadata.MappingMetadata; import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.elasticsearch.index.reindex.ScrollableHitSource; import org.springframework.data.elasticsearch.core.cluster.ClusterHealth; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.index.AliasData; import org.springframework.data.elasticsearch.core.index.Settings; import org.springframework.data.elasticsearch.core.index.TemplateData; +import org.springframework.data.elasticsearch.core.query.ByQueryResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -313,4 +317,71 @@ public static ClusterHealth clusterHealth(ClusterHealthResponse clusterHealthRes } // endregion + + // region byQueryResponse + public static ByQueryResponse byQueryResponseOf(BulkByScrollResponse bulkByScrollResponse) { + final List failures = bulkByScrollResponse.getBulkFailures() // + .stream() // + .map(ResponseConverter::byQueryResponseFailureOf) // + .collect(Collectors.toList()); // + + final List searchFailures = bulkByScrollResponse.getSearchFailures() // + .stream() // + .map(ResponseConverter::byQueryResponseSearchFailureOf) // + .collect(Collectors.toList());// + + return ByQueryResponse.builder() // + .withTook(bulkByScrollResponse.getTook().getMillis()) // + .withTimedOut(bulkByScrollResponse.isTimedOut()) // + .withTotal(bulkByScrollResponse.getTotal()) // + .withUpdated(bulkByScrollResponse.getUpdated()) // + .withDeleted(bulkByScrollResponse.getDeleted()) // + .withBatches(bulkByScrollResponse.getBatches()) // + .withVersionConflicts(bulkByScrollResponse.getVersionConflicts()) // + .withNoops(bulkByScrollResponse.getNoops()) // + .withBulkRetries(bulkByScrollResponse.getBulkRetries()) // + .withSearchRetries(bulkByScrollResponse.getSearchRetries()) // + .withReasonCancelled(bulkByScrollResponse.getReasonCancelled()) // + .withFailures(failures) // + .withSearchFailure(searchFailures) // + .build(); // + } + + /** + * Create a new {@link ByQueryResponse.Failure} from {@link BulkItemResponse.Failure} + * + * @param failure {@link BulkItemResponse.Failure} to translate + * @return a new {@link ByQueryResponse.Failure} + */ + public static ByQueryResponse.Failure byQueryResponseFailureOf(BulkItemResponse.Failure failure) { + return ByQueryResponse.Failure.builder() // + .withIndex(failure.getIndex()) // + .withType(failure.getType()) // + .withId(failure.getId()) // + .withStatus(failure.getStatus().getStatus()) // + .withAborted(failure.isAborted()) // + .withCause(failure.getCause()) // + .withSeqNo(failure.getSeqNo()) // + .withTerm(failure.getTerm()) // + .build(); // + } + + /** + * Create a new {@link ByQueryResponse.SearchFailure} from {@link ScrollableHitSource.SearchFailure} + * + * @param searchFailure {@link ScrollableHitSource.SearchFailure} to translate + * @return a new {@link ByQueryResponse.SearchFailure} + */ + public static ByQueryResponse.SearchFailure byQueryResponseSearchFailureOf( + ScrollableHitSource.SearchFailure searchFailure) { + return ByQueryResponse.SearchFailure.builder() // + .withReason(searchFailure.getReason()) // + .withIndex(searchFailure.getIndex()) // + .withNodeId(searchFailure.getNodeId()) // + .withShardId(searchFailure.getShardId()) // + .withStatus(searchFailure.getStatus().getStatus()) // + .build(); // + } + + // endregion } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java index 04a4382dd..23df5306f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/RestIndexTemplate.java @@ -15,7 +15,6 @@ */ package org.springframework.data.elasticsearch.core; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -28,7 +27,6 @@ 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; @@ -39,7 +37,6 @@ 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.cluster.metadata.MappingMetadata; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -140,19 +137,6 @@ protected Map doGetMapping(IndexCoordinates index) { }); } - @Override - protected List doQueryForAlias(IndexCoordinates index) { - - GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index); - - return restTemplate.execute(client -> { - GetAliasesResponse alias = client.indices().getAlias(getAliasesRequest, RequestOptions.DEFAULT); - // we only return data for the first index name that was requested (always have done so) - String index1 = getAliasesRequest.indices()[0]; - return new ArrayList<>(alias.getAliases().get(index1)); - }); - } - @Override protected Map> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java index 2de504a38..e6cb50a16 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/SearchOperations.java @@ -220,4 +220,23 @@ default SearchHit searchOne(Query query, Class clazz, IndexCoordinates * are completed. */ SearchHitsIterator searchForStream(Query query, Class clazz, IndexCoordinates index); + + /** + * Creates a {@link Query} to get all documents. Must be implemented by the concrete implementations to provide an + * appropriate query using the respective client. + * + * @return a query to find all documents + * @since 4.3 + */ + Query matchAllQuery(); + + /** + * Creates a {@link Query} to find get all documents with given ids. Must be implemented by the concrete + * implementations to provide an appropriate query using the respective client. + * + * @param ids the list of ids must not be {@literal null} + * @return query returning the documents with the given ids + * @since 4.3 + */ + Query idsQuery(List ids); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java index 753221bf7..a08989776 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/TransportIndexTemplate.java @@ -75,14 +75,13 @@ class TransportIndexTemplate extends AbstractIndexTemplate implements IndexOpera private final Client client; - public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, - Class boundClass) { + public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, Class boundClass) { super(elasticsearchConverter, boundClass); this.client = client; } public TransportIndexTemplate(Client client, ElasticsearchConverter elasticsearchConverter, - IndexCoordinates boundIndex) { + IndexCoordinates boundIndex) { super(elasticsearchConverter, boundIndex); this.client = client; } @@ -144,13 +143,6 @@ protected Map doGetMapping(IndexCoordinates index) { return mappings.iterator().next().value.get(IndexCoordinates.TYPE).getSourceAsMap(); } - @Override - protected List doQueryForAlias(IndexCoordinates index) { - - GetAliasesRequest getAliasesRequest = requestFactory.getAliasesRequest(index); - return client.admin().indices().getAliases(getAliasesRequest).actionGet().getAliases().get(index.getIndexName()); - } - @Override protected Map> doGetAliases(@Nullable String[] aliasNames, @Nullable String[] indexNames) { diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java index 35aefa1c0..c79061312 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/GeoShapeMappingParameters.java @@ -17,11 +17,12 @@ import java.io.IOException; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.springframework.data.elasticsearch.annotations.GeoShapeField; import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import com.fasterxml.jackson.databind.node.ObjectNode; + /** * @author Peter-Josef Meisch */ @@ -41,7 +42,7 @@ final class GeoShapeMappingParameters { /** * Creates a GeoShapeMappingParameters from the given annotation. - * + * * @param annotation if null, default values are set in the returned object * @return a parameters object */ @@ -63,27 +64,42 @@ private GeoShapeMappingParameters(boolean coerce, boolean ignoreMalformed, boole this.orientation = orientation; } - public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException { + public boolean isCoerce() { + return coerce; + } + + public boolean isIgnoreMalformed() { + return ignoreMalformed; + } + + public boolean isIgnoreZValue() { + return ignoreZValue; + } + + public GeoShapeField.Orientation getOrientation() { + return orientation; + } - Assert.notNull(builder, "builder must ot be null"); + public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException { + + Assert.notNull(objectNode, "objectNode must not be null"); if (coerce) { - builder.field(FIELD_PARAM_COERCE, coerce); + objectNode.put(FIELD_PARAM_COERCE, coerce); } if (ignoreMalformed) { - builder.field(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed); + objectNode.put(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed); } if (!ignoreZValue) { - builder.field(FIELD_PARAM_IGNORE_Z_VALUE, ignoreZValue); + objectNode.put(FIELD_PARAM_IGNORE_Z_VALUE, ignoreZValue); } if (orientation != GeoShapeField.Orientation.ccw) { - builder.field(FIELD_PARAM_ORIENTATION, orientation.name()); + objectNode.put(FIELD_PARAM_ORIENTATION, orientation.name()); } - builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_SHAPE); - + objectNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_SHAPE); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java index 981b7ec52..7d4cfc4fb 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java @@ -15,18 +15,16 @@ */ package org.springframework.data.elasticsearch.core.index; -import static org.elasticsearch.common.xcontent.XContentFactory.*; import static org.springframework.data.elasticsearch.core.index.MappingParameters.*; import static org.springframework.util.StringUtils.*; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.annotation.Annotation; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.Iterator; +import java.util.stream.Collectors; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; @@ -35,16 +33,22 @@ import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.ResourceUtil; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; +import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fasterxml.jackson.databind.util.RawValue; /** * @author Rizwan Idrees @@ -75,6 +79,7 @@ public class MappingBuilder { private static final String FIELD_CONTEXT_PATH = "path"; private static final String FIELD_CONTEXT_PRECISION = "precision"; private static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates"; + private static final String FIELD_INCLUDE_IN_PARENT = "include_in_parent"; private static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators"; private static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments"; @@ -86,6 +91,7 @@ public class MappingBuilder { private static final String TYPE_DYNAMIC = "dynamic"; private static final String TYPE_VALUE_KEYWORD = "keyword"; private static final String TYPE_VALUE_GEO_POINT = "geo_point"; + private static final String TYPE_VALUE_GEO_SHAPE = "geo_shape"; private static final String TYPE_VALUE_JOIN = "join"; private static final String TYPE_VALUE_COMPLETION = "completion"; @@ -98,6 +104,7 @@ public class MappingBuilder { private static final String RUNTIME = "runtime"; protected final ElasticsearchConverter elasticsearchConverter; + private final ObjectMapper objectMapper = new ObjectMapper(); private boolean writeTypeHints = true; @@ -126,61 +133,58 @@ protected String buildPropertyMapping(ElasticsearchPersistentEntity entity, writeTypeHints = entity.writeTypeHints(); - XContentBuilder builder = jsonBuilder().startObject(); + ObjectNode objectNode = objectMapper.createObjectNode(); // Dynamic templates - addDynamicTemplatesMapping(builder, entity); + addDynamicTemplatesMapping(objectNode, entity); - mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class), + mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class), runtimeFields); - builder.endObject() // root object - .close(); - - return builder.getOutputStream().toString(); + return objectMapper.writer().writeValueAsString(objectNode); } catch (IOException e) { throw new MappingException("could not build mapping", e); } } - private void writeTypeHintMapping(XContentBuilder builder) throws IOException { + private void writeTypeHintMapping(ObjectNode propertiesNode) throws IOException { if (writeTypeHints) { - builder.startObject(TYPEHINT_PROPERTY) // - .field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // - .field(FIELD_PARAM_INDEX, false) // - .field(FIELD_PARAM_DOC_VALUES, false) // - .endObject(); + propertiesNode.set(TYPEHINT_PROPERTY, objectMapper.createObjectNode() // + .put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // + .put(FIELD_PARAM_INDEX, false) // + .put(FIELD_PARAM_DOC_VALUES, false)); } } - private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, - boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, - @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping, - @Nullable org.springframework.data.elasticsearch.core.document.Document runtimeFields) throws IOException { + private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject, + String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, + @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping, @Nullable Document runtimeFields) + throws IOException { if (entity != null && entity.isAnnotationPresent(Mapping.class)) { Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class); if (!mappingAnnotation.enabled()) { - builder.field(MAPPING_ENABLED, false); + objectNode.put(MAPPING_ENABLED, false); return; } if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) { - builder.field(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name())); + objectNode.put(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name())); } if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) { - builder.field(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name())); + objectNode.put(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name())); } if (mappingAnnotation.dynamicDateFormats().length > 0) { - builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats()); + objectNode.putArray(DYNAMIC_DATE_FORMATS).addAll( + Arrays.stream(mappingAnnotation.dynamicDateFormats()).map(TextNode::valueOf).collect(Collectors.toList())); } if (runtimeFields != null) { - builder.field(RUNTIME, runtimeFields); + objectNode.set(RUNTIME, objectMapper.convertValue(runtimeFields, JsonNode.class)); } } @@ -189,23 +193,29 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten String type = nestedOrObjectField ? fieldType.toString().toLowerCase() : FieldType.Object.toString().toLowerCase(); - builder.startObject(nestedObjectFieldName).field(FIELD_PARAM_TYPE, type); + + ObjectNode nestedObjectNode = objectMapper.createObjectNode(); + nestedObjectNode.put(FIELD_PARAM_TYPE, type); if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null && parentFieldAnnotation.includeInParent()) { - builder.field("include_in_parent", true); + nestedObjectNode.put(FIELD_INCLUDE_IN_PARENT, true); } + + objectNode.set(nestedObjectFieldName, nestedObjectNode); + // now go on with the nested one + objectNode = nestedObjectNode; } if (entity != null && entity.dynamic() != Dynamic.INHERIT) { - builder.field(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase()); + objectNode.put(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase()); } else if (dynamicMapping != null) { - builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + objectNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); } - builder.startObject(FIELD_PROPERTIES); + ObjectNode propertiesNode = objectNode.putObject(FIELD_PROPERTIES); - writeTypeHintMapping(builder); + writeTypeHintMapping(propertiesNode); if (entity != null) { entity.doWithProperties((PropertyHandler) property -> { @@ -223,19 +233,12 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten return; } - buildPropertyMapping(builder, isRootObject, property); + buildPropertyMapping(propertiesNode, isRootObject, property); } catch (IOException e) { logger.warn("error mapping property with name {}", property.getName(), e); } }); } - - builder.endObject(); - - if (writeNestedProperties) { - builder.endObject(); - } - } @Nullable @@ -256,7 +259,7 @@ private org.springframework.data.elasticsearch.core.document.Document getRuntime return null; } - private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, + private void buildPropertyMapping(ObjectNode propertiesNode, boolean isRootObject, ElasticsearchPersistentProperty property) throws IOException { if (property.isAnnotationPresent(Mapping.class)) { @@ -270,27 +273,28 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, ClassPathResource mappings = new ClassPathResource(mappingPath); if (mappings.exists()) { - builder.rawField(property.getFieldName(), mappings.getInputStream(), XContentType.JSON); + propertiesNode.putRawValue(property.getFieldName(), + new RawValue(StreamUtils.copyToString(mappings.getInputStream(), Charset.defaultCharset()))); return; } } } else { - applyDisabledPropertyMapping(builder, property); + applyDisabledPropertyMapping(propertiesNode, property); return; } } if (property.isGeoPointProperty()) { - applyGeoPointFieldMapping(builder, property); + applyGeoPointFieldMapping(propertiesNode, property); return; } if (property.isGeoShapeProperty()) { - applyGeoShapeMapping(builder, property); + applyGeoShapeMapping(propertiesNode, property); } if (property.isJoinFieldProperty()) { - addJoinFieldMapping(builder, property); + addJoinFieldMapping(propertiesNode, property); } Field fieldAnnotation = property.findAnnotation(Field.class); @@ -310,7 +314,7 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, ? elasticsearchConverter.getMappingContext().getPersistentEntity(iterator.next()) : null; - mapEntity(builder, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(), + mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(), fieldAnnotation, dynamicMapping, null); return; } @@ -320,15 +324,15 @@ private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject, if (isCompletionProperty) { CompletionField completionField = property.findAnnotation(CompletionField.class); - applyCompletionFieldMapping(builder, property, completionField); + applyCompletionFieldMapping(propertiesNode, property, completionField); } if (isRootObject && fieldAnnotation != null && property.isIdProperty()) { - applyDefaultIdFieldMapping(builder, property); + applyDefaultIdFieldMapping(propertiesNode, property); } else if (multiField != null) { - addMultiFieldMapping(builder, property, multiField, isNestedOrObjectProperty, dynamicMapping); + addMultiFieldMapping(propertiesNode, property, multiField, isNestedOrObjectProperty, dynamicMapping); } else if (fieldAnnotation != null) { - addSingleFieldMapping(builder, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping); + addSingleFieldMapping(propertiesNode, property, fieldAnnotation, isNestedOrObjectProperty, dynamicMapping); } } @@ -339,73 +343,70 @@ private boolean hasRelevantAnnotation(ElasticsearchPersistentProperty property) || property.findAnnotation(CompletionField.class) != null; } - private void applyGeoPointFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) + private void applyGeoPointFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) throws IOException { - builder.startObject(property.getFieldName()).field(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT).endObject(); + propertiesNode.set(property.getFieldName(), + objectMapper.createObjectNode().put(FIELD_PARAM_TYPE, TYPE_VALUE_GEO_POINT)); } - private void applyGeoShapeMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) + private void applyGeoShapeMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) throws IOException { - builder.startObject(property.getFieldName()); - GeoShapeMappingParameters.from(property.findAnnotation(GeoShapeField.class)).writeTypeAndParametersTo(builder); - builder.endObject(); + ObjectNode shapeNode = propertiesNode.putObject(property.getFieldName()); + GeoShapeMappingParameters mappingParameters = GeoShapeMappingParameters + .from(property.findAnnotation(GeoShapeField.class)); + mappingParameters.writeTypeAndParametersTo(shapeNode); } - private void applyCompletionFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property, + private void applyCompletionFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property, @Nullable CompletionField annotation) throws IOException { - builder.startObject(property.getFieldName()); - builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION); + ObjectNode completionNode = propertyNode.putObject(property.getFieldName()); + completionNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_COMPLETION); if (annotation != null) { + completionNode.put(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength()); + completionNode.put(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements()); + completionNode.put(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators()); - builder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength()); - builder.field(COMPLETION_PRESERVE_POSITION_INCREMENTS, annotation.preservePositionIncrements()); - builder.field(COMPLETION_PRESERVE_SEPARATORS, annotation.preserveSeparators()); - if (!StringUtils.isEmpty(annotation.searchAnalyzer())) { - builder.field(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer()); + if (StringUtils.hasLength(annotation.searchAnalyzer())) { + completionNode.put(FIELD_PARAM_SEARCH_ANALYZER, annotation.searchAnalyzer()); } - if (!StringUtils.isEmpty(annotation.analyzer())) { - builder.field(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer()); + + if (StringUtils.hasLength(annotation.analyzer())) { + completionNode.put(FIELD_PARAM_INDEX_ANALYZER, annotation.analyzer()); } if (annotation.contexts().length > 0) { - builder.startArray(COMPLETION_CONTEXTS); + ArrayNode contextsNode = completionNode.putArray(COMPLETION_CONTEXTS); for (CompletionContext context : annotation.contexts()) { - builder.startObject(); - builder.field(FIELD_CONTEXT_NAME, context.name()); - builder.field(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase()); + ObjectNode contextNode = contextsNode.addObject(); + contextNode.put(FIELD_CONTEXT_NAME, context.name()); + contextNode.put(FIELD_CONTEXT_TYPE, context.type().name().toLowerCase()); if (context.precision().length() > 0) { - builder.field(FIELD_CONTEXT_PRECISION, context.precision()); + contextNode.put(FIELD_CONTEXT_PRECISION, context.precision()); } if (StringUtils.hasText(context.path())) { - builder.field(FIELD_CONTEXT_PATH, context.path()); + contextNode.put(FIELD_CONTEXT_PATH, context.path()); } - - builder.endObject(); } - builder.endArray(); } - } - builder.endObject(); } - private void applyDefaultIdFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) + private void applyDefaultIdFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property) throws IOException { - builder.startObject(property.getFieldName()) // - .field(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // - .field(FIELD_INDEX, true) // - .endObject(); // + propertyNode.set(property.getFieldName(), objectMapper.createObjectNode()// + .put(FIELD_PARAM_TYPE, TYPE_VALUE_KEYWORD) // + .put(FIELD_INDEX, true) // + ); } - private void applyDisabledPropertyMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) - throws IOException { + private void applyDisabledPropertyMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) { try { Field field = property.getRequiredAnnotation(Field.class); @@ -414,10 +415,11 @@ private void applyDisabledPropertyMapping(XContentBuilder builder, Elasticsearch throw new IllegalArgumentException("Field type must be 'object"); } - builder.startObject(property.getFieldName()) // - .field(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) // - .field(MAPPING_ENABLED, false) // - .endObject(); // + propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() // + .put(FIELD_PARAM_TYPE, field.type().name().toLowerCase()) // + .put(MAPPING_ENABLED, false) // + ); + } catch (Exception e) { throw new MappingException("Could not write enabled: false mapping for " + property.getFieldName(), e); } @@ -428,33 +430,29 @@ private void applyDisabledPropertyMapping(XContentBuilder builder, Elasticsearch * * @throws IOException */ - private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property, + private void addSingleFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property, Field annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { // build the property json, if empty skip it as this is no valid mapping - XContentBuilder propertyBuilder = jsonBuilder().startObject(); - addFieldMappingParameters(propertyBuilder, annotation, nestedOrObjectField); - propertyBuilder.endObject().close(); + ObjectNode fieldNode = objectMapper.createObjectNode(); + addFieldMappingParameters(fieldNode, annotation, nestedOrObjectField); - if ("{}".equals(propertyBuilder.getOutputStream().toString())) { + if (fieldNode.isEmpty()) { return; } - builder.startObject(property.getFieldName()); + propertiesNode.set(property.getFieldName(), fieldNode); if (nestedOrObjectField) { if (annotation.dynamic() != Dynamic.INHERIT) { - builder.field(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase()); + fieldNode.put(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase()); } else if (dynamicMapping != null) { - builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + fieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); } } - - addFieldMappingParameters(builder, annotation, nestedOrObjectField); - builder.endObject(); } - private void addJoinFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property) + private void addJoinFieldMapping(ObjectNode propertiesNode, ElasticsearchPersistentProperty property) throws IOException { JoinTypeRelation[] joinTypeRelations = property.getRequiredAnnotation(JoinTypeRelations.class).relations(); @@ -464,24 +462,23 @@ private void addJoinFieldMapping(XContentBuilder builder, ElasticsearchPersisten property.getFieldName()); return; } - builder.startObject(property.getFieldName()); - builder.field(FIELD_PARAM_TYPE, TYPE_VALUE_JOIN); + ObjectNode propertyNode = propertiesNode.putObject(property.getFieldName()); + propertyNode.put(FIELD_PARAM_TYPE, TYPE_VALUE_JOIN); - builder.startObject(JOIN_TYPE_RELATIONS); + ObjectNode relationsNode = propertyNode.putObject(JOIN_TYPE_RELATIONS); for (JoinTypeRelation joinTypeRelation : joinTypeRelations) { String parent = joinTypeRelation.parent(); String[] children = joinTypeRelation.children(); if (children.length > 1) { - builder.array(parent, children); + relationsNode.putArray(parent) + .addAll(Arrays.stream(children).map(TextNode::valueOf).collect(Collectors.toList())); } else if (children.length == 1) { - builder.field(parent, children[0]); + relationsNode.put(parent, children[0]); } } - builder.endObject(); - builder.endObject(); } /** @@ -489,43 +486,43 @@ private void addJoinFieldMapping(XContentBuilder builder, ElasticsearchPersisten * * @throws IOException */ - private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersistentProperty property, + private void addMultiFieldMapping(ObjectNode propertyNode, ElasticsearchPersistentProperty property, MultiField annotation, boolean nestedOrObjectField, @Nullable DynamicMapping dynamicMapping) throws IOException { // main field - builder.startObject(property.getFieldName()); + ObjectNode mainFieldNode = objectMapper.createObjectNode(); + propertyNode.set(property.getFieldName(), mainFieldNode); if (nestedOrObjectField) { if (annotation.mainField().dynamic() != Dynamic.INHERIT) { - builder.field(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase()); + mainFieldNode.put(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase()); } else if (dynamicMapping != null) { - builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); + mainFieldNode.put(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase()); } } - addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField); + addFieldMappingParameters(mainFieldNode, annotation.mainField(), nestedOrObjectField); // inner fields - builder.startObject("fields"); + ObjectNode innerFieldsNode = mainFieldNode.putObject("fields"); + for (InnerField innerField : annotation.otherFields()) { - builder.startObject(innerField.suffix()); - addFieldMappingParameters(builder, innerField, false); - builder.endObject(); - } - builder.endObject(); - builder.endObject(); + ObjectNode innerFieldNode = innerFieldsNode.putObject(innerField.suffix()); + addFieldMappingParameters(innerFieldNode, innerField, false); + + } } - private void addFieldMappingParameters(XContentBuilder builder, Annotation annotation, boolean nestedOrObjectField) + private void addFieldMappingParameters(ObjectNode fieldNode, Annotation annotation, boolean nestedOrObjectField) throws IOException { MappingParameters mappingParameters = MappingParameters.from(annotation); if (!nestedOrObjectField && mappingParameters.isStore()) { - builder.field(FIELD_PARAM_STORE, true); + fieldNode.put(FIELD_PARAM_STORE, true); } - mappingParameters.writeTypeAndParametersTo(builder); + mappingParameters.writeTypeAndParametersTo(fieldNode); } /** @@ -533,7 +530,7 @@ private void addFieldMappingParameters(XContentBuilder builder, Annotation annot * * @throws IOException */ - private void addDynamicTemplatesMapping(XContentBuilder builder, ElasticsearchPersistentEntity entity) + private void addDynamicTemplatesMapping(ObjectNode objectNode, ElasticsearchPersistentEntity entity) throws IOException { if (entity.isAnnotationPresent(DynamicTemplates.class)) { @@ -543,11 +540,9 @@ private void addDynamicTemplatesMapping(XContentBuilder builder, ElasticsearchPe String jsonString = ResourceUtil.readFileFromClasspath(mappingPath); if (hasText(jsonString)) { - ObjectMapper objectMapper = new ObjectMapper(); JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates"); if (jsonNode != null && jsonNode.isArray()) { - String json = objectMapper.writeValueAsString(jsonNode); - builder.rawField(FIELD_DYNAMIC_TEMPLATES, new ByteArrayInputStream(json.getBytes()), XContentType.JSON); + objectNode.set(FIELD_DYNAMIC_TEMPLATES, jsonNode); } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java index 38bdbe7e7..0f6b80795 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/MappingParameters.java @@ -18,11 +18,11 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; -import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @@ -32,9 +32,13 @@ import org.springframework.data.elasticsearch.annotations.NullValueType; import org.springframework.data.elasticsearch.annotations.Similarity; import org.springframework.data.elasticsearch.annotations.TermVector; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + /** * A class to hold the mapping parameters that might be set on * {@link org.springframework.data.elasticsearch.annotations.Field } or @@ -58,6 +62,7 @@ public final class MappingParameters { static final String FIELD_PARAM_FORMAT = "format"; static final String FIELD_PARAM_IGNORE_ABOVE = "ignore_above"; static final String FIELD_PARAM_IGNORE_MALFORMED = "ignore_malformed"; + static final String FIELD_PARAM_IGNORE_Z_VALUE = "ignore_z_value"; static final String FIELD_PARAM_INDEX = "index"; static final String FIELD_PARAM_INDEX_OPTIONS = "index_options"; static final String FIELD_PARAM_INDEX_PHRASES = "index_phrases"; @@ -70,8 +75,9 @@ public final class MappingParameters { static final String FIELD_PARAM_NORMS = "norms"; static final String FIELD_PARAM_NULL_VALUE = "null_value"; static final String FIELD_PARAM_POSITION_INCREMENT_GAP = "position_increment_gap"; + static final String FIELD_PARAM_ORIENTATION = "orientation"; static final String FIELD_PARAM_POSITIVE_SCORE_IMPACT = "positive_score_impact"; - static final String FIELD_PARAM_DIMS = "dims"; + static final String FIELD_PARAM_DIMS = "dims"; static final String FIELD_PARAM_SCALING_FACTOR = "scaling_factor"; static final String FIELD_PARAM_SEARCH_ANALYZER = "search_analyzer"; static final String FIELD_PARAM_STORE = "store"; @@ -101,7 +107,7 @@ public final class MappingParameters { private final NullValueType nullValueType; private final Integer positionIncrementGap; private final boolean positiveScoreImpact; - private final Integer dims; + private final Integer dims; private final String searchAnalyzer; private final double scalingFactor; private final Similarity similarity; @@ -163,7 +169,8 @@ private MappingParameters(Field field) { positiveScoreImpact = field.positiveScoreImpact(); dims = field.dims(); if (type == FieldType.Dense_Vector) { - Assert.isTrue(dims >= 1 && dims <= 2048, "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048."); + Assert.isTrue(dims >= 1 && dims <= 2048, + "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048."); } Assert.isTrue(field.enabled() || type == FieldType.Object, "enabled false is only allowed for field type object"); enabled = field.enabled(); @@ -205,7 +212,8 @@ private MappingParameters(InnerField field) { positiveScoreImpact = field.positiveScoreImpact(); dims = field.dims(); if (type == FieldType.Dense_Vector) { - Assert.isTrue(dims >= 1 && dims <= 2048, "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048."); + Assert.isTrue(dims >= 1 && dims <= 2048, + "Invalid required parameter! Dense_Vector value \"dims\" must be between 1 and 2048."); } enabled = true; eagerGlobalOrdinals = field.eagerGlobalOrdinals(); @@ -216,20 +224,20 @@ public boolean isStore() { } /** - * writes the different fields to the builder. + * writes the different fields to an {@link ObjectNode}. * - * @param builder must not be {@literal null}. + * @param objectNode must not be {@literal null} */ - public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException { + public void writeTypeAndParametersTo(ObjectNode objectNode) throws IOException { - Assert.notNull(builder, "builder must ot be null"); + Assert.notNull(objectNode, "objectNode must not be null"); if (fielddata) { - builder.field(FIELD_PARAM_DATA, fielddata); + objectNode.put(FIELD_PARAM_DATA, fielddata); } if (type != FieldType.Auto) { - builder.field(FIELD_PARAM_TYPE, type.name().toLowerCase()); + objectNode.put(FIELD_PARAM_TYPE, type.toString().toLowerCase()); if (type == FieldType.Date) { List formats = new ArrayList<>(); @@ -246,125 +254,123 @@ public void writeTypeAndParametersTo(XContentBuilder builder) throws IOException Collections.addAll(formats, dateFormatPatterns); if (!formats.isEmpty()) { - builder.field(FIELD_PARAM_FORMAT, String.join("||", formats)); + objectNode.put(FIELD_PARAM_FORMAT, String.join("||", formats)); } } } if (!index) { - builder.field(FIELD_PARAM_INDEX, index); + objectNode.put(FIELD_PARAM_INDEX, index); } - if (!StringUtils.isEmpty(analyzer)) { - builder.field(FIELD_PARAM_INDEX_ANALYZER, analyzer); + if (StringUtils.hasLength(analyzer)) { + objectNode.put(FIELD_PARAM_INDEX_ANALYZER, analyzer); } - if (!StringUtils.isEmpty(searchAnalyzer)) { - builder.field(FIELD_PARAM_SEARCH_ANALYZER, searchAnalyzer); + if (StringUtils.hasLength(searchAnalyzer)) { + objectNode.put(FIELD_PARAM_SEARCH_ANALYZER, searchAnalyzer); } - if (!StringUtils.isEmpty(normalizer)) { - builder.field(FIELD_PARAM_NORMALIZER, normalizer); + if (StringUtils.hasLength(normalizer)) { + objectNode.put(FIELD_PARAM_NORMALIZER, normalizer); } if (copyTo != null && copyTo.length > 0) { - builder.field(FIELD_PARAM_COPY_TO, copyTo); + objectNode.putArray(FIELD_PARAM_COPY_TO) + .addAll(Arrays.stream(copyTo).map(TextNode::valueOf).collect(Collectors.toList())); } if (ignoreAbove != null) { Assert.isTrue(ignoreAbove >= 0, "ignore_above must be a positive value"); - builder.field(FIELD_PARAM_IGNORE_ABOVE, ignoreAbove); + objectNode.put(FIELD_PARAM_IGNORE_ABOVE, ignoreAbove); } if (!coerce) { - builder.field(FIELD_PARAM_COERCE, coerce); + objectNode.put(FIELD_PARAM_COERCE, coerce); } if (!docValues) { - builder.field(FIELD_PARAM_DOC_VALUES, docValues); + objectNode.put(FIELD_PARAM_DOC_VALUES, docValues); } if (ignoreMalformed) { - builder.field(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed); + objectNode.put(FIELD_PARAM_IGNORE_MALFORMED, ignoreMalformed); } if (indexOptions != IndexOptions.none) { - builder.field(FIELD_PARAM_INDEX_OPTIONS, indexOptions); + objectNode.put(FIELD_PARAM_INDEX_OPTIONS, indexOptions.toString()); } if (indexPhrases) { - builder.field(FIELD_PARAM_INDEX_PHRASES, indexPhrases); + objectNode.put(FIELD_PARAM_INDEX_PHRASES, indexPhrases); } if (indexPrefixes != null) { - builder.startObject(FIELD_PARAM_INDEX_PREFIXES); + ObjectNode prefixNode = objectNode.putObject(FIELD_PARAM_INDEX_PREFIXES); if (indexPrefixes.minChars() != IndexPrefixes.MIN_DEFAULT) { - builder.field(FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS, indexPrefixes.minChars()); + prefixNode.put(FIELD_PARAM_INDEX_PREFIXES_MIN_CHARS, indexPrefixes.minChars()); } if (indexPrefixes.maxChars() != IndexPrefixes.MAX_DEFAULT) { - builder.field(FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS, indexPrefixes.maxChars()); + prefixNode.put(FIELD_PARAM_INDEX_PREFIXES_MAX_CHARS, indexPrefixes.maxChars()); } - builder.endObject(); } if (!norms) { - builder.field(FIELD_PARAM_NORMS, norms); + objectNode.put(FIELD_PARAM_NORMS, norms); } - if (!StringUtils.isEmpty(nullValue)) { - Object value; + if (StringUtils.hasLength(nullValue)) { switch (nullValueType) { case Integer: - value = Integer.valueOf(nullValue); + objectNode.put(FIELD_PARAM_NULL_VALUE, Integer.valueOf(nullValue)); break; case Long: - value = Long.valueOf(nullValue); + objectNode.put(FIELD_PARAM_NULL_VALUE, Long.valueOf(nullValue)); break; case Double: - value = Double.valueOf(nullValue); + objectNode.put(FIELD_PARAM_NULL_VALUE, Double.valueOf(nullValue)); break; case String: default: - value = nullValue; + objectNode.put(FIELD_PARAM_NULL_VALUE, nullValue); break; } - builder.field(FIELD_PARAM_NULL_VALUE, value); } if (positionIncrementGap != null && positionIncrementGap >= 0) { - builder.field(FIELD_PARAM_POSITION_INCREMENT_GAP, positionIncrementGap); + objectNode.put(FIELD_PARAM_POSITION_INCREMENT_GAP, positionIncrementGap); } if (similarity != Similarity.Default) { - builder.field(FIELD_PARAM_SIMILARITY, similarity); + objectNode.put(FIELD_PARAM_SIMILARITY, similarity.toString()); } if (termVector != TermVector.none) { - builder.field(FIELD_PARAM_TERM_VECTOR, termVector); + objectNode.put(FIELD_PARAM_TERM_VECTOR, termVector.toString()); } if (type == FieldType.Scaled_Float) { - builder.field(FIELD_PARAM_SCALING_FACTOR, scalingFactor); + objectNode.put(FIELD_PARAM_SCALING_FACTOR, scalingFactor); } if (maxShingleSize != null) { - builder.field(FIELD_PARAM_MAX_SHINGLE_SIZE, maxShingleSize); + objectNode.put(FIELD_PARAM_MAX_SHINGLE_SIZE, maxShingleSize); } if (!positiveScoreImpact) { - builder.field(FIELD_PARAM_POSITIVE_SCORE_IMPACT, positiveScoreImpact); + objectNode.put(FIELD_PARAM_POSITIVE_SCORE_IMPACT, positiveScoreImpact); } if (type == FieldType.Dense_Vector) { - builder.field(FIELD_PARAM_DIMS, dims); + objectNode.put(FIELD_PARAM_DIMS, dims); } if (!enabled) { - builder.field(FIELD_PARAM_ENABLED, enabled); + objectNode.put(FIELD_PARAM_ENABLED, enabled); } if (eagerGlobalOrdinals) { - builder.field(FIELD_PARAM_EAGER_GLOBAL_ORDINALS, eagerGlobalOrdinals); + objectNode.put(FIELD_PARAM_EAGER_GLOBAL_ORDINALS, eagerGlobalOrdinals); } } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java index f06b71494..3902f3955 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/index/ReactiveMappingBuilder.java @@ -28,7 +28,7 @@ import org.springframework.lang.Nullable; /** - * Subclass of {@link MappingBuilder} with specialized methods TO inhibit blocking CALLS + * Subclass of {@link MappingBuilder} with specialized methods To inhibit blocking calls * * @author Peter-Josef Meisch * @since 4.3 diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java index 2bb134cbb..d88c4c21d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java @@ -15,7 +15,7 @@ */ package org.springframework.data.elasticsearch.core.mapping; -import org.elasticsearch.index.VersionType; +import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Dynamic; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.core.index.Settings; @@ -61,7 +61,7 @@ public interface ElasticsearchPersistentEntity extends PersistentEntity extends BasicPersistentEntit private final Lazy settingsParameter; private @Nullable ElasticsearchPersistentProperty seqNoPrimaryTermProperty; private @Nullable ElasticsearchPersistentProperty joinFieldProperty; - private @Nullable VersionType versionType; + private @Nullable Document.VersionType versionType; private boolean createIndexAndMapping; private final Dynamic dynamic; private final Map fieldNamePropertyCache = new ConcurrentHashMap<>(); @@ -156,7 +155,7 @@ public String getRefreshInterval() { @Nullable @Override - public VersionType getVersionType() { + public Document.VersionType getVersionType() { return versionType; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java index 3e5f1a386..7281da289 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/AbstractQuery.java @@ -23,9 +23,6 @@ import java.util.List; import java.util.Optional; -import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.common.unit.TimeValue; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.lang.Nullable; @@ -52,7 +49,7 @@ abstract class AbstractQuery implements Query { protected float minScore; @Nullable protected Collection ids; @Nullable protected String route; - protected SearchType searchType = SearchType.DFS_QUERY_THEN_FETCH; + protected SearchType searchType = SearchType.QUERY_THEN_FETCH; @Nullable protected IndicesOptions indicesOptions; protected boolean trackScores; @Nullable protected String preference; @@ -61,7 +58,7 @@ abstract class AbstractQuery implements Query { @Nullable private Boolean trackTotalHits; @Nullable private Integer trackTotalHitsUpTo; @Nullable private Duration scrollTime; - @Nullable private TimeValue timeout; + @Nullable private Duration timeout; private boolean explain = false; @Nullable private List searchAfter; protected List rescorerQueries = new ArrayList<>(); @@ -271,7 +268,7 @@ public void setScrollTime(@Nullable Duration scrollTime) { @Nullable @Override - public TimeValue getTimeout() { + public Duration getTimeout() { return timeout; } @@ -281,7 +278,7 @@ public TimeValue getTimeout() { * @param timeout * @since 4.2 */ - public void setTimeout(@Nullable TimeValue timeout) { + public void setTimeout(@Nullable Duration timeout) { this.timeout = timeout; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/BulkOptions.java b/src/main/java/org/springframework/data/elasticsearch/core/query/BulkOptions.java index 415143be3..c88b24ee2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/BulkOptions.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/BulkOptions.java @@ -15,18 +15,20 @@ */ package org.springframework.data.elasticsearch.core.query; +import java.time.Duration; import java.util.List; -import org.elasticsearch.action.support.ActiveShardCount; -import org.elasticsearch.action.support.WriteRequest; -import org.elasticsearch.common.unit.TimeValue; +import org.springframework.data.elasticsearch.core.ActiveShardCount; +import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.lang.Nullable; /** * Options that may be passed to an - * {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkIndex(List, BulkOptions, IndexCoordinates)} or - * {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkUpdate(List, BulkOptions, IndexCoordinates)} call.
+ * {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkIndex(List, BulkOptions, IndexCoordinates)} + * or + * {@link org.springframework.data.elasticsearch.core.DocumentOperations#bulkUpdate(List, BulkOptions, IndexCoordinates)} + * call.
* Use {@link BulkOptions#builder()} to obtain a builder, then set the desired properties and call * {@link BulkOptionsBuilder#build()} to get the BulkOptions object. * @@ -38,13 +40,13 @@ public class BulkOptions { private static final BulkOptions defaultOptions = builder().build(); - private final @Nullable TimeValue timeout; - private final @Nullable WriteRequest.RefreshPolicy refreshPolicy; + private final @Nullable Duration timeout; + private final @Nullable RefreshPolicy refreshPolicy; private final @Nullable ActiveShardCount waitForActiveShards; private final @Nullable String pipeline; private final @Nullable String routingId; - private BulkOptions(@Nullable TimeValue timeout, @Nullable WriteRequest.RefreshPolicy refreshPolicy, + private BulkOptions(@Nullable Duration timeout, @Nullable RefreshPolicy refreshPolicy, @Nullable ActiveShardCount waitForActiveShards, @Nullable String pipeline, @Nullable String routingId) { this.timeout = timeout; this.refreshPolicy = refreshPolicy; @@ -54,12 +56,12 @@ private BulkOptions(@Nullable TimeValue timeout, @Nullable WriteRequest.RefreshP } @Nullable - public TimeValue getTimeout() { + public Duration getTimeout() { return timeout; } @Nullable - public WriteRequest.RefreshPolicy getRefreshPolicy() { + public RefreshPolicy getRefreshPolicy() { return refreshPolicy; } @@ -101,20 +103,20 @@ public static BulkOptions defaultOptions() { */ public static class BulkOptionsBuilder { - private @Nullable TimeValue timeout; - private @Nullable WriteRequest.RefreshPolicy refreshPolicy; + private @Nullable Duration timeout; + private @Nullable RefreshPolicy refreshPolicy; private @Nullable ActiveShardCount waitForActiveShards; private @Nullable String pipeline; private @Nullable String routingId; private BulkOptionsBuilder() {} - public BulkOptionsBuilder withTimeout(TimeValue timeout) { + public BulkOptionsBuilder withTimeout(Duration timeout) { this.timeout = timeout; return this; } - public BulkOptionsBuilder withRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) { + public BulkOptionsBuilder withRefreshPolicy(RefreshPolicy refreshPolicy) { this.refreshPolicy = refreshPolicy; return this; } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java b/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java index 42476b507..7986815a1 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/ByQueryResponse.java @@ -17,11 +17,7 @@ import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import org.elasticsearch.action.bulk.BulkItemResponse; -import org.elasticsearch.index.reindex.BulkByScrollResponse; -import org.elasticsearch.index.reindex.ScrollableHitSource; import org.springframework.lang.Nullable; /** @@ -47,8 +43,8 @@ public class ByQueryResponse { private final List searchFailures; private ByQueryResponse(long took, boolean timedOut, long total, long updated, long deleted, int batches, - long versionConflicts, long noops, long bulkRetries, long searchRetries, - @Nullable String reasonCancelled, List failures, List searchFailures) { + long versionConflicts, long noops, long bulkRetries, long searchRetries, @Nullable String reasonCancelled, + List failures, List searchFailures) { this.took = took; this.timedOut = timedOut; this.total = total; @@ -61,8 +57,8 @@ private ByQueryResponse(long took, boolean timedOut, long total, long updated, l this.searchRetries = searchRetries; this.reasonCancelled = reasonCancelled; this.failures = failures; - this.searchFailures = searchFailures; - } + this.searchFailures = searchFailures; + } /** * The number of milliseconds from start to end of the whole operation. @@ -151,14 +147,14 @@ public List getFailures() { return failures; } - /** - * Failures during search phase - */ - public List getSearchFailures() { - return searchFailures; - } + /** + * Failures during search phase + */ + public List getSearchFailures() { + return searchFailures; + } - /** + /** * Create a new {@link ByQueryResponseBuilder} to build {@link ByQueryResponse} * * @return a new {@link ByQueryResponseBuilder} to build {@link ByQueryResponse} @@ -167,34 +163,6 @@ public static ByQueryResponseBuilder builder() { return new ByQueryResponseBuilder(); } - public static ByQueryResponse of(BulkByScrollResponse bulkByScrollResponse) { - final List failures = bulkByScrollResponse.getBulkFailures() // - .stream() // - .map(Failure::of) // - .collect(Collectors.toList()); // - - final List searchFailures = bulkByScrollResponse.getSearchFailures() // - .stream() // - .map(SearchFailure::of) // - .collect(Collectors.toList());// - - return ByQueryResponse.builder() // - .withTook(bulkByScrollResponse.getTook().getMillis()) // - .withTimedOut(bulkByScrollResponse.isTimedOut()) // - .withTotal(bulkByScrollResponse.getTotal()) // - .withUpdated(bulkByScrollResponse.getUpdated()) // - .withDeleted(bulkByScrollResponse.getDeleted()) // - .withBatches(bulkByScrollResponse.getBatches()) // - .withVersionConflicts(bulkByScrollResponse.getVersionConflicts()) // - .withNoops(bulkByScrollResponse.getNoops()) // - .withBulkRetries(bulkByScrollResponse.getBulkRetries()) // - .withSearchRetries(bulkByScrollResponse.getSearchRetries()) // - .withReasonCancelled(bulkByScrollResponse.getReasonCancelled()) // - .withFailures(failures) // - .withSearchFailure(searchFailures) // - .build(); // - } - public static class Failure { @Nullable private final String index; @@ -267,25 +235,6 @@ public static FailureBuilder builder() { return new FailureBuilder(); } - /** - * Create a new {@link Failure} from {@link BulkItemResponse.Failure} - * - * @param failure {@link BulkItemResponse.Failure} to translate - * @return a new {@link Failure} - */ - public static Failure of(BulkItemResponse.Failure failure) { - return builder() // - .withIndex(failure.getIndex()) // - .withType(failure.getType()) // - .withId(failure.getId()) // - .withStatus(failure.getStatus().getStatus()) // - .withAborted(failure.isAborted()) // - .withCause(failure.getCause()) // - .withSeqNo(failure.getSeqNo()) // - .withTerm(failure.getTerm()) // - .build(); // - } - /** * Builder for {@link Failure} */ @@ -348,113 +297,97 @@ public Failure build() { } public static class SearchFailure { - private final Throwable reason; - @Nullable private final Integer status; - @Nullable private final String index; - @Nullable private final Integer shardId; - @Nullable private final String nodeId; - - private SearchFailure(Throwable reason, @Nullable Integer status, @Nullable String index, - @Nullable Integer shardId, @Nullable String nodeId) { - this.reason = reason; - this.status = status; - this.index = index; - this.shardId = shardId; - this.nodeId = nodeId; - } - - public Throwable getReason() { - return reason; - } - - @Nullable - public Integer getStatus() { - return status; - } - - @Nullable - public String getIndex() { - return index; - } - - @Nullable - public Integer getShardId() { - return shardId; - } - - @Nullable - public String getNodeId() { - return nodeId; - } - - /** - * Create a new {@link SearchFailureBuilder} to build {@link SearchFailure} - * - * @return a new {@link SearchFailureBuilder} to build {@link SearchFailure} - */ - public static SearchFailureBuilder builder() { - return new SearchFailureBuilder(); - } - - /** - * Create a new {@link SearchFailure} from {@link ScrollableHitSource.SearchFailure} - * - * @param searchFailure {@link ScrollableHitSource.SearchFailure} to translate - * @return a new {@link SearchFailure} - */ - public static SearchFailure of(ScrollableHitSource.SearchFailure searchFailure) { - return builder() // - .withReason(searchFailure.getReason()) // - .withIndex(searchFailure.getIndex()) // - .withNodeId(searchFailure.getNodeId()) // - .withShardId(searchFailure.getShardId()) // - .withStatus(searchFailure.getStatus().getStatus()) // - .build(); // - } - - /** - * Builder for {@link SearchFailure} - */ - public static final class SearchFailureBuilder { - private Throwable reason; - @Nullable private Integer status; - @Nullable private String index; - @Nullable private Integer shardId; - @Nullable private String nodeId; - - private SearchFailureBuilder() {} - - public SearchFailureBuilder withReason(Throwable reason) { - this.reason = reason; - return this; - } - - public SearchFailureBuilder withStatus(Integer status) { - this.status = status; - return this; - } - - public SearchFailureBuilder withIndex(String index) { - this.index = index; - return this; - } - - public SearchFailureBuilder withShardId(Integer shardId) { - this.shardId = shardId; - return this; - } - - public SearchFailureBuilder withNodeId(String nodeId) { - this.nodeId = nodeId; - return this; - } - - public SearchFailure build() { - return new SearchFailure(reason, status, index, shardId, nodeId); - } - } - - } + private final Throwable reason; + @Nullable private final Integer status; + @Nullable private final String index; + @Nullable private final Integer shardId; + @Nullable private final String nodeId; + + private SearchFailure(Throwable reason, @Nullable Integer status, @Nullable String index, @Nullable Integer shardId, + @Nullable String nodeId) { + this.reason = reason; + this.status = status; + this.index = index; + this.shardId = shardId; + this.nodeId = nodeId; + } + + public Throwable getReason() { + return reason; + } + + @Nullable + public Integer getStatus() { + return status; + } + + @Nullable + public String getIndex() { + return index; + } + + @Nullable + public Integer getShardId() { + return shardId; + } + + @Nullable + public String getNodeId() { + return nodeId; + } + + /** + * Create a new {@link SearchFailureBuilder} to build {@link SearchFailure} + * + * @return a new {@link SearchFailureBuilder} to build {@link SearchFailure} + */ + public static SearchFailureBuilder builder() { + return new SearchFailureBuilder(); + } + + /** + * Builder for {@link SearchFailure} + */ + public static final class SearchFailureBuilder { + private Throwable reason; + @Nullable private Integer status; + @Nullable private String index; + @Nullable private Integer shardId; + @Nullable private String nodeId; + + private SearchFailureBuilder() {} + + public SearchFailureBuilder withReason(Throwable reason) { + this.reason = reason; + return this; + } + + public SearchFailureBuilder withStatus(Integer status) { + this.status = status; + return this; + } + + public SearchFailureBuilder withIndex(String index) { + this.index = index; + return this; + } + + public SearchFailureBuilder withShardId(Integer shardId) { + this.shardId = shardId; + return this; + } + + public SearchFailureBuilder withNodeId(String nodeId) { + this.nodeId = nodeId; + return this; + } + + public SearchFailure build() { + return new SearchFailure(reason, status, index, shardId, nodeId); + } + } + + } public static final class ByQueryResponseBuilder { private long took; @@ -534,9 +467,9 @@ public ByQueryResponseBuilder withFailures(List failures) { } public ByQueryResponseBuilder withSearchFailure(List searchFailures) { - this.searchFailures = searchFailures; - return this; - } + this.searchFailures = searchFailures; + return this; + } public ByQueryResponse build() { return new ByQueryResponse(took, timedOut, total, updated, deleted, batches, versionConflicts, noops, bulkRetries, diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQuery.java b/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQuery.java index 19e927afd..152ca5244 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQuery.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQuery.java @@ -15,23 +15,35 @@ */ package org.springframework.data.elasticsearch.core.query; -import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; +import org.springframework.data.elasticsearch.core.query.highlight.Highlight; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** - * Encapsulates an Elasticsearch {@link org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder} to prevent - * leaking of Elasticsearch classes into the query API. - * + * Combines a {@link Highlight} definition with the type of the entity where it's present on a method. + * * @author Peter-Josef Meisch * @since 4.0 */ public class HighlightQuery { - private final HighlightBuilder highlightBuilder; - public HighlightQuery(HighlightBuilder highlightBuilder) { - this.highlightBuilder = highlightBuilder; + private final Highlight highlight; + @Nullable private final Class type; + + public HighlightQuery(Highlight highlight, @Nullable Class type) { + + Assert.notNull(highlight, "highlight must not be null"); + + this.highlight = highlight; + this.type = type; + } + + public Highlight getHighlight() { + return highlight; } - public HighlightBuilder getHighlightBuilder() { - return highlightBuilder; + @Nullable + public Class getType() { + return type; } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java index 0c94044fd..c5269a39a 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/HighlightQueryBuilder.java @@ -20,19 +20,20 @@ import org.elasticsearch.search.fetch.subphase.highlight.AbstractHighlighterBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.springframework.data.elasticsearch.annotations.Highlight; -import org.springframework.data.elasticsearch.annotations.HighlightField; -import org.springframework.data.elasticsearch.annotations.HighlightParameters; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.elasticsearch.core.query.highlight.Highlight; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightCommonParameters; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightField; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters; +import org.springframework.data.elasticsearch.core.query.highlight.HighlightParameters; import org.springframework.data.mapping.context.MappingContext; import org.springframework.lang.Nullable; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Converts the {@link Highlight} annotation from a method to an Elasticsearch {@link HighlightBuilder}. - * + * * @author Peter-Josef Meisch */ public class HighlightQueryBuilder { @@ -45,115 +46,114 @@ public HighlightQueryBuilder( } /** - * creates a HighlightBuilder from an annotation - * + * creates an Elasticsearch HighlightBuilder from an annotation. + * * @param highlight, must not be {@literal null} * @param type the entity type, used to map field names. If null, field names are not mapped. * @return the builder for the highlight */ - public HighlightQuery getHighlightQuery(Highlight highlight, @Nullable Class type) { - - Assert.notNull(highlight, "highlight must not be null"); - + public HighlightBuilder getHighlightBuilder(Highlight highlight, @Nullable Class type) { HighlightBuilder highlightBuilder = new HighlightBuilder(); - addParameters(highlight.parameters(), highlightBuilder, type); + addParameters(highlight.getParameters(), highlightBuilder, type); - for (HighlightField highlightField : highlight.fields()) { - String mappedName = mapFieldName(highlightField.name(), type); + for (HighlightField highlightField : highlight.getFields()) { + String mappedName = mapFieldName(highlightField.getName(), type); HighlightBuilder.Field field = new HighlightBuilder.Field(mappedName); - addParameters(highlightField.parameters(), field, type); + addParameters(highlightField.getParameters(), field, type); highlightBuilder.field(field); } - - return new HighlightQuery(highlightBuilder); + return highlightBuilder; } - private void addParameters(HighlightParameters parameters, AbstractHighlighterBuilder builder, Class type) { + private

void addParameters(P parameters, AbstractHighlighterBuilder builder, + @Nullable Class type) { - if (StringUtils.hasLength(parameters.boundaryChars())) { - builder.boundaryChars(parameters.boundaryChars().toCharArray()); + if (StringUtils.hasLength(parameters.getBoundaryChars())) { + builder.boundaryChars(parameters.getBoundaryChars().toCharArray()); } - if (parameters.boundaryMaxScan() > -1) { - builder.boundaryMaxScan(parameters.boundaryMaxScan()); + if (parameters.getBoundaryMaxScan() > -1) { + builder.boundaryMaxScan(parameters.getBoundaryMaxScan()); } - if (StringUtils.hasLength(parameters.boundaryScanner())) { - builder.boundaryScannerType(parameters.boundaryScanner()); + if (StringUtils.hasLength(parameters.getBoundaryScanner())) { + builder.boundaryScannerType(parameters.getBoundaryScanner()); } - if (StringUtils.hasLength(parameters.boundaryScannerLocale())) { - builder.boundaryScannerLocale(parameters.boundaryScannerLocale()); + if (StringUtils.hasLength(parameters.getBoundaryScannerLocale())) { + builder.boundaryScannerLocale(parameters.getBoundaryScannerLocale()); } - if (parameters.forceSource()) { // default is false - builder.forceSource(parameters.forceSource()); + if (parameters.getForceSource()) { // default is false + builder.forceSource(true); } - if (StringUtils.hasLength(parameters.fragmenter())) { - builder.fragmenter(parameters.fragmenter()); + if (StringUtils.hasLength(parameters.getFragmenter())) { + builder.fragmenter(parameters.getFragmenter()); } - if (parameters.fragmentSize() > -1) { - builder.fragmentSize(parameters.fragmentSize()); + if (parameters.getFragmentSize() > -1) { + builder.fragmentSize(parameters.getFragmentSize()); } - if (parameters.noMatchSize() > -1) { - builder.noMatchSize(parameters.noMatchSize()); + if (parameters.getNoMatchSize() > -1) { + builder.noMatchSize(parameters.getNoMatchSize()); } - if (parameters.numberOfFragments() > -1) { - builder.numOfFragments(parameters.numberOfFragments()); + if (parameters.getNumberOfFragments() > -1) { + builder.numOfFragments(parameters.getNumberOfFragments()); } - if (StringUtils.hasLength(parameters.order())) { - builder.order(parameters.order()); + if (StringUtils.hasLength(parameters.getOrder())) { + builder.order(parameters.getOrder()); } - if (parameters.phraseLimit() > -1) { - builder.phraseLimit(parameters.phraseLimit()); + if (parameters.getPhraseLimit() > -1) { + builder.phraseLimit(parameters.getPhraseLimit()); } - if (parameters.preTags().length > 0) { - builder.preTags(parameters.preTags()); + if (parameters.getPreTags().length > 0) { + builder.preTags(parameters.getPreTags()); } - if (parameters.postTags().length > 0) { - builder.postTags(parameters.postTags()); + if (parameters.getPostTags().length > 0) { + builder.postTags(parameters.getPostTags()); } - if (!parameters.requireFieldMatch()) { // default is true - builder.requireFieldMatch(parameters.requireFieldMatch()); + if (!parameters.getRequireFieldMatch()) { // default is true + builder.requireFieldMatch(false); } - if (StringUtils.hasLength(parameters.type())) { - builder.highlighterType(parameters.type()); + if (StringUtils.hasLength(parameters.getType())) { + builder.highlighterType(parameters.getType()); } - if (builder instanceof HighlightBuilder) { + if (builder instanceof HighlightBuilder && parameters instanceof HighlightParameters) { HighlightBuilder highlightBuilder = (HighlightBuilder) builder; + HighlightParameters highlightParameters = (HighlightParameters) parameters; - if (StringUtils.hasLength(parameters.encoder())) { - highlightBuilder.encoder(parameters.encoder()); + if (StringUtils.hasLength(highlightParameters.getEncoder())) { + highlightBuilder.encoder(highlightParameters.getEncoder()); } - if (StringUtils.hasLength(parameters.tagsSchema())) { - highlightBuilder.tagsSchema(parameters.tagsSchema()); + if (StringUtils.hasLength(highlightParameters.getTagsSchema())) { + highlightBuilder.tagsSchema(highlightParameters.getTagsSchema()); } } - if (builder instanceof HighlightBuilder.Field) { + if (builder instanceof HighlightBuilder.Field && parameters instanceof HighlightFieldParameters) { HighlightBuilder.Field field = (HighlightBuilder.Field) builder; + HighlightFieldParameters fieldParameters = (HighlightFieldParameters) parameters; - if (parameters.fragmentOffset() > -1) { - field.fragmentOffset(parameters.fragmentOffset()); + if ((fieldParameters).getFragmentOffset() > -1) { + field.fragmentOffset(fieldParameters.getFragmentOffset()); } - if (parameters.matchedFields().length > 0) { - field.matchedFields(Arrays.stream(parameters.matchedFields()) // + if (fieldParameters.getMatchedFields().length > 0) { + field.matchedFields(Arrays.stream(fieldParameters.getMatchedFields()) // .map(fieldName -> mapFieldName(fieldName, type)) // .collect(Collectors.toList()) // .toArray(new String[] {})); // diff --git a/src/main/java/org/springframework/data/elasticsearch/core/query/IndicesOptions.java b/src/main/java/org/springframework/data/elasticsearch/core/query/IndicesOptions.java new file mode 100644 index 000000000..7bcb41d57 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/query/IndicesOptions.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.elasticsearch.core.query; + +import java.util.EnumSet; + +/** + * Class mirroring the IndicesOptions from Elasticsearch in Spring Data Elasticsearch API. + * + * @author Peter-Josef Meisch + * @since 4.3 + */ +public class IndicesOptions { + + private EnumSet