Browse Source

DATAMONGO-1854 - Allow Collation to be configured on entity level.

The collation can now also be configured on entity level and gets applied via MongoTemplate. However one can alway override a default collation by adding the collation explicitly to either the Query or one of the Options available for various operations.
When it comes to the repository level the hierarchy is method parameter over query annotation over entity metadata.

Remove collation annotation in favor of attributes of Query / Document.

Original pull request: #644.
pull/743/head
Christoph Strobl 7 years ago committed by Mark Paluch
parent
commit
ac1873a163
  1. 38
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java
  2. 51
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java
  3. 35
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java
  4. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndReplaceOptions.java
  5. 146
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  6. 134
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  7. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java
  8. 52
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java
  9. 10
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java
  10. 26
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java
  11. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Collation.java
  12. 1
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
  13. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoEntityInformation.java
  14. 10
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java
  15. 30
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java
  16. 23
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java
  17. 121
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsUnitTests.java
  18. 126
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsUnitTests.java
  19. 334
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
  20. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java
  21. 270
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
  22. 36
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java
  23. 5
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
  24. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java
  25. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java
  26. 139
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryUnitTests.java
  27. 150
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepositoryUnitTests.java
  28. 14
      src/main/asciidoc/reference/mongodb.adoc

38
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java

