From 1b0006f9f704064e836a75a87444004864873f00 Mon Sep 17 00:00:00 2001 From: Nordine Bittich Date: Fri, 23 Mar 2018 14:50:26 +0100 Subject: [PATCH] DATAES-420 - Analyzer of main field ignored when using @MultiField annotation --- .../elasticsearch/annotations/InnerField.java | 6 +- .../elasticsearch/core/MappingBuilder.java | 151 ++++++++++-------- .../core/MappingBuilderTests.java | 24 ++- .../core/facet/ArticleEntity.java | 4 +- .../data/elasticsearch/entities/Book.java | 10 ++ 5 files changed, 125 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java index ee850d8fe..2a0a95def 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/InnerField.java @@ -33,11 +33,15 @@ public @interface InnerField { boolean index() default true; + DateFormat format() default DateFormat.none; + + String pattern() default ""; + boolean store() default false; boolean fielddata() default false; String searchAnalyzer() default ""; - String indexAnalyzer() default ""; + String analyzer() 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 cff1f3b26..78fd60498 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -51,6 +51,7 @@ import static org.springframework.util.StringUtils.*; * @author Pavel Luhin * @author Mark Paluch * @author Sascha Woo + * @author Nordine Bittich */ class MappingBuilder { @@ -221,83 +222,101 @@ class MappingBuilder { } /** - * Apply mapping for a single @Field annotation + * Add mapping for @Field annotation * * @throws IOException */ - private static void addSingleFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field, - Field fieldAnnotation, boolean nestedOrObjectField) throws IOException { - xContentBuilder.startObject(field.getName()); - if(!nestedOrObjectField) { - xContentBuilder.field(FIELD_STORE, fieldAnnotation.store()); + private static void addSingleFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, Field annotation, boolean nestedOrObjectField) throws IOException { + builder.startObject(field.getName()); + addFieldMappingParameters(builder, annotation, nestedOrObjectField); + builder.endObject(); + } + + /** + * Add mapping for @MultiField annotation + * + * @throws IOException + */ + private static void addMultiFieldMapping( + XContentBuilder builder, + java.lang.reflect.Field field, + MultiField annotation, + boolean nestedOrObjectField) throws IOException { + + // main field + builder.startObject(field.getName()); + addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField); + + // inner fields + builder.startObject("fields"); + for (InnerField innerField : annotation.otherFields()) { + builder.startObject(innerField.suffix()); + addFieldMappingParameters(builder, innerField, false); + builder.endObject(); } - if(fieldAnnotation.fielddata()) { - xContentBuilder.field(FIELD_DATA, fieldAnnotation.fielddata()); + builder.endObject(); + + builder.endObject(); + } + + private static void addFieldMappingParameters(XContentBuilder builder, Object annotation, boolean nestedOrObjectField) throws IOException { + boolean index = true; + boolean store = false; + boolean fielddata = false; + FieldType type = null; + DateFormat dateFormat = null; + String datePattern = null; + String analyzer = null; + String searchAnalyzer = null; + + if (annotation instanceof Field) { + // @Field + Field fieldAnnotation = (Field) annotation; + index = fieldAnnotation.index(); + store = fieldAnnotation.store(); + fielddata = fieldAnnotation.fielddata(); + type = fieldAnnotation.type(); + dateFormat = fieldAnnotation.format(); + datePattern = fieldAnnotation.pattern(); + analyzer = fieldAnnotation.analyzer(); + searchAnalyzer = fieldAnnotation.searchAnalyzer(); + } else if (annotation instanceof InnerField) { + // @InnerField + InnerField fieldAnnotation = (InnerField) annotation; + index = fieldAnnotation.index(); + store = fieldAnnotation.store(); + fielddata = fieldAnnotation.fielddata(); + type = fieldAnnotation.type(); + dateFormat = fieldAnnotation.format(); + datePattern = fieldAnnotation.pattern(); + analyzer = fieldAnnotation.analyzer(); + searchAnalyzer = fieldAnnotation.searchAnalyzer(); + } else { + throw new IllegalArgumentException("annotation must be an instance of @Field or @InnerField"); } - if (FieldType.Auto != fieldAnnotation.type()) { - xContentBuilder.field(FIELD_TYPE, fieldAnnotation.type().name().toLowerCase()); - if (FieldType.Date == fieldAnnotation.type() && DateFormat.none != fieldAnnotation.format()) { - xContentBuilder.field(FIELD_FORMAT, DateFormat.custom == fieldAnnotation.format() - ? fieldAnnotation.pattern() : fieldAnnotation.format()); + if (!nestedOrObjectField) { + builder.field(FIELD_STORE, store); + } + if (fielddata) { + builder.field(FIELD_DATA, fielddata); + } + if (type != FieldType.Auto) { + builder.field(FIELD_TYPE, type.name().toLowerCase()); + + if (type == FieldType.Date && dateFormat != DateFormat.none) { + builder.field(FIELD_FORMAT, dateFormat == DateFormat.custom ? datePattern : dateFormat.toString()); } } - if(!fieldAnnotation.index()) { - xContentBuilder.field(FIELD_INDEX, fieldAnnotation.index()); + if (!index) { + builder.field(FIELD_INDEX, index); } - if (isNotBlank(fieldAnnotation.searchAnalyzer())) { - xContentBuilder.field(FIELD_SEARCH_ANALYZER, fieldAnnotation.searchAnalyzer()); + if (isNotBlank(analyzer)) { + builder.field(FIELD_INDEX_ANALYZER, analyzer); } - if (isNotBlank(fieldAnnotation.analyzer())) { - xContentBuilder.field(FIELD_INDEX_ANALYZER, fieldAnnotation.analyzer()); + if (isNotBlank(searchAnalyzer)) { + builder.field(FIELD_SEARCH_ANALYZER, searchAnalyzer); } - xContentBuilder.endObject(); - } - - /** - * Apply mapping for a single nested @Field annotation - * - * @throws IOException - */ - private static void addNestedFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, - InnerField annotation) throws IOException { - builder.startObject(annotation.suffix()); - //builder.field(FIELD_STORE, annotation.store()); - if (FieldType.Auto != annotation.type()) { - builder.field(FIELD_TYPE, annotation.type().name().toLowerCase()); - } - if(!annotation.index()) { - builder.field(FIELD_INDEX, annotation.index()); - } - if (isNotBlank(annotation.searchAnalyzer())) { - builder.field(FIELD_SEARCH_ANALYZER, annotation.searchAnalyzer()); - } - if (isNotBlank(annotation.indexAnalyzer())) { - builder.field(FIELD_INDEX_ANALYZER, annotation.indexAnalyzer()); - } - if (annotation.fielddata()) { - builder.field(FIELD_DATA, annotation.fielddata()); - } - builder.endObject(); - } - - /** - * Multi field mappings for string type fields, support for sorts and facets - * - * @throws IOException - */ - private static void addMultiFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, - MultiField annotation, boolean nestedOrObjectField) throws IOException { - builder.startObject(field.getName()); - builder.field(FIELD_TYPE, annotation.mainField().type().name().toLowerCase()); - builder.startObject("fields"); - //add standard field - //addSingleFieldMapping(builder, field, annotation.mainField(), nestedOrObjectField); - for (InnerField innerField : annotation.otherFields()) { - addNestedFieldMapping(builder, field, innerField); - } - builder.endObject(); - builder.endObject(); } protected static boolean isEntity(java.lang.reflect.Field field) { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/MappingBuilderTests.java b/src/test/java/org/springframework/data/elasticsearch/core/MappingBuilderTests.java index d47978b33..ebd1754d5 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/MappingBuilderTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/MappingBuilderTests.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.math.BigDecimal; import java.util.Date; import java.util.List; +import java.util.Map; import org.elasticsearch.common.xcontent.XContentBuilder; import org.junit.Test; @@ -43,6 +44,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Jakub Vavrik * @author Mohsin Husen * @author Keivn Leturc + * @author Nordine Bittich */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:elasticsearch-template-test.xml") @@ -189,8 +191,28 @@ public class MappingBuilderTests { elasticsearchTemplate.createIndex(Book.class); elasticsearchTemplate.putMapping(Book.class); //when - //then } + + @Test // DATAES-420 + public void shouldUseBothAnalyzer() { + //given + elasticsearchTemplate.deleteIndex(Book.class); + elasticsearchTemplate.createIndex(Book.class); + elasticsearchTemplate.putMapping(Book.class); + + //when + Map mapping = elasticsearchTemplate.getMapping(Book.class); + Map descriptionMapping = (Map) ((Map) mapping.get("properties")).get("description"); + Map prefixDescription = (Map) ((Map) descriptionMapping.get("fields")).get("prefix"); + + //then + assertThat(prefixDescription.size(), is(3)); + assertThat(prefixDescription.get("type"), equalTo("text")); + assertThat(prefixDescription.get("analyzer"), equalTo("stop")); + assertThat(prefixDescription.get("search_analyzer"), equalTo("standard")); + assertThat(descriptionMapping.get("type"), equalTo("text")); + assertThat(descriptionMapping.get("analyzer"), equalTo("whitespace")); + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java b/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java index fe5ddaed8..5a64c1ebe 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/facet/ArticleEntity.java @@ -42,8 +42,8 @@ public class ArticleEntity { @MultiField( mainField = @Field(type = Text), otherFields = { - @InnerField(suffix = "untouched", type = Text, store = true, fielddata = true, indexAnalyzer = "keyword"), - @InnerField(suffix = "sort", type = Text, store = true, indexAnalyzer = "keyword") + @InnerField(suffix = "untouched", type = Text, store = true, fielddata = true, analyzer = "keyword"), + @InnerField(suffix = "sort", type = Text, store = true, analyzer = "keyword") } ) private List authors = new ArrayList<>(); diff --git a/src/test/java/org/springframework/data/elasticsearch/entities/Book.java b/src/test/java/org/springframework/data/elasticsearch/entities/Book.java index 15179a89b..43d4ab20d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/entities/Book.java +++ b/src/test/java/org/springframework/data/elasticsearch/entities/Book.java @@ -29,10 +29,13 @@ import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.InnerField; +import org.springframework.data.elasticsearch.annotations.MultiField; /** * @author Rizwan Idrees * @author Mohsin Husen + * @author Nordine Bittich */ @Setter @Getter @@ -49,4 +52,11 @@ public class Book { private Author author; @Field(type = FieldType.Nested) private Map> buckets = new HashMap<>(); + @MultiField( + mainField = @Field(type = FieldType.Text, analyzer = "whitespace"), + otherFields = { + @InnerField(suffix = "prefix", type = FieldType.Text, analyzer = "stop", searchAnalyzer = "standard") + } + ) + private String description; }