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