@ -120,19 +120,14 @@ public class DefaultIndexOperations implements IndexOperations { @@ -120,19 +120,14 @@ public class DefaultIndexOperations implements IndexOperations {
return execute(collection -> {
Document indexOptions = indexDefinition.getIndexOptions();
MongoPersistentEntity<?> entity = lookupPersistentEntity(type, collectionName);
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
IndexOptions indexOptions = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity);
indexOptions = addDefaultCollationIfRequired(indexOptions, entity);
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
ops.partialFilterExpression(mapper.getMappedObject((Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY),
lookupPersistentEntity(type, collectionName)));
}
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
return collection.createIndex(indexDefinition.getIndexKeys(), indexOptions);
});
}
@ -192,7 +187,7 @@ public class DefaultIndexOperations implements IndexOperations { @@ -192,7 +187,7 @@ public class DefaultIndexOperations implements IndexOperations {
private List<IndexInfo> getIndexData(MongoCursor<Document> cursor) {
List<IndexInfo> indexInfoList = new ArrayList<IndexInfo>();
List<IndexInfo> indexInfoList = new ArrayList<>();
while (cursor.hasNext()) {
@ -217,4 +212,25 @@ public class DefaultIndexOperations implements IndexOperations { @@ -217,4 +212,25 @@ public class DefaultIndexOperations implements IndexOperations {
return mongoOperations.execute(collectionName, callback);
}
private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions,
@Nullable MongoPersistentEntity<?> entity) {
if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
return ops;
}
Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
return ops.partialFilterExpression(
mapper.getMappedObject((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity));
}
private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, MongoPersistentEntity<?> entity) {
if (ops.getCollation() != null || entity == null || !entity.hasCollation()) {
return ops;
}
return ops.collation(entity.getCollation().toMongoCollation());
}
}

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

@ -94,23 +94,16 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations { @@ -94,23 +94,16 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
return mongoOperations.execute(collectionName, collection -> {
Document indexOptions = indexDefinition.getIndexOptions();
MongoPersistentEntity<?> entity = type
.map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
.orElseGet(() -> lookupPersistentEntity(collectionName));
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
IndexOptions indexOptions = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity);
indexOptions = addDefaultCollationIfRequired(indexOptions, entity);
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
MongoPersistentEntity<?> entity = type
.map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
.orElseGet(() -> lookupPersistentEntity(collectionName));
ops = ops.partialFilterExpression(
queryMapper.getMappedObject(indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY, Document.class), entity));
}
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
return collection.createIndex(indexDefinition.getIndexKeys(), indexOptions);
}).next();
}
@ -126,21 +119,24 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations { @@ -126,21 +119,24 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
.orElse(null);
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#dropIndex(java.lang.String)
*/
public Mono<Void> dropIndex(final String name) {
return mongoOperations.execute(collectionName, collection -> collection.dropIndex(name)).then();
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#dropAllIndexes()
*/
public Mono<Void> dropAllIndexes() {
return dropIndex("*");
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#getIndexInfo()
*/
public Flux<IndexInfo> getIndexInfo() {
@ -148,4 +144,25 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations { @@ -148,4 +144,25 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
return mongoOperations.execute(collectionName, collection -> collection.listIndexes(Document.class)) //
.map(IndexConverters.documentToIndexInfoConverter()::convert);
}
private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions,
@Nullable MongoPersistentEntity<?> entity) {
if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
return ops;
}
Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
return ops.partialFilterExpression(
queryMapper.getMappedObject((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity));
}
private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, MongoPersistentEntity<?> entity) {
if (ops.getCollation() != null || entity == null || !entity.hasCollation()) {
return ops;
}
return ops.collation(entity.getCollation().toMongoCollation());
}
}

35
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java

@ -33,6 +33,31 @@ public class FindAndModifyOptions { @@ -33,6 +33,31 @@ public class FindAndModifyOptions {
private @Nullable Collation collation;
private static final FindAndModifyOptions NONE = new FindAndModifyOptions() {
private static final String ERROR_MSG = "FindAndModifyOptions.none() cannot be changed. Please use FindAndModifyOptions.options() instead.";
@Override
public FindAndModifyOptions returnNew(boolean returnNew) {
throw new UnsupportedOperationException(ERROR_MSG);
}
@Override
public FindAndModifyOptions upsert(boolean upsert) {
throw new UnsupportedOperationException(ERROR_MSG);
}
@Override
public FindAndModifyOptions remove(boolean remove) {
throw new UnsupportedOperationException(ERROR_MSG);
}
@Override
public FindAndModifyOptions collation(Collation collation) {
throw new UnsupportedOperationException(ERROR_MSG);
}
};
/**
* Static factory method to create a FindAndModifyOptions instance
*
@ -42,6 +67,16 @@ public class FindAndModifyOptions { @@ -42,6 +67,16 @@ public class FindAndModifyOptions {
return new FindAndModifyOptions();
}
/**
* Static factory method returning an unmodifiable {@link FindAndModifyOptions} instance.
*
* @return unmodifiable {@link FindAndModifyOptions} instance.
* @since 2.2
*/
public static FindAndModifyOptions none() {
return NONE;
}
/**
* Create new {@link FindAndModifyOptions} based on option of given {@litearl source}.
*

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndReplaceOptions.java

@ -36,6 +36,21 @@ public class FindAndReplaceOptions { @@ -36,6 +36,21 @@ public class FindAndReplaceOptions {
private boolean returnNew;
private boolean upsert;
private static final FindAndReplaceOptions NONE = new FindAndReplaceOptions() {
private static final String ERROR_MSG = "FindAndReplaceOptions.none() cannot be changed. Please use FindAndReplaceOptions.options() instead.";
@Override
public FindAndReplaceOptions returnNew() {
throw new UnsupportedOperationException(ERROR_MSG);
}
@Override
public FindAndReplaceOptions upsert() {
throw new UnsupportedOperationException(ERROR_MSG);
}
};
/**
* Static factory method to create a {@link FindAndReplaceOptions} instance.
* <dl>
@ -51,6 +66,16 @@ public class FindAndReplaceOptions { @@ -51,6 +66,16 @@ public class FindAndReplaceOptions {
return new FindAndReplaceOptions();
}
/**
* Static factory method returning an unmodifiable {@link FindAndReplaceOptions} instance.
*
* @return unmodifiable {@link FindAndReplaceOptions} instance.
* @since 2.2
*/
public static FindAndReplaceOptions none() {
return NONE;
}
/**
* Static factory method to create a {@link FindAndReplaceOptions} instance with
* <dl>

146
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -515,7 +515,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -515,7 +515,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
sortObject, fieldsObject, collectionName);
}
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, documentCallbackHandler,
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject, null), preparer, documentCallbackHandler,
collectionName);
}
@ -602,7 +602,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -602,7 +602,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class)
*/
public <T> MongoCollection<Document> createCollection(Class<T> entityClass) {
return createCollection(operations.determineCollectionName(entityClass));
return createCollection(entityClass, CollectionOptions.empty());
}
/*
@ -613,8 +613,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -613,8 +613,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Nullable CollectionOptions collectionOptions) {
Assert.notNull(entityClass, "EntityClass must not be null!");
return doCreateCollection(operations.determineCollectionName(entityClass),
convertToDocument(collectionOptions, entityClass));
CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
options = Optionals
.firstNonEmpty(() -> collectionOptions != null ? collectionOptions.getCollation() : Optional.empty(),
() -> getCollationForType(entityClass)) //
.map(options::collation).orElse(options);
return doCreateCollection(operations.determineCollectionName(entityClass), convertToDocument(options, entityClass));
}
/*
@ -786,8 +792,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -786,8 +792,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(entityClass, "EntityClass must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
if (ObjectUtils.isEmpty(query.getSortObject()) && !query.getCollation().isPresent()) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
if (ObjectUtils.isEmpty(query.getSortObject())) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(),
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
.map(Collation::toMongoCollation).orElse(null),
entityClass);
} else {
query.limit(1);
List<T> results = find(query, entityClass, collectionName);
@ -816,7 +826,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -816,7 +826,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
return execute(collectionName,
new ExistsCallback(mappedQuery, query.getCollation().map(Collation::toMongoCollation).orElse(null)));
new ExistsCallback(mappedQuery,
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
.map(Collation::toMongoCollation).orElse(null)));
}
// Find methods that take a Query to express the query and that return a List of objects.
@ -906,7 +918,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -906,7 +918,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
DistinctIterable<T> iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
return query.getCollation().map(Collation::toMongoCollation).map(iterable::collation).orElse(iterable);
return Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
.map(Collation::toMongoCollation) //
.map(iterable::collation) //
.orElse(iterable);
});
if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
@ -1063,7 +1078,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1063,7 +1078,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
});
query.getCollation().ifPresent(optionsToUse::collation);
Optionals
.firstNonEmpty(() -> query.getCollation(), () -> options.getCollation(), () -> getCollationForType(entityClass))
.ifPresent(optionsToUse::collation);
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
@ -1096,8 +1113,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1096,8 +1113,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
query.getCollation().map(Collation::toMongoCollation).orElse(null), entityType, mappedReplacement, options,
resultType);
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityType))
.map(Collation::toMongoCollation).orElse(null),
entityType, mappedReplacement, options, resultType);
}
// Find methods that take a Query to express the query and that return a single object that is also removed from the
@ -1118,7 +1136,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1118,7 +1136,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(collectionName, "CollectionName must not be null!");
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
getMappedSortObject(query, entityClass),
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null),
entityClass);
}
@Override
@ -1610,11 +1630,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1610,11 +1630,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document queryObj = new Document();
if (query != null) {
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
query.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
}
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
.map(Collation::toMongoCollation) //
.ifPresent(opts::collation);
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject()
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
@ -1717,7 +1739,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1717,7 +1739,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document removeQuery = queryObject;
DeleteOptions options = new DeleteOptions();
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
.map(Collation::toMongoCollation) //
.ifPresent(options::collation);
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName,
entityClass, null, queryObject);
@ -1764,8 +1788,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1764,8 +1788,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Override
public <T> List<T> findAll(Class<T> entityClass, String collectionName) {
return executeFindMultiInternal(new FindCallback(new Document(), new Document()), null,
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
return executeFindMultiInternal(
new FindCallback(new Document(), new Document(),
getCollationForType(entityClass).map(Collation::toMongoCollation).orElse(null)),
null, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
}
@Override
@ -1879,6 +1905,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1879,6 +1905,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
}
if(!collation.isPresent()) {
collation = getCollationForType(domainType);
}
mapReduce = collation.map(Collation::toMongoCollation).map(mapReduce::collation).orElse(mapReduce);
List<T> mappedResults = new ArrayList<>();
@ -2123,8 +2153,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2123,8 +2153,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
List<Document> rawResult = new ArrayList<>();
Optional<Collation> collation = Optionals.firstNonEmpty(() -> options.getCollation(), () -> getCollationForType(
aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null));
AggregateIterable<Document> aggregateIterable = collection.aggregate(pipeline, Document.class) //
.collation(options.getCollation().map(Collation::toMongoCollation).orElse(null)) //
.collation(collation.map(Collation::toMongoCollation).orElse(null)) //
.allowDiskUse(options.isAllowDiskUse());
if (options.getCursorBatchSize() != null) {
@ -2165,16 +2198,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2165,16 +2198,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return execute(collectionName, (CollectionCallback<CloseableIterator<O>>) collection -> {
AggregateIterable<Document> cursor = collection.aggregate(pipeline, Document.class) //
.allowDiskUse(options.isAllowDiskUse()) //
.useCursor(true);
.allowDiskUse(options.isAllowDiskUse());
if (options.getCursorBatchSize() != null) {
cursor = cursor.batchSize(options.getCursorBatchSize());
}
if (options.getCollation().isPresent()) {
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
}
Optionals
.firstNonEmpty(() -> options.getCollation(),
() -> getCollationForType(
aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null)) //
.map(Collation::toMongoCollation) //
.ifPresent(cursor::collation);
return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator, readCallback);
});
@ -2368,6 +2403,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2368,6 +2403,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @return the {@link List} of converted objects.
*/
protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
return doFindOne(collectionName, query, fields, null, entityClass);
}
/**
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
* The query document is specified as a standard {@link Document} and so is the fields specification.
*
* @param collectionName name of the collection to retrieve the objects from.
* @param query the query document that specifies the criteria used to find a record.
* @param fields the document that specifies the fields to be returned.
* @param entityClass the parameterized type of the returned list.
* @return the {@link List} of converted objects.
*/
protected <T> T doFindOne(String collectionName, Document query, Document fields,
@Nullable com.mongodb.client.model.Collation collation, Class<T> entityClass) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
@ -2378,7 +2428,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2378,7 +2428,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
mappedFields, entityClass, collectionName);
}
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, collation),
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
}
@ -2429,7 +2479,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2429,7 +2479,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName);
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback,
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer, objectCallback,
collectionName);
}
@ -2452,7 +2502,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2452,7 +2502,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName);
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer,
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
}
@ -2819,15 +2869,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2819,15 +2869,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final Document query;
private final Optional<Document> fields;
private final @Nullable com.mongodb.client.model.Collation collation;
public FindOneCallback(Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation) {
public FindOneCallback(Document query, Document fields) {
this.query = query;
this.fields = Optional.of(fields).filter(it -> !ObjectUtils.isEmpty(fields));
this.collation = collation;
}
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
FindIterable<Document> iterable = collection.find(query, Document.class);
if (collation != null) {
iterable = iterable.collation(collation);
}
if (LOGGER.isDebugEnabled()) {
@ -2856,20 +2912,27 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2856,20 +2912,27 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final Document query;
private final Document fields;
private final @Nullable com.mongodb.client.model.Collation collation;
public FindCallback(Document query, Document fields) {
public FindCallback(Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(fields, "Fields must not be null!");
this.query = query;
this.fields = fields;
this.collation = collation;
}
public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
return collection.find(query, Document.class).projection(fields);
FindIterable<Document> findIterable = collection.find(query, Document.class).projection(fields);
if (collation != null) {
findIterable = findIterable.collation(collation);
}
return findIterable;
}
}
@ -3156,20 +3219,23 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -3156,20 +3219,23 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
public FindIterable<Document> prepare(FindIterable<Document> cursor) {
FindIterable<Document> cursorToUse = cursor;
Optionals
.firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), () -> getCollationForType(type))
.map(Collation::toMongoCollation) //
.ifPresent(cursorToUse::collation);
if (query == null) {
return cursor;
return cursorToUse;
}
Meta meta = query.getMeta();
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues() && !query.getCollation().isPresent()) {
return cursor;
return cursorToUse;
}
FindIterable<Document> cursorToUse;
cursorToUse = query.getCollation().map(Collation::toMongoCollation).map(cursor::collation).orElse(cursor);
try {
if (query.getSkip() > 0) {
cursorToUse = cursorToUse.skip((int) query.getSkip());
@ -3417,4 +3483,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -3417,4 +3483,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return execute(collectionName, collection -> collection.countDocuments(filter, options));
}
}
@Nullable
private Optional<Collation> getCollationForType(Class<?> type) {
if (type == null || type == Document.class) {
return Optional.empty();
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
return entity != null ? Optional.ofNullable(entity.getCollation()) : Optional.empty();
}
}

