From 2bcb64d42dd8ab430f85b2946582ebb02c6a3dad Mon Sep 17 00:00:00 2001 From: Peter-Josef Meisch Date: Sat, 17 Apr 2021 14:51:50 +0200 Subject: [PATCH] DynamicMapping annotation should be applicable to any object field. --- .../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;