Skip to content

Commit a1c1659

Browse files
christophstroblmp911de
authored andcommitted
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 Original pull request: #3661.
1 parent 3872b37 commit a1c1659

File tree

2 files changed

+83
-27
lines changed

2 files changed

+83
-27
lines changed

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);
@@ -677,7 +682,8 @@ private Object createReferenceFor(Object source, MongoPersistentProperty propert
677682
return (DBRef) source;
678683
}
679684

680-
if(property != null && (property.isDocumentReference() || (!property.isDbReference() && property.findAnnotation(Reference.class) != null))) {
685+
if (property != null && (property.isDocumentReference()
686+
|| (!property.isDbReference() && property.findAnnotation(Reference.class) != null))) {
681687
return converter.toDocumentPointer(source, property).getPointer();
682688
}
683689

@@ -1178,38 +1184,55 @@ private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpre
11781184
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
11791185

11801186
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
1181-
return mappingContext
1182-
.getPersistentPropertyPath(PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
1187+
return mappingContext.getPersistentPropertyPath(
1188+
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
11831189
}
11841190

11851191
PropertyPath path = forName(rawPath);
11861192
if (path == null || isPathToJavaLangClassProperty(path)) {
11871193
return null;
11881194
}
11891195

1190-
try {
1196+
PersistentPropertyPath<MongoPersistentProperty> propertyPath = tryToResolvePersistentPropertyPath(path);
11911197

1192-
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
1198+
if (propertyPath == null) {
11931199

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

1197-
while (iterator.hasNext()) {
1211+
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
1212+
boolean associationDetected = false;
11981213

1199-
MongoPersistentProperty property = iterator.next();
1214+
while (iterator.hasNext()) {
12001215

1201-
if (property.isAssociation()) {
1202-
associationDetected = true;
1203-
continue;
1204-
}
1216+
MongoPersistentProperty property = iterator.next();
12051217

1206-
if (associationDetected && !property.isIdProperty()) {
1207-
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
1208-
}
1218+
if (property.isAssociation()) {
1219+
associationDetected = true;
1220+
continue;
12091221
}
12101222

1211-
return propertyPath;
1212-
} catch (InvalidPersistentPropertyPath e) {
1223+
if (associationDetected && !property.isIdProperty()) {
1224+
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
1225+
}
1226+
}
1227+
1228+
return propertyPath;
1229+
}
1230+
1231+
private PersistentPropertyPath<MongoPersistentProperty> tryToResolvePersistentPropertyPath(PropertyPath path) {
1232+
1233+
try {
1234+
return mappingContext.getPersistentPropertyPath(path);
1235+
} catch (MappingException e) {
12131236
return null;
12141237
}
12151238
}

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;
@@ -1285,6 +1288,26 @@ void mapsNullBsonTypeForFieldWithCustomTargetType() {
12851288
.isThrownBy(() -> mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class)));
12861289
}
12871290

1291+
@Test // GH-3659
1292+
void allowsUsingFieldPathsForPropertiesHavingCustomConversionRegistered() {
1293+
1294+
Query query = query(where("address.street").is("1007 Mountain Drive"));
1295+
1296+
MongoCustomConversions mongoCustomConversions = new MongoCustomConversions(Collections.singletonList(new MyAddressToDocumentConverter()));
1297+
1298+
this.context = new MongoMappingContext();
1299+
this.context.setSimpleTypeHolder(mongoCustomConversions.getSimpleTypeHolder());
1300+
this.context.afterPropertiesSet();
1301+
1302+
this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
1303+
this.converter.setCustomConversions(mongoCustomConversions);
1304+
this.converter.afterPropertiesSet();
1305+
1306+
this.mapper = new QueryMapper(converter);
1307+
1308+
assertThat(mapper.getMappedSort(query.getQueryObject(), context.getPersistentEntity(Customer.class))).isEqualTo(new org.bson.Document("address.street", "1007 Mountain Drive"));
1309+
}
1310+
12881311
class WithDeepArrayNesting {
12891312

12901313
List<WithNestedArray> level0;
@@ -1516,17 +1539,27 @@ static class WithPropertyUsingUnderscoreInName {
15161539
String renamed_fieldname_with_underscores;
15171540
}
15181541

1519-
static class WithDocumentReferences {
1542+
@Document
1543+
static class Customer {
15201544

1521-
@DocumentReference
1522-
Sample sample;
1545+
@Id
1546+
private ObjectId id;
1547+
private String name;
1548+
private MyAddress address;
1549+
}
15231550

1524-
@DocumentReference
1525-
SimpeEntityWithoutId noId;
1551+
static class MyAddress {
1552+
private String street;
1553+
}
15261554

1527-
@DocumentReference(lookup = "{ 'stringProperty' : ?#{stringProperty} }")
1528-
SimpeEntityWithoutId noIdButLookupQuery;
1555+
@WritingConverter
1556+
public static class MyAddressToDocumentConverter implements Converter<MyAddress, org.bson.Document> {
15291557

1558+
@Override
1559+
public org.bson.Document convert(MyAddress address) {
1560+
org.bson.Document doc = new org.bson.Document();
1561+
doc.put("street", address.street);
1562+
return doc;
1563+
}
15301564
}
1531-
15321565
}

0 commit comments

Comments
 (0)