Compare commits

...

29 Commits
3.2.1 ... 3.2.3

Author SHA1 Message Date
Jens Schauder
74791d0bca Release version 3.2.3 (2021.0.3).
See #3682
2021-07-16 11:35:22 +02:00
Jens Schauder
f4d2287011 Prepare 3.2.3 (2021.0.3).
See #3682
2021-07-16 11:34:27 +02:00
Jens Schauder
ab6ba194c1 Updated changelog.
See #3682
2021-07-16 11:34:20 +02:00
Mark Paluch
595a346705 Polishing.
Support DBObject and Map that as source for entity materialization and map conversion.

See #3702
Original pull request: #3704.
2021-07-15 10:00:00 +02:00
Christoph Strobl
08c5e5a810 Fix raw document conversion in Collection like properties.
Along the lines make sure to convert map like structures correctly if they do not come as a Document, eg. cause they got converted to a plain Map in a post load, pre convert event.

Closes #3702
Original pull request: #3704.
2021-07-15 09:59:50 +02:00
Christoph Strobl
f987217c3c Custom Converter should also be applicable for simple types.
This commit fixes a regression that prevented custom converters from being applied to types considered store native ones.

Original pull request: #3703.
Fixes #3670
2021-07-15 09:00:35 +02:00
Christoph Strobl
92a22978c2 Polishing.
Simplify KeyMapper current property/index setup.

Original Pull Request: #3689
2021-07-06 12:56:10 +02:00
David Julia
2e2e076b5b Fix Regression in generating queries with nested maps with numeric keys.
While maps that have numeric keys work if there is only one map with an integer key, when there are multiple maps with numeric keys in a given query, it fails.

Take the following example for a map called outer with numeric keys holding reference to another object with a map called inner with numeric keys: Updates that are meant to generate {"$set": {"outerMap.1234.inner.5678": "hello"}} are instead generating {"$set": {"outerMap.1234.inner.inner": "hello"}}, repeating the later map property name instead of using the integer key value.

This commit adds unit tests both for the UpdateMapper and QueryMapper, which check multiple consecutive maps with numeric keys, and adds a fix in the KeyMapper. Because we cannot easily change the path parsing to somehow parse path parts corresponding to map keys differently, we address the issue in the KeyMapper. We keep track of the partial path corresponding to the current property and use it to skip adding the duplicated property name for the map to the query, and instead add the key.

This is a bit redundant in that we now have both an iterator and an index-based way of accessing the path parts, but it gets the tests passing and fixes the issue without making a large change to the current approach.

Fixes: #3688
Original Pull Request: #3689
2021-07-06 12:05:58 +02:00
Christoph Strobl
0c50d97887 Fix NPE when reading/mapping null value inside collection.
Closes: #3686
2021-07-01 11:16:13 +02:00
Christoph Strobl
c10d4b6af0 Favor ObjectUtils over Objects for equals/hashCode.
Original Pull Request: #3684
2021-06-24 13:43:55 +02:00
Gatto
6644ac6875 Add equals and hashCode to UnwrappedMongoPersistentProperty.
Fixes #3683
Original Pull Request: #3684
2021-06-24 13:42:12 +02:00
Mark Paluch
708def0df1 After release cleanups.
See #3650
2021-06-22 16:05:22 +02:00
Mark Paluch
889e5d52bb Prepare next development iteration.
See #3650
2021-06-22 16:05:19 +02:00
Mark Paluch
8930091b33 Release version 3.2.2 (2021.0.2).
See #3650
2021-06-22 15:52:27 +02:00
Mark Paluch
b1d750efed Prepare 3.2.2 (2021.0.2).
See #3650
2021-06-22 15:51:38 +02:00
Mark Paluch
7a19593f02 Updated changelog.
See #3650
2021-06-22 15:51:33 +02:00
Mark Paluch
9021445ccd Updated changelog.
See #3649
2021-06-22 15:29:52 +02:00
Mark Paluch
db92c37502 Update reference docs to use correct MongoClient.
Closes #3666
2021-06-22 14:37:02 +02:00
larsw
99e5e2596e Add closing quote to GeoJson javadoc.
Closes #3677
2021-06-21 13:58:40 +02:00
Christoph Strobl
d0bf0e2e62 Fix field projection value conversion.
The field projection conversion should actually only map field names and avoid value conversion. In the MongoId case an inclusion parameter (1) was unintentionally converted into its String representation which causes trouble on Mongo 4.4 servers.

Fixes: #3668
Original pull request: #3678.
2021-06-21 13:45:54 +02:00
Christoph Strobl
28efb3afbe Polishing.
Fix typo in class name and make sure MongoTestTemplate uses the configured simple types.

See: #3659
Original pull request: #3661.
2021-06-18 14:27:18 +02:00
Christoph Strobl
99eb849c93 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.
2021-06-18 14:13:33 +02:00
Christoph Strobl
d33aa682e5 Fix $or / $nor keyword mapping in query mapper.
This commit fixes an issue with the pattern used for detecting $or / $nor which also matched other keywords like $floor.

Closes: #3635
Original pull request: #3637.
2021-06-18 13:49:37 +02:00
Mark Paluch
f6db089f6f Polishing.
Add nullability annotation. Return early on null value conversion.

See #3633
Original pull request: #3643.
2021-06-09 14:14:49 +02:00
Christoph Strobl
13ae5e17bb Fix NPE in QueryMapper when trying to apply target type on null value.
Closes #3633
Original pull request: #3643.
2021-06-09 14:14:44 +02:00
Mark Paluch
ee203bf22a Polishing.
Reformat code.

See #3660.
Original pull request: #3662.
2021-06-09 11:34:20 +02:00
Christoph Strobl
990696ba11 Fix conversion for types having a converter registered.
Fixes: #3660
Original pull request: #3662.
2021-06-09 11:33:34 +02:00
Mark Paluch
4bc2f108fe After release cleanups.
See #3629
2021-05-14 12:34:20 +02:00
Mark Paluch
5064ba5b24 Prepare next development iteration.
See #3629
2021-05-14 12:34:16 +02:00
21 changed files with 769 additions and 96 deletions

