From 73bd06340e469d8e871fbd219fff8b6b25a78c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Kukr=C3=A1l?= Date: Tue, 12 Mar 2019 21:07:31 +0100 Subject: [PATCH] DATAES-535 - Add mapping annotation @DynamicTemplates. Original pull request: #238 --- .../annotations/DynamicTemplates.java | 27 ++++++++++ .../elasticsearch/core/MappingBuilder.java | 33 +++++++++++++ .../SimpleDynamicTemplatesMappingTests.java | 49 +++++++++++++++++++ .../SampleDynamicTemplatesEntity.java | 25 ++++++++++ .../SampleDynamicTemplatesEntityTwo.java | 25 ++++++++++ .../test-dynamic_templates_mappings.json | 13 +++++ .../test-dynamic_templates_mappings_two.json | 22 +++++++++ 7 files changed, 194 insertions(+) create mode 100644 src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/core/SimpleDynamicTemplatesMappingTests.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntity.java create mode 100644 src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntityTwo.java create mode 100644 src/test/resources/mappings/test-dynamic_templates_mappings.json create mode 100644 src/test/resources/mappings/test-dynamic_templates_mappings_two.json diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java new file mode 100644 index 000000000..aa831c787 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java @@ -0,0 +1,27 @@ +package org.springframework.data.elasticsearch.annotations; + +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; + +/** + * 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 ommited if {@link Mapping} annotation is used. + * + * @author Petr Kukral + */ +@Persistent +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface DynamicTemplates { + + String mappingPath() default ""; + +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java index b78ed20d2..95d26dc2d 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.core; import static org.elasticsearch.common.xcontent.XContentFactory.*; import static org.springframework.util.StringUtils.*; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -32,6 +33,7 @@ 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.DateFormat; +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.GeoPointField; @@ -45,6 +47,9 @@ import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.util.StringUtils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * @author Rizwan Idrees * @author Mohsin Husen @@ -57,6 +62,7 @@ import org.springframework.util.StringUtils; * @author Sascha Woo * @author Nordine Bittich * @author Robert Gruendler + * @author Petr Kukral */ class MappingBuilder { @@ -74,6 +80,7 @@ class MappingBuilder { public static final String FIELD_CONTEXT_NAME = "name"; public static final String FIELD_CONTEXT_TYPE = "type"; public static final String FIELD_CONTEXT_PRECISION = "precision"; + public static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates"; public static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators"; public static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments"; @@ -91,6 +98,10 @@ class MappingBuilder { static XContentBuilder buildMapping(Class clazz, String indexType, String idFieldName, String parentType) throws IOException { XContentBuilder mapping = jsonBuilder().startObject().startObject(indexType); + + // Dynamic templates + addDynamicTemplatesMapping(mapping, clazz); + // Parent if (hasText(parentType)) { mapping.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject(); @@ -355,6 +366,28 @@ class MappingBuilder { } } + /** + * Apply mapping for dynamic templates. + * + * @throws IOException + */ + private static void addDynamicTemplatesMapping(XContentBuilder builder, Class clazz) throws IOException { + if (clazz.isAnnotationPresent(DynamicTemplates.class)){ + String mappingPath = ((DynamicTemplates) clazz.getAnnotation(DynamicTemplates.class)).mappingPath(); + if (hasText(mappingPath)) { + String jsonString = ElasticsearchTemplate.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); + } + } + } + } + } + protected static boolean isEntity(java.lang.reflect.Field field) { TypeInformation typeInformation = ClassTypeInformation.from(field.getType()); Class clazz = getFieldType(field); diff --git a/src/test/java/org/springframework/data/elasticsearch/core/SimpleDynamicTemplatesMappingTests.java b/src/test/java/org/springframework/data/elasticsearch/core/SimpleDynamicTemplatesMappingTests.java new file mode 100644 index 000000000..f1a1591bc --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/core/SimpleDynamicTemplatesMappingTests.java @@ -0,0 +1,49 @@ +package org.springframework.data.elasticsearch.core; + +import java.io.IOException; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.elasticsearch.entities.SampleDynamicTemplatesEntity; +import org.springframework.data.elasticsearch.entities.SampleDynamicTemplatesEntityTwo; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Dynamic templates tests + * + * @author Petr Kukral + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:elasticsearch-template-test.xml") +public class SimpleDynamicTemplatesMappingTests { + + @Test + public void testCorrectDynamicTemplatesMappings() throws IOException { + XContentBuilder xContentBuilder = MappingBuilder.buildMapping(SampleDynamicTemplatesEntity.class, + "test-dynamictemplatestype", "id", null); + String EXPECTED_MAPPING_ONE = "{\"test-dynamictemplatestype\":{\"dynamic_templates\":" + + "[{\"with_custom_analyzer\":{" + + "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," + + "\"path_match\":\"names.*\"}}]," + + "\"properties\":{\"names\":{\"type\":\"object\"}}}}"; + Assert.assertEquals(EXPECTED_MAPPING_ONE, xContentBuilder.string()); + } + + @Test + public void testCorrectDynamicTemplatesMappingsTwo() throws IOException { + XContentBuilder xContentBuilder = MappingBuilder.buildMapping(SampleDynamicTemplatesEntityTwo.class, + "test-dynamictemplatestype", "id", null); + String EXPECTED_MAPPING_TWO = "{\"test-dynamictemplatestype\":{\"dynamic_templates\":" + + "[{\"with_custom_analyzer\":{" + + "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," + + "\"path_match\":\"names.*\"}}," + + "{\"participantA1_with_custom_analyzer\":{" + + "\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," + + "\"path_match\":\"participantA1.*\"}}]," + + "\"properties\":{\"names\":{\"type\":\"object\"}}}}"; + Assert.assertEquals(EXPECTED_MAPPING_TWO, xContentBuilder.string()); + } +} diff --git a/src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntity.java b/src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntity.java new file mode 100644 index 000000000..570f4fe68 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntity.java @@ -0,0 +1,25 @@ +package org.springframework.data.elasticsearch.entities; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.DynamicTemplates; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +/** + * @author Petr Kukral + */ +@Document(indexName = "test-dynamictemplates", type = "test-dynamictemplatestype", indexStoreType = "memory", shards = 1, + replicas = 0, refreshInterval = "-1") +@DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings.json") +public class SampleDynamicTemplatesEntity { + + @Id + private String id; + + @Field(type = FieldType.Object) + private Map names = new HashMap(); +} diff --git a/src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntityTwo.java b/src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntityTwo.java new file mode 100644 index 000000000..bd3663d04 --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/entities/SampleDynamicTemplatesEntityTwo.java @@ -0,0 +1,25 @@ +package org.springframework.data.elasticsearch.entities; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.DynamicTemplates; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +/** + * @author Petr Kukral + */ +@Document(indexName = "test-dynamictemplates", type = "test-dynamictemplatestype", indexStoreType = "memory", shards = 1, + replicas = 0, refreshInterval = "-1") +@DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings_two.json") +public class SampleDynamicTemplatesEntityTwo { + + @Id + private String id; + + @Field(type = FieldType.Object) + private Map names = new HashMap(); +} diff --git a/src/test/resources/mappings/test-dynamic_templates_mappings.json b/src/test/resources/mappings/test-dynamic_templates_mappings.json new file mode 100644 index 000000000..61c847789 --- /dev/null +++ b/src/test/resources/mappings/test-dynamic_templates_mappings.json @@ -0,0 +1,13 @@ +{ + "dynamic_templates": [ + { + "with_custom_analyzer": { + "mapping": { + "type": "string", + "analyzer": "standard_lowercase_asciifolding" + }, + "path_match": "names.*" + } + } + ] +} diff --git a/src/test/resources/mappings/test-dynamic_templates_mappings_two.json b/src/test/resources/mappings/test-dynamic_templates_mappings_two.json new file mode 100644 index 000000000..32ffe560c --- /dev/null +++ b/src/test/resources/mappings/test-dynamic_templates_mappings_two.json @@ -0,0 +1,22 @@ +{ + "dynamic_templates": [ + { + "with_custom_analyzer": { + "mapping": { + "type": "string", + "analyzer": "standard_lowercase_asciifolding" + }, + "path_match": "names.*" + } + }, + { + "participantA1_with_custom_analyzer": { + "mapping": { + "type": "string", + "analyzer": "standard_lowercase_asciifolding" + }, + "path_match": "participantA1.*" + } + } + ] +}