Skip to content

Commit 2bd4ef7

Browse files
authored
CriteriaQuery must support nested queries.
Original Pull Request: #1757 Closes #1753
1 parent 4ad0027 commit 2bd4ef7

File tree

4 files changed

+172
-48
lines changed

4 files changed

+172
-48
lines changed

Diff for: src/main/java/org/springframework/data/elasticsearch/core/CriteriaQueryProcessor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525

2626
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
27+
import org.apache.lucene.search.join.ScoreMode;
2728
import org.elasticsearch.index.query.BoolQueryBuilder;
2829
import org.elasticsearch.index.query.QueryBuilder;
2930
import org.springframework.data.elasticsearch.annotations.FieldType;
@@ -136,7 +137,7 @@ private QueryBuilder queryForEntries(Criteria criteria) {
136137
return null;
137138

138139
String fieldName = field.getName();
139-
Assert.notNull(fieldName, "Unknown field");
140+
Assert.notNull(fieldName, "Unknown field " + fieldName);
140141

141142
Iterator<Criteria.CriteriaEntry> it = criteria.getQueryCriteriaEntries().iterator();
142143
QueryBuilder query;
@@ -152,6 +153,14 @@ private QueryBuilder queryForEntries(Criteria criteria) {
152153
}
153154

154155
addBoost(query, criteria.getBoost());
156+
157+
int dotPosition = fieldName.lastIndexOf('.');
158+
159+
if (dotPosition > 0) {
160+
String nestedPath = fieldName.substring(0, dotPosition);
161+
query = nestedQuery(nestedPath, query, ScoreMode.Avg);
162+
}
163+
155164
return query;
156165
}
157166

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

+80-38
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,15 @@ public void afterPropertiesSet() {
168168
@SuppressWarnings("unchecked")
169169
@Override
170170
public <R> R read(Class<R> type, Document source) {
171+
171172
TypeInformation<R> typeHint = ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type));
172-
return read(typeHint, source);
173+
R r = read(typeHint, source);
174+
175+
if (r == null) {
176+
throw new ConversionException("could not convert into object of class " + type);
177+
}
178+
179+
return r;
173180
}
174181

175182
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
@@ -188,7 +195,7 @@ protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String,
188195

189196
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
190197

191-
@SuppressWarnings({ "unchecked", "ConstantConditions" })
198+
@SuppressWarnings({ "unchecked" })
192199
R instance = (R) instantiator.createInstance(targetEntity, propertyValueProvider);
193200

194201
if (!targetEntity.requiresPropertyPopulation()) {
@@ -246,6 +253,7 @@ private ParameterValueProvider<ElasticsearchPersistentProperty> getParameterProv
246253
ElasticsearchPropertyValueProvider provider = new ElasticsearchPropertyValueProvider(source, evaluator);
247254

248255
// TODO: Support for non-static inner classes via ObjectPath
256+
// noinspection ConstantConditions
249257
PersistentEntityParameterValueProvider<ElasticsearchPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<>(
250258
entity, provider, null);
251259

@@ -281,7 +289,6 @@ protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instan
281289
return accessor.getBean();
282290
}
283291

284-
@SuppressWarnings("unchecked")
285292
@Nullable
286293
protected <R> R readValue(@Nullable Object value, ElasticsearchPersistentProperty property, TypeInformation<?> type) {
287294

@@ -349,7 +356,7 @@ private <R> R read(TypeInformation<R> type, Map<String, Object> source) {
349356
}
350357

351358
if (typeToUse.isMap()) {
352-
return (R) readMap(typeToUse, source);
359+
return readMap(typeToUse, source);
353360
}
354361

355362
if (typeToUse.equals(ClassTypeInformation.OBJECT)) {
@@ -549,7 +556,7 @@ public void write(Object source, Document sink) {
549556
}
550557

551558
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
552-
TypeInformation<? extends Object> typeInformation = ClassTypeInformation.from(entityType);
559+
TypeInformation<?> typeInformation = ClassTypeInformation.from(entityType);
553560

554561
if (requiresTypeHint(entityType)) {
555562
typeMapper.writeType(typeInformation, sink);
@@ -561,9 +568,9 @@ public void write(Object source, Document sink) {
561568
/**
562569
* Internal write conversion method which should be used for nested invocations.
563570
*
564-
* @param source
565-
* @param sink
566-
* @param typeInformation
571+
* @param source the object to write
572+
* @param sink the write destination
573+
* @param typeInformation type information for the source
567574
*/
568575
@SuppressWarnings("unchecked")
569576
protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
@@ -578,7 +585,10 @@ protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
578585

579586
if (customTarget.isPresent()) {
580587
Map<String, Object> result = conversionService.convert(source, Map.class);
581-
sink.putAll(result);
588+
589+
if (result != null) {
590+
sink.putAll(result);
591+
}
582592
return;
583593
}
584594

@@ -600,9 +610,9 @@ protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
600610
/**
601611
* Internal write conversion method which should be used for nested invocations.
602612
*
603-
* @param source
604-
* @param sink
605-
* @param entity
613+
* @param source the object to write
614+
* @param sink the write destination
615+
* @param entity entity for the source
606616
*/
607617
protected void writeInternal(@Nullable Object source, Map<String, Object> sink,
608618
@Nullable ElasticsearchPersistentEntity<?> entity) {
@@ -734,7 +744,6 @@ protected void writeProperty(ElasticsearchPersistentProperty property, Object va
734744
*
735745
* @param collection must not be {@literal null}.
736746
* @param property must not be {@literal null}.
737-
* @return
738747
*/
739748
protected List<Object> createCollection(Collection<?> collection, ElasticsearchPersistentProperty property) {
740749
return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>(collection.size()));
@@ -745,7 +754,6 @@ protected List<Object> createCollection(Collection<?> collection, ElasticsearchP
745754
*
746755
* @param map must not {@literal null}.
747756
* @param property must not be {@literal null}.
748-
* @return
749757
*/
750758
protected Map<String, Object> createMap(Map<?, ?> map, ElasticsearchPersistentProperty property) {
751759

@@ -761,7 +769,6 @@ protected Map<String, Object> createMap(Map<?, ?> map, ElasticsearchPersistentPr
761769
* @param source must not be {@literal null}.
762770
* @param sink must not be {@literal null}.
763771
* @param propertyType must not be {@literal null}.
764-
* @return
765772
*/
766773
protected Map<String, Object> writeMapInternal(Map<?, ?> source, Map<String, Object> sink,
767774
TypeInformation<?> propertyType) {
@@ -801,7 +808,6 @@ protected Map<String, Object> writeMapInternal(Map<?, ?> source, Map<String, Obj
801808
* @param source the collection to create a {@link Collection} for, must not be {@literal null}.
802809
* @param type the {@link TypeInformation} to consider or {@literal null} if unknown.
803810
* @param sink the {@link Collection} to write to.
804-
* @return
805811
*/
806812
@SuppressWarnings("unchecked")
807813
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
@@ -837,26 +843,30 @@ private List<Object> writeCollectionInternal(Collection<?> source, @Nullable Typ
837843
/**
838844
* Returns a {@link String} representation of the given {@link Map} key
839845
*
840-
* @param key
841-
* @return
846+
* @param key the key to convert
842847
*/
843848
private String potentiallyConvertMapKey(Object key) {
844849

845850
if (key instanceof String) {
846851
return (String) key;
847852
}
848853

849-
return conversions.hasCustomWriteTarget(key.getClass(), String.class)
850-
? (String) getPotentiallyConvertedSimpleWrite(key, Object.class)
851-
: key.toString();
854+
if (conversions.hasCustomWriteTarget(key.getClass(), String.class)) {
855+
Object potentiallyConvertedSimpleWrite = getPotentiallyConvertedSimpleWrite(key, Object.class);
856+
857+
if (potentiallyConvertedSimpleWrite == null) {
858+
return key.toString();
859+
}
860+
return (String) potentiallyConvertedSimpleWrite;
861+
}
862+
return key.toString();
852863
}
853864

854865
/**
855866
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple Elasticsearch
856867
* type. Returns the converted value if so. If not, we perform special enum handling or simply return the value as is.
857868
*
858-
* @param value
859-
* @return
869+
* @param value value to convert
860870
*/
861871
@Nullable
862872
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nullable Class<?> typeHint) {
@@ -869,6 +879,10 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, @Nulla
869879

870880
if (conversionService.canConvert(value.getClass(), typeHint)) {
871881
value = conversionService.convert(value, typeHint);
882+
883+
if (value == null) {
884+
return null;
885+
}
872886
}
873887
}
874888

@@ -908,8 +922,8 @@ protected Object getWriteSimpleValue(Object value) {
908922
* @deprecated since 4.2, use {@link #writeInternal(Object, Map, TypeInformation)} instead.
909923
*/
910924
@Deprecated
911-
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint,
912-
Object value) {
925+
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property,
926+
@SuppressWarnings("unused") TypeInformation<?> typeHint, Object value) {
913927

914928
Document document = Document.create();
915929
writeInternal(value, document, property.getTypeInformation());
@@ -928,11 +942,19 @@ protected Object getWriteComplexValue(ElasticsearchPersistentProperty property,
928942
*
929943
* @param source must not be {@literal null}.
930944
* @param sink must not be {@literal null}.
931-
* @param type
945+
* @param type type to compare to
932946
*/
933-
protected void addCustomTypeKeyIfNecessary(Object source, Map<String, Object> sink, @Nullable TypeInformation<?> type) {
947+
protected void addCustomTypeKeyIfNecessary(Object source, Map<String, Object> sink,
948+
@Nullable TypeInformation<?> type) {
934949

935-
Class<?> reference = type != null ? type.getActualType().getType() : Object.class;
950+
Class<?> reference;
951+
952+
if (type == null) {
953+
reference = Object.class;
954+
} else {
955+
TypeInformation<?> actualType = type.getActualType();
956+
reference = actualType == null ? Object.class : actualType.getType();
957+
}
936958
Class<?> valueType = ClassUtils.getUserClass(source.getClass());
937959

938960
boolean notTheSameClass = !valueType.equals(reference);
@@ -987,8 +1009,7 @@ private boolean isSimpleType(Class<?> type) {
9871009
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
9881010
* collection for everything else.
9891011
*
990-
* @param source
991-
* @return
1012+
* @param source object to convert
9921013
*/
9931014
private static Collection<?> asCollection(Object source) {
9941015

@@ -1019,21 +1040,42 @@ public void updateCriteriaQuery(CriteriaQuery criteriaQuery, Class<?> domainClas
10191040
}
10201041

10211042
private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
1043+
10221044
Field field = criteria.getField();
10231045

10241046
if (field == null) {
10251047
return;
10261048
}
10271049

1028-
String name = field.getName();
1029-
ElasticsearchPersistentProperty property = persistentEntity.getPersistentProperty(name);
1050+
String[] fieldNames = field.getName().split("\\.");
1051+
ElasticsearchPersistentEntity<?> currentEntity = persistentEntity;
1052+
ElasticsearchPersistentProperty persistentProperty = null;
1053+
for (int i = 0; i < fieldNames.length; i++) {
1054+
persistentProperty = currentEntity.getPersistentProperty(fieldNames[i]);
1055+
1056+
if (persistentProperty != null) {
1057+
fieldNames[i] = persistentProperty.getFieldName();
1058+
try {
1059+
currentEntity = mappingContext.getPersistentEntity(persistentProperty.getActualType());
1060+
} catch (Exception e) {
1061+
// using system types like UUIDs will lead to java.lang.reflect.InaccessibleObjectException in JDK 16
1062+
// so if we cannot get an entity here, bail out.
1063+
currentEntity = null;
1064+
}
1065+
}
1066+
1067+
if (currentEntity == null) {
1068+
break;
1069+
}
1070+
}
1071+
1072+
field.setName(String.join(".", fieldNames));
10301073

1031-
if (property != null && property.getName().equals(name)) {
1032-
field.setName(property.getFieldName());
1074+
if (persistentProperty != null) {
10331075

1034-
if (property.hasPropertyConverter()) {
1076+
if (persistentProperty.hasPropertyConverter()) {
10351077
ElasticsearchPersistentPropertyConverter propertyConverter = Objects
1036-
.requireNonNull(property.getPropertyConverter());
1078+
.requireNonNull(persistentProperty.getPropertyConverter());
10371079
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
10381080
Object value = criteriaEntry.getValue();
10391081
if (value.getClass().isArray()) {
@@ -1047,14 +1089,15 @@ private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?>
10471089
});
10481090
}
10491091

1050-
org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property
1092+
org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = persistentProperty
10511093
.findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class);
10521094

10531095
if (fieldAnnotation != null) {
10541096
field.setFieldType(fieldAnnotation.type());
10551097
}
10561098
}
10571099
}
1100+
10581101
// endregion
10591102

10601103
static class MapValueAccessor {
@@ -1148,7 +1191,6 @@ class ElasticsearchPropertyValueProvider implements PropertyValueProvider<Elasti
11481191
this.evaluator = evaluator;
11491192
}
11501193

1151-
@SuppressWarnings("unchecked")
11521194
@Override
11531195
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
11541196

0 commit comments

Comments
 (0)