Skip to content

Commit e934a2e

Browse files
committed
Fix mapping of property values into a collection.
When reading from Elasticsearch into a property of type Collection<T> (List<T> or Set<T>) the MappingElasticsearchConverter now can read both from the returned JSON: an array of T objects - will put the objects in a corresponding collection a single T object will put the single object into a corrsponding colletcion This is implemented and tested for both: entities where the properties have setters and immutable classes that only provide an all-args constructor. Original Pull Request #2282 Closes #2280 (cherry picked from commit 86634ce) (cherry picked from commit 346c5cc)
1 parent 468b573 commit e934a2e

File tree

2 files changed

+622
-92
lines changed

2 files changed

+622
-92
lines changed

src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121
import java.util.concurrent.ConcurrentHashMap;
2222
import java.util.stream.Collectors;
2323

24+
import org.apache.commons.logging.Log;
25+
import org.apache.commons.logging.LogFactory;
26+
import java.time.temporal.TemporalAccessor;
27+
import java.util.*;
28+
import java.util.Map.Entry;
29+
import java.util.concurrent.ConcurrentHashMap;
30+
import java.util.stream.Collectors;
31+
2432
import org.slf4j.Logger;
2533
import org.slf4j.LoggerFactory;
2634
import org.springframework.beans.BeansException;
@@ -51,16 +59,7 @@
5159
import org.springframework.data.mapping.PersistentPropertyAccessor;
5260
import org.springframework.data.mapping.PreferredConstructor;
5361
import org.springframework.data.mapping.context.MappingContext;
54-
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
55-
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
56-
import org.springframework.data.mapping.model.EntityInstantiator;
57-
import org.springframework.data.mapping.model.EntityInstantiators;
58-
import org.springframework.data.mapping.model.ParameterValueProvider;
59-
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
60-
import org.springframework.data.mapping.model.PropertyValueProvider;
61-
import org.springframework.data.mapping.model.SpELContext;
62-
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
63-
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
62+
import org.springframework.data.mapping.model.*;
6463
import org.springframework.data.util.ClassTypeInformation;
6564
import org.springframework.data.util.TypeInformation;
6665
import org.springframework.format.datetime.DateFormatterRegistrar;
@@ -196,7 +195,7 @@ private Base(
196195
}
197196

