Skip to content

Commit 11ba4c5

Browse files
Petr Kukrálxhaggi
authored andcommitted
DATAES-535 - Add mapping annotation @DynamicTemplates.
Original pull request: spring-projects#238
1 parent efb5d9e commit 11ba4c5

File tree

7 files changed

+195
-0
lines changed

7 files changed

+195
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.springframework.data.elasticsearch.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Inherited;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
import org.springframework.data.annotation.Persistent;
10+
11+
/**
12+
* Elasticsearch dynamic templates mapping.
13+
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
14+
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
15+
* DynamicTemplates annotation is ommited if {@link Mapping} annotation is used.
16+
*
17+
* @author Petr Kukral
18+
*/
19+
@Persistent
20+
@Inherited
21+
@Retention(RetentionPolicy.RUNTIME)
22+
@Target({ElementType.TYPE})
23+
public @interface DynamicTemplates {
24+
25+
String mappingPath() default "";
26+
27+
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.elasticsearch.common.xcontent.XContentFactory.*;
1919
import static org.springframework.util.StringUtils.*;
2020

21+
import java.io.ByteArrayInputStream;
2122
import java.io.IOException;
2223
import java.util.ArrayList;
2324
import java.util.Arrays;
@@ -32,6 +33,7 @@
3233
import org.springframework.data.elasticsearch.annotations.CompletionContext;
3334
import org.springframework.data.elasticsearch.annotations.CompletionField;
3435
import org.springframework.data.elasticsearch.annotations.DateFormat;
36+
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
3537
import org.springframework.data.elasticsearch.annotations.Field;
3638
import org.springframework.data.elasticsearch.annotations.FieldType;
3739
import org.springframework.data.elasticsearch.annotations.GeoPointField;
@@ -45,6 +47,9 @@
4547
import org.springframework.data.util.TypeInformation;
4648
import org.springframework.util.StringUtils;
4749

50+
import com.fasterxml.jackson.databind.JsonNode;
51+
import com.fasterxml.jackson.databind.ObjectMapper;
52+
4853
/**
4954
* @author Rizwan Idrees
5055
* @author Mohsin Husen
@@ -57,6 +62,7 @@
5762
* @author Sascha Woo
5863
* @author Nordine Bittich
5964
* @author Robert Gruendler
65+
* @author Petr Kukral
6066
*/
6167
class MappingBuilder {
6268

@@ -74,6 +80,7 @@ class MappingBuilder {
7480
public static final String FIELD_CONTEXT_NAME = "name";
7581
public static final String FIELD_CONTEXT_TYPE = "type";
7682
public static final String FIELD_CONTEXT_PRECISION = "precision";
83+
public static final String FIELD_DYNAMIC_TEMPLATES = "dynamic_templates";
7784

7885
public static final String COMPLETION_PRESERVE_SEPARATORS = "preserve_separators";
7986
public static final String COMPLETION_PRESERVE_POSITION_INCREMENTS = "preserve_position_increments";
@@ -91,6 +98,10 @@ class MappingBuilder {
9198
static XContentBuilder buildMapping(Class<?> clazz, String indexType, String idFieldName, String parentType) throws IOException {
9299

93100
XContentBuilder mapping = jsonBuilder().startObject().startObject(indexType);
101+
102+
// Dynamic templates
103+
addDynamicTemplatesMapping(mapping, clazz);
104+
94105
// Parent
95106
if (hasText(parentType)) {
96107
mapping.startObject(FIELD_PARENT).field(FIELD_TYPE, parentType).endObject();
@@ -355,6 +366,28 @@ private static void addFieldMappingParameters(XContentBuilder builder, Object an
355366
}
356367
}
357368

369+
/**
370+
* Apply mapping for dynamic templates.
371+
*
372+
* @throws IOException
373+
*/
374+
private static void addDynamicTemplatesMapping(XContentBuilder builder, Class<?> clazz) throws IOException {
375+
if (clazz.isAnnotationPresent(DynamicTemplates.class)){
376+
String mappingPath = ((DynamicTemplates) clazz.getAnnotation(DynamicTemplates.class)).mappingPath();
377+
if (hasText(mappingPath)) {
378+
String jsonString = ElasticsearchTemplate.readFileFromClasspath(mappingPath);
379+
if (hasText(jsonString)) {
380+
ObjectMapper objectMapper = new ObjectMapper();
381+
JsonNode jsonNode = objectMapper.readTree(jsonString).get("dynamic_templates");
382+
if (jsonNode != null && jsonNode.isArray()){
383+
String json = objectMapper.writeValueAsString(jsonNode);
384+
builder.rawField(FIELD_DYNAMIC_TEMPLATES, new ByteArrayInputStream(json.getBytes()), XContentType.JSON);
385+
}
386+
}
387+
}
388+
}
389+
}
390+
358391
protected static boolean isEntity(java.lang.reflect.Field field) {
359392
TypeInformation<?> typeInformation = ClassTypeInformation.from(field.getType());
360393
Class<?> clazz = getFieldType(field);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.springframework.data.elasticsearch.core;
2+
3+
import java.io.IOException;
4+
5+
import org.elasticsearch.common.Strings;
6+
import org.elasticsearch.common.xcontent.XContentBuilder;
7+
import org.junit.Assert;
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
import org.springframework.data.elasticsearch.entities.SampleDynamicTemplatesEntity;
11+
import org.springframework.data.elasticsearch.entities.SampleDynamicTemplatesEntityTwo;
12+
import org.springframework.test.context.ContextConfiguration;
13+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
14+
15+
/**
16+
* Dynamic templates tests
17+
*
18+
* @author Petr Kukral
19+
*/
20+
@RunWith(SpringJUnit4ClassRunner.class)
21+
@ContextConfiguration("classpath:elasticsearch-template-test.xml")
22+
public class SimpleDynamicTemplatesMappingTests {
23+
24+
@Test
25+
public void testCorrectDynamicTemplatesMappings() throws IOException {
26+
XContentBuilder xContentBuilder = MappingBuilder.buildMapping(SampleDynamicTemplatesEntity.class,
27+
"test-dynamictemplatestype", "id", null);
28+
String EXPECTED_MAPPING_ONE = "{\"test-dynamictemplatestype\":{\"dynamic_templates\":" +
29+
"[{\"with_custom_analyzer\":{" +
30+
"\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," +
31+
"\"path_match\":\"names.*\"}}]," +
32+
"\"properties\":{\"names\":{\"type\":\"object\"}}}}";
33+
Assert.assertEquals(EXPECTED_MAPPING_ONE, Strings.toString(xContentBuilder));
34+
}
35+
36+
@Test
37+
public void testCorrectDynamicTemplatesMappingsTwo() throws IOException {
38+
XContentBuilder xContentBuilder = MappingBuilder.buildMapping(SampleDynamicTemplatesEntityTwo.class,
39+
"test-dynamictemplatestype", "id", null);
40+
String EXPECTED_MAPPING_TWO = "{\"test-dynamictemplatestype\":{\"dynamic_templates\":" +
41+
"[{\"with_custom_analyzer\":{" +
42+
"\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," +
43+
"\"path_match\":\"names.*\"}}," +
44+
"{\"participantA1_with_custom_analyzer\":{" +
45+
"\"mapping\":{\"type\":\"string\",\"analyzer\":\"standard_lowercase_asciifolding\"}," +
46+
"\"path_match\":\"participantA1.*\"}}]," +
47+
"\"properties\":{\"names\":{\"type\":\"object\"}}}}";
48+
Assert.assertEquals(EXPECTED_MAPPING_TWO, Strings.toString(xContentBuilder));
49+
}
50+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.springframework.data.elasticsearch.entities;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import org.springframework.data.annotation.Id;
7+
import org.springframework.data.elasticsearch.annotations.Document;
8+
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
9+
import org.springframework.data.elasticsearch.annotations.Field;
10+
import org.springframework.data.elasticsearch.annotations.FieldType;
11+
12+
/**
13+
* @author Petr Kukral
14+
*/
15+
@Document(indexName = "test-dynamictemplates", type = "test-dynamictemplatestype", indexStoreType = "memory", shards = 1,
16+
replicas = 0, refreshInterval = "-1")
17+
@DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings.json")
18+
public class SampleDynamicTemplatesEntity {
19+
20+
@Id
21+
private String id;
22+
23+
@Field(type = FieldType.Object)
24+
private Map<String, String> names = new HashMap<String, String>();
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.springframework.data.elasticsearch.entities;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
import org.springframework.data.annotation.Id;
7+
import org.springframework.data.elasticsearch.annotations.Document;
8+
import org.springframework.data.elasticsearch.annotations.DynamicTemplates;
9+
import org.springframework.data.elasticsearch.annotations.Field;
10+
import org.springframework.data.elasticsearch.annotations.FieldType;
11+
12+
/**
13+
* @author Petr Kukral
14+
*/
15+
@Document(indexName = "test-dynamictemplates", type = "test-dynamictemplatestype", indexStoreType = "memory", shards = 1,
16+
replicas = 0, refreshInterval = "-1")
17+
@DynamicTemplates(mappingPath = "/mappings/test-dynamic_templates_mappings_two.json")
18+
public class SampleDynamicTemplatesEntityTwo {
19+
20+
@Id
21+
private String id;
22+
23+
@Field(type = FieldType.Object)
24+
private Map<String, String> names = new HashMap<String, String>();
25+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"dynamic_templates": [
3+
{
4+
"with_custom_analyzer": {
5+
"mapping": {
6+
"type": "string",
7+
"analyzer": "standard_lowercase_asciifolding"
8+
},
9+
"path_match": "names.*"
10+
}
11+
}
12+
]
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"dynamic_templates": [
3+
{
4+
"with_custom_analyzer": {
5+
"mapping": {
6+
"type": "string",
7+
"analyzer": "standard_lowercase_asciifolding"
8+
},
9+
"path_match": "names.*"
10+
}
11+
},
12+
{
13+
"participantA1_with_custom_analyzer": {
14+
"mapping": {
15+
"type": "string",
16+
"analyzer": "standard_lowercase_asciifolding"
17+
},
18+
"path_match": "participantA1.*"
19+
}
20+
}
21+
]
22+
}

0 commit comments

Comments
 (0)