Skip to content

Commit 53c24e1

Browse files
committed
Move dynamic mapping parameter configuration to @document and @field
1 parent 039e59d commit 53c24e1

File tree

10 files changed

+233
-12
lines changed

10 files changed

+233
-12
lines changed

src/main/java/org/springframework/data/elasticsearch/annotations/Document.java

+9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* @author Ivan Greene
3434
* @author Mark Paluch
3535
* @author Peter-Josef Meisch
36+
* @author Sascha Woo
3637
*/
3738
@Persistent
3839
@Inherited
@@ -112,4 +113,12 @@
112113
* @since 4.3
113114
*/
114115
WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT;
116+
117+
/**
118+
* Controls how Elasticsearch dynamically adds fields to the document.
119+
*
120+
* @since 4.3
121+
*/
122+
Dynamic dynamic() default Dynamic.INHERIT;
123+
115124
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.annotations;
17+
18+
/**
19+
* Values for the {@code dynamic} mapping parameter.
20+
*
21+
* @author Sascha Woo
22+
* @since 4.3
23+
*/
24+
public enum Dynamic {
25+
/**
26+
* New fields are added to the mapping.
27+
*/
28+
TRUE,
29+
/**
30+
* New fields are added to the mapping as
31+
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
32+
* fields are not indexed, and are loaded from {@code _source} at query time.
33+
*/
34+
RUNTIME,
35+
/**
36+
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
37+
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
38+
* explicitly.
39+
*/
40+
FALSE,
41+
/**
42+
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
43+
* added to the mapping.
44+
*/
45+
STRICT,
46+
/**
47+
* Inherit the dynamic setting from their parent object or from the mapping type.
48+
*/
49+
INHERIT
50+
}

src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java

+3
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
2727
*
2828
* @author Peter-Josef Meisch
29+
* @author Sascha Woo
2930
* @since 4.0
31+
* @deprecated use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
3032
*/
3133
@Retention(RetentionPolicy.RUNTIME)
3234
@Target({ ElementType.TYPE, ElementType.FIELD })
3335
@Documented
36+
@Deprecated
3437
public @interface DynamicMapping {
3538

3639
DynamicMappingValue value() default DynamicMappingValue.True;

src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMappingValue.java

+3
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
* values for the {@link DynamicMapping annotation}
2020
*
2121
* @author Peter-Josef Meisch
22+
* @author Sascha Woo
2223
* @since 4.0
24+
* @deprecated use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
2325
*/
26+
@Deprecated
2427
public enum DynamicMappingValue {
2528
True, False, Strict
2629
}

src/main/java/org/springframework/data/elasticsearch/annotations/Field.java

+8
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,12 @@
195195
* @since 4.2
196196
*/
197197
int dims() default -1;
198+
199+
/**
200+
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
201+
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
202+
*
203+
* @since 4.3
204+
*/
205+
Dynamic dynamic() default Dynamic.INHERIT;
198206
}

src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,12 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten
197197
}
198198
}
199199

