Browse Source

Polishing.

Update tests to make use of ValueSource.
Replace regex based path inspection with segment by segment analysis.

Original Pull Request: #4427
3.4.x
Christoph Strobl 3 years ago
parent
commit
31e949638f
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 51
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  2. 31
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

51
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

@ -1077,7 +1077,7 @@ public class QueryMapper {
protected static class MetadataBackedField extends Field { protected static class MetadataBackedField extends Field {
private static final Pattern POSITIONAL_PARAMETER_PATTERN = Pattern.compile("\\.\\$(\\[.*?\\])?"); private static final Pattern POSITIONAL_PARAMETER_PATTERN = Pattern.compile("\\.\\$(\\[.*?\\])?");
private static final Pattern DOT_POSITIONAL_PATTERN = Pattern.compile("\\.\\d+(?!$)"); private static final Pattern NUMERIC_SEGMENT = Pattern.compile("\\d+");
private static final String INVALID_ASSOCIATION_REFERENCE = "Invalid path reference %s! Associations can only be pointed to directly or via their id property!"; private static final String INVALID_ASSOCIATION_REFERENCE = "Invalid path reference %s! Associations can only be pointed to directly or via their id property!";
private final MongoPersistentEntity<?> entity; private final MongoPersistentEntity<?> entity;
@ -1243,26 +1243,13 @@ public class QueryMapper {
private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression, private PersistentPropertyPath<MongoPersistentProperty> getPath(String pathExpression,
@Nullable MongoPersistentProperty sourceProperty) { @Nullable MongoPersistentProperty sourceProperty) {
String rawPath = removePlaceholders(POSITIONAL_OPERATOR,
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
// fix xx.11.22.33 becomes xx3, it should be xx.33, then path should be null. (test mapNestedLastBigIntegerFieldCorrectly)
if (pathExpression.contains(".")) {
String lastDotString = pathExpression.substring(pathExpression.lastIndexOf("."));
int lastDotLength = lastDotString.length();
int newLength = 0;
if (rawPath.contains(".")) {
newLength = rawPath.substring(rawPath.lastIndexOf(".")).length();
}
if (lastDotLength != newLength) {
rawPath = rawPath.substring(0, rawPath.length() - 1) + lastDotString;
}
}
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) { if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
return mappingContext.getPersistentPropertyPath( return mappingContext.getPersistentPropertyPath(
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation())); PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
} }
String rawPath = resolvePath(pathExpression);
PropertyPath path = forName(rawPath); PropertyPath path = forName(rawPath);
if (path == null || isPathToJavaLangClassProperty(path)) { if (path == null || isPathToJavaLangClassProperty(path)) {
return null; return null;
@ -1357,6 +1344,38 @@ public class QueryMapper {
return false; return false;
} }
private static String resolvePath(String source) {
String[] segments = source.split("\\.");
if (segments.length == 1) {
return source;
}
List<String> path = new ArrayList<>(segments.length);
/* always start from a property, so we can skip the first segment.
from there remove any position placeholder */
for(int i=1; i < segments.length; i++) {
String segment = segments[i];
if (segment.startsWith("[") && segment.endsWith("]")) {
continue;
}
if (NUMERIC_SEGMENT.matcher(segment).matches()) {
continue;
}
path.add(segment);
}
// when property is followed only by placeholders eg. 'values.0.3.90'
// or when there is no difference in the number of segments
if (path.isEmpty() || segments.length == path.size() + 1) {
return source;
}
path.add(0, segments[0]);
return StringUtils.collectionToDelimitedString(path, ".");
}
/** /**
* Return the {@link Converter} to be used to created the mapped key. Default implementation will use * Return the {@link Converter} to be used to created the mapped key. Default implementation will use
* {@link PropertyToFieldNameConverter}. * {@link PropertyToFieldNameConverter}.

31
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

@ -35,6 +35,8 @@ import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@ -1212,34 +1214,26 @@ class UpdateMapperUnitTests {
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.a.b.d", "e"))); assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.a.b.d", "e")));
} }
@Test // GH-3775 @ParameterizedTest // GH-3775, GH-4426
void mapNestedIntegerFieldCorrectly() { @ValueSource(strings = {"levelOne.0.1.3", "levelOne.0.1.32", "levelOne2.0.1.32", "levelOne2.0.1.320"})
void mapNestedIntegerFieldCorrectly(String path) {
Update update = new Update().set("levelOne.0.1.3", "4");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.3", "4"))); Update update = new Update().set(path, "4");
}
@Test
void mapNestedLastBigIntegerFieldCorrectly() {
Update update = new Update().set("levelOne.0.1.32", "4");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class)); context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.32", "4"))); assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document(path, "4")));
} }
@Test // GH-3775 @ParameterizedTest // GH-3775, GH-4426
void mapNestedMixedStringIntegerFieldCorrectly() { @ValueSource(strings = {"levelOne.0.1.c", "levelOne.0.1.c.32", "levelOne2.0.1.32.c", "levelOne2.0.1.c.320"})
void mapNestedMixedStringIntegerFieldCorrectly(String path) {
Update update = new Update().set("levelOne.0.1.c", "4"); Update update = new Update().set(path, "4");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(), Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithNestedMap.class)); context.getPersistentEntity(EntityWithNestedMap.class));
assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document("levelOne.0.1.c", "4"))); assertThat(mappedUpdate).isEqualTo(new org.bson.Document("$set", new org.bson.Document(path, "4")));
} }
@Test // GH-3775 @Test // GH-3775
@ -1729,7 +1723,6 @@ class UpdateMapperUnitTests {
static class EntityWithNestedMap { static class EntityWithNestedMap {
Map<String, Map<String, Map<String, Object>>> levelOne; Map<String, Map<String, Map<String, Object>>> levelOne;
// for test mapNestedLastBigIntegerFieldCorrectly()
Map<String, Map<String, Map<String, Object>>> levelOne2; Map<String, Map<String, Map<String, Object>>> levelOne2;
} }

Loading…
Cancel
Save