134
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -40,7 +40,6 @@ import org.reactivestreams.Publisher; @@ -40,7 +40,6 @@ import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@ -70,7 +69,16 @@ import org.springframework.data.mongodb.core.aggregation.AggregationOptions; @@ -70,7 +69,16 @@ import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.*;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
import org.springframework.data.mongodb.core.index.ReactiveMongoPersistentEntityIndexCreator;
@ -113,11 +121,29 @@ import com.mongodb.Mongo; @@ -113,11 +121,29 @@ import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.client.model.*;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.client.model.ReturnDocument;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.model.ValidationOptions;
import com.mongodb.client.model.changestream.FullDocument;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.reactivestreams.client.*;
import com.mongodb.reactivestreams.client.AggregatePublisher;
import com.mongodb.reactivestreams.client.ChangeStreamPublisher;
import com.mongodb.reactivestreams.client.ClientSession;
import com.mongodb.reactivestreams.client.DistinctPublisher;
import com.mongodb.reactivestreams.client.FindPublisher;
import com.mongodb.reactivestreams.client.MapReducePublisher;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoCollection;
import com.mongodb.reactivestreams.client.MongoDatabase;
import com.mongodb.reactivestreams.client.Success;
/**
* Primary implementation of {@link ReactiveMongoOperations}. It simplifies the use of Reactive MongoDB usage and helps
@ -609,7 +635,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -609,7 +635,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class)
*/
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass) {
return createCollection(determineCollectionName(entityClass));
return createCollection(entityClass, CollectionOptions.empty());
}
/*
@ -618,8 +644,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -618,8 +644,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
@Nullable CollectionOptions collectionOptions) {
Assert.notNull(entityClass, "EntityClass must not be null!");
CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
options = Optionals
.firstNonEmpty(() -> collectionOptions != null ? collectionOptions.getCollation() : Optional.empty(),
() -> getCollationForType(entityClass)) //
.map(options::collation).orElse(options);
return doCreateCollection(determineCollectionName(entityClass),
convertToCreateCollectionOptions(collectionOptions, entityClass));
convertToCreateCollectionOptions(options, entityClass));
}
/*
@ -719,7 +754,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -719,7 +754,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (ObjectUtils.isEmpty(query.getSortObject())) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
query.getCollation().orElse(null));
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null));
}
query.limit(1);
@ -762,8 +797,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -762,8 +797,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
LOGGER.debug("exists: {} in collection: {}", serializeToJsonSafely(filter), collectionName);
}
findPublisher = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
findPublisher = Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
.map(Collation::toMongoCollation).map(findPublisher::collation).orElse(findPublisher);
return findPublisher.limit(1);
}).hasElements();
@ -849,8 +884,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -849,8 +884,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
DistinctPublisher<T> publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
return query.getCollation().map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
return Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
.map(Collation::toMongoCollation) //
.map(publisher::collation) //
.orElse(publisher);
});
if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
@ -960,11 +997,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -960,11 +997,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
ReadDocumentCallback<O> readCallback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName);
return execute(collectionName, collection -> aggregateAndMap(collection, pipeline, options, readCallback));
return execute(collectionName, collection -> aggregateAndMap(collection, pipeline, options, readCallback,
aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null));
}
private <O> Flux<O> aggregateAndMap(MongoCollection<Document> collection, List<Document> pipeline,
AggregationOptions options, ReadDocumentCallback<O> readCallback) {
AggregationOptions options, ReadDocumentCallback<O> readCallback, @Nullable Class<?> inputType) {
AggregatePublisher<Document> cursor = collection.aggregate(pipeline, Document.class)
.allowDiskUse(options.isAllowDiskUse());
@ -973,9 +1011,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -973,9 +1011,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
cursor = cursor.batchSize(options.getCursorBatchSize());
}
if (options.getCollation().isPresent()) {
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
}
Optionals.firstNonEmpty(() -> options.getCollation(), () -> getCollationForType(inputType)) //
.map(Collation::toMongoCollation) //
.ifPresent(cursor::collation);
return Flux.from(cursor).map(readCallback::doWith);
}
@ -1072,6 +1110,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1072,6 +1110,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
String collectionName) {
Assert.notNull(options, "Options must not be null! ");
FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
@ -1079,7 +1119,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1079,7 +1119,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
});
query.getCollation().ifPresent(optionsToUse::collation);
Optionals
.firstNonEmpty(() -> query.getCollation(), () -> options.getCollation(), () -> getCollationForType(entityClass))
.ifPresent(optionsToUse::collation);
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
@ -1112,8 +1154,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1112,8 +1154,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
query.getCollation().map(Collation::toMongoCollation).orElse(null), entityType, mappedReplacement, options,
resultType);
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityType))
.map(Collation::toMongoCollation).orElse(null),
entityType, mappedReplacement, options, resultType);
}
/*
@ -1131,7 +1174,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1131,7 +1174,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public <T> Mono<T> findAndRemove(Query query, Class<T> entityClass, String collectionName) {
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
getMappedSortObject(query, entityClass),
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null),
entityClass);
}
/*
@ -1180,6 +1225,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1180,6 +1225,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
}
Optionals
.firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(),
() -> getCollationForType(entityClass))
.map(Collation::toMongoCollation) //
.ifPresent(options::collation);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Executing count: {} in collection: {}", serializeToJsonSafely(filter), collectionName);
}
@ -1647,7 +1698,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1647,7 +1698,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
query.getCollation().map(Collation::toMongoCollation).ifPresent(updateOptions::collation);
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
.map(Collation::toMongoCollation) //
.ifPresent(updateOptions::collation);
if (update.hasArrayFilters()) {
updateOptions.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument)
@ -1811,7 +1864,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1811,7 +1864,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
null, removeQuery);
DeleteOptions deleteOptions = new DeleteOptions();
query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
.map(Collation::toMongoCollation) //
.ifPresent(deleteOptions::collation);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
@ -2381,7 +2437,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2381,7 +2437,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
collectionName));
}
return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
return executeFindOneInternal(
new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate,
update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
});
}
@ -2781,12 +2839,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2781,12 +2839,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
}
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort, arrayFilters);
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort,
arrayFilters);
return collection.findOneAndUpdate(query, update, findOneAndUpdateOptions);
}
private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options, Document fields,
Document sort, List<Document> arrayFilters) {
private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options,
Document fields, Document sort, List<Document> arrayFilters) {
FindOneAndUpdateOptions result = new FindOneAndUpdateOptions();
@ -3023,15 +3082,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -3023,15 +3082,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@SuppressWarnings("deprecation")
public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
FindPublisher<T> findPublisherToUse = Optionals //
.firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), () -> getCollationForType(type))
.map(Collation::toMongoCollation) //
.map(findPublisher::collation) //
.orElse(findPublisher);
if (query == null) {
return findPublisher;
return findPublisherToUse;
}
FindPublisher<T> findPublisherToUse;
findPublisherToUse = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
.orElse(findPublisher);
Meta meta = query.getMeta();
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues()) {
@ -3205,4 +3265,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -3205,4 +3265,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
}
}
@Nullable
private Optional<Collation> getCollationForType(Class<?> type) {
if (type == null) {
return Optional.empty();
}
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
return entity != null ? Optional.ofNullable(entity.getCollation()) : Optional.empty();
}
}

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java

@ -123,7 +123,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { @@ -123,7 +123,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
String collectionName = getCollectionName();
return template.findAndModify(query, update, findAndModifyOptions, targetType, collectionName);
return template.findAndModify(query, update, findAndModifyOptions != null ? findAndModifyOptions : FindAndModifyOptions.none(), targetType, collectionName);
}
/*
@ -133,7 +133,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { @@ -133,7 +133,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
@Override
public Mono<T> findAndReplace() {
return template.findAndReplace(query, replacement,
findAndReplaceOptions != null ? findAndReplaceOptions : new FindAndReplaceOptions(), (Class) domainType,
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.none(), (Class) domainType,
getCollectionName(), targetType);
}

52
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntity.java

@ -60,6 +60,9 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong @@ -60,6 +60,9 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
private final @Nullable Expression expression;
private final @Nullable String collation;
private final @Nullable Expression collationExpression;
/**
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
* collection name to the entities simple type name.
@ -78,12 +81,16 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong @@ -78,12 +81,16 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
this.collection = StringUtils.hasText(document.collection()) ? document.collection() : fallback;
this.language = StringUtils.hasText(document.language()) ? document.language() : "";
this.expression = detectExpression(document);
this.expression = detectExpression(document.collection());
this.collation = document.collation();
this.collationExpression = detectExpression(document.collation());
} else {
this.collection = fallback;
this.language = "";
this.expression = null;
this.collation = null;
this.collationExpression = null;
}
}
@ -126,6 +133,33 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong @@ -126,6 +133,33 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
return getTextScoreProperty() != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getCollation()
*/
@Override
public org.springframework.data.mongodb.core.query.Collation getCollation() {
Object collationValue = collationExpression != null ? expression.getValue(getEvaluationContext(null), String.class)
: this.collation;
if (collationValue == null) {
return null;
}
if (collationValue instanceof org.bson.Document) {
return org.springframework.data.mongodb.core.query.Collation.from((org.bson.Document) collationValue);
}
if (collationValue instanceof org.springframework.data.mongodb.core.query.Collation) {
return org.springframework.data.mongodb.core.query.Collation.class.cast(collationValue);
}
return StringUtils.hasText(collationValue.toString())
? org.springframework.data.mongodb.core.query.Collation.parse(collationValue.toString())
: null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.BasicPersistentEntity#verify()
@ -246,24 +280,20 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong @@ -246,24 +280,20 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
}
/**
* Returns a SpEL {@link Expression} frór the collection String expressed in the given {@link Document} annotation if
* present or {@literal null} otherwise. Will also return {@literal null} it the collection {@link String} evaluates
* to a {@link LiteralExpression} (indicating that no subsequent evaluation is necessary).
* Returns a SpEL {@link Expression} if the given {@link String} is actually an expression that does not evaluate to a
* {@link LiteralExpression} (indicating that no subsequent evaluation is necessary).
*
* @param document can be {@literal null}
* @param potentialExpression can be {@literal null}
* @return
*/
@Nullable
private static Expression detectExpression(Document document) {
private static Expression detectExpression(@Nullable String potentialExpression) {
String collection = document.collection();
if (!StringUtils.hasText(collection)) {
if (!StringUtils.hasText(potentialExpression)) {
return null;
}
Expression expression = PARSER.parseExpression(document.collection(), ParserContext.TEMPLATE_EXPRESSION);
Expression expression = PARSER.parseExpression(potentialExpression, ParserContext.TEMPLATE_EXPRESSION);
return expression instanceof LiteralExpression ? null : expression;
}

10
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java

@ -60,9 +60,17 @@ public @interface Document { @@ -60,9 +60,17 @@ public @interface Document {
/**
* Defines the default language to be used with this document.
*
* @since 1.6
* @return
* @since 1.6
*/
String language() default "";
/**
* Defines the collation to apply when executing a query or creating indexes.
*
* @return an empty {@link String} by default.
* @since 2.2
*/
String collation() default "";
}

26
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPersistentEntity.java

@ -36,17 +36,17 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi @@ -36,17 +36,17 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
/**
* Returns the default language to be used for this entity.
*
* @since 1.6
* @return
* @since 1.6
*/
String getLanguage();
/**
* Returns the property holding text score value.
*
* @since 1.6
* @see #hasTextScoreProperty()
* @return {@literal null} if not present.
* @see #hasTextScoreProperty()
* @since 1.6
*/
@Nullable
MongoPersistentProperty getTextScoreProperty();
@ -54,9 +54,27 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi @@ -54,9 +54,27 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
/**
* Returns whether the entity has a {@link TextScore} property.
*
* @since 1.6
* @return true if property annotated with {@link TextScore} is present.
* @since 1.6
*/
boolean hasTextScoreProperty();
/**
* Returns the collation of the entity evaluating a potential SpEL expression within the current context.
*
* @return {@literal null} if not set.
* @since 2.2
*/
@Nullable
org.springframework.data.mongodb.core.query.Collation getCollation();
/**
* @return {@literal true} if the entity is annotated with
* {@link org.springframework.data.mongodb.core.mapping.Collation}.
* @since 2.2
*/
default boolean hasCollation() {
return getCollation() != null;
}
}

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Collation.java

@ -403,6 +403,26 @@ public class Collation { @@ -403,6 +403,26 @@ public class Collation {
return toDocument().toJson();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Collation that = (Collation) o;
return this.toDocument().equals(that.toDocument());
}
@Override
public int hashCode() {
return toDocument().hashCode();
}
private Collation copy() {
Collation collation = new Collation(locale);

1
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java

@ -121,6 +121,7 @@ public @interface Query { @@ -121,6 +121,7 @@ public @interface Query {
* List<Entry> findAllByDynamicSpElCollation(String collation);
* </pre>
*
* @return an empty {@link String} by default.
* @since 2.2
*/
String collation() default "";

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoEntityInformation.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.query;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.lang.Nullable;
@ -61,4 +62,23 @@ public interface MongoEntityInformation<T, ID> extends EntityInformation<T, ID> @@ -61,4 +62,23 @@ public interface MongoEntityInformation<T, ID> extends EntityInformation<T, ID>
default Object getVersion(T entity) {
return null;
}
/**
* Returns whether the entity defines a specific collation.
*
* @return {@literal true} if the entity defines a collation.
* @since 2.2
*/
default boolean hasCollation() {
return getCollation() != null;
}
/**
* Return the collation for the entity or {@literal null} if {@link #hasCollation() not defined}.
*
* @return can be {@literal null}.
* @since 2.2
*/
@Nullable
Collation getCollation();
}

10
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.support; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.support;
import org.bson.types.ObjectId;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.repository.core.support.PersistentEntityInformation;
import org.springframework.lang.Nullable;
@ -143,4 +144,13 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform @@ -143,4 +144,13 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform
return accessor.getProperty(this.entityMetadata.getRequiredVersionProperty());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getCollation()
*/
@Nullable
public Collation getCollation() {
return this.entityMetadata.getCollation();
}
}

30
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepository.java

@ -227,7 +227,7 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { @@ -227,7 +227,7 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Long count = count();
List<T> list = findAll(new Query().with(pageable));
return new PageImpl<T>(list, pageable, count);
return new PageImpl<>(list, pageable, count);
}
/*
@ -282,7 +282,9 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { @@ -282,7 +282,9 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Assert.notNull(example, "Sample must not be null!");
Assert.notNull(pageable, "Pageable must not be null!");
Query query = new Query(new Criteria().alike(example)).with(pageable);
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation()).with(pageable); //
List<S> list = mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
return PageableExecutionUtils.getPage(list, pageable,
@ -299,9 +301,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { @@ -299,9 +301,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Assert.notNull(example, "Sample must not be null!");
Assert.notNull(sort, "Sort must not be null!");
Query q = new Query(new Criteria().alike(example)).with(sort);
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation()) //
.with(sort);
return mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName());
return mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
}
/*
@ -322,9 +326,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { @@ -322,9 +326,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation());
return Optional
.ofNullable(mongoOperations.findOne(q, example.getProbeType(), entityInformation.getCollectionName()));
.ofNullable(mongoOperations.findOne(query, example.getProbeType(), entityInformation.getCollectionName()));
}
/*
@ -336,8 +342,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { @@ -336,8 +342,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
return mongoOperations.count(q, example.getProbeType(), entityInformation.getCollectionName());
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation());
return mongoOperations.count(query, example.getProbeType(), entityInformation.getCollectionName());
}
/*
@ -349,8 +357,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> { @@ -349,8 +357,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
return mongoOperations.exists(q, example.getProbeType(), entityInformation.getCollectionName());
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation());
return mongoOperations.exists(query, example.getProbeType(), entityInformation.getCollectionName());
}
private Query getIdQuery(Object id) {

23
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.java

@ -91,10 +91,11 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement @@ -91,10 +91,11 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
q.limit(2);
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation()) //
.limit(2);
return mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName()).buffer(2)
return mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName()).buffer(2)
.map(vals -> {
if (vals.size() > 1) {
@ -140,8 +141,10 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement @@ -140,8 +141,10 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
return mongoOperations.exists(q, example.getProbeType(), entityInformation.getCollectionName());
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation());
return mongoOperations.exists(query, example.getProbeType(), entityInformation.getCollectionName());
}
/*
@ -200,7 +203,9 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement @@ -200,7 +203,9 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
Assert.notNull(example, "Sample must not be null!");
Assert.notNull(sort, "Sort must not be null!");
Query query = new Query(new Criteria().alike(example)).with(sort);
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation()) //
.with(sort);
return mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
}
@ -235,8 +240,10 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement @@ -235,8 +240,10 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
Assert.notNull(example, "Sample must not be null!");
Query q = new Query(new Criteria().alike(example));
return mongoOperations.count(q, example.getProbeType(), entityInformation.getCollectionName());
Query query = new Query(new Criteria().alike(example)) //
.collation(entityInformation.getCollation());
return mongoOperations.count(query, example.getProbeType(), entityInformation.getCollectionName());
}
/*

121
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsUnitTests.java

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.Data;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Collation;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.IndexOptions;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultIndexOperationsUnitTests {
MongoTemplate template;
@Mock MongoDbFactory factory;
@Mock MongoDatabase db;
@Mock MongoCollection<Document> collection;
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
MappingMongoConverter converter;
MongoMappingContext mappingContext;
@Before
public void setUp() {
when(factory.getDb()).thenReturn(db);
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
when(db.getCollection(any(), any(Class.class))).thenReturn(collection);
when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn("OK");
this.mappingContext = new MongoMappingContext();
this.converter = spy(new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext));
this.template = new MongoTemplate(factory, converter);
}
@Test // DATAMONGO-1854
public void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() {
indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC));
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
assertThat(options.getValue().getCollation()).isNull();
}
@Test // DATAMONGO-1854
public void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() {
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC));
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
assertThat(options.getValue().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
}
@Test // DATAMONGO-1854
public void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() {
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US")));
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
assertThat(options.getValue().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build());
}
private DefaultIndexOperations indexOpsFor(Class<?> type) {
return new DefaultIndexOperations(template, template.getCollectionName(type), type);
}
@Data
static class Jedi {
@Field("firstname") String name;
}
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
static class Sith {
@Field("firstname") String name;
}
}

126
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsUnitTests.java

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.Data;
import org.bson.Document;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.Collation;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.reactivestreams.client.MongoCollection;
import com.mongodb.reactivestreams.client.MongoDatabase;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultReactiveIndexOperationsUnitTests {
ReactiveMongoTemplate template;
@Mock ReactiveMongoDatabaseFactory factory;
@Mock MongoDatabase db;
@Mock MongoCollection<Document> collection;
@Mock Publisher publisher;
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
MappingMongoConverter converter;
MongoMappingContext mappingContext;
@Before
public void setUp() {
when(factory.getMongoDatabase()).thenReturn(db);
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
when(db.getCollection(any(), any(Class.class))).thenReturn(collection);
when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn(publisher);
this.mappingContext = new MongoMappingContext();
this.converter = spy(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext));
this.template = new ReactiveMongoTemplate(factory, converter);
}
@Test // DATAMONGO-1854
public void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() {
indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe();
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
assertThat(options.getValue().getCollation()).isNull();
}
@Test // DATAMONGO-1854
public void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() {
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe();
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
assertThat(options.getValue().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
}
@Test // DATAMONGO-1854
public void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() {
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US")))
.subscribe();
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
verify(collection).createIndex(any(), options.capture());
assertThat(options.getValue().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build());
}
private DefaultReactiveIndexOperations indexOpsFor(Class<?> type) {
return new DefaultReactiveIndexOperations(template, template.getCollectionName(type),
new QueryMapper(template.getConverter()), type);
}
@Data
static class Jedi {
@Field("firstname") String name;
}
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
static class Sith {
@Field("firstname") String name;
}
}

334
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

@ -88,14 +88,17 @@ import com.mongodb.MongoNamespace; @@ -88,14 +88,17 @@ import com.mongodb.MongoNamespace;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.DistinctIterable;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MapReduceIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.MapReduceAction;
import com.mongodb.client.model.ReplaceOptions;
@ -124,6 +127,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -124,6 +127,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Mock FindIterable<Document> findIterable;
@Mock AggregateIterable aggregateIterable;
@Mock MapReduceIterable mapReduceIterable;
@Mock DistinctIterable distinctIterable;
@Mock UpdateResult updateResult;
@Mock DeleteResult deleteResult;
@ -143,12 +147,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -143,12 +147,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(db.runCommand(any(), any(Class.class))).thenReturn(commandResultDocument);
when(collection.find(any(org.bson.Document.class), any(Class.class))).thenReturn(findIterable);
when(collection.mapReduce(any(), any(), eq(Document.class))).thenReturn(mapReduceIterable);
when(collection.count(any(Bson.class), any(CountOptions.class))).thenReturn(1L); // TODO: MongoDB 4 - fix me decprecated
when(collection.count(any(Bson.class), any(CountOptions.class))).thenReturn(1L); // TODO: MongoDB 4 - fix me
when(collection.getNamespace()).thenReturn(new MongoNamespace("db.mock-collection"));
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
when(collection.withReadPreference(any())).thenReturn(collection);
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class))).thenReturn(updateResult);
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
when(findIterable.projection(any())).thenReturn(findIterable);
when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
@ -166,6 +171,9 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -166,6 +171,9 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(aggregateIterable.batchSize(anyInt())).thenReturn(aggregateIterable);
when(aggregateIterable.map(any())).thenReturn(aggregateIterable);
when(aggregateIterable.into(any())).thenReturn(Collections.emptyList());
when(distinctIterable.collation(any())).thenReturn(distinctIterable);
when(distinctIterable.map(any())).thenReturn(distinctIterable);
when(distinctIterable.into(any())).thenReturn(Collections.emptyList());
this.mappingContext = new MongoMappingContext();
mappingContext.afterPropertiesSet();
@ -1006,14 +1014,6 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1006,14 +1014,6 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
.containing("near.coordinates.[0]", 1D).containing("near.coordinates.[1]", 2D));
}
static class WithNamedFields {
@Id String id;
String name;
@Field("custom-named-field") String customName;
}
@Test // DATAMONGO-2155
public void saveVersionedEntityShouldCallUpdateCorrectly() {
@ -1085,6 +1085,308 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1085,6 +1085,308 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
.contains(new org.bson.Document("element", new Document("$gte", 100)));
}
@Test // DATAMONGO-1854
public void streamQueryShouldUseDefaultCollationWhenPresent() {
template.stream(new BasicQuery("{}"), Sith.class).next();
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findShouldNotUseCollationWhenNoDefaultPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}"), Jedi.class);
verify(findIterable, never()).collation(any());
}
@Test // DATAMONGO-1854
public void findShouldUseDefaultCollationWhenPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}"), Sith.class);
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findOneShouldUseDefaultCollationWhenPresent() {
template.findOne(new BasicQuery("{'foo' : 'bar'}"), Sith.class);
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void existsShouldUseDefaultCollationWhenPresent() {
template.exists(new BasicQuery("{}"), Sith.class);
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
verify(collection).count(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(equalTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build())));
}
@Test // DATAMONGO-1854
public void findAndModfiyShoudUseDefaultCollationWhenPresent() {
template.findAndModify(new BasicQuery("{}"), new Update(), Sith.class);
ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
verify(collection).findOneAndUpdate(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findAndRemoveShouldUseDefaultCollationWhenPresent() {
template.findAndRemove(new BasicQuery("{}"), Sith.class);
ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
verify(collection).findOneAndDelete(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void createCollectionShouldNotCollationIfNotPresent() {
template.createCollection(AutogenerateableId.class);
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
Assertions.assertThat(options.getValue().getCollation()).isNull();
}
@Test // DATAMONGO-1854
public void createCollectionShouldApplyDefaultCollation() {
template.createCollection(Sith.class);
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
template.createCollection(Sith.class, CollectionOptions.just(Collation.of("en_US")));
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("en_US").build()));
}
@Test // DATAMONGO-1854
public void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
template.createCollection(Sith.class, null);
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void aggreateShouldUseDefaultCollationIfPresent() {
template.aggregate(newAggregation(Sith.class, project("id")), AutogenerateableId.class, Document.class);
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void aggreateShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {
template.aggregateStream(newAggregation(Sith.class, project("id")).withOptions(
newAggregationOptions().collation(Collation.of("fr")).build()), AutogenerateableId.class, Document.class);
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void aggreateStreamShouldUseDefaultCollationIfPresent() {
template.aggregate(newAggregation(Sith.class, project("id")), AutogenerateableId.class, Document.class);
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void aggreateStreamShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {
template.aggregateStream(newAggregation(Sith.class, project("id")).withOptions(
newAggregationOptions().collation(Collation.of("fr")).build()), AutogenerateableId.class, Document.class);
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void findAndReplaceShouldUseCollationWhenPresent() {
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new AutogenerateableId());
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
verify(collection).findOneAndReplace(any(), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
}
@Test // DATAMONGO-1854
public void findOneWithSortShouldUseCollationWhenPresent() {
template.findOne(new BasicQuery("{}").collation(Collation.of("fr")).with(Sort.by("id")), Sith.class);
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void findOneWithSortShouldUseDefaultCollationWhenPresent() {
template.findOne(new BasicQuery("{}").with(Sort.by("id")), Sith.class);
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findAndReplaceShouldUseDefaultCollationWhenPresent() {
template.findAndReplace(new BasicQuery("{}"), new Sith());
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
verify(collection).findOneAndReplace(any(), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale(), is("de_AT"));
}
@Test // DATAMONGO-18545
public void findAndReplaceShouldUseCollationEvenIfDefaultCollationIsPresent() {
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new Sith());
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
verify(collection).findOneAndReplace(any(), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
}
@Test // DATAMONGO-1854
public void findDistinctShouldUseDefaultCollationWhenPresent() {
template.findDistinct(new BasicQuery("{}"), "name", Sith.class, String.class);
verify(distinctIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findDistinctPreferCollationFromQueryOverDefaultCollation() {
template.findDistinct(new BasicQuery("{}").collation(Collation.of("fr")), "name", Sith.class, String.class);
verify(distinctIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void updateFirstShouldUseDefaultCollationWhenPresent() {
template.updateFirst(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class);
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateOne(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void updateFirstShouldPreferExplicitCollationOverDefaultCollation() {
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class);
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateOne(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void updateMultiShouldUseDefaultCollationWhenPresent() {
template.updateMulti(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class);
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateMany(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void updateMultiShouldPreferExplicitCollationOverDefaultCollation() {
template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class);
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateMany(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void removeShouldUseDefaultCollationWhenPresent() {
template.remove(new BasicQuery("{}"), Sith.class);
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
verify(collection).deleteMany(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void removeShouldPreferExplicitCollationOverDefaultCollation() {
template.remove(new BasicQuery("{}").collation(Collation.of("fr")), Sith.class);
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
verify(collection).deleteMany(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void mapReduceShouldUseDefaultCollationWhenPresent() {
template.mapReduce("", "", "", MapReduceOptions.options(), Sith.class);
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void mapReduceShouldPreferExplicitCollationOverDefaultCollation() {
template.mapReduce("", "", "", MapReduceOptions.options().collation(Collation.of("fr")), Sith.class);
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
class AutogenerateableId {
@Id BigInteger id;
@ -1157,6 +1459,20 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1157,6 +1459,20 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
List<Integer> grades;
}
static class WithNamedFields {
@Id String id;
String name;
@Field("custom-named-field") String customName;
}
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
static class Sith {
@Field("firstname") String name;
}
/**
* Mocks out the {@link MongoTemplate#getDb()} method to return the {@link DB} mock instead of executing the actual
* behaviour.

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java

@ -514,7 +514,7 @@ public class ReactiveMongoTemplateTests { @@ -514,7 +514,7 @@ public class ReactiveMongoTemplateTests {
p = template.findAndModify(query, update, new FindAndModifyOptions().returnNew(true), Person.class).block();
assertThat(p.getAge()).isEqualTo(26);
p = template.findAndModify(query, update, null, Person.class, "person").block();
p = template.findAndModify(query, update, FindAndModifyOptions.none(), Person.class, "person").block();
assertThat(p.getAge()).isEqualTo(26);
p = template.findOne(query, Person.class).block();
assertThat(p.getAge()).isEqualTo(27);

270
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java

@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.*; @@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.any;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import lombok.Data;
import reactor.core.publisher.Mono;
@ -39,6 +40,7 @@ import org.mockito.ArgumentCaptor; @@ -39,6 +40,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.AutogenerateableId;
@ -56,12 +58,15 @@ import org.springframework.data.mongodb.core.query.Update; @@ -56,12 +58,15 @@ import org.springframework.data.mongodb.core.query.Update;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.reactivestreams.client.AggregatePublisher;
import com.mongodb.reactivestreams.client.DistinctPublisher;
import com.mongodb.reactivestreams.client.FindPublisher;
import com.mongodb.reactivestreams.client.MapReducePublisher;
import com.mongodb.reactivestreams.client.MongoClient;
@ -88,6 +93,8 @@ public class ReactiveMongoTemplateUnitTests { @@ -88,6 +93,8 @@ public class ReactiveMongoTemplateUnitTests {
@Mock Publisher runCommandPublisher;
@Mock Publisher updatePublisher;
@Mock Publisher findAndUpdatePublisher;
@Mock DistinctPublisher distinctPublisher;
@Mock Publisher deletePublisher;
@Mock MapReducePublisher mapReducePublisher;
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
@ -102,12 +109,20 @@ public class ReactiveMongoTemplateUnitTests { @@ -102,12 +109,20 @@ public class ReactiveMongoTemplateUnitTests {
when(db.getCollection(any())).thenReturn(collection);
when(db.getCollection(any(), any())).thenReturn(collection);
when(db.runCommand(any(), any(Class.class))).thenReturn(runCommandPublisher);
when(db.createCollection(any(), any(CreateCollectionOptions.class))).thenReturn(runCommandPublisher);
when(collection.find(any(Class.class))).thenReturn(findPublisher);
when(collection.find(any(Document.class), any(Class.class))).thenReturn(findPublisher);
when(collection.aggregate(anyList())).thenReturn(aggregatePublisher);
when(collection.aggregate(anyList(), any(Class.class))).thenReturn(aggregatePublisher);
when(collection.count(any(), any(CountOptions.class))).thenReturn(Mono.just(0L));
when(collection.updateOne(any(), any(), any(UpdateOptions.class))).thenReturn(updatePublisher);
when(collection.updateMany(any(Bson.class), any(), any())).thenReturn(updatePublisher);
when(collection.findOneAndUpdate(any(), any(), any(FindOneAndUpdateOptions.class)))
.thenReturn(findAndUpdatePublisher);
when(collection.findOneAndReplace(any(Bson.class), any(), any())).thenReturn(findPublisher);
when(collection.findOneAndDelete(any(), any(FindOneAndDeleteOptions.class))).thenReturn(findPublisher);
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctPublisher);
when(collection.deleteMany(any(Bson.class), any())).thenReturn(deletePublisher);
when(collection.findOneAndUpdate(any(), any(), any(FindOneAndUpdateOptions.class)))
.thenReturn(findAndUpdatePublisher);
when(collection.mapReduce(anyString(), anyString(), any())).thenReturn(mapReducePublisher);
@ -405,6 +420,255 @@ public class ReactiveMongoTemplateUnitTests { @@ -405,6 +420,255 @@ public class ReactiveMongoTemplateUnitTests {
.contains(new org.bson.Document("element", new Document("$gte", 100)));
}
@Test // DATAMONGO-1854
public void findShouldNotUseCollationWhenNoDefaultPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}"), Jedi.class).subscribe();
verify(findPublisher, never()).collation(any());
}
@Test // DATAMONGO-1854
public void findShouldUseDefaultCollationWhenPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}"), Sith.class).subscribe();
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findOneShouldUseDefaultCollationWhenPresent() {
template.findOne(new BasicQuery("{'foo' : 'bar'}"), Sith.class).subscribe();
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void existsShouldUseDefaultCollationWhenPresent() {
template.exists(new BasicQuery("{}"), Sith.class).subscribe();
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findAndModfiyShoudUseDefaultCollationWhenPresent() {
template.findAndModify(new BasicQuery("{}"), new Update(), Sith.class).subscribe();
ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
verify(collection).findOneAndUpdate(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findAndRemoveShouldUseDefaultCollationWhenPresent() {
template.findAndRemove(new BasicQuery("{}"), Sith.class).subscribe();
ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
verify(collection).findOneAndDelete(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void createCollectionShouldNotCollationIfNotPresent() {
template.createCollection(AutogenerateableId.class).subscribe();
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
Assertions.assertThat(options.getValue().getCollation()).isNull();
}
@Test // DATAMONGO-1854
public void createCollectionShouldApplyDefaultCollation() {
template.createCollection(Sith.class).subscribe();
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
template.createCollection(Sith.class, CollectionOptions.just(Collation.of("en_US"))).subscribe();
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("en_US").build()));
}
@Test // DATAMONGO-1854
public void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
template.createCollection(Sith.class, null).subscribe();
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void aggreateShouldUseDefaultCollationIfPresent() {
template.aggregate(newAggregation(Sith.class, project("id")), AutogenerateableId.class, Document.class).subscribe();
verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void aggreateShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {
template
.aggregate(
newAggregation(Sith.class, project("id"))
.withOptions(newAggregationOptions().collation(Collation.of("fr")).build()),
AutogenerateableId.class, Document.class)
.subscribe();
verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-18545
public void findAndReplaceShouldUseCollationWhenPresent() {
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new Jedi()).subscribe();
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
verify(collection).findOneAndReplace(any(Bson.class), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
}
@Test // DATAMONGO-18545
public void findAndReplaceShouldUseDefaultCollationWhenPresent() {
template.findAndReplace(new BasicQuery("{}"), new Sith()).subscribe();
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
verify(collection).findOneAndReplace(any(Bson.class), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale(), is("de_AT"));
}
@Test // DATAMONGO-18545
public void findAndReplaceShouldUseCollationEvenIfDefaultCollationIsPresent() {
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new MongoTemplateUnitTests.Sith())
.subscribe();
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
verify(collection).findOneAndReplace(any(Bson.class), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
}
@Test // DATAMONGO-1854
public void findDistinctShouldUseDefaultCollationWhenPresent() {
template.findDistinct(new BasicQuery("{}"), "name", Sith.class, String.class).subscribe();
verify(distinctPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void findDistinctPreferCollationFromQueryOverDefaultCollation() {
template.findDistinct(new BasicQuery("{}").collation(Collation.of("fr")), "name", Sith.class, String.class)
.subscribe();
verify(distinctPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void updateFirstShouldUseDefaultCollationWhenPresent() {
template.updateFirst(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class).subscribe();
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateOne(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void updateFirstShouldPreferExplicitCollationOverDefaultCollation() {
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class)
.subscribe();
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateOne(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void updateMultiShouldUseDefaultCollationWhenPresent() {
template.updateMulti(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class).subscribe();
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateMany(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void updateMultiShouldPreferExplicitCollationOverDefaultCollation() {
template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class)
.subscribe();
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
verify(collection).updateMany(any(), any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Test // DATAMONGO-1854
public void removeShouldUseDefaultCollationWhenPresent() {
template.remove(new BasicQuery("{}"), Sith.class).subscribe();
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
verify(collection).deleteMany(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // DATAMONGO-1854
public void removeShouldPreferExplicitCollationOverDefaultCollation() {
template.remove(new BasicQuery("{}").collation(Collation.of("fr")), Sith.class).subscribe();
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
verify(collection).deleteMany(any(), options.capture());
assertThat(options.getValue().getCollation(),
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
}
@Data
@org.springframework.data.mongodb.core.mapping.Document(collection = "star-wars")
static class Person {
@ -434,6 +698,12 @@ public class ReactiveMongoTemplateUnitTests { @@ -434,6 +698,12 @@ public class ReactiveMongoTemplateUnitTests {
@Field("firstname") String name;
}
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
static class Sith {
@Field("firstname") String name;
}
static class EntityWithListOfSimple {
List<Integer> grades;
}

36
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java

@ -23,7 +23,7 @@ import java.lang.annotation.Retention; @@ -23,7 +23,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
@ -237,6 +237,24 @@ public class BasicMongoPersistentEntityUnitTests { @@ -237,6 +237,24 @@ public class BasicMongoPersistentEntityUnitTests {
assertThat(entity.getCollection()).isEqualTo("collectionName");
}
@Test // DATAMONGO-1854
public void readsSimpleCollation() {
BasicMongoPersistentEntity<WithSimpleCollation> entity = new BasicMongoPersistentEntity<>(
ClassTypeInformation.from(WithSimpleCollation.class));
assertThat(entity.getCollation()).isEqualTo(org.springframework.data.mongodb.core.query.Collation.of("en_US"));
}
@Test // DATAMONGO-1854
public void readsDocumentCollation() {
BasicMongoPersistentEntity<WithDocumentCollation> entity = new BasicMongoPersistentEntity<>(
ClassTypeInformation.from(WithDocumentCollation.class));
assertThat(entity.getCollation()).isEqualTo(org.springframework.data.mongodb.core.query.Collation.of("en_US"));
}
@Document("contacts")
class Contact {}
@ -283,10 +301,18 @@ public class BasicMongoPersistentEntityUnitTests { @@ -283,10 +301,18 @@ public class BasicMongoPersistentEntityUnitTests {
}
// DATAMONGO-1874
@Document("#{myProperty}")
class MappedWithExtension {}
@Document(collation = "#{myCollation}")
class WithCollationFromSpEL {}
@Document(collation = "en_US")
class WithSimpleCollation {}
@Document(collation = "{ 'locale' : 'en_US' }")
class WithDocumentCollation {}
static class SampleExtension implements EvaluationContextExtension {
/*
@ -304,7 +330,11 @@ public class BasicMongoPersistentEntityUnitTests { @@ -304,7 +330,11 @@ public class BasicMongoPersistentEntityUnitTests {
*/
@Override
public Map<String, Object> getProperties() {
return Collections.singletonMap("myProperty", "collectionName");
Map<String, Object> properties = new LinkedHashMap<>();
properties.put("myProperty", "collectionName");
properties.put("myCollation", "en_US");
return properties;
}
}
}

5
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java

@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
package org.springframework.data.mongodb.repository.query;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@ -33,7 +35,6 @@ import org.mockito.ArgumentCaptor; @@ -33,7 +35,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQueryUnitTests.java

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -30,7 +30,6 @@ import org.mockito.ArgumentCaptor; @@ -30,7 +30,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.mongodb.core.Person;
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ReactiveFindOperation.ReactiveFind;

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryTests.java

@ -42,6 +42,7 @@ import org.springframework.data.mongodb.MongoTransactionManager; @@ -42,6 +42,7 @@ import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.repository.Address;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Person.Sex;
@ -485,6 +486,11 @@ public class SimpleMongoRepositoryTests { @@ -485,6 +486,11 @@ public class SimpleMongoRepositoryTests {
public String getIdAttribute() {
return "id";
}
@Override
public Collation getCollation() {
return null;
}
}
@Document

139
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleMongoRepositoryUnitTests.java

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class SimpleMongoRepositoryUnitTests {
SimpleMongoRepository<Object, Object> repository;
@Mock MongoOperations mongoOperations;
@Mock MongoEntityInformation<Object, Object> entityInformation;
@Before
public void setUp() {
repository = new SimpleMongoRepository<>(entityInformation, mongoOperations);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToCountForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.count(Example.of(new TestDummy()));
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).count(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToExistsForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.exists(Example.of(new TestDummy()));
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).exists(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findAll(Example.of(new TestDummy()));
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).find(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindWithSortForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findAll(Example.of(new TestDummy()), Sort.by("nothing"));
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).find(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindWithPageableForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findAll(Example.of(new TestDummy()), PageRequest.of(1, 1, Sort.by("nothing")));
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).find(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindOneForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findOne(Example.of(new TestDummy()));
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).findOne(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
static class TestDummy {
}
}

150
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepositoryUnitTests.java

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
/*
* Copyright 2019. the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.repository.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
/**
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
public class SimpleReactiveMongoRepositoryUnitTests {
SimpleReactiveMongoRepository<Object, String> repository;
@Mock Mono mono;
@Mock Flux flux;
@Mock ReactiveMongoOperations mongoOperations;
@Mock MongoEntityInformation<Object, String> entityInformation;
@Before
public void setUp() {
when(mongoOperations.count(any(), any(), any())).thenReturn(mono);
when(mongoOperations.exists(any(), any(), any())).thenReturn(mono);
when(mongoOperations.find(any(), any(), any())).thenReturn(flux);
repository = new SimpleReactiveMongoRepository<>(entityInformation, mongoOperations);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToCountForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.count(Example.of(new TestDummy())).subscribe();
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).count(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToExistsForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.exists(Example.of(new TestDummy())).subscribe();
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).exists(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findAll(Example.of(new TestDummy())).subscribe();
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).find(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindWithSortForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findAll(Example.of(new TestDummy()), Sort.by("nothing")).subscribe();
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).find(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
@Test // DATAMONGO-1854
public void shouldAddDefaultCollationToFindOneForExampleIfPresent() {
Collation collation = Collation.of("en_US");
when(entityInformation.getCollation()).thenReturn(collation);
repository.findOne(Example.of(new TestDummy())).subscribe();
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
verify(mongoOperations).find(query.capture(), any(), any());
assertThat(query.getValue().getCollation()).contains(collation);
}
static class TestDummy {
}
}

14
src/main/asciidoc/reference/mongodb.adoc

@ -1687,7 +1687,15 @@ Collation collation = Collation.of("fr") <1> @@ -1687,7 +1687,15 @@ Collation collation = Collation.of("fr") <1>
<6> Specify whether to check whether text requires normalization and whether to perform normalization.
====
Collations can be used to create collections and indexes. If you create a collection that specifies a collation, the collation is applied to index creation and queries unless you specify a different collation. A collation is valid for a whole operation and cannot be specified on a per-field basis, as the following example shows:
Collations can be used to create collections and indexes. If you create a collection that specifies a collation, the
collation is applied to index creation and queries unless you specify a different collation. A collation is valid for a
whole operation and cannot be specified on a per-field basis.
Like other metadata, collations can be be derived from the domain type via the `collation` attribute of the `@Document`
annotation and will be applied directly when executing queries, creating collections or indexes.
NOTE: Annotated collations will not be used when a collection is auto created by MongoDB on first interaction. This would
require additional store interaction delaying the entire process. Please use `MongoOperations.createCollection` for those cases.
[source,java]
----
@ -1738,7 +1746,7 @@ WARNING: Indexes are only used if the collation used for the operation matches t @@ -1738,7 +1746,7 @@ WARNING: Indexes are only used if the collation used for the operation matches t
include::./mongo-json-schema.adoc[leveloffset=+1]
<<mongo.repositories>> support `Collations` via the `@org.springframework.data.mongodb.repository.Query` annotation.
<<mongo.repositories>> support `Collations` via the `collation` attribute of the `@Query` annotation.
.Collation support for Repositories
====
@ -1774,6 +1782,8 @@ and `Document` (eg. new Document("locale", "en_US")) @@ -1774,6 +1782,8 @@ and `Document` (eg. new Document("locale", "en_US"))
NOTE: In case you enabled the automatic index creation for repository finder methods a potential static collation definition,
as shown in (1) and (2), will be included when creating the index.
TIP: The most specifc `Collation` outroules potentially defined others. Which means Method argument over query method annotation over doamin type annotation.
====
[[mongo.jsonSchema]]

Loading…
Cancel
Save