200-
if (dynamicMapping != null) {
200+
if (isRootObject && entity != null && entity.dynamic() != Dynamic.INHERIT) {
201+
builder.field(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
202+
} else if (nestedOrObjectField && parentFieldAnnotation != null
203+
&& parentFieldAnnotation.dynamic() != Dynamic.INHERIT) {
204+
builder.field(TYPE_DYNAMIC, parentFieldAnnotation.dynamic().name().toLowerCase());
205+
} else if (dynamicMapping != null) {
201206
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
202207
}
203208

@@ -440,8 +445,12 @@ private void addSingleFieldMapping(XContentBuilder builder, ElasticsearchPersist
440445

441446
builder.startObject(property.getFieldName());
442447

443-
if (nestedOrObjectField && dynamicMapping != null) {
444-
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
448+
if (nestedOrObjectField) {
449+
if (annotation.dynamic() != Dynamic.INHERIT) {
450+
builder.field(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
451+
} else if (dynamicMapping != null) {
452+
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
453+
}
445454
}
446455

447456
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
@@ -489,8 +498,12 @@ private void addMultiFieldMapping(XContentBuilder builder, ElasticsearchPersiste
489498
// main field
490499
builder.startObject(property.getFieldName());
491500

492-
if (nestedOrObjectField && dynamicMapping != null) {
493-
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
501+
if (nestedOrObjectField) {
502+
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
503+
builder.field(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
504+
} else if (dynamicMapping != null) {
505+
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
506+
}
494507
}
495508

496509
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);

src/main/java/org/springframework/data/elasticsearch/core/mapping/ElasticsearchPersistentEntity.java

+7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.elasticsearch.core.mapping;
1717

1818
import org.elasticsearch.index.VersionType;
19+
import org.springframework.data.elasticsearch.annotations.Dynamic;
1920
import org.springframework.data.elasticsearch.annotations.Field;
2021
import org.springframework.data.elasticsearch.core.index.Settings;
2122
import org.springframework.data.elasticsearch.core.join.JoinField;
@@ -160,4 +161,10 @@ default ElasticsearchPersistentProperty getRequiredSeqNoPrimaryTermProperty() {
160161
* @since 4.3
161162
*/
162163
boolean writeTypeHints();
164+
165+
/**
166+
* @return the {@code dynamic} mapping parameter value.
167+
* @since 4.3
168+
*/
169+
Dynamic dynamic();
163170
}

src/main/java/org/springframework/data/elasticsearch/core/mapping/SimpleElasticsearchPersistentEntity.java

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.core.annotation.AnnotatedElementUtils;
2626
import org.springframework.dao.InvalidDataAccessApiUsageException;
2727
import org.springframework.data.elasticsearch.annotations.Document;
28+
import org.springframework.data.elasticsearch.annotations.Dynamic;
2829
import org.springframework.data.elasticsearch.annotations.Field;
2930
import org.springframework.data.elasticsearch.annotations.FieldType;
3031
import org.springframework.data.elasticsearch.annotations.Routing;
@@ -73,6 +74,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
7374
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
7475
private @Nullable VersionType versionType;
7576
private boolean createIndexAndMapping;
77+
private Dynamic dynamic = Dynamic.INHERIT;
7678
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
7779
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
7880
private @Nullable String routing;
@@ -102,6 +104,7 @@ public SimpleElasticsearchPersistentEntity(TypeInformation<T> typeInformation,
102104
this.indexName = document.indexName();
103105
this.versionType = document.versionType();
104106
this.createIndexAndMapping = document.createIndex();
107+
this.dynamic = document.dynamic();
105108
}
106109

107110
Routing routingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Routing.class);
@@ -559,4 +562,9 @@ public FieldNamingStrategy getFieldNamingStrategy() {
559562
return fieldNamingStrategy;
560563
}
561564
}
565+
566+
@Override
567+
public Dynamic dynamic() {
568+
return dynamic;
569+
}
562570
}

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java

+38-3
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,18 @@ void shouldWriteMappingForDisabledProperty() {
287287
}
288288

289289
@Test // #1767
290-
@DisplayName("should write dynamic mapping entries")
291-
void shouldWriteDynamicMappingEntries() {
290+
@DisplayName("should write dynamic mapping annotations")
291+
void shouldWriteDynamicMappingAnnotations() {
292+
293+
IndexOperations indexOps = operations.indexOps(DynamicMappingAnnotationEntity.class);
294+
indexOps.create();
295+
indexOps.putMapping();
296+
297+
}
298+
299+
@Test // #1871
300+
@DisplayName("should write dynamic mapping")
301+
void shouldWriteDynamicMapping() {
292302

293303
IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class);
294304
indexOps.create();
@@ -1106,7 +1116,7 @@ public void setDense_vector(@Nullable float[] dense_vector) {
11061116

11071117
@Document(indexName = "dynamic-mapping")
11081118
@DynamicMapping(DynamicMappingValue.False)
1109-
static class DynamicMappingEntity {
1119+
static class DynamicMappingAnnotationEntity {
11101120

11111121
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
11121122
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
@@ -1124,6 +1134,31 @@ public void setAuthor(Author author) {
11241134
}
11251135
}
11261136

1137+
@Document(indexName = "dynamic-mapping", dynamic = Dynamic.FALSE)
1138+
static class DynamicMappingEntity {
1139+
1140+
@Nullable @Field(type = FieldType.Object) //
1141+
private Map<String, Object> objectInherit;
1142+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
1143+
private Map<String, Object> objectFalse;
1144+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
1145+
private Map<String, Object> objectTrue;
1146+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
1147+
private Map<String, Object> objectStrict;
1148+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
1149+
private Map<String, Object> objectRuntime;
1150+
@Nullable @Field(type = FieldType.Nested) //
1151+
private List<Map<String, Object>> nestedObjectInherit;
1152+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
1153+
private List<Map<String, Object>> nestedObjectFalse;
1154+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
1155+
private List<Map<String, Object>> nestedObjectTrue;
1156+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
1157+
private List<Map<String, Object>> nestedObjectStrict;
1158+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
1159+
private List<Map<String, Object>> nestedObjectRuntime;
1160+
}
1161+
11271162
@Document(indexName = "dynamic-detection-mapping-true")
11281163
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
11291164
dynamicDateFormats = { "MM/dd/yyyy" })

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java

+89-4
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ public void shouldSetFieldMappingProperties() throws JSONException {
419419
}
420420

421421
@Test // DATAES-148, #1767
422-
void shouldWriteDynamicMappingSettings() throws JSONException {
422+
void shouldWriteDynamicMappingFromAnnotation() throws JSONException {
423423

424424
String expected = "{\n" + //
425425
" \"dynamic\": \"false\",\n" + //
@@ -451,7 +451,65 @@ void shouldWriteDynamicMappingSettings() throws JSONException {
451451
" }\n" + //
452452
"}"; //
453453

454-
String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
454+
String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingAnnotationEntity.class);
455+
456+
assertEquals(expected, mapping, true);
457+
}
458+
459+
@Test // #1871
460+
void shouldWriteDynamicMapping() throws JSONException {
461+
462+
String expected = "{\n" //
463+
+ " \"dynamic\": \"false\",\n" //
464+
+ " \"properties\": {\n" //
465+
+ " \"_class\": {\n" //
466+
+ " \"type\": \"keyword\",\n" //
467+
+ " \"index\": false,\n" //
468+
+ " \"doc_values\": false\n" //
469+
+ " },\n" //
470+
+ " \"objectInherit\": {\n" //
471+
+ " \"type\": \"object\"\n" //
472+
+ " },\n" //
473+
+ " \"objectFalse\": {\n" //
474+
+ " \"dynamic\": \"false\",\n" //
475+
+ " \"type\": \"object\"\n" //
476+
+ " },\n" //
477+
+ " \"objectTrue\": {\n" //
478+
+ " \"dynamic\": \"true\",\n" //
479+
+ " \"type\": \"object\"\n" //
480+
+ " },\n" //
481+
+ " \"objectStrict\": {\n" //
482+
+ " \"dynamic\": \"strict\",\n" //
483+
+ " \"type\": \"object\"\n" //
484+
+ " },\n" //
485+
+ " \"objectRuntime\": {\n" //
486+
+ " \"dynamic\": \"runtime\",\n" //
487+
+ " \"type\": \"object\"\n" //
488+
+ " },\n" //
489+
+ " \"nestedObjectInherit\": {\n" //
490+
+ " \"type\": \"nested\"\n" //
491+
+ " },\n" //
492+
+ " \"nestedObjectFalse\": {\n" //
493+
+ " \"dynamic\": \"false\",\n" //
494+
+ " \"type\": \"nested\"\n" //
495+
+ " },\n" //
496+
+ " \"nestedObjectTrue\": {\n" //
497+
+ " \"dynamic\": \"true\",\n" //
498+
+ " \"type\": \"nested\"\n" //
499+
+ " },\n" //
500+
+ " \"nestedObjectStrict\": {\n" //
501+
+ " \"dynamic\": \"strict\",\n" //
502+
+ " \"type\": \"nested\"\n" //
503+
+ " },\n" //
504+
+ " \"nestedObjectRuntime\": {\n" //
505+
+ " \"dynamic\": \"runtime\",\n" //
506+
+ " \"type\": \"nested\"\n" //
507+
+ " }\n" //
508+
+ " }\n" //
509+
+ "}\n" //
510+
+ "";
511+
512+
String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingEntity.class);
455513

456514
assertEquals(expected, mapping, true);
457515
}
@@ -865,7 +923,8 @@ void shouldWriteRuntimeFields() throws JSONException {
865923
" \"day_of_week\": {\n" + //
866924
" \"type\": \"keyword\",\n" + //
867925
" \"script\": {\n" + //
868-
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + //
926+
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n"
927+
+ //
869928
" }\n" + //
870929
" }\n" + //
871930
" },\n" + //
@@ -1441,7 +1500,7 @@ static class FieldMappingParameters {
14411500

14421501
@Document(indexName = "test-index-configure-dynamic-mapping")
14431502
@DynamicMapping(DynamicMappingValue.False)
1444-
static class ConfigureDynamicMappingEntity {
1503+
static class DynamicMappingAnnotationEntity {
14451504

14461505
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
14471506
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
@@ -1459,6 +1518,32 @@ public void setAuthor(Author author) {
14591518
}
14601519
}
14611520

1521+
@Document(indexName = "test-index-configure-dynamic-mapping", dynamic = Dynamic.FALSE)
1522+
static class DynamicMappingEntity {
1523+
1524+
@Nullable @Field(type = FieldType.Object) //
1525+
private Map<String, Object> objectInherit;
1526+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
1527+
private Map<String, Object> objectFalse;
1528+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
1529+
private Map<String, Object> objectTrue;
1530+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
1531+
private Map<String, Object> objectStrict;
1532+
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
1533+
private Map<String, Object> objectRuntime;
1534+
@Nullable @Field(type = FieldType.Nested) //
1535+
private List<Map<String, Object>> nestedObjectInherit;
1536+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
1537+
private List<Map<String, Object>> nestedObjectFalse;
1538+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
1539+
private List<Map<String, Object>> nestedObjectTrue;
1540+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
1541+
private List<Map<String, Object>> nestedObjectStrict;
1542+
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
1543+
private List<Map<String, Object>> nestedObjectRuntime;
1544+
1545+
}
1546+
14621547
static class ValueObject {
14631548
private final String value;
14641549

0 commit comments

Comments
 (0)