From e683c8f08f4ec93363a238ec0f4093709a8ee906 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 11 Apr 2019 12:29:27 +0200 Subject: [PATCH] DATAMONGO-1854 - Polishing. Extract common collation resolution code into EntityOperations (TypedOperations). Original pull request: #644. --- .../data/mongodb/core/EntityOperations.java | 122 ++++- .../mongodb/core/FindAndModifyOptions.java | 4 +- .../data/mongodb/core/MongoTemplate.java | 445 +++++++++--------- .../mongodb/core/ReactiveMongoTemplate.java | 92 ++-- src/main/asciidoc/new-features.adoc | 1 + 5 files changed, 387 insertions(+), 277 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java index 539fad2f3..cc2cfe88f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java @@ -21,12 +21,15 @@ import lombok.RequiredArgsConstructor; import java.util.Collection; import java.util.Map; +import java.util.Optional; import org.bson.Document; + import org.springframework.core.convert.ConversionService; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; @@ -34,6 +37,7 @@ import org.springframework.data.mongodb.core.convert.MongoWriter; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; +import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.lang.Nullable; @@ -176,6 +180,20 @@ class EntityOperations { } } + public TypedOperations forType(@Nullable Class entityClass) { + + if (entityClass != null) { + + MongoPersistentEntity entity = context.getPersistentEntity(entityClass); + + if (entity != null) { + return new TypedEntityOperations(entity); + } + + } + return UntypedOperations.instance(); + } + /** * A representation of information about an entity. * @@ -263,7 +281,7 @@ class EntityOperations { /** * Returns whether the entity is considered to be new. - * + * * @return * @since 2.1.2 */ @@ -414,7 +432,7 @@ class EntityOperations { return map; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.EntityOperations.Entity#isNew() */ @@ -585,7 +603,7 @@ class EntityOperations { return propertyAccessor.getBean(); } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.EntityOperations.Entity#isNew() */ @@ -698,4 +716,102 @@ class EntityOperations { return propertyAccessor.getBean(); } } + + /** + * Type-specific operations abstraction. + * + * @author Mark Paluch + * @param + * @since 2.2 + */ + interface TypedOperations { + + /** + * Return the optional {@link Collation} for the underlying entity. + * + * @return + */ + Optional getCollation(); + + /** + * Return the optional {@link Collation} from the given {@link Query} and fall back to the collation configured for + * the underlying entity. + * + * @return + */ + Optional getCollation(Query query); + } + + /** + * {@link TypedOperations} for generic entities that are not represented with {@link PersistentEntity} (e.g. custom + * conversions). + */ + @RequiredArgsConstructor + enum UntypedOperations implements TypedOperations { + + INSTANCE; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static TypedOperations instance() { + return (TypedOperations) INSTANCE; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.EntityOperations.TypedOperations#getCollation() + */ + @Override + public Optional getCollation() { + return Optional.empty(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.EntityOperations.TypedOperations#getCollation(org.springframework.data.mongodb.core.query.Query) + */ + @Override + public Optional getCollation(Query query) { + + if (query == null) { + return Optional.empty(); + } + + return query.getCollation(); + } + } + + /** + * {@link TypedOperations} backed by {@link MongoPersistentEntity}. + * + * @param + */ + @RequiredArgsConstructor + static class TypedEntityOperations implements TypedOperations { + + private final @NonNull MongoPersistentEntity entity; + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.EntityOperations.TypedOperations#getCollation() + */ + @Override + public Optional getCollation() { + return Optional.ofNullable(entity.getCollation()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.EntityOperations.TypedOperations#getCollation(org.springframework.data.mongodb.core.query.Query) + */ + @Override + public Optional getCollation(Query query) { + + if (query.getCollation().isPresent()) { + return query.getCollation(); + } + + return Optional.ofNullable(entity.getCollation()); + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java index 0c18a38a1..c9adf1f84 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FindAndModifyOptions.java @@ -53,7 +53,7 @@ public class FindAndModifyOptions { } @Override - public FindAndModifyOptions collation(Collation collation) { + public FindAndModifyOptions collation(@Nullable Collation collation) { throw new UnsupportedOperationException(ERROR_MSG); } }; @@ -79,7 +79,7 @@ public class FindAndModifyOptions { /** * Create new {@link FindAndModifyOptions} based on option of given {@litearl source}. - * + * * @param source can be {@literal null}. * @return new instance of {@link FindAndModifyOptions}. * @since 2.0 diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 48572b074..3beda20b4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -23,7 +23,18 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Scanner; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -33,6 +44,7 @@ import org.bson.codecs.Codec; import org.bson.conversions.Bson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -136,7 +148,17 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoIterable; -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.ValidationAction; +import com.mongodb.client.model.ValidationLevel; import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult; @@ -404,6 +426,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return doStream(query, entityType, collectionName, entityType); } + @SuppressWarnings("ConstantConditions") protected CloseableIterator doStream(final Query query, final Class entityType, final String collectionName, Class returnType) { @@ -412,23 +435,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Assert.hasText(collectionName, "Collection name must not be null or empty!"); Assert.notNull(returnType, "ReturnType must not be null!"); - return execute(collectionName, new CollectionCallback>() { + return execute(collectionName, (CollectionCallback>) collection -> { - @Override - public CloseableIterator doInCollection(MongoCollection collection) - throws MongoException, DataAccessException { + MongoPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entityType); - MongoPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(entityType); + Document mappedFields = getMappedFieldsObject(query.getFieldsObject(), persistentEntity, returnType); + Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); - Document mappedFields = getMappedFieldsObject(query.getFieldsObject(), persistentEntity, returnType); - Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); + FindIterable cursor = new QueryCursorPreparer(query, entityType) + .prepare(collection.find(mappedQuery, Document.class).projection(mappedFields)); - FindIterable cursor = new QueryCursorPreparer(query, entityType) - .prepare(collection.find(mappedQuery, Document.class).projection(mappedFields)); - - return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator, - new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName)); - } + return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator, + new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName)); }); } @@ -442,15 +460,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(java.lang.String) */ @Override + @SuppressWarnings("ConstantConditions") public Document executeCommand(final String jsonCommand) { Assert.hasText(jsonCommand, "JsonCommand must not be null nor empty!"); - return execute(new DbCallback() { - public Document doInDB(MongoDatabase db) throws MongoException, DataAccessException { - return db.runCommand(Document.parse(jsonCommand), Document.class); - } - }); + return execute(db -> db.runCommand(Document.parse(jsonCommand), Document.class)); } /* @@ -458,6 +473,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(org.bson.Document) */ @Override + @SuppressWarnings("ConstantConditions") public Document executeCommand(final Document command) { Assert.notNull(command, "Command must not be null!"); @@ -469,7 +485,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(org.bson.Document, com.mongodb.ReadPreference) */ + @Override + @SuppressWarnings("ConstantConditions") public Document executeCommand(Document command, @Nullable ReadPreference readPreference) { Assert.notNull(command, "Command must not be null!"); @@ -616,8 +634,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty(); options = Optionals - .firstNonEmpty(() -> collectionOptions != null ? collectionOptions.getCollation() : Optional.empty(), - () -> getCollationForType(entityClass)) // + .firstNonEmpty(() -> Optional.ofNullable(collectionOptions).flatMap(CollectionOptions::getCollation), + () -> operations.forType(entityClass).getCollation()) // .map(options::collation).orElse(options); return doCreateCollection(operations.determineCollectionName(entityClass), convertToDocument(options, entityClass)); @@ -649,15 +667,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoOperations#getCollection(java.lang.String) */ + @SuppressWarnings("ConstantConditions") public MongoCollection getCollection(final String collectionName) { Assert.notNull(collectionName, "CollectionName must not be null!"); - return execute(new DbCallback>() { - public MongoCollection doInDB(MongoDatabase db) throws MongoException, DataAccessException { - return db.getCollection(collectionName, Document.class); - } - }); + return execute(db -> db.getCollection(collectionName, Document.class)); } /* @@ -672,20 +687,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollection(java.lang.String) */ + @SuppressWarnings("ConstantConditions") public boolean collectionExists(final String collectionName) { Assert.notNull(collectionName, "CollectionName must not be null!"); - return execute(new DbCallback() { - public Boolean doInDB(MongoDatabase db) throws MongoException, DataAccessException { + return execute(db -> { - for (String name : db.listCollectionNames()) { - if (name.equals(collectionName)) { - return true; - } + for (String name : db.listCollectionNames()) { + if (name.equals(collectionName)) { + return true; } - return false; } + return false; }); } @@ -795,8 +809,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, if (ObjectUtils.isEmpty(query.getSortObject())) { return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) - .map(Collation::toMongoCollation).orElse(null), + operations.forType(entityClass).getCollation(query).map(Collation::toMongoCollation).orElse(null), entityClass); } else { query.limit(1); @@ -816,6 +829,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, } @Override + @SuppressWarnings("ConstantConditions") public boolean exists(Query query, @Nullable Class entityClass, String collectionName) { if (query == null) { @@ -825,10 +839,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass)); - return execute(collectionName, - new ExistsCallback(mappedQuery, - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) - .map(Collation::toMongoCollation).orElse(null))); + return execute(collectionName, new ExistsCallback(mappedQuery, + operations.forType(entityClass).getCollation(query).map(Collation::toMongoCollation).orElse(null))); } // Find methods that take a Query to express the query and that return a List of objects. @@ -918,7 +930,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, DistinctIterable iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType); - return Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) + return operations.forType(entityClass) // + .getCollation(query) // .map(Collation::toMongoCollation) // .map(iterable::collation) // .orElse(iterable); @@ -1078,9 +1091,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, "Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two."); }); - Optionals - .firstNonEmpty(() -> query.getCollation(), () -> options.getCollation(), () -> getCollationForType(entityClass)) - .ifPresent(optionsToUse::collation); + if (!options.getCollation().isPresent()) { + operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation); + } return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse); @@ -1113,9 +1126,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument(); return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityType)) - .map(Collation::toMongoCollation).orElse(null), - entityType, mappedReplacement, options, resultType); + operations.forType(entityType).getCollation(query).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 @@ -1136,8 +1148,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Assert.notNull(collectionName, "CollectionName must not be null!"); return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), - getMappedSortObject(query, entityClass), - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null), + getMappedSortObject(query, entityClass), operations.forType(entityClass).getCollation(query).orElse(null), entityClass); } @@ -1178,6 +1189,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return doCount(collectionName, document, options); } + @SuppressWarnings("ConstantConditions") protected long doCount(String collectionName, Document filter, CountOptions options) { if (LOGGER.isDebugEnabled()) { @@ -1468,26 +1480,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return saved; } + @SuppressWarnings("ConstantConditions") protected Object insertDocument(final String collectionName, final Document document, final Class entityClass) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Inserting Document containing fields: {} in collection: {}", document.keySet(), collectionName); } - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(MongoCollection collection) throws MongoException, DataAccessException { - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, - entityClass, document, null); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + return execute(collectionName, collection -> { + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass, + document, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - if (writeConcernToUse == null) { - collection.insertOne(document); - } else { - collection.withWriteConcern(writeConcernToUse).insertOne(document); - } - - return operations.forEntity(document).getId(); + if (writeConcernToUse == null) { + collection.insertOne(document); + } else { + collection.withWriteConcern(writeConcernToUse).insertOne(document); } + + return operations.forEntity(document).getId(); }); } @@ -1604,6 +1615,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return doUpdate(collectionName, query, update, entityClass, false, true); } + @SuppressWarnings("ConstantConditions") protected UpdateResult doUpdate(final String collectionName, final Query query, final UpdateDefinition update, @Nullable final Class entityClass, final boolean upsert, final boolean multi) { @@ -1611,63 +1623,60 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, Assert.notNull(query, "Query must not be null!"); Assert.notNull(update, "Update must not be null!"); - return execute(collectionName, new CollectionCallback() { - public UpdateResult doInCollection(MongoCollection collection) - throws MongoException, DataAccessException { + return execute(collectionName, collection -> { - MongoPersistentEntity entity = entityClass == null ? null : getPersistentEntity(entityClass); + MongoPersistentEntity entity = entityClass == null ? null : getPersistentEntity(entityClass); - increaseVersionForUpdateIfNecessary(entity, update); + increaseVersionForUpdateIfNecessary(entity, update); - UpdateOptions opts = new UpdateOptions(); - opts.upsert(upsert); + UpdateOptions opts = new UpdateOptions(); + opts.upsert(upsert); - if (update.hasArrayFilters()) { - opts.arrayFilters( - update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList())); - } + if (update.hasArrayFilters()) { + opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList())); + } - Document queryObj = new Document(); + Document queryObj = new Document(); - if (query != null) { - queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity)); - } + if (query != null) { + queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity)); + } - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) // - .map(Collation::toMongoCollation) // - .ifPresent(opts::collation); + operations.forType(entityClass) // + .getCollation(query) // + .map(Collation::toMongoCollation) // + .ifPresent(opts::collation); - Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject() - : updateMapper.getMappedObject(update.getUpdateObject(), entity); + Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject() + : updateMapper.getMappedObject(update.getUpdateObject(), entity); - if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) { - queryObj.put("$isolated", 1); - } + if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) { + queryObj.put("$isolated", 1); + } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", - serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName); - } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", serializeToJsonSafely(queryObj), + serializeToJsonSafely(updateObj), collectionName); + } - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, - entityClass, updateObj, queryObj); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass, + updateObj, queryObj); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection; + collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection; - if (!UpdateMapper.isUpdateObject(updateObj)) { + if (!UpdateMapper.isUpdateObject(updateObj)) { - ReplaceOptions replaceOptions = new ReplaceOptions(); - replaceOptions.collation(opts.getCollation()); - replaceOptions.upsert(opts.isUpsert()); + ReplaceOptions replaceOptions = new ReplaceOptions(); + replaceOptions.collation(opts.getCollation()); + replaceOptions.upsert(opts.isUpsert()); - return collection.replaceOne(queryObj, updateObj, replaceOptions); + return collection.replaceOne(queryObj, updateObj, replaceOptions); + } else { + if (multi) { + return collection.updateMany(queryObj, updateObj, opts); } else { - if (multi) { - return collection.updateMany(queryObj, updateObj, opts); - } else { - return collection.updateOne(queryObj, updateObj, opts); - } + return collection.updateOne(queryObj, updateObj, opts); } } }); @@ -1720,6 +1729,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return doRemove(collectionName, query, entityClass, true); } + @SuppressWarnings("ConstantConditions") protected DeleteResult doRemove(final String collectionName, final Query query, @Nullable final Class entityClass, boolean multi) { @@ -1729,55 +1739,53 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, final MongoPersistentEntity entity = getPersistentEntity(entityClass); final Document queryObject = queryMapper.getMappedObject(query.getQueryObject(), entity); - return execute(collectionName, new CollectionCallback() { + return execute(collectionName, collection -> { - public DeleteResult doInCollection(MongoCollection collection) - throws MongoException, DataAccessException { + maybeEmitEvent(new BeforeDeleteEvent<>(queryObject, entityClass, collectionName)); - maybeEmitEvent(new BeforeDeleteEvent<>(queryObject, entityClass, collectionName)); + Document removeQuery = queryObject; - Document removeQuery = queryObject; + DeleteOptions options = new DeleteOptions(); - DeleteOptions options = new DeleteOptions(); - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) // - .map(Collation::toMongoCollation) // - .ifPresent(options::collation); + operations.forType(entityClass) // + .getCollation(query) // + .map(Collation::toMongoCollation) // + .ifPresent(options::collation); - MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, - entityClass, null, queryObject); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, entityClass, + null, queryObject); - WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Remove using query: {} in collection: {}.", - new Object[] { serializeToJsonSafely(removeQuery), collectionName }); - } - - if (query.getLimit() > 0 || query.getSkip() > 0) { - - MongoCursor cursor = new QueryCursorPreparer(query, entityClass) - .prepare(collection.find(removeQuery).projection(MappedDocument.getIdOnlyProjection())) // - .iterator(); - - Set ids = new LinkedHashSet<>(); - while (cursor.hasNext()) { - ids.add(MappedDocument.of(cursor.next()).getId()); - } - - removeQuery = MappedDocument.getIdIn(ids); - } - - MongoCollection collectionToUse = writeConcernToUse != null - ? collection.withWriteConcern(writeConcernToUse) - : collection; - - DeleteResult result = multi ? collectionToUse.deleteMany(removeQuery, options) - : collectionToUse.deleteOne(removeQuery, options); - - maybeEmitEvent(new AfterDeleteEvent<>(queryObject, entityClass, collectionName)); - - return result; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Remove using query: {} in collection: {}.", + new Object[] { serializeToJsonSafely(removeQuery), collectionName }); } + + if (query.getLimit() > 0 || query.getSkip() > 0) { + + MongoCursor cursor = new QueryCursorPreparer(query, entityClass) + .prepare(collection.find(removeQuery).projection(MappedDocument.getIdOnlyProjection())) // + .iterator(); + + Set ids = new LinkedHashSet<>(); + while (cursor.hasNext()) { + ids.add(MappedDocument.of(cursor.next()).getId()); + } + + removeQuery = MappedDocument.getIdIn(ids); + } + + MongoCollection collectionToUse = writeConcernToUse != null + ? collection.withWriteConcern(writeConcernToUse) + : collection; + + DeleteResult result = multi ? collectionToUse.deleteMany(removeQuery, options) + : collectionToUse.deleteOne(removeQuery, options); + + maybeEmitEvent(new AfterDeleteEvent<>(queryObject, entityClass, collectionName)); + + return result; }); } @@ -1790,7 +1798,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, public List findAll(Class entityClass, String collectionName) { return executeFindMultiInternal( new FindCallback(new Document(), new Document(), - getCollationForType(entityClass).map(Collation::toMongoCollation).orElse(null)), + operations.forType(entityClass).getCollation().map(Collation::toMongoCollation).orElse(null)), null, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName); } @@ -1905,8 +1913,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, } } - if(!collation.isPresent()) { - collation = getCollationForType(domainType); + if (!collation.isPresent()) { + collation = operations.forType(domainType).getCollation(); } mapReduce = collation.map(Collation::toMongoCollation).map(mapReduce::collation).orElse(mapReduce); @@ -2122,6 +2130,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return doAggregate(aggregation, collectionName, outputType, contextToUse); } + @SuppressWarnings("ConstantConditions") protected AggregationResults doAggregate(Aggregation aggregation, String collectionName, Class outputType, AggregationOperationContext context) { @@ -2153,8 +2162,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, List rawResult = new ArrayList<>(); - Optional collation = Optionals.firstNonEmpty(() -> options.getCollation(), () -> getCollationForType( - aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null)); + Class domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() + : null; + + Optional collation = Optionals.firstNonEmpty(options::getCollation, + () -> operations.forType(domainType) // + .getCollation()); AggregateIterable aggregateIterable = collection.aggregate(pipeline, Document.class) // .collation(collation.map(Collation::toMongoCollation).orElse(null)) // @@ -2175,6 +2188,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, }); } + @SuppressWarnings("ConstantConditions") protected CloseableIterator aggregateStream(Aggregation aggregation, String collectionName, Class outputType, @Nullable AggregationOperationContext context) { @@ -2204,10 +2218,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, cursor = cursor.batchSize(options.getCursorBatchSize()); } - Optionals - .firstNonEmpty(() -> options.getCollation(), - () -> getCollationForType( - aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null)) // + Class domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() + : null; + + Optionals.firstNonEmpty(options::getCollation, // + () -> operations.forType(domainType).getCollation()) // .map(Collation::toMongoCollation) // .ifPresent(cursor::collation); @@ -2302,15 +2317,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * (non-Javadoc) * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollectionNames() */ + @SuppressWarnings("ConstantConditions") public Set getCollectionNames() { - return execute(new DbCallback>() { - public Set doInDB(MongoDatabase db) throws MongoException, DataAccessException { - Set result = new LinkedHashSet<>(); - for (String name : db.listCollectionNames()) { - result.add(name); - } - return result; + return execute(db -> { + Set result = new LinkedHashSet<>(); + for (String name : db.listCollectionNames()) { + result.add(name); } + return result; }); } @@ -2342,53 +2356,51 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * @param collectionOptions * @return the collection that was created */ - protected MongoCollection doCreateCollection(final String collectionName, - final Document collectionOptions) { - return execute(new DbCallback>() { - public MongoCollection doInDB(MongoDatabase db) throws MongoException, DataAccessException { + @SuppressWarnings("ConstantConditions") + protected MongoCollection doCreateCollection(String collectionName, Document collectionOptions) { + return execute(db -> { - CreateCollectionOptions co = new CreateCollectionOptions(); + CreateCollectionOptions co = new CreateCollectionOptions(); - if (collectionOptions.containsKey("capped")) { - co.capped((Boolean) collectionOptions.get("capped")); - } - if (collectionOptions.containsKey("size")) { - co.sizeInBytes(((Number) collectionOptions.get("size")).longValue()); - } - if (collectionOptions.containsKey("max")) { - co.maxDocuments(((Number) collectionOptions.get("max")).longValue()); - } - - if (collectionOptions.containsKey("collation")) { - co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class))); - } - - if (collectionOptions.containsKey("validator")) { - - com.mongodb.client.model.ValidationOptions options = new com.mongodb.client.model.ValidationOptions(); - - if (collectionOptions.containsKey("validationLevel")) { - options.validationLevel(ValidationLevel.fromString(collectionOptions.getString("validationLevel"))); - } - if (collectionOptions.containsKey("validationAction")) { - options.validationAction(ValidationAction.fromString(collectionOptions.getString("validationAction"))); - } - - options.validator(collectionOptions.get("validator", Document.class)); - co.validationOptions(options); - } - - db.createCollection(collectionName, co); - - MongoCollection coll = db.getCollection(collectionName, Document.class); - - // TODO: Emit a collection created event - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Created collection [{}]", - coll.getNamespace() != null ? coll.getNamespace().getCollectionName() : collectionName); - } - return coll; + if (collectionOptions.containsKey("capped")) { + co.capped((Boolean) collectionOptions.get("capped")); } + if (collectionOptions.containsKey("size")) { + co.sizeInBytes(((Number) collectionOptions.get("size")).longValue()); + } + if (collectionOptions.containsKey("max")) { + co.maxDocuments(((Number) collectionOptions.get("max")).longValue()); + } + + if (collectionOptions.containsKey("collation")) { + co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class))); + } + + if (collectionOptions.containsKey("validator")) { + + com.mongodb.client.model.ValidationOptions options = new com.mongodb.client.model.ValidationOptions(); + + if (collectionOptions.containsKey("validationLevel")) { + options.validationLevel(ValidationLevel.fromString(collectionOptions.getString("validationLevel"))); + } + if (collectionOptions.containsKey("validationAction")) { + options.validationAction(ValidationAction.fromString(collectionOptions.getString("validationAction"))); + } + + options.validator(collectionOptions.get("validator", Document.class)); + co.validationOptions(options); + } + + db.createCollection(collectionName, co); + + MongoCollection coll = db.getCollection(collectionName, Document.class); + + // TODO: Emit a collection created event + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Created collection [{}]", + coll.getNamespace() != null ? coll.getNamespace().getCollectionName() : collectionName); + } + return coll; }); } @@ -2415,7 +2427,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * @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. + * @since 2.2 */ + @SuppressWarnings("ConstantConditions") protected T doFindOne(String collectionName, Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation, Class entityClass) { @@ -2581,6 +2595,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, * @param entityClass the parameterized type of the returned list. * @return the List of converted objects. */ + @SuppressWarnings("ConstantConditions") protected T doFindAndRemove(String collectionName, Document query, Document fields, Document sort, @Nullable Collation collation, Class entityClass) { @@ -2598,6 +2613,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, new ReadDocumentCallback<>(readerToUse, entityClass, collectionName), collectionName); } + @SuppressWarnings("ConstantConditions") protected T doFindAndModify(String collectionName, Document query, Document fields, Document sort, Class entityClass, Update update, @Nullable FindAndModifyOptions options) { @@ -3045,7 +3061,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, private final FindAndReplaceOptions options; FindAndReplaceCallback(Document query, Document fields, Document sort, Document update, - com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) { + @Nullable com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) { this.query = query; this.fields = fields; @@ -3204,10 +3220,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, class QueryCursorPreparer implements CursorPreparer { - private final @Nullable Query query; + private final Query query; private final @Nullable Class type; - public QueryCursorPreparer(@Nullable Query query, @Nullable Class type) { + public QueryCursorPreparer(Query query, @Nullable Class type) { this.query = query; this.type = type; @@ -3221,15 +3237,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, FindIterable cursorToUse = cursor; - Optionals - .firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), () -> getCollationForType(type)) + operations.forType(type).getCollation(query) // .map(Collation::toMongoCollation) // .ifPresent(cursorToUse::collation); - if (query == null) { - 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()) { @@ -3483,14 +3494,4 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return execute(collectionName, collection -> collection.countDocuments(filter, options)); } } - - @Nullable - private Optional 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(); - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 0ce065269..328e92e30 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -25,7 +25,17 @@ import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Function; @@ -40,6 +50,7 @@ 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; @@ -649,8 +660,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty(); options = Optionals - .firstNonEmpty(() -> collectionOptions != null ? collectionOptions.getCollation() : Optional.empty(), - () -> getCollationForType(entityClass)) // + .firstNonEmpty(() -> Optional.ofNullable(collectionOptions).flatMap(CollectionOptions::getCollation), + () -> operations.forType(entityClass).getCollation()) // .map(options::collation).orElse(options); return doCreateCollection(determineCollectionName(entityClass), @@ -754,7 +765,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati if (ObjectUtils.isEmpty(query.getSortObject())) { return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass, - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null)); + operations.forType(entityClass).getCollation(query).orElse(null)); } query.limit(1); @@ -797,8 +808,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati LOGGER.debug("exists: {} in collection: {}", serializeToJsonSafely(filter), collectionName); } - findPublisher = Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) - .map(Collation::toMongoCollation).map(findPublisher::collation).orElse(findPublisher); + findPublisher = operations.forType(entityClass).getCollation(query).map(Collation::toMongoCollation) + .map(findPublisher::collation).orElse(findPublisher); return findPublisher.limit(1); }).hasElements(); @@ -884,7 +895,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati } DistinctPublisher publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType); - return Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) + return operations.forType(entityClass).getCollation(query) // .map(Collation::toMongoCollation) // .map(publisher::collation) // .orElse(publisher); @@ -1011,7 +1022,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati cursor = cursor.batchSize(options.getCursorBatchSize()); } - Optionals.firstNonEmpty(() -> options.getCollation(), () -> getCollationForType(inputType)) // + Optionals.firstNonEmpty(options::getCollation, () -> operations.forType(inputType).getCollation()) // .map(Collation::toMongoCollation) // .ifPresent(cursor::collation); @@ -1119,9 +1130,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati "Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two."); }); - Optionals - .firstNonEmpty(() -> query.getCollation(), () -> options.getCollation(), () -> getCollationForType(entityClass)) - .ifPresent(optionsToUse::collation); + if (!optionsToUse.getCollation().isPresent()) { + operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation); + ; + } return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse); @@ -1154,9 +1166,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument(); return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityType)) - .map(Collation::toMongoCollation).orElse(null), - entityType, mappedReplacement, options, resultType); + operations.forType(entityType).getCollation(query).map(Collation::toMongoCollation).orElse(null), entityType, + mappedReplacement, options, resultType); } /* @@ -1173,9 +1184,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati */ public Mono findAndRemove(Query query, Class entityClass, String collectionName) { + operations.forType(entityClass).getCollation(query); return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), - getMappedSortObject(query, entityClass), - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null), + getMappedSortObject(query, entityClass), operations.forType(entityClass).getCollation(query).orElse(null), entityClass); } @@ -1209,26 +1220,20 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati return createMono(collectionName, collection -> { - Document filter = query == null ? null - : queryMapper.getMappedObject(query.getQueryObject(), - entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); + Document filter = queryMapper.getMappedObject(query.getQueryObject(), + entityClass == null ? null : mappingContext.getPersistentEntity(entityClass)); CountOptions options = new CountOptions(); - if (query != null) { - query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation); + query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation); - if (query.getLimit() > 0) { - options.limit(query.getLimit()); - } - if (query.getSkip() > 0) { - options.skip((int) query.getSkip()); - } + if (query.getLimit() > 0) { + options.limit(query.getLimit()); + } + if (query.getSkip() > 0) { + options.skip((int) query.getSkip()); } - Optionals - .firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), - () -> getCollationForType(entityClass)) - .map(Collation::toMongoCollation) // + operations.forType(entityClass).getCollation(query).map(Collation::toMongoCollation) // .ifPresent(options::collation); if (LOGGER.isDebugEnabled()) { @@ -1698,7 +1703,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati MongoCollection collectionToUse = prepareCollection(collection, writeConcernToUse); UpdateOptions updateOptions = new UpdateOptions().upsert(upsert); - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) // + operations.forType(entityClass).getCollation(query) // .map(Collation::toMongoCollation) // .ifPresent(updateOptions::collation); @@ -1865,7 +1870,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati DeleteOptions deleteOptions = new DeleteOptions(); - Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) // + operations.forType(entityClass).getCollation(query) // .map(Collation::toMongoCollation) // .ifPresent(deleteOptions::collation); @@ -3070,10 +3075,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati */ class QueryFindPublisherPreparer implements FindPublisherPreparer { - private final @Nullable Query query; + private final Query query; private final @Nullable Class type; - QueryFindPublisherPreparer(@Nullable Query query, @Nullable Class type) { + QueryFindPublisherPreparer(Query query, @Nullable Class type) { this.query = query; this.type = type; @@ -3082,16 +3087,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @SuppressWarnings("deprecation") public FindPublisher prepare(FindPublisher findPublisher) { - FindPublisher findPublisherToUse = Optionals // - .firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), () -> getCollationForType(type)) + FindPublisher findPublisherToUse = operations.forType(type) // + .getCollation(query) // .map(Collation::toMongoCollation) // .map(findPublisher::collation) // .orElse(findPublisher); - if (query == null) { - return findPublisherToUse; - } - Meta meta = query.getMeta(); if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject()) && !StringUtils.hasText(query.getHint()) && !meta.hasValues()) { @@ -3266,13 +3267,4 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati } } - @Nullable - private Optional getCollationForType(Class type) { - - if (type == null) { - return Optional.empty(); - } - MongoPersistentEntity entity = mappingContext.getPersistentEntity(type); - return entity != null ? Optional.ofNullable(entity.getCollation()) : Optional.empty(); - } } diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index a7098caa8..8050d212f 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -15,6 +15,7 @@ * Support of array filters in `Update` operations. * <> from domain types. * SpEL support in for expressions in `@Indexed`. +* Annotation-based Collation support through `@Document` and `@Query`. [[new-features.2-1-0]] == What's New in Spring Data MongoDB 2.1