From 16689390f30dd53194fb4db0b265ccf5dffce452 Mon Sep 17 00:00:00 2001 From: Petar Tahchiev Date: Wed, 11 May 2016 09:42:47 +0200 Subject: [PATCH 1/4] DATAES-20 --- .../data/elasticsearch/annotations/Field.java | 2 +- .../elasticsearch/core/MappingBuilder.java | 162 +++++++++++------- 2 files changed, 100 insertions(+), 64 deletions(-) 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 29dcdf2f1..a4e9899d3 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -31,7 +31,7 @@ * @author Kevin Leturc */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target(value = {ElementType.FIELD, ElementType.METHOD}) @Documented @Inherited public @interface Field { 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 249a5422e..f191e0329 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -19,7 +19,10 @@ import static org.elasticsearch.common.xcontent.XContentFactory.*; import static org.springframework.util.StringUtils.*; +import java.beans.Introspector; import java.io.IOException; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -91,9 +94,13 @@ static XContentBuilder buildMapping(Class clazz, String indexType, String idFiel private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, boolean isRootObject, String idFieldName, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, Field fieldAnnotation) throws IOException { - java.lang.reflect.Field[] fields = retrieveFields(clazz); + final List members = new ArrayList(); - if (!isRootObject && (isAnyPropertyAnnotatedAsField(fields) || nestedOrObjectField)) { + final java.lang.reflect.Field[] fields = clazz.getDeclaredFields(); + members.addAll(Arrays.asList(fields)); + members.addAll(Arrays.asList(clazz.getDeclaredMethods())); + + if (!isRootObject && (isAnyPropertyAnnotatedAsField(members) || nestedOrObjectField)) { String type = FieldType.Object.toString().toLowerCase(); if (nestedOrObjectField) { type = fieldType.toString().toLowerCase(); @@ -106,59 +113,59 @@ private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, bool t.startObject(FIELD_PROPERTIES); } - for (java.lang.reflect.Field field : fields) { + for (java.lang.reflect.AccessibleObject member : members) { - if (field.isAnnotationPresent(Transient.class) || isInIgnoreFields(field)) { + if (member.isAnnotationPresent(Transient.class) || isInIgnoreFields(member)) { continue; } - if (field.isAnnotationPresent(Mapping.class)) { - String mappingPath = field.getAnnotation(Mapping.class).mappingPath(); + if (member.isAnnotationPresent(Mapping.class)) { + String mappingPath = member.getAnnotation(Mapping.class).mappingPath(); if (isNotBlank(mappingPath)) { ClassPathResource mappings = new ClassPathResource(mappingPath); if (mappings.exists()) { - xContentBuilder.rawField(field.getName(), mappings.getInputStream()); + xContentBuilder.rawField(getMemberName(member), mappings.getInputStream()); continue; } } } - boolean isGeoPointField = isGeoPointField(field); - boolean isCompletionField = isCompletionField(field); + boolean isGeoPointField = isGeoPointField(member); + boolean isCompletionField = isCompletionField(member); - Field singleField = field.getAnnotation(Field.class); - if (!isGeoPointField && !isCompletionField && isEntity(field) && isAnnotated(field)) { + Field singleField = member.getAnnotation(Field.class); + if (!isGeoPointField && !isCompletionField && isEntity(member) && isAnnotated(member)) { if (singleField == null) { continue; } - boolean nestedOrObject = isNestedOrObjectField(field); - mapEntity(xContentBuilder, getFieldType(field), false, EMPTY, field.getName(), nestedOrObject, singleField.type(), field.getAnnotation(Field.class)); + boolean nestedOrObject = isNestedOrObjectField(member); + mapEntity(xContentBuilder, getFieldType(member), false, EMPTY, getMemberName(member), nestedOrObject, singleField.type(), member.getAnnotation(Field.class)); if (nestedOrObject) { continue; } } - MultiField multiField = field.getAnnotation(MultiField.class); + MultiField multiField = member.getAnnotation(MultiField.class); if (isGeoPointField) { - applyGeoPointFieldMapping(xContentBuilder, field); + applyGeoPointFieldMapping(xContentBuilder, member); } if (isCompletionField) { - CompletionField completionField = field.getAnnotation(CompletionField.class); - applyCompletionFieldMapping(xContentBuilder, field, completionField); + CompletionField completionField = member.getAnnotation(CompletionField.class); + applyCompletionFieldMapping(xContentBuilder, member, completionField); } - if (isRootObject && singleField != null && isIdField(field, idFieldName)) { - applyDefaultIdFieldMapping(xContentBuilder, field); + if (isRootObject && singleField != null && isIdField(member, idFieldName)) { + applyDefaultIdFieldMapping(xContentBuilder, member); } else if (multiField != null) { - addMultiFieldMapping(xContentBuilder, field, multiField, isNestedOrObjectField(field)); + addMultiFieldMapping(xContentBuilder, member, multiField, isNestedOrObjectField(member)); } else if (singleField != null) { - addSingleFieldMapping(xContentBuilder, field, singleField, isNestedOrObjectField(field)); + addSingleFieldMapping(xContentBuilder, member, singleField, isNestedOrObjectField(member)); } } - if (!isRootObject && isAnyPropertyAnnotatedAsField(fields) || nestedOrObjectField) { + if (!isRootObject && isAnyPropertyAnnotatedAsField(members) || nestedOrObjectField) { xContentBuilder.endObject().endObject(); } } @@ -178,18 +185,18 @@ private static java.lang.reflect.Field[] retrieveFields(Class clazz) { return fields.toArray(new java.lang.reflect.Field[fields.size()]); } - private static boolean isAnnotated(java.lang.reflect.Field field) { - return field.getAnnotation(Field.class) != null || - field.getAnnotation(MultiField.class) != null || - field.getAnnotation(GeoPointField.class) != null || - field.getAnnotation(CompletionField.class) != null; + private static boolean isAnnotated(java.lang.reflect.AccessibleObject member) { + return member.getAnnotation(Field.class) != null || + member.getAnnotation(MultiField.class) != null || + member.getAnnotation(GeoPointField.class) != null || + member.getAnnotation(CompletionField.class) != null; } - private static void applyGeoPointFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) throws IOException { - xContentBuilder.startObject(field.getName()); + private static void applyGeoPointFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.AccessibleObject member) throws IOException { + xContentBuilder.startObject(getMemberName(member)); xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_GEO_POINT); - GeoPointField annotation = field.getAnnotation(GeoPointField.class); + GeoPointField annotation = member.getAnnotation(GeoPointField.class); if (annotation != null) { if (annotation.geoHashPrefix()) { xContentBuilder.field(TYPE_VALUE_GEO_HASH_PREFIX, true); @@ -206,8 +213,8 @@ private static void applyGeoPointFieldMapping(XContentBuilder xContentBuilder, j xContentBuilder.endObject(); } - private static void applyCompletionFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field, CompletionField annotation) throws IOException { - xContentBuilder.startObject(field.getName()); + private static void applyCompletionFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.AccessibleObject member, CompletionField annotation) throws IOException { + xContentBuilder.startObject(getMemberName(member)); xContentBuilder.field(FIELD_TYPE, TYPE_VALUE_COMPLETION); if (annotation != null) { xContentBuilder.field(COMPLETION_MAX_INPUT_LENGTH, annotation.maxInputLength()); @@ -224,9 +231,9 @@ private static void applyCompletionFieldMapping(XContentBuilder xContentBuilder, xContentBuilder.endObject(); } - private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field) + private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.AccessibleObject member) throws IOException { - xContentBuilder.startObject(field.getName()) + xContentBuilder.startObject(getMemberName(member)) .field(FIELD_TYPE, TYPE_VALUE_STRING) .field(FIELD_INDEX, INDEX_VALUE_NOT_ANALYZED); xContentBuilder.endObject(); @@ -237,9 +244,9 @@ private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, * * @throws IOException */ - private static void addSingleFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.Field field, + private static void addSingleFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.AccessibleObject member, Field fieldAnnotation, boolean nestedOrObjectField) throws IOException { - xContentBuilder.startObject(field.getName()); + xContentBuilder.startObject(getMemberName(member)); if(!nestedOrObjectField) { xContentBuilder.field(FIELD_STORE, fieldAnnotation.store()); } @@ -267,10 +274,10 @@ private static void addSingleFieldMapping(XContentBuilder xContentBuilder, java. * * @throws IOException */ - private static void addNestedFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, + private static void addNestedFieldMapping(XContentBuilder builder, java.lang.reflect.AccessibleObject member, InnerField annotation) throws IOException { builder.startObject(annotation.suffix()); - //builder.field(FIELD_STORE, annotation.store()); + //builder.member(FIELD_STORE, annotation.store()); if (FieldType.Auto != annotation.type()) { builder.field(FIELD_TYPE, annotation.type().name().toLowerCase()); } @@ -291,40 +298,44 @@ private static void addNestedFieldMapping(XContentBuilder builder, java.lang.ref * * @throws IOException */ - private static void addMultiFieldMapping(XContentBuilder builder, java.lang.reflect.Field field, + private static void addMultiFieldMapping(XContentBuilder builder, java.lang.reflect.AccessibleObject member, MultiField annotation, boolean nestedOrObjectField) throws IOException { - builder.startObject(field.getName()); + builder.startObject(getMemberName(member)); builder.field(FIELD_TYPE, "multi_field"); builder.startObject("fields"); //add standard field - addSingleFieldMapping(builder, field, annotation.mainField(),nestedOrObjectField); + addSingleFieldMapping(builder, member, annotation.mainField(),nestedOrObjectField); for (InnerField innerField : annotation.otherFields()) { - addNestedFieldMapping(builder, field, innerField); + addNestedFieldMapping(builder, member, innerField); } builder.endObject(); builder.endObject(); } - protected static boolean isEntity(java.lang.reflect.Field field) { - TypeInformation typeInformation = ClassTypeInformation.from(field.getType()); - Class clazz = getFieldType(field); + protected static boolean isEntity(java.lang.reflect.AccessibleObject member) { + TypeInformation typeInformation = ClassTypeInformation.from(getMemberType(member)); + Class clazz = getMemberType(member); boolean isComplexType = !SIMPLE_TYPE_HOLDER.isSimpleType(clazz); return isComplexType && !Map.class.isAssignableFrom(typeInformation.getType()); } - protected static Class getFieldType(java.lang.reflect.Field field) { - Class clazz = field.getType(); + protected static Class getFieldType(java.lang.reflect.AccessibleObject member) { + Class clazz = getMemberType(member); TypeInformation typeInformation = ClassTypeInformation.from(clazz); if (typeInformation.isCollectionLike()) { - clazz = GenericCollectionTypeResolver.getCollectionFieldType(field) != null ? GenericCollectionTypeResolver.getCollectionFieldType(field) : typeInformation.getComponentType().getType(); + if (member instanceof java.lang.reflect.Field) { + clazz = GenericCollectionTypeResolver.getCollectionFieldType((java.lang.reflect.Field) member) != null + ? GenericCollectionTypeResolver.getCollectionFieldType((java.lang.reflect.Field) member) + : typeInformation.getComponentType().getType(); + } } return clazz; } - private static boolean isAnyPropertyAnnotatedAsField(java.lang.reflect.Field[] fields) { - if (fields != null) { - for (java.lang.reflect.Field field : fields) { - if (field.isAnnotationPresent(Field.class)) { + private static boolean isAnyPropertyAnnotatedAsField(List members) { + if (members != null) { + for (java.lang.reflect.AccessibleObject member : members) { + if (member.isAnnotationPresent(Field.class)) { return true; } } @@ -332,29 +343,54 @@ private static boolean isAnyPropertyAnnotatedAsField(java.lang.reflect.Field[] f return false; } - private static boolean isIdField(java.lang.reflect.Field field, String idFieldName) { - return idFieldName.equals(field.getName()); + private static boolean isIdField(java.lang.reflect.AccessibleObject member, String idFieldName) { + return idFieldName.equals(getMemberName(member)); } - private static boolean isInIgnoreFields(java.lang.reflect.Field field) { - Field fieldAnnotation = field.getAnnotation(Field.class); + private static boolean isInIgnoreFields(java.lang.reflect.AccessibleObject member) { + Field fieldAnnotation = member.getAnnotation(Field.class); if (null != fieldAnnotation) { String[] ignoreFields = fieldAnnotation.ignoreFields(); - return Arrays.asList(ignoreFields).contains(field.getName()); + return Arrays.asList(ignoreFields).contains(getMemberName(member)); } return false; } - private static boolean isNestedOrObjectField(java.lang.reflect.Field field) { - Field fieldAnnotation = field.getAnnotation(Field.class); + private static boolean isNestedOrObjectField(java.lang.reflect.AccessibleObject member) { + Field fieldAnnotation = member.getAnnotation(Field.class); return fieldAnnotation != null && (FieldType.Nested == fieldAnnotation.type() || FieldType.Object == fieldAnnotation.type()); } - private static boolean isGeoPointField(java.lang.reflect.Field field) { - return field.getType() == GeoPoint.class || field.getAnnotation(GeoPointField.class) != null; + private static boolean isGeoPointField(java.lang.reflect.AccessibleObject member) { + return getMemberType(member) == GeoPoint.class || member.getAnnotation(GeoPointField.class) != null; + } + + private static boolean isCompletionField(java.lang.reflect.AccessibleObject member) { + return getMemberType(member) == Completion.class; + } + + private static String getMemberName(final java.lang.reflect.AccessibleObject member) { + if (member instanceof java.lang.reflect.Field) { + return ((java.lang.reflect.Field) member).getName(); + } else { + final Method setter = (Method) member; + final String sname = setter.getName(); + if (sname.startsWith("set")) { + return Introspector.decapitalize(sname.substring(3)); + } + return null; + } } - private static boolean isCompletionField(java.lang.reflect.Field field) { - return field.getType() == Completion.class; + private static Class getMemberType(final java.lang.reflect.AccessibleObject member) { + if (member instanceof java.lang.reflect.Field) { + return ((java.lang.reflect.Field) member).getType(); + } else { + final Method setter = (Method) member; + if (setter.getParameterTypes() != null && setter.getParameterTypes().length > 0) { + return setter.getParameterTypes()[0]; + } + } + return null; } } From 1c142213cdf59824bc7e6563bd1e6ef56ce85619 Mon Sep 17 00:00:00 2001 From: Petar Tahchiev Date: Wed, 11 May 2016 09:52:55 +0200 Subject: [PATCH 2/4] Add myself for javadoc --- .../springframework/data/elasticsearch/annotations/Field.java | 1 + .../springframework/data/elasticsearch/core/MappingBuilder.java | 1 + 2 files changed, 2 insertions(+) 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 a4e9899d3..c4ee8c491 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/Field.java @@ -29,6 +29,7 @@ * @author Jonathan Yan * @author Jakub Vavrik * @author Kevin Leturc + * @author Petar Tahchiev */ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.FIELD, ElementType.METHOD}) 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 f191e0329..189ff6096 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -48,6 +48,7 @@ * @author Kevin Leturc * @author Alexander Volz * @author Dennis Maaß + * @author Petar Tahchiev */ class MappingBuilder { From 217bf81fce48811b3ab439a87de3c0313fa11780 Mon Sep 17 00:00:00 2001 From: Petar Tahchiev Date: Wed, 11 May 2016 11:15:47 +0200 Subject: [PATCH 3/4] Add a null-pointer check and resolve methods from parent too --- .../elasticsearch/core/MappingBuilder.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) 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 189ff6096..1a3413dfe 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -97,9 +97,8 @@ private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, bool final List members = new ArrayList(); - final java.lang.reflect.Field[] fields = clazz.getDeclaredFields(); - members.addAll(Arrays.asList(fields)); - members.addAll(Arrays.asList(clazz.getDeclaredMethods())); + members.addAll(Arrays.asList(retrieveFields(clazz))); + members.addAll(Arrays.asList(retrieveMethods(clazz))); if (!isRootObject && (isAnyPropertyAnnotatedAsField(members) || nestedOrObjectField)) { String type = FieldType.Object.toString().toLowerCase(); @@ -186,6 +185,21 @@ private static java.lang.reflect.Field[] retrieveFields(Class clazz) { return fields.toArray(new java.lang.reflect.Field[fields.size()]); } + private static java.lang.reflect.Method[] retrieveMethods(Class clazz) { + // Create list of methods. + List methods = new ArrayList(); + + // Keep backing up the inheritance hierarchy. + Class targetClass = clazz; + do { + methods.addAll(Arrays.asList(targetClass.getDeclaredMethods())); + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + + return methods.toArray(new java.lang.reflect.Method[methods.size()]); + } + private static boolean isAnnotated(java.lang.reflect.AccessibleObject member) { return member.getAnnotation(Field.class) != null || member.getAnnotation(MultiField.class) != null || @@ -314,9 +328,12 @@ private static void addMultiFieldMapping(XContentBuilder builder, java.lang.refl } protected static boolean isEntity(java.lang.reflect.AccessibleObject member) { - TypeInformation typeInformation = ClassTypeInformation.from(getMemberType(member)); - Class clazz = getMemberType(member); - boolean isComplexType = !SIMPLE_TYPE_HOLDER.isSimpleType(clazz); + final Class memberType = getMemberType(member); + if (memberType == null) { + return false; + } + TypeInformation typeInformation = ClassTypeInformation.from(memberType); + boolean isComplexType = !SIMPLE_TYPE_HOLDER.isSimpleType(memberType); return isComplexType && !Map.class.isAssignableFrom(typeInformation.getType()); } From a52b055f20aed2724e284a6a01c7b14ab0722819 Mon Sep 17 00:00:00 2001 From: Petar Tahchiev Date: Wed, 11 May 2016 13:34:59 +0200 Subject: [PATCH 4/4] Field anntotaions to work on setter methods too. Iterate and find all the geopoint members and apply only those with an annotation. --- .../annotations/CompletionField.java | 3 +- .../annotations/GeoPointField.java | 8 +-- .../elasticsearch/annotations/MultiField.java | 3 +- .../elasticsearch/core/MappingBuilder.java | 50 ++++++++++------ .../core/MappingBuilderTests.java | 15 ++++- .../data/elasticsearch/entities/Country.java | 58 +++++++++++++++++++ 6 files changed, 111 insertions(+), 26 deletions(-) create mode 100644 src/test/java/org/springframework/data/elasticsearch/entities/Country.java diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java index 4d8418fcf..d435f6d23 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/CompletionField.java @@ -21,9 +21,10 @@ * Based on the reference doc - http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters-completion.html * * @author Mewes Kochheim + * @author Petar Tahchiev */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target(value = {ElementType.FIELD, ElementType.METHOD}) @Documented @Inherited public @interface CompletionField { diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java index f38263da3..5f8a31e00 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/GeoPointField.java @@ -19,14 +19,14 @@ /** * @author Artur Konczak + * @author Petar Tahchiev */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target(value = {ElementType.FIELD, ElementType.METHOD}) @Documented public @interface GeoPointField { - boolean geoHashPrefix() default false; - - String geoHashPrecision() default "0"; + boolean geoHashPrefix() default false; + String geoHashPrecision() default "0"; } diff --git a/src/main/java/org/springframework/data/elasticsearch/annotations/MultiField.java b/src/main/java/org/springframework/data/elasticsearch/annotations/MultiField.java index 1b5024576..385ccd337 100644 --- a/src/main/java/org/springframework/data/elasticsearch/annotations/MultiField.java +++ b/src/main/java/org/springframework/data/elasticsearch/annotations/MultiField.java @@ -22,9 +22,10 @@ * @author Mohsin Husen * @author Artur Konczak * @author Jonathan Yan + * @author Petar Tahchiev */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) +@Target(value = {ElementType.FIELD, ElementType.METHOD}) @Documented public @interface MultiField { 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 1a3413dfe..c13d02cbd 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/MappingBuilder.java @@ -23,10 +23,7 @@ import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; @@ -113,18 +110,23 @@ private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, bool t.startObject(FIELD_PROPERTIES); } + final Map geoPointMembers = new HashMap(); + final Map completionMembers = new HashMap(); + for (java.lang.reflect.AccessibleObject member : members) { if (member.isAnnotationPresent(Transient.class) || isInIgnoreFields(member)) { continue; } + final String memberName = getMemberName(member); + if (member.isAnnotationPresent(Mapping.class)) { String mappingPath = member.getAnnotation(Mapping.class).mappingPath(); if (isNotBlank(mappingPath)) { ClassPathResource mappings = new ClassPathResource(mappingPath); if (mappings.exists()) { - xContentBuilder.rawField(getMemberName(member), mappings.getInputStream()); + xContentBuilder.rawField(memberName, mappings.getInputStream()); continue; } } @@ -139,7 +141,7 @@ private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, bool continue; } boolean nestedOrObject = isNestedOrObjectField(member); - mapEntity(xContentBuilder, getFieldType(member), false, EMPTY, getMemberName(member), nestedOrObject, singleField.type(), member.getAnnotation(Field.class)); + mapEntity(xContentBuilder, getFieldType(member), false, EMPTY, memberName, nestedOrObject, singleField.type(), member.getAnnotation(Field.class)); if (nestedOrObject) { continue; } @@ -147,13 +149,12 @@ private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, bool MultiField multiField = member.getAnnotation(MultiField.class); - if (isGeoPointField) { - applyGeoPointFieldMapping(xContentBuilder, member); + if (isGeoPointField && (!geoPointMembers.containsKey(memberName) || (geoPointMembers.containsKey(memberName) && isAnnotated(member)))) { + geoPointMembers.put(memberName, member); } - if (isCompletionField) { - CompletionField completionField = member.getAnnotation(CompletionField.class); - applyCompletionFieldMapping(xContentBuilder, member, completionField); + if (isCompletionField && (!completionMembers.containsKey(memberName) || (completionMembers.containsKey(memberName) && isAnnotated(member)))) { + completionMembers.put(memberName, member); } if (isRootObject && singleField != null && isIdField(member, idFieldName)) { @@ -165,6 +166,15 @@ private static void mapEntity(XContentBuilder xContentBuilder, Class clazz, bool } } + for (Map.Entry entry : geoPointMembers.entrySet()) { + applyGeoPointFieldMapping(xContentBuilder, entry.getValue()); + } + + for (Map.Entry entry : completionMembers.entrySet()) { + CompletionField completionField = entry.getValue().getAnnotation(CompletionField.class); + applyCompletionFieldMapping(xContentBuilder, entry.getValue(), completionField); + } + if (!isRootObject && isAnyPropertyAnnotatedAsField(members) || nestedOrObjectField) { xContentBuilder.endObject().endObject(); } @@ -262,7 +272,7 @@ private static void applyDefaultIdFieldMapping(XContentBuilder xContentBuilder, private static void addSingleFieldMapping(XContentBuilder xContentBuilder, java.lang.reflect.AccessibleObject member, Field fieldAnnotation, boolean nestedOrObjectField) throws IOException { xContentBuilder.startObject(getMemberName(member)); - if(!nestedOrObjectField) { + if (!nestedOrObjectField) { xContentBuilder.field(FIELD_STORE, fieldAnnotation.store()); } if (FieldType.Auto != fieldAnnotation.type()) { @@ -319,7 +329,7 @@ private static void addMultiFieldMapping(XContentBuilder builder, java.lang.refl builder.field(FIELD_TYPE, "multi_field"); builder.startObject("fields"); //add standard field - addSingleFieldMapping(builder, member, annotation.mainField(),nestedOrObjectField); + addSingleFieldMapping(builder, member, annotation.mainField(), nestedOrObjectField); for (InnerField innerField : annotation.otherFields()) { addNestedFieldMapping(builder, member, innerField); } @@ -339,12 +349,14 @@ protected static boolean isEntity(java.lang.reflect.AccessibleObject member) { protected static Class getFieldType(java.lang.reflect.AccessibleObject member) { Class clazz = getMemberType(member); - TypeInformation typeInformation = ClassTypeInformation.from(clazz); - if (typeInformation.isCollectionLike()) { - if (member instanceof java.lang.reflect.Field) { - clazz = GenericCollectionTypeResolver.getCollectionFieldType((java.lang.reflect.Field) member) != null - ? GenericCollectionTypeResolver.getCollectionFieldType((java.lang.reflect.Field) member) - : typeInformation.getComponentType().getType(); + if (clazz != null) { + TypeInformation typeInformation = ClassTypeInformation.from(clazz); + if (typeInformation.isCollectionLike()) { + if (member instanceof java.lang.reflect.Field) { + clazz = GenericCollectionTypeResolver.getCollectionFieldType((java.lang.reflect.Field) member) != null + ? GenericCollectionTypeResolver.getCollectionFieldType((java.lang.reflect.Field) member) + : typeInformation.getComponentType().getType(); + } } } return clazz; 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 f7f7a7495..b98bbb538 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/MappingBuilderTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/MappingBuilderTests.java @@ -34,7 +34,6 @@ import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.SearchQuery; import org.springframework.data.elasticsearch.entities.*; -import org.springframework.data.elasticsearch.entities.GeoEntity; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -109,6 +108,20 @@ public void shouldCreateMappingForSpecifiedParentType() throws IOException { assertThat(xContentBuilder.string(), is(expected)); } + /** + * DATAES-20 + */ + @Test + public void shouldWorkWithAnntationsOnSetterMethods() throws IOException { + final String expected = "{\"mapping\":{\"properties\":{\"name\":{\"store\":true,\"" + + "type\":\"string\"}" + + ",\"founded\":{\"store\":false}" + + ",\"coorinates\":{\"type\":\"geo_point\"}}}}"; + + XContentBuilder xContentBuilder = MappingBuilder.buildMapping(Country.class, "mapping", "id", null); + assertThat(xContentBuilder.string(), is(expected)); + } + /* * DATAES-76 */ diff --git a/src/test/java/org/springframework/data/elasticsearch/entities/Country.java b/src/test/java/org/springframework/data/elasticsearch/entities/Country.java new file mode 100644 index 000000000..206d72a4e --- /dev/null +++ b/src/test/java/org/springframework/data/elasticsearch/entities/Country.java @@ -0,0 +1,58 @@ +package org.springframework.data.elasticsearch.entities; + +import java.util.Date; + +import org.springframework.data.annotation.Id; +import org.springframework.data.elasticsearch.annotations.*; +import org.springframework.data.elasticsearch.core.geo.GeoPoint; + +/** + * @author Petar Tahchiev + */ +@Document(indexName = "test-country-index", type = "test-country-type", shards = 1, replicas = 0, refreshInterval = "-1") +public class Country { + + private String id; + + private String name; + + private Date founded; + + private GeoPoint coorinates; + + public String getId() { + return id; + } + + @Id + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + @Field(type = FieldType.String, store = true) + public void setName(String name) { + this.name = name; + } + + public Date getFounded() { + return founded; + } + + @Field(format = DateFormat.basic_date) + public void setFounded(Date founded) { + this.founded = founded; + } + + public GeoPoint getCoorinates() { + return coorinates; + } + + @GeoPointField + public void setCoorinates(GeoPoint coorinates) { + this.coorinates = coorinates; + } +}