198197
/**
199-
* Class to do the actual writing. The methods originally were in the MappingElasticsearchConverter class, but are
198+
* Class to do the actual reading. The methods originally were in the MappingElasticsearchConverter class, but are
200199
* refactored to allow for keeping state during the conversion of an object.
201200
*/
202201
private static class Reader extends Base {
@@ -215,10 +214,17 @@ public Reader(
215214
}
216215

217216
@SuppressWarnings("unchecked")
217+
/**
218+
* Reads the given source into the given type.
219+
*
220+
* @param type they type to convert the given source to.
221+
* @param source the source to create an object of the given type from.
222+
* @return the object that was read
223+
*/
218224
<R> R read(Class<R> type, Document source) {
219225

220-
TypeInformation<R> typeHint = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
221-
R r = read(typeHint, source);
226+
TypeInformation<R> typeInformation = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
227+
R r = read(typeInformation, source);
222228

223229
if (r == null) {
224230
throw new ConversionException("could not convert into object of class " + type);
@@ -229,11 +235,11 @@ <R> R read(Class<R> type, Document source) {
229235

230236
@Nullable
231237
@SuppressWarnings("unchecked")
232-
private <R> R read(TypeInformation<R> type, Map<String, Object> source) {
238+
private <R> R read(TypeInformation<R> typeInformation, Map<String, Object> source) {
233239

234240
Assert.notNull(source, "Source must not be null!");
235241

236-
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
242+
TypeInformation<? extends R> typeToUse = typeMapper.readType(source, typeInformation);
237243
Class<? extends R> rawType = typeToUse.getType();
238244

239245
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
@@ -251,8 +257,8 @@ private <R> R read(TypeInformation<R> type, Map<String, Object> source) {
251257
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
252258
return (R) source;
253259
}
254-
// Retrieve persistent entity info
255260

261+
// Retrieve persistent entity info
256262
ElasticsearchPersistentEntity<?> entity = mappingContext.getPersistentEntity(typeToUse);
257263

258264
if (entity == null) {
@@ -370,7 +376,6 @@ private <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Ob
370376
}
371377

372378
return result;
373-
374379
}
375380

376381
private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProvider(
@@ -460,12 +465,44 @@ private <T> T readValue(Object value, TypeInformation<?> type) {
460465
} else if (value.getClass().isArray()) {
461466
return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value));
462467
} else if (value instanceof Map) {
468+
469+
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
470+
if (collectionComponentType != null) {
471+
Object o = read(collectionComponentType, (Map<String, Object>) value);
472+
return getCollectionWithSingleElement(type, collectionComponentType, o);
473+
}
463474
return (T) read(type, (Map<String, Object>) value);
464475
} else {
476+
477+
TypeInformation<?> collectionComponentType = getCollectionComponentType(type);
478+
if (collectionComponentType != null
479+
&& collectionComponentType.isAssignableFrom(ClassTypeInformation.from(value.getClass()))) {
480+
Object o = getPotentiallyConvertedSimpleRead(value, collectionComponentType);
481+
return getCollectionWithSingleElement(type, collectionComponentType, o);
482+
}
483+
465484
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
466485
}
467486
}
468487

488+
@SuppressWarnings("unchecked")
489+
private static <T> T getCollectionWithSingleElement(TypeInformation<?> collectionType,
490+
TypeInformation<?> componentType, Object element) {
491+
Collection<Object> collection = CollectionFactory.createCollection(collectionType.getType(),
492+
componentType.getType(), 1);
493+
collection.add(element);
494+
return (T) collection;
495+
}
496+
497+
/**
498+
* @param type the type to check
499+
* @return true if type is a collectoin, null otherwise,
500+
*/
501+
@Nullable
502+
TypeInformation<?> getCollectionComponentType(TypeInformation<?> type) {
503+
return type.isCollectionLike() ? type.getComponentType() : null;
504+
}
505+
469506
private Object propertyConverterRead(ElasticsearchPersistentProperty property, Object source) {
470507
PropertyValueConverter propertyValueConverter = Objects.requireNonNull(property.getPropertyValueConverter());
471508

@@ -1131,17 +1168,18 @@ public void updateQuery(Query query, @Nullable Class<?> domainClass) {
11311168

11321169
Assert.notNull(query, "query must not be null");
11331170

1134-
if (domainClass != null) {
1171+
if (domainClass == null) {
1172+
return;
1173+
}
11351174

1136-
updateFieldsAndSourceFilter(query, domainClass);
1175+
updatePropertiesInFieldsAndSourceFilter(query, domainClass);
11371176

1138-
if (query instanceof CriteriaQuery) {
1139-
updateCriteriaQuery((CriteriaQuery) query, domainClass);
1140-
}
1177+
if (query instanceof CriteriaQuery) {
1178+
updatePropertiesInCriteriaQuery((CriteriaQuery) query, domainClass);
11411179
}
11421180
}
11431181

1144-
private void updateFieldsAndSourceFilter(Query query, Class<?> domainClass) {
1182+
private void updatePropertiesInFieldsAndSourceFilter(Query query, Class<?> domainClass) {
11451183

11461184
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
11471185

@@ -1174,14 +1212,22 @@ private void updateFieldsAndSourceFilter(Query query, Class<?> domainClass) {
11741212
}
11751213
}
11761214

1177-
private List<String> updateFieldNames(List<String> fields, ElasticsearchPersistentEntity<?> persistentEntity) {
1178-
return fields.stream().map(fieldName -> {
1215+
/**
1216+
* relaces the fieldName with the property name of a property of the persistentEntity with the corresponding
1217+
* fieldname. If no such property exists, the original fieldName is kept.
1218+
*
1219+
* @param fieldNames list of fieldnames
1220+
* @param persistentEntity the persistent entity to check
1221+
* @return an updated list of field names
1222+
*/
1223+
private List<String> updateFieldNames(List<String> fieldNames, ElasticsearchPersistentEntity<?> persistentEntity) {
1224+
return fieldNames.stream().map(fieldName -> {
11791225
ElasticsearchPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(fieldName);
11801226
return persistentProperty != null ? persistentProperty.getFieldName() : fieldName;
11811227
}).collect(Collectors.toList());
11821228
}
11831229

1184-
private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
1230+
private void updatePropertiesInCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClass) {
11851231

11861232
Assert.notNull(criteriaQuery, "criteriaQuery must not be null");
11871233
Assert.notNull(domainClass, "domainClass must not be null");
@@ -1190,17 +1236,17 @@ private void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainCla
11901236

11911237
if (persistentEntity != null) {
11921238
for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
1193-
updateCriteria(chainedCriteria, persistentEntity);
1239+
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
11941240
}
11951241
for (Criteria subCriteria : criteriaQuery.getCriteria().getSubCriteria()) {
11961242
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
1197-
updateCriteria(chainedCriteria, persistentEntity);
1243+
updatePropertiesInCriteria(chainedCriteria, persistentEntity);
11981244
}
11991245
}
12001246
}
12011247
}
12021248

1203-
private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
1249+
private void updatePropertiesInCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
12041250

12051251
Field field = criteria.getField();
12061252

0 commit comments

Comments
 (0)