View File

@@ -5,7 +5,7 @@
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.2.1</version>
<version>3.2.3</version>
<packaging>pom</packaging>
<name>Spring Data MongoDB</name>
@@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>2.5.1</version>
<version>2.5.3</version>
</parent>
<modules>
@@ -26,7 +26,7 @@
<properties>
<project.type>multi</project.type>
<dist.id>spring-data-mongodb</dist.id>
<springdata.commons>2.5.1</springdata.commons>
<springdata.commons>2.5.3</springdata.commons>
<mongo>4.2.3</mongo>
<mongo.reactivestreams>${mongo}</mongo.reactivestreams>
<jmh.version>1.19</jmh.version>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.2.1</version>
<version>3.2.3</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.2.1</version>
<version>3.2.3</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.2.1</version>
<version>3.2.3</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -141,7 +141,6 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this::getWriteTarget);
this.idMapper = new QueryMapper(this);
this.spELContext = new SpELContext(DocumentPropertyAccessor.INSTANCE);
this.dbRefProxyHandler = new DefaultDbRefProxyHandler(spELContext, mappingContext,
(prop, bson, evaluator, path) -> {
@@ -161,8 +160,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.notNull(path, "ObjectPath must not be null");
return new ConversionContext(path, this::readDocument, this::readCollectionOrArray, this::readMap, this::readDBRef,
this::getPotentiallyConvertedSimpleRead);
return new ConversionContext(conversions, path, this::readDocument, this::readCollectionOrArray, this::readMap,
this::readDBRef, this::getPotentiallyConvertedSimpleRead);
}
/**
@@ -376,8 +375,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
private <S> S populateProperties(ConversionContext context, MongoPersistentEntity<S> entity,
DocumentAccessor documentAccessor,
SpELExpressionEvaluator evaluator, S instance) {
DocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) {
PersistentPropertyAccessor<S> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
@@ -423,8 +421,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@Nullable
private Object readIdValue(ConversionContext context, SpELExpressionEvaluator evaluator,
MongoPersistentProperty idProperty,
Object rawId) {
MongoPersistentProperty idProperty, Object rawId) {
String expression = idProperty.getSpelExpression();
Object resolvedValue = expression != null ? evaluator.evaluate(expression) : rawId;
@@ -434,8 +431,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private void readProperties(ConversionContext context, MongoPersistentEntity<?> entity,
PersistentPropertyAccessor<?> accessor, DocumentAccessor documentAccessor,
MongoDbPropertyValueProvider valueProvider,
SpELExpressionEvaluator evaluator) {
MongoDbPropertyValueProvider valueProvider, SpELExpressionEvaluator evaluator) {
DbRefResolverCallback callback = null;
@@ -505,8 +501,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@Nullable
private Object readUnwrapped(ConversionContext context, DocumentAccessor documentAccessor,
MongoPersistentProperty prop,
MongoPersistentEntity<?> unwrappedEntity) {
MongoPersistentProperty prop, MongoPersistentEntity<?> unwrappedEntity) {
if (prop.findAnnotation(Unwrapped.class).onEmpty().equals(OnEmpty.USE_EMPTY)) {
return read(context, unwrappedEntity, (Document) documentAccessor.getDocument());
@@ -1045,7 +1040,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@SuppressWarnings({ "rawtypes", "unchecked" })
private Object getPotentiallyConvertedSimpleRead(Object value, @Nullable Class<?> target) {
if (target == null || ClassUtils.isAssignableValue(target, value)) {
if (target == null) {
return value;
}
@@ -1053,6 +1048,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return doConvert(value, target);
}
if (ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
@@ -1141,7 +1140,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
for (Object element : source) {
items.add(context.convert(element, componentType));
items.add(element != null ? context.convert(element, componentType) : element);
}
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
@@ -1206,7 +1205,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
Object value = entry.getValue();
map.put(key, context.convert(value, valueType));
map.put(key, value == null ? value : context.convert(value, valueType));
}
return map;
@@ -1447,8 +1446,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
T target = null;
if (document != null) {
maybeEmitEvent(
new AfterLoadEvent<>(document, (Class<T>) type.getType(), collectionName));
maybeEmitEvent(new AfterLoadEvent<>(document, (Class<T>) type.getType(), collectionName));
target = (T) readDocument(context, document, type);
}
@@ -1541,9 +1539,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
@SuppressWarnings("ConstantConditions")
private <T extends Object> T doConvert(Object value, Class<? extends T> target, @Nullable Class<? extends T> fallback) {
private <T extends Object> T doConvert(Object value, Class<? extends T> target,
@Nullable Class<? extends T> fallback) {
if(conversionService.canConvert(value.getClass(), target) || fallback == null) {
if (conversionService.canConvert(value.getClass(), target) || fallback == null) {
return conversionService.convert(value, target);
}
return conversionService.convert(value, fallback);
@@ -1853,6 +1852,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
*/
protected static class ConversionContext {
private final org.springframework.data.convert.CustomConversions conversions;
private final ObjectPath path;
private final ContainerValueConverter<Bson> documentConverter;
private final ContainerValueConverter<Collection<?>> collectionConverter;
@@ -1860,10 +1860,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private final ContainerValueConverter<DBRef> dbRefConverter;
private final ValueConverter<Object> elementConverter;
ConversionContext(ObjectPath path, ContainerValueConverter<Bson> documentConverter,
ContainerValueConverter<Collection<?>> collectionConverter, ContainerValueConverter<Bson> mapConverter,
ContainerValueConverter<DBRef> dbRefConverter, ValueConverter<Object> elementConverter) {
ConversionContext(org.springframework.data.convert.CustomConversions customConversions, ObjectPath path,
ContainerValueConverter<Bson> documentConverter, ContainerValueConverter<Collection<?>> collectionConverter,
ContainerValueConverter<Bson> mapConverter, ContainerValueConverter<DBRef> dbRefConverter,
ValueConverter<Object> elementConverter) {
this.conversions = customConversions;
this.path = path;
this.documentConverter = documentConverter;
this.collectionConverter = collectionConverter;
@@ -1882,8 +1884,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@SuppressWarnings("unchecked")
public <S extends Object> S convert(Object source, TypeInformation<? extends S> typeHint) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(typeHint, "TypeInformation must not be null");
if (conversions.hasCustomReadTarget(source.getClass(), typeHint.getType())) {
return (S) elementConverter.convert(source, typeHint);
}
if (source instanceof Collection) {
Class<?> rawType = typeHint.getType();
@@ -1900,7 +1907,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
}
if (typeHint.isMap()) {
return (S) mapConverter.convert(this, (Bson) source, typeHint);
if(ClassUtils.isAssignable(Document.class, typeHint.getType())) {
return (S) documentConverter.convert(this, BsonUtils.asBson(source), typeHint);
}
if (BsonUtils.supportsBson(source)) {
return (S) mapConverter.convert(this, BsonUtils.asBson(source), typeHint);
}
throw new IllegalArgumentException(String.format("Expected map like structure but found %s", source.getClass()));
}
if (source instanceof DBRef) {
@@ -1912,8 +1928,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
String.format(INCOMPATIBLE_TYPES, source, BasicDBList.class, typeHint.getType(), getPath()));
}
if (source instanceof Bson) {
return (S) documentConverter.convert(this, (Bson) source, typeHint);
if (BsonUtils.supportsBson(source)) {
return (S) documentConverter.convert(this, BsonUtils.asBson(source), typeHint);
}
return (S) elementConverter.convert(source, typeHint);
@@ -1929,8 +1945,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
Assert.notNull(currentPath, "ObjectPath must not be null");
return new ConversionContext(currentPath, documentConverter, collectionConverter, mapConverter, dbRefConverter,
elementConverter);
return new ConversionContext(conversions, currentPath, documentConverter, collectionConverter, mapConverter,
dbRefConverter, elementConverter);
}
public ObjectPath getPath() {

View File

@@ -19,11 +19,14 @@ import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Example;
@@ -65,9 +68,12 @@ import com.mongodb.DBRef;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author David Julia
*/
public class QueryMapper {
protected static final Logger LOGGER = LoggerFactory.getLogger(QueryMapper.class);
private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
private static final Document META_TEXT_SCORE = new Document("$meta", "textScore");
static final ClassTypeInformation<?> NESTED_DOCUMENT = ClassTypeInformation.from(NestedDocument.class);
@@ -189,19 +195,7 @@ public class QueryMapper {
return new Document();
}
sortObject = filterUnwrappedObjects(sortObject, entity);
Document mappedSort = new Document();
for (Map.Entry<String, Object> entry : BsonUtils.asMap(sortObject).entrySet()) {
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
if (field.getProperty() != null && field.getProperty().isUnwrapped()) {
continue;
}
mappedSort.put(field.getMappedKey(), entry.getValue());
}
Document mappedSort = mapFieldsToPropertyNames(sortObject, entity);
mapMetaAttributes(mappedSort, entity, MetaMapping.WHEN_PRESENT);
return mappedSort;
}
@@ -219,13 +213,30 @@ public class QueryMapper {
Assert.notNull(fieldsObject, "FieldsObject must not be null!");
fieldsObject = filterUnwrappedObjects(fieldsObject, entity);
Document mappedFields = getMappedObject(fieldsObject, entity);
Document mappedFields = mapFieldsToPropertyNames(fieldsObject, entity);
mapMetaAttributes(mappedFields, entity, MetaMapping.FORCE);
return mappedFields;
}
private Document mapFieldsToPropertyNames(Document fields, @Nullable MongoPersistentEntity<?> entity) {
if (fields.isEmpty()) {
return new Document();
}
Document target = new Document();
for (Map.Entry<String, Object> entry : BsonUtils.asMap(filterUnwrappedObjects(fields, entity)).entrySet()) {
Field field = createPropertyField(entity, entry.getKey(), mappingContext);
if (field.getProperty() != null && field.getProperty().isUnwrapped()) {
continue;
}
target.put(field.getMappedKey(), entry.getValue());
}
return target;
}
private void mapMetaAttributes(Document source, @Nullable MongoPersistentEntity<?> entity, MetaMapping metaMapping) {
if (entity == null) {
@@ -444,6 +455,10 @@ public class QueryMapper {
}
}
if (value == null) {
return null;
}
if (isNestedKeyword(value)) {
return getMappedKeyword(new Keyword((Bson) value), documentField.getPropertyEntity());
}
@@ -706,7 +721,7 @@ public class QueryMapper {
* @param candidate
* @return
*/
protected boolean isNestedKeyword(Object candidate) {
protected boolean isNestedKeyword(@Nullable Object candidate) {
if (!(candidate instanceof Document)) {
return false;
@@ -751,12 +766,13 @@ public class QueryMapper {
* converted one by one.
*
* @param documentField the field and its meta data
* @param value the actual value
* @param value the actual value. Can be {@literal null}.
* @return the potentially converted target value.
*/
private Object applyFieldTargetTypeHintToValue(Field documentField, Object value) {
@Nullable
private Object applyFieldTargetTypeHintToValue(Field documentField, @Nullable Object value) {
if (documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) {
if (value == null || documentField.getProperty() == null || !documentField.getProperty().hasExplicitWriteTarget()) {
return value;
}
@@ -787,7 +803,6 @@ public class QueryMapper {
*/
static class Keyword {
private static final String N_OR_PATTERN = "\\$.*or";
private static final Set<String> NON_DBREF_CONVERTING_KEYWORDS = new HashSet<>(
Arrays.asList("$", "$size", "$slice", "$gt", "$lt"));
@@ -818,7 +833,7 @@ public class QueryMapper {
}
public boolean isOrOrNor() {
return key.matches(N_OR_PATTERN);
return key.equalsIgnoreCase("$or") || key.equalsIgnoreCase("$nor");
}
/**
@@ -1169,8 +1184,8 @@ public class QueryMapper {
removePlaceholders(DOT_POSITIONAL_PATTERN, pathExpression));
if (sourceProperty != null && sourceProperty.getOwner().equals(entity)) {
return mappingContext
.getPersistentPropertyPath(PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
return mappingContext.getPersistentPropertyPath(
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
}
PropertyPath path = forName(rawPath);
@@ -1178,29 +1193,47 @@ public class QueryMapper {
return null;
}
try {
PersistentPropertyPath<MongoPersistentProperty> propertyPath = tryToResolvePersistentPropertyPath(path);
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext.getPersistentPropertyPath(path);
if (propertyPath == null) {
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
boolean associationDetected = false;
if (QueryMapper.LOGGER.isInfoEnabled()) {
while (iterator.hasNext()) {
String types = StringUtils.collectionToDelimitedString(
path.stream().map(it -> it.getType().getSimpleName()).collect(Collectors.toList()), " -> ");
QueryMapper.LOGGER.info(
"Could not map '{}'. Maybe a fragment in '{}' is considered a simple type. Mapper continues with {}.",
path, types, pathExpression);
}
return null;
}
MongoPersistentProperty property = iterator.next();
Iterator<MongoPersistentProperty> iterator = propertyPath.iterator();
boolean associationDetected = false;
if (property.isAssociation()) {
associationDetected = true;
continue;
}
while (iterator.hasNext()) {
if (associationDetected && !property.isIdProperty()) {
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
}
MongoPersistentProperty property = iterator.next();
if (property.isAssociation()) {
associationDetected = true;
continue;
}
return propertyPath;
} catch (InvalidPersistentPropertyPath e) {
if (associationDetected && !property.isIdProperty()) {
throw new MappingException(String.format(INVALID_ASSOCIATION_REFERENCE, pathExpression));
}
}
return propertyPath;
}
@Nullable
private PersistentPropertyPath<MongoPersistentProperty> tryToResolvePersistentPropertyPath(PropertyPath path) {
try {
return mappingContext.getPersistentPropertyPath(path);
} catch (MappingException e) {
return null;
}
}
@@ -1329,12 +1362,17 @@ public class QueryMapper {
static class KeyMapper {
private final Iterator<String> iterator;
private int currentIndex;
private String currentPropertyRoot;
private final List<String> pathParts;
public KeyMapper(String key,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
this.iterator = Arrays.asList(key.split("\\.")).iterator();
this.iterator.next();
this.pathParts = Arrays.asList(key.split("\\."));
this.iterator = pathParts.iterator();
this.currentPropertyRoot = iterator.next();
this.currentIndex = 0;
}
/**
@@ -1346,21 +1384,31 @@ public class QueryMapper {
protected String mapPropertyName(MongoPersistentProperty property) {
StringBuilder mappedName = new StringBuilder(PropertyToFieldNameConverter.INSTANCE.convert(property));
boolean inspect = iterator.hasNext();
while (inspect) {
String partial = iterator.next();
currentIndex++;
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike();
boolean isPositional = isPositionalParameter(partial) && property.isCollectionLike() ;
if(property.isMap() && currentPropertyRoot.equals(partial) && iterator.hasNext()){
partial = iterator.next();
currentIndex++;
}
if (isPositional || property.isMap()) {
if (isPositional || property.isMap() && !currentPropertyRoot.equals(partial)) {
mappedName.append(".").append(partial);
}
inspect = isPositional && iterator.hasNext();
}
if(currentIndex + 1 < pathParts.size()) {
currentIndex++;
currentPropertyRoot = pathParts.get(currentIndex);
}
return mappedName.toString();
}

View File

@@ -16,7 +16,7 @@
package org.springframework.data.mongodb.core.geo;
/**
* Interface definition for structures defined in <a href="https://geojson.org/>GeoJSON</a> format.
* Interface definition for structures defined in <a href="https://geojson.org/">GeoJSON</a> format.
*
* @author Christoph Strobl
* @since 1.7

View File

@@ -15,8 +15,11 @@
*/
package org.springframework.data.mongodb.core.mapping;
import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @author Rogério Meneguelli Gatto
* @since 3.2
*/
class UnwrapEntityContext {
@@ -30,4 +33,32 @@ class UnwrapEntityContext {
public MongoPersistentProperty getProperty() {
return property;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
UnwrapEntityContext that = (UnwrapEntityContext) obj;
return ObjectUtils.nullSafeEquals(property, that.property);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(property);
}
}

View File

@@ -24,11 +24,13 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Unwrapped variant of {@link MongoPersistentProperty}.
*
* @author Christoph Strobl
* @author Rogério Meneguelli Gatto
* @since 3.2
* @see Unwrapped
*/
@@ -304,4 +306,38 @@ class UnwrappedMongoPersistentProperty implements MongoPersistentProperty {
public <T> PersistentPropertyAccessor<T> getAccessorForOwner(T owner) {
return delegate.getAccessorForOwner(owner);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
UnwrappedMongoPersistentProperty that = (UnwrappedMongoPersistentProperty) obj;
if (!ObjectUtils.nullSafeEquals(delegate, that.delegate)) {
return false;
}
return ObjectUtils.nullSafeEquals(context, that.context);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(delegate);
result = 31 * result + ObjectUtils.nullSafeHashCode(context);
return result;
}
}

View File

@@ -488,6 +488,49 @@ public class BsonUtils {
return null;
}
/**
* Returns the given source object as {@link Bson}, i.e. {@link Document}s and maps as is or throw
* {@link IllegalArgumentException}.
*
* @param source
* @return the converted/casted source object.
* @throws IllegalArgumentException if {@code source} cannot be converted/cast to {@link Bson}.
* @since 3.2.3
* @see #supportsBson(Object)
*/
@SuppressWarnings("unchecked")
public static Bson asBson(Object source) {
if (source instanceof Document) {
return (Document) source;
}
if (source instanceof BasicDBObject) {
return (BasicDBObject) source;
}
if (source instanceof DBObject) {
return new Document(((DBObject) source).toMap());
}
if (source instanceof Map) {
return new Document((Map<String, Object>) source);
}
throw new IllegalArgumentException(String.format("Cannot convert %s to Bson", source));
}
/**
* Returns the given source can be used/converted as {@link Bson}.
*
* @param source
* @return {@literal true} if the given source can be converted to {@link Bson}.
* @since 3.2.3
*/
public static boolean supportsBson(Object source) {
return source instanceof DBObject || source instanceof Map;
}
/**
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element

View File

@@ -30,7 +30,7 @@ import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import org.assertj.core.api.Assertions;
import org.bson.types.Binary;
import org.bson.types.Code;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;
@@ -42,6 +42,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.factory.annotation.Value;
@@ -81,6 +82,8 @@ import org.springframework.data.mongodb.core.mapping.TextScore;
import org.springframework.data.mongodb.core.mapping.Unwrapped;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.BasicDBList;
@@ -527,7 +530,7 @@ class MappingMongoConverterUnitTests {
}
@Test
public void convertsObjectsIfNecessary() {
void convertsObjectsIfNecessary() {
ObjectId id = new ObjectId();
assertThat(converter.convertToMongoType(id)).isEqualTo(id);
@@ -931,10 +934,11 @@ class MappingMongoConverterUnitTests {
assertThat(readResult.iterator().next()).isInstanceOf(Address.class);
}
@Test // DATAMONGO-402
@Test // DATAMONGO-402, GH-3702
void readsMemberClassCorrectly() {
org.bson.Document document = new org.bson.Document("inner", new org.bson.Document("value", "FOO!"));
org.bson.Document document = new org.bson.Document("inner",
new LinkedHashMap<>(new org.bson.Document("value", "FOO!")));
Outer outer = converter.read(Outer.class, document);
assertThat(outer.inner).isNotNull();
@@ -2111,21 +2115,21 @@ class MappingMongoConverterUnitTests {
}
@Test // DATAMONGO-2479
public void entityCallbacksAreNotSetByDefault() {
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isNull();
void entityCallbacksAreNotSetByDefault() {
assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isNull();
}
@Test // DATAMONGO-2479
public void entityCallbacksShouldBeInitiatedOnSettingApplicationContext() {
void entityCallbacksShouldBeInitiatedOnSettingApplicationContext() {
ApplicationContext ctx = new StaticApplicationContext();
converter.setApplicationContext(ctx);
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isNotNull();
assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isNotNull();
}
@Test // DATAMONGO-2479
public void setterForEntityCallbackOverridesContextInitializedOnes() {
void setterForEntityCallbackOverridesContextInitializedOnes() {
ApplicationContext ctx = new StaticApplicationContext();
converter.setApplicationContext(ctx);
@@ -2133,11 +2137,11 @@ class MappingMongoConverterUnitTests {
EntityCallbacks callbacks = EntityCallbacks.create();
converter.setEntityCallbacks(callbacks);
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isSameAs(callbacks);
assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isSameAs(callbacks);
}
@Test // DATAMONGO-2479
public void setterForApplicationContextShouldNotOverrideAlreadySetEntityCallbacks() {
void setterForApplicationContextShouldNotOverrideAlreadySetEntityCallbacks() {
EntityCallbacks callbacks = EntityCallbacks.create();
ApplicationContext ctx = new StaticApplicationContext();
@@ -2145,11 +2149,11 @@ class MappingMongoConverterUnitTests {
converter.setEntityCallbacks(callbacks);
converter.setApplicationContext(ctx);
Assertions.assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isSameAs(callbacks);
assertThat(ReflectionTestUtils.getField(converter, "entityCallbacks")).isSameAs(callbacks);
}
@Test // DATAMONGO-2479
public void resolveDBRefMapValueShouldInvokeCallbacks() {
void resolveDBRefMapValueShouldInvokeCallbacks() {
AfterConvertCallback<Person> afterConvertCallback = spy(new ReturningAfterConvertCallback());
converter.setEntityCallbacks(EntityCallbacks.create(afterConvertCallback));
@@ -2166,7 +2170,7 @@ class MappingMongoConverterUnitTests {
}
@Test // DATAMONGO-2300
public void readAndConvertDBRefNestedByMapCorrectly() {
void readAndConvertDBRefNestedByMapCorrectly() {
org.bson.Document cluster = new org.bson.Document("_id", 100L);
DBRef dbRef = new DBRef("clusters", 100L);
@@ -2428,6 +2432,178 @@ class MappingMongoConverterUnitTests {
verify(subTypeOfGenericTypeConverter).convert(eq(source));
}
@Test // GH-3660
void usesCustomConverterForMapTypesOnWrite() {
converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new TypeImplementingMapToDocumentConverter());
}));
converter.afterPropertiesSet();
TypeImplementingMap source = new TypeImplementingMap("one", 2);
org.bson.Document target = new org.bson.Document();
converter.write(source, target);
assertThat(target).containsEntry("1st", "one").containsEntry("2nd", 2);
}
@Test // GH-3660
void usesCustomConverterForTypesImplementingMapOnWrite() {
converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new TypeImplementingMapToDocumentConverter());
}));
converter.afterPropertiesSet();
TypeImplementingMap source = new TypeImplementingMap("one", 2);
org.bson.Document target = new org.bson.Document();
converter.write(source, target);
assertThat(target).containsEntry("1st", "one").containsEntry("2nd", 2);
}
@Test // GH-3660
void usesCustomConverterForTypesImplementingMapOnRead() {
converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new DocumentToTypeImplementingMapConverter());
}));
converter.afterPropertiesSet();
org.bson.Document source = new org.bson.Document("1st", "one")
.append("2nd", 2)
.append("_class", TypeImplementingMap.class.getName());
TypeImplementingMap target = converter.read(TypeImplementingMap.class, source);
assertThat(target).isEqualTo(new TypeImplementingMap("one", 2));
}
@Test // GH-3660
void usesCustomConverterForPropertiesUsingTypesThatImplementMapOnWrite() {
converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new TypeImplementingMapToDocumentConverter());
}));
converter.afterPropertiesSet();
TypeWrappingTypeImplementingMap source = new TypeWrappingTypeImplementingMap();
source.typeImplementingMap = new TypeImplementingMap("one", 2);
org.bson.Document target = new org.bson.Document();
converter.write(source, target);
assertThat(target).containsEntry("typeImplementingMap", new org.bson.Document("1st", "one").append("2nd", 2));
}
@Test // GH-3660
void usesCustomConverterForPropertiesUsingTypesImplementingMapOnRead() {
converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new DocumentToTypeImplementingMapConverter());
}));
converter.afterPropertiesSet();
org.bson.Document source = new org.bson.Document("typeImplementingMap",
new org.bson.Document("1st", "one")
.append("2nd", 2))
.append("_class", TypeWrappingTypeImplementingMap.class.getName());
TypeWrappingTypeImplementingMap target = converter.read(TypeWrappingTypeImplementingMap.class, source);
assertThat(target.typeImplementingMap).isEqualTo(new TypeImplementingMap("one", 2));
}
@Test // GH-3686
void readsCollectionContainingNullValue() {
org.bson.Document source = new org.bson.Document("items", Arrays.asList(new org.bson.Document("itemKey", "i1"), null, new org.bson.Document("itemKey", "i3")));
Order target = converter.read(Order.class, source);
assertThat(target.items)
.map(it -> it != null ? it.itemKey : null)
.containsExactly("i1", null, "i3");
}
@Test // GH-3686
void readsArrayContainingNullValue() {
org.bson.Document source = new org.bson.Document("arrayOfStrings", Arrays.asList("i1", null, "i3"));
WithArrays target = converter.read(WithArrays.class, source);
assertThat(target.arrayOfStrings).containsExactly("i1", null, "i3");
}
@Test // GH-3686
void readsMapContainingNullValue() {
org.bson.Document source = new org.bson.Document("mapOfObjects", new org.bson.Document("item1", "i1").append("item2", null).append("item3", "i3"));
ClassWithMapProperty target = converter.read(ClassWithMapProperty.class, source);
assertThat(target.mapOfObjects)
.containsEntry("item1", "i1")
.containsEntry("item2", null)
.containsEntry("item3", "i3");
}
@Test // GH-3670
void appliesCustomConverterEvenToSimpleTypes() {
converter = new MappingMongoConverter(resolver, mappingContext);
converter.setCustomConversions(MongoCustomConversions.create(it -> {
it.registerConverter(new MongoSimpleTypeConverter());
}));
converter.afterPropertiesSet();
org.bson.Document source = new org.bson.Document("content", new Binary(new byte[] {0x00, 0x42}));
GenericType<Object> target = converter.read(GenericType.class, source);
assertThat(target.content).isInstanceOf(byte[].class);
}
@Test // GH-3702
void readsRawDocument() {
org.bson.Document source = new org.bson.Document("_id", "id-1").append("raw", new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1)));
WithRawDocumentProperties target = converter.read(WithRawDocumentProperties.class, source);
assertThat(target.raw).isInstanceOf(org.bson.Document.class).isEqualTo( new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1)));
}
@Test // GH-3702
void readsListOfRawDocument() {
org.bson.Document source = new org.bson.Document("_id", "id-1").append("listOfRaw", Arrays.asList(new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1))));
WithRawDocumentProperties target = converter.read(WithRawDocumentProperties.class, source);
assertThat(target.listOfRaw)
.containsExactly(new org.bson.Document("simple", 1).append("document", new org.bson.Document("inner-doc", 1)));
}
@Test // GH-3692
void readsMapThatDoesNotComeAsDocument() {
org.bson.Document source = new org.bson.Document("_id", "id-1").append("mapOfObjects", Collections.singletonMap("simple", 1));
ClassWithMapProperty target = converter.read(ClassWithMapProperty.class, source);
assertThat(target.mapOfObjects).containsEntry("simple",1);
}
static class GenericType<T> {
T content;
}
@@ -2789,6 +2965,10 @@ class MappingMongoConverterUnitTests {
}
static class WithArrays {
String[] arrayOfStrings;
}
// DATAMONGO-1898
// DATACMNS-1278
@@ -2971,4 +3151,122 @@ class MappingMongoConverterUnitTests {
return target;
}
}
@WritingConverter
static class TypeImplementingMapToDocumentConverter implements Converter<TypeImplementingMap, org.bson.Document> {
@Nullable
@Override
public org.bson.Document convert(TypeImplementingMap source) {
return new org.bson.Document("1st", source.val1).append("2nd", source.val2);
}
}
@ReadingConverter
static class DocumentToTypeImplementingMapConverter implements Converter<org.bson.Document, TypeImplementingMap> {
@Nullable
@Override
public TypeImplementingMap convert(org.bson.Document source) {
return new TypeImplementingMap(source.getString("1st"), source.getInteger("2nd"));
}
}
@ReadingConverter
public static class MongoSimpleTypeConverter implements Converter<Binary, Object> {
@Override
public byte[] convert(Binary source) {
return source.getData();
}
}
static class TypeWrappingTypeImplementingMap {
String id;
TypeImplementingMap typeImplementingMap;
}
@EqualsAndHashCode
static class TypeImplementingMap implements Map<String,String> {
String val1;
int val2;
TypeImplementingMap(String val1, int val2) {
this.val1 = val1;
this.val2 = val2;
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean containsKey(Object key) {
return false;
}
@Override
public boolean containsValue(Object value) {
return false;
}
@Override
public String get(Object key) {
return null;
}
@Nullable
@Override
public String put(String key, String value) {
return null;
}
@Override
public String remove(Object key) {
return null;
}
@Override
public void putAll(@NonNull Map<? extends String, ? extends String> m) {
}
@Override
public void clear() {
}
@NonNull
@Override
public Set<String> keySet() {
return null;
}
@NonNull
@Override
public Collection<String> values() {
return null;
}
@NonNull
@Override
public Set<Entry<String, String>> entrySet() {
return null;
}
}
static class WithRawDocumentProperties {
String id;
org.bson.Document raw;
List<org.bson.Document> listOfRaw;
}
}

View File

@@ -36,8 +36,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.geo.Point;
@@ -50,6 +55,7 @@ import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
import org.springframework.data.mongodb.core.mapping.MongoId;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.TextScore;
@@ -71,8 +77,10 @@ import com.mongodb.client.model.Filters;
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
* @author David Julia
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class QueryMapperUnitTests {
private QueryMapper mapper;
@@ -732,6 +740,28 @@ public class QueryMapperUnitTests {
assertThat(document).containsKey("map.1.stringProperty");
}
@Test // GH-3688
void mappingShouldRetainNestedNumericMapKeys() {
Query query = query(where("outerMap.1.map.2.stringProperty").is("ba'alzamon"));
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(EntityWithIntKeyedMapOfMap.class));
assertThat(document).containsKey("outerMap.1.map.2.stringProperty");
}
@Test // GH-3688
void mappingShouldAllowSettingEntireNestedNumericKeyedMapValue() {
Query query = query(where("outerMap.1.map").is(null)); //newEntityWithComplexValueTypeMap()
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(EntityWithIntKeyedMapOfMap.class));
assertThat(document).containsKey("outerMap.1.map");
}
@Test // DATAMONGO-1269
void mappingShouldRetainNumericPositionInList() {
@@ -1257,6 +1287,52 @@ public class QueryMapperUnitTests {
assertThat(document).isEqualTo(new org.bson.Document("double_underscore.renamed", new org.bson.Document("$exists", true)));
}
@Test // GH-3633
void mapsNullValueForFieldWithCustomTargetType() {
Query query = query(where("stringAsOid").is(null));
org.bson.Document document = mapper.getMappedObject(query.getQueryObject(),
context.getPersistentEntity(NonIdFieldWithObjectIdTargetType.class));
assertThat(document).isEqualTo(new org.bson.Document("stringAsOid", null));
}
@Test // GH-3635
void $floorKeywordDoesNotMatch$or$norPattern() {
Query query = new BasicQuery(" { $expr: { $gt: [ \"$spent\" , { $floor : \"$budget\" } ] } }");
assertThatNoException()
.isThrownBy(() -> mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(Foo.class)));
}
@Test // GH-3659
void allowsUsingFieldPathsForPropertiesHavingCustomConversionRegistered() {
Query query = query(where("address.street").is("1007 Mountain Drive"));
MongoCustomConversions mongoCustomConversions = new MongoCustomConversions(Collections.singletonList(new MyAddressToDocumentConverter()));
this.context = new MongoMappingContext();
this.context.setSimpleTypeHolder(mongoCustomConversions.getSimpleTypeHolder());
this.context.afterPropertiesSet();
this.converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
this.converter.setCustomConversions(mongoCustomConversions);
this.converter.afterPropertiesSet();
this.mapper = new QueryMapper(converter);
assertThat(mapper.getMappedSort(query.getQueryObject(), context.getPersistentEntity(Customer.class))).isEqualTo(new org.bson.Document("address.street", "1007 Mountain Drive"));
}
@Test // GH-3668
void mapStringIdFieldProjection() {
org.bson.Document mappedFields = mapper.getMappedFields(new org.bson.Document("id", 1), context.getPersistentEntity(WithStringId.class));
assertThat(mappedFields).containsEntry("_id", 1);
}
class WithDeepArrayNesting {
List<WithNestedArray> level0;
@@ -1320,6 +1396,12 @@ public class QueryMapperUnitTests {
@Id private String foo;
}
class WithStringId {
@MongoId String id;
String name;
}
class BigIntegerId {
@Id private BigInteger id;
@@ -1396,18 +1478,22 @@ public class QueryMapperUnitTests {
@Field("geoJsonPointWithNameViaFieldAnnotation") GeoJsonPoint namedGeoJsonPoint;
}
static class SimpeEntityWithoutId {
static class SimpleEntityWithoutId {
String stringProperty;
Integer integerProperty;
}
static class EntityWithComplexValueTypeMap {
Map<Integer, SimpeEntityWithoutId> map;
Map<Integer, SimpleEntityWithoutId> map;
}
static class EntityWithIntKeyedMapOfMap{
Map<Integer, EntityWithComplexValueTypeMap> outerMap;
}
static class EntityWithComplexValueTypeList {
List<SimpeEntityWithoutId> list;
List<SimpleEntityWithoutId> list;
}
static class WithExplicitTargetTypes {
@@ -1487,4 +1573,28 @@ public class QueryMapperUnitTests {
@Field("renamed")
String renamed_fieldname_with_underscores;
}
@Document
static class Customer {
@Id
private ObjectId id;
private String name;
private MyAddress address;
}
static class MyAddress {
private String street;
}
@WritingConverter
public static class MyAddressToDocumentConverter implements Converter<MyAddress, org.bson.Document> {
@Override
public org.bson.Document convert(MyAddress address) {
org.bson.Document doc = new org.bson.Document();
doc.put("street", address.street);
return doc;
}
}
}

View File

@@ -66,6 +66,7 @@ import com.mongodb.DBRef;
* @author Thomas Darimont
* @author Mark Paluch
* @author Pavel Vodrazka
* @author David Julia
*/
@ExtendWith(MockitoExtension.class)
class UpdateMapperUnitTests {
@@ -1179,6 +1180,16 @@ class UpdateMapperUnitTests {
assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"map.601218778970110001827396.value\": \"testing\"}}");
}
@Test // GH-3688
void multipleNumericKeysInNestedPath() {
Update update = new Update().set("intKeyedMap.12345.map.0", "testing");
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
context.getPersistentEntity(EntityWithIntKeyedMap.class));
assertThat(mappedUpdate).isEqualTo("{\"$set\": {\"intKeyedMap.12345.map.0\": \"testing\"}}");
}
@Test // GH-3566
void mapsObjectClassPropertyFieldInMapValueTypeAsKey() {
@@ -1425,6 +1436,10 @@ class UpdateMapperUnitTests {
Map<Object, NestedDocument> concreteMap;
}
static class EntityWithIntKeyedMap{
Map<Integer, EntityWithObjectMap> intKeyedMap;
}
static class ClassWithEnum {
Allocation allocation;

View File

@@ -60,7 +60,9 @@ import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.repository.Person.Sex;
import org.springframework.data.mongodb.repository.SampleEvaluationContextExtension.SampleSecurityContextHolder;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
@@ -1422,4 +1424,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
Person target = repository.findWithAggregationInProjection(alicia.getId());
assertThat(target.getFirstname()).isEqualTo(alicia.getFirstname().toUpperCase());
}
@Test // GH-3633
void annotatedQueryWithNullEqualityCheckShouldWork() {
operations.updateFirst(Query.query(Criteria.where("id").is(dave.getId())), Update.update("age", null), Person.class);
Person byQueryWithNullEqualityCheck = repository.findByQueryWithNullEqualityCheck();
assertThat(byQueryWithNullEqualityCheck.getId()).isEqualTo(dave.getId());
}
}

View File

@@ -410,4 +410,7 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
List<Person> findByUnwrappedUserUsername(String username);
List<Person> findByUnwrappedUser(User user);
@Query("{ 'age' : null }")
Person findByQueryWithNullEqualityCheck();
}

View File

@@ -119,6 +119,9 @@ public class MongoTestTemplateConfiguration {
mappingContext = new MongoMappingContext();
mappingContext.setInitialEntitySet(mappingContextConfigurer.initialEntitySet());
mappingContext.setAutoIndexCreation(mappingContextConfigurer.autocreateIndex);
if(mongoConverterConfigurer.customConversions != null) {
mappingContext.setSimpleTypeHolder(mongoConverterConfigurer.customConversions.getSimpleTypeHolder());
}
mappingContext.afterPropertiesSet();
}

View File

@@ -19,7 +19,7 @@ import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Collections;
import org.bson.BsonDouble;
import org.bson.BsonInt32;
@@ -29,10 +29,16 @@ import org.bson.BsonString;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.util.BsonUtils;
import com.mongodb.BasicDBList;
/**
* Unit tests for {@link BsonUtils}.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
class BsonUtilsTest {
@@ -111,4 +117,13 @@ class BsonUtilsTest {
assertThat((Collection)BsonUtils.asCollection(source)).containsExactly(source);
}
@Test // GH-3702
void supportsBsonShouldReportIfConversionSupported() {
assertThat(BsonUtils.supportsBson("foo")).isFalse();
assertThat(BsonUtils.supportsBson(new Document())).isTrue();
assertThat(BsonUtils.supportsBson(new BasicDBList())).isTrue();
assertThat(BsonUtils.supportsBson(Collections.emptyMap())).isTrue();
}
}

View File

@@ -383,6 +383,13 @@ class ParameterBindingJsonReaderUnitTests {
.parse("{ 'stores.location' : { $geoWithin: { $centerSphere: [ [ 1.948516, 48.799029 ] , 0.004 ] } } }"));
}
@Test // GH-3633
void parsesNullValue() {
Document target = parse("{ 'parent' : null }");
assertThat(target).isEqualTo(new Document("parent", null));
}
private static Document parse(String json, Object... args) {
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);

View File

@@ -214,7 +214,7 @@ public class AppConfig {
----
====
To access the `com.mongodb.client.MongoClient` object created by the `MongoClientFactoryBean` in other `@Configuration` classes or your own classes, use a `private @Autowired Mongo mongo;` field.
To access the `com.mongodb.client.MongoClient` object created by the `MongoClientFactoryBean` in other `@Configuration` classes or your own classes, use a `private @Autowired MongoClient mongoClient;` field.
[[mongo.mongo-xml-config]]
=== Registering a Mongo Instance by Using XML-based Metadata

View File

@@ -1,6 +1,37 @@
Spring Data MongoDB Changelog
=============================
Changes in version 3.2.3 (2021-07-16)
-------------------------------------
* #3702 - `MappingMongoConverter` incorrectly processes an object property of type `org.bson.Document`.
* #3689 - Fix Regression in generating queries with nested maps with numeric keys.
* #3688 - Multiple maps with numeric keys in a single update produces the wrong query (Regression).
* #3686 - reading a document with a list with a null element fails with Spring Data Mongo 3.2.2, works with 3.2.1.
* #3684 - Add equals and hashcode to UnwrappedMongoPersistentProperty (fixes #3683).
* #3683 - Memory Leak: instances of UnwrappedMongoPersistentProperty are accumulating in PreferredConstructor.isPropertyParameterCache.
* #3670 - `Binary` not deserialized to `byte[]` for property of type `Object`.
Changes in version 3.2.2 (2021-06-22)
-------------------------------------
* #3677 - Add missing double quote to GeoJson.java JSDoc header.
* #3668 - Projection on the _id field returns wrong result when using `@MongoId` (MongoDB 4.4).
* #3666 - Documentation references outdated `Mongo` client.
* #3660 - MappingMongoConverter problem: ConversionContext#convert does not try to use custom converters first.
* #3659 - [3.2.1] Indexing Class with Custom Converter -> Couldn't find PersistentEntity for property private [...].
* #3635 - $floor isOrOrNor() return true.
* #3633 - NPE in QueryMapper when use Query with `null` as value.
Changes in version 3.1.10 (2021-06-22)
--------------------------------------
* #3677 - Add missing double quote to GeoJson.java JSDoc header.
* #3666 - Documentation references outdated `Mongo` client.
* #3659 - [3.2.1] Indexing Class with Custom Converter -> Couldn't find PersistentEntity for property private [...].
* #3635 - $floor isOrOrNor() return true.
* #3633 - NPE in QueryMapper when use Query with `null` as value.
Changes in version 3.2.1 (2021-05-14)
-------------------------------------
* #3638 - Introduce template method for easier customization of fragments.
@@ -3440,6 +3471,9 @@ Repository

View File

@@ -1,4 +1,4 @@
Spring Data MongoDB 3.2.1 (2021.0.1)
Spring Data MongoDB 3.2.3 (2021.0.3)
Copyright (c) [2010-2019] Pivotal Software, Inc.
This product is licensed to you under the Apache License, Version 2.0 (the "License").
@@ -26,5 +26,7 @@ conditions of the subcomponent's license, as noted in the LICENSE file.