diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java
new file mode 100644
index 000000000..3d79732ec
--- /dev/null
+++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMapping.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 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;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to set the dynamic mapping mode
+ * {@see elasticsearch doc}
+ *
+ * @author Peter-Josef Meisch
+ * @since 4.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.FIELD })
+@Documented
+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
new file mode 100644
index 000000000..3cec02048
--- /dev/null
+++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicMappingValue.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 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 {@link DynamicMapping annotation}
+ *
+ * @author Peter-Josef Meisch
+ * @since 4.0
+ */
+public enum DynamicMappingValue {
+ True, False, Strict
+}
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 7e3753176..7355a9511 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
@@ -33,6 +33,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.CompletionContext;
import org.springframework.data.elasticsearch.annotations.CompletionField;
+import org.springframework.data.elasticsearch.annotations.DynamicMapping;
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@@ -86,6 +87,7 @@ 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 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_COMPLETION = "completion";
@@ -120,13 +122,9 @@ public class MappingBuilder {
builder.startObject(FIELD_PARENT).field(FIELD_PARAM_TYPE, parentType).endObject();
}
- // Properties
- builder.startObject(FIELD_PROPERTIES);
+ mapEntity(builder, entity, true, "", false, FieldType.Auto, null, entity.findAnnotation(DynamicMapping.class));
- mapEntity(builder, entity, true, "", false, FieldType.Auto, null);
-
- builder.endObject() // FIELD_PROPERTIES
- .endObject() // indexType
+ builder.endObject() // indexType
.endObject() // root object
.close();
@@ -135,7 +133,7 @@ public class MappingBuilder {
private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersistentEntity entity, boolean isRootObject,
String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
- @Nullable Field parentFieldAnnotation) throws IOException {
+ @Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException {
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
if (writeNestedProperties) {
@@ -146,14 +144,17 @@ public class MappingBuilder {
if (nestedOrObjectField && FieldType.Nested == fieldType && parentFieldAnnotation != null
&& parentFieldAnnotation.includeInParent()) {
-
builder.field("include_in_parent", parentFieldAnnotation.includeInParent());
}
-
- builder.startObject(FIELD_PROPERTIES);
}
- if (entity != null) {
+ if (dynamicMapping != null) {
+ builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
+ }
+
+ builder.startObject(FIELD_PROPERTIES);
+
+ if (entity != null) {
entity.doWithProperties((PropertyHandler) property -> {
try {
if (property.isAnnotationPresent(Transient.class) || isInIgnoreFields(property, parentFieldAnnotation)) {
@@ -167,9 +168,12 @@ public class MappingBuilder {
});
}
+ builder.endObject();
+
if (writeNestedProperties) {
- builder.endObject().endObject();
+ builder.endObject();
}
+
}
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
@@ -205,7 +209,7 @@ public class MappingBuilder {
: null;
mapEntity(builder, persistentEntity, false, property.getFieldName(), isNestedOrObjectProperty,
- fieldAnnotation.type(), fieldAnnotation);
+ fieldAnnotation.type(), fieldAnnotation, property.findAnnotation(DynamicMapping.class));
if (isNestedOrObjectProperty) {
return;
diff --git a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderTests.java b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderTests.java
index f2bbab71c..f6cdd63bc 100644
--- a/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderTests.java
+++ b/src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderTests.java
@@ -514,6 +514,27 @@ public class MappingBuilderTests extends MappingContextBaseTests {
assertEquals(expected, mapping, true);
}
+ @Test
+ void shouldWriteDynamicMappingSettings() throws IOException, JSONException {
+
+ String expected = "{\n" + //
+ " \"dms\": {\n" + //
+ " \"dynamic\": \"false\",\n" + //
+ " \"properties\": {\n" + //
+ " \"author\": {\n" + //
+ " \"dynamic\": \"strict\",\n" + //
+ " \"type\": \"object\",\n" + //
+ " \"properties\": {}\n" + //
+ " }\n" + //
+ " }\n" + //
+ " }\n" + //
+ "}\n";
+
+ String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
+
+ assertEquals(expected, mapping, true);
+ }
+
/**
* @author Xiao Yu
*/
@@ -948,4 +969,19 @@ public class MappingBuilderTests extends MappingContextBaseTests {
@Field(termVector = TermVector.with_offsets) private String termVectorWithOffsets;
@Field(type = FieldType.Scaled_Float, scalingFactor = 100.0) Double scaledFloat;
}
+
+ @Document(indexName = "test-index-configure-dynamic-mapping", type = "dms")
+ @DynamicMapping(DynamicMappingValue.False)
+ static class ConfigureDynamicMappingEntity {
+
+ @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
+
+ public Author getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(Author author) {
+ this.author = author;
+ }
+ }
}