Skip to content

Commit cf3e850

Browse files
Fix query mapper path resolution for types considered simple ones.
spring-projects/spring-data-commons#2293 changed how PersistentProperty paths get resolved and considers potentially registered converters for those, which made the path resolution fail in during the query mapping process. This commit makes sure to capture the according exception and continue with the given user input. Fixes: #3659
1 parent 47fe456 commit cf3e850

File tree

2 files changed

+83
-27
lines changed

2 files changed

+83
-27
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

+41-18
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
import java.util.Map.Entry;
2020
import java.util.regex.Matcher;
2121
import java.util.regex.Pattern;
22+
import java.util.stream.Collectors;
2223

2324
import org.bson.BsonValue;
2425
import org.bson.Document;
2526
import org.bson.conversions.Bson;
2627
import org.bson.types.ObjectId;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
2730
import org.springframework.core.convert.ConversionService;
2831
import org.springframework.core.convert.converter.Converter;
2932
import org.springframework.data.annotation.Reference;
@@ -69,6 +72,8 @@
6972
*/
7073
public class QueryMapper {
7174

75+
protected static final Logger LOGGER = LoggerFactory.getLogger(QueryMapper.class);
76+
7277
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
7378
private static final Document META_TEXT_SCORE = new Document("$meta", "textScore");
7479
static final ClassTypeInformation<?> NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class);
@@ -673,7 +678,8 @@ private Object createReferenceFor(Object source, MongoPersistentProperty propert
673678
return (DBRef) source;
674679
}
675680

676-
if(property != null && (property.isDocumentReference() || (!property.isDbReference() && property.findAnnotation(Reference.class) != null))) {
681+
if (property != null && (property.isDocumentReference()
682+
|| (!property.isDbReference() && property.findAnnotation(Reference.class) != null))) {
677683
return converter.toDocumentPointer(source, property).getPointer();
678684
}
679685

@@ -1174,38 +1180,55 @@ private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpre
11741180
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
11751181

11761182
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
1177-
return mappingContext
1178-
.getPersistentPropertyPath(PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
1183+
return mappingContext.getPersistentPropertyPath(
1184+
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
11791185
}
11801186

11811187
PropertyPath path = forName(rawPath);
11821188
if (path == null || isPathToJavaLangClassProperty(path)) {
11831189
return null;
11841190
}
11851191

1186-
try {
1192+
PersistentPropertyPath<MongoPersistentProperty> propertyPath = tryToResolvePersistentPropertyPath(path);
11871193

1188-
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
1194+
if (propertyPath == null) {
11891195

1190-
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
1191-
boolean associationDetected = false;
1196+
if (QueryMapper.LOGGER.isInfoEnabled()) {
1197+
1198+
String types = StringUtils.collectionToDelimitedString(
1199+
path.stream().map(it -> it.getType().getSimpleName()).collect(Collectors.toList()), " -> ");
1200+
QueryMapper.LOGGER.info(
1201+
"Could not map '{}'. Maybe a fragment in '{}' is considered a simple type. Mapper continues with {}.",
1202+
path, types, pathExpression);
1203+
}
1204+
return null;
1205+
}
11921206

1193-
while (iterator.hasNext()) {
1207+
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
1208+
boolean associationDetected = false;
11941209

1195-
MongoPersistentProperty property = iterator.next();
1210+
while (iterator.hasNext()) {
11961211

1197-
if (property.isAssociation()) {
1198-
associationDetected = true;
1199-
continue;
1200-
}
1212+
MongoPersistentProperty property = iterator.next();
12011213

1202-
if (associationDetected && !property.isIdProperty()) {
1203-
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
1204-
}
1214+
if (property.isAssociation()) {
1215+
associationDetected = true;
1216+
continue;
12051217
}
12061218

1207-
return propertyPath;
1208-
} catch (InvalidPersistentPropertyPath e) {
1219+
if (associationDetected && !property.isIdProperty()) {
1220+
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
1221+
}
1222+
}
1223+
1224+
return propertyPath;
1225+
}
1226+
1227+
private PersistentPropertyPath<MongoPersistentProperty> tryToResolvePersistentPropertyPath(PropertyPath path) {
1228+
1229+
try {
1230+
return mappingContext.getPersistentPropertyPath(path);
1231+
} catch (MappingException e) {
12091232
return null;
12101233
}
12111234
}

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java

+42-9
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@
2828
import java.util.Map;
2929
import java.util.Optional;
3030

31+
import lombok.Data;
3132
import org.bson.conversions.Bson;
3233
import org.bson.types.Code;
3334
import org.bson.types.ObjectId;
3435
import org.junit.jupiter.api.BeforeEach;
3536
import org.junit.jupiter.api.Test;
3637
import org.junit.jupiter.api.extension.ExtendWith;
3738
import org.mockito.junit.jupiter.MockitoExtension;
38-
39+
import org.springframework.core.convert.converter.Converter;
3940
import org.springframework.data.annotation.Id;
4041
import org.springframework.data.annotation.Transient;
42+
import org.springframework.data.convert.WritingConverter;
4143
import org.springframework.data.domain.Sort;
4244
import org.springframework.data.domain.Sort.Direction;
4345
import org.springframework.data.geo.Point;
@@ -52,6 +54,7 @@
5254
import org.springframework.data.mongodb.core.mapping.FieldType;
5355
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
5456
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
57+
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
5558
import org.springframework.data.mongodb.core.mapping.TextScore;
5659
import org.springframework.data.mongodb.core.mapping.Unwrapped;
5760
import org.springframework.data.mongodb.core.query.BasicQuery;
@@ -1255,6 +1258,26 @@ void resolvesFieldNameWithUnderscoreOnNestedMappedFieldnameWithUnderscoresCorrec
12551258
assertThat(document).isEqualTo(new org.bson.Document("double_underscore.renamed", new org.bson.Document("$exists", true)));
12561259
}
12571260

1261+
@Test // GH-3659
1262+
void allowsUsingFieldPathsForPropertiesHavingCustomConversionRegistered() {
1263+
1264+
Query query = query(where("address.street").is("1007 Mountain Drive"));
1265+
1266+
MongoCustomConversions mongoCustomConversions = new MongoCustomConversions(Collections.singletonList(new MyAddressToDocumentConverter()));
1267+
1268+
this.context = new MongoMappingContext();
1269+
this.context.setSimpleTypeHolder(mongoCustomConversions.getSimpleTypeHolder());
1270+
this.context.afterPropertiesSet();
1271+
1272+
this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
1273+
this.converter.setCustomConversions(mongoCustomConversions);
1274+
this.converter.afterPropertiesSet();
1275+
1276+
this.mapper = new QueryMapper(converter);
1277+
1278+
assertThat(mapper.getMappedSort(query.getQueryObject(), context.getPersistentEntity(Customer.class))).isEqualTo(new org.bson.Document("address.street", "1007 Mountain Drive"));
1279+
}
1280+
12581281
class WithDeepArrayNesting {
12591282

12601283
List<WithNestedArray> level0;
@@ -1486,17 +1509,27 @@ static class WithPropertyUsingUnderscoreInName {
14861509
String renamed_fieldname_with_underscores;
14871510
}
14881511

1489-
static class WithDocumentReferences {
1512+
@Document
1513+
static class Customer {
14901514

1491-
@DocumentReference
1492-
Sample sample;
1515+
@Id
1516+
private ObjectId id;
1517+
private String name;
1518+
private MyAddress address;
1519+
}
14931520

1494-
@DocumentReference
1495-
SimpeEntityWithoutId noId;
1521+
static class MyAddress {
1522+
private String street;
1523+
}
14961524

1497-
@DocumentReference(lookup = "{ 'stringProperty' : ?#{stringProperty} }")
1498-
SimpeEntityWithoutId noIdButLookupQuery;
1525+
@WritingConverter
1526+
public static class MyAddressToDocumentConverter implements Converter<MyAddress, org.bson.Document> {
14991527

1528+
@Override
1529+
public org.bson.Document convert(MyAddress address) {
1530+
org.bson.Document doc = new org.bson.Document();
1531+
doc.put("street", address.street);
1532+
return doc;
1533+
}
15001534
}
1501-
15021535
}

0 commit comments

Comments
 (0)