PageRenderTime 40ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

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

http://github.com/SpringSource/spring-data-mongodb
Java | 3541 lines | 2135 code | 736 blank | 670 comment | 257 complexity | 5371130db57092c99f8e93476fd99e82 MD5 | raw file
  1. /*
  2. * Copyright 2010-2021 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * https://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.data.mongodb.core;
  17. import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
  18. import java.io.IOException;
  19. import java.math.BigDecimal;
  20. import java.math.RoundingMode;
  21. import java.util.*;
  22. import java.util.concurrent.TimeUnit;
  23. import java.util.stream.Collectors;
  24. import org.bson.Document;
  25. import org.bson.conversions.Bson;
  26. import org.slf4j.Logger;
  27. import org.slf4j.LoggerFactory;
  28. import org.springframework.beans.BeansException;
  29. import org.springframework.context.ApplicationContext;
  30. import org.springframework.context.ApplicationContextAware;
  31. import org.springframework.context.ApplicationEventPublisher;
  32. import org.springframework.context.ApplicationEventPublisherAware;
  33. import org.springframework.context.ApplicationListener;
  34. import org.springframework.context.ConfigurableApplicationContext;
  35. import org.springframework.core.io.Resource;
  36. import org.springframework.core.io.ResourceLoader;
  37. import org.springframework.dao.DataAccessException;
  38. import org.springframework.dao.InvalidDataAccessApiUsageException;
  39. import org.springframework.dao.OptimisticLockingFailureException;
  40. import org.springframework.dao.support.PersistenceExceptionTranslator;
  41. import org.springframework.data.convert.EntityReader;
  42. import org.springframework.data.geo.Distance;
  43. import org.springframework.data.geo.GeoResult;
  44. import org.springframework.data.geo.GeoResults;
  45. import org.springframework.data.geo.Metric;
  46. import org.springframework.data.mapping.MappingException;
  47. import org.springframework.data.mapping.callback.EntityCallbacks;
  48. import org.springframework.data.mapping.context.MappingContext;
  49. import org.springframework.data.mongodb.MongoDatabaseFactory;
  50. import org.springframework.data.mongodb.MongoDatabaseUtils;
  51. import org.springframework.data.mongodb.SessionSynchronization;
  52. import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
  53. import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
  54. import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
  55. import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
  56. import org.springframework.data.mongodb.core.QueryOperations.CountContext;
  57. import org.springframework.data.mongodb.core.QueryOperations.DeleteContext;
  58. import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext;
  59. import org.springframework.data.mongodb.core.QueryOperations.QueryContext;
  60. import org.springframework.data.mongodb.core.QueryOperations.UpdateContext;
  61. import org.springframework.data.mongodb.core.aggregation.Aggregation;
  62. import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
  63. import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
  64. import org.springframework.data.mongodb.core.aggregation.AggregationResults;
  65. import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
  66. import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
  67. import org.springframework.data.mongodb.core.convert.DbRefResolver;
  68. import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
  69. import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
  70. import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
  71. import org.springframework.data.mongodb.core.convert.MongoConverter;
  72. import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
  73. import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
  74. import org.springframework.data.mongodb.core.convert.MongoWriter;
  75. import org.springframework.data.mongodb.core.convert.QueryMapper;
  76. import org.springframework.data.mongodb.core.convert.UpdateMapper;
  77. import org.springframework.data.mongodb.core.index.IndexOperations;
  78. import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
  79. import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
  80. import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
  81. import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
  82. import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
  83. import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
  84. import org.springframework.data.mongodb.core.mapping.event.*;
  85. import org.springframework.data.mongodb.core.mapreduce.GroupBy;
  86. import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
  87. import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
  88. import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
  89. import org.springframework.data.mongodb.core.query.BasicQuery;
  90. import org.springframework.data.mongodb.core.query.Collation;
  91. import org.springframework.data.mongodb.core.query.Criteria;
  92. import org.springframework.data.mongodb.core.query.Meta;
  93. import org.springframework.data.mongodb.core.query.Meta.CursorOption;
  94. import org.springframework.data.mongodb.core.query.NearQuery;
  95. import org.springframework.data.mongodb.core.query.Query;
  96. import org.springframework.data.mongodb.core.query.UpdateDefinition;
  97. import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
  98. import org.springframework.data.mongodb.core.timeseries.Granularity;
  99. import org.springframework.data.mongodb.core.validation.Validator;
  100. import org.springframework.data.mongodb.util.BsonUtils;
  101. import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
  102. import org.springframework.data.util.CloseableIterator;
  103. import org.springframework.data.util.Optionals;
  104. import org.springframework.lang.Nullable;
  105. import org.springframework.util.Assert;
  106. import org.springframework.util.ClassUtils;
  107. import org.springframework.util.CollectionUtils;
  108. import org.springframework.util.NumberUtils;
  109. import org.springframework.util.ObjectUtils;
  110. import org.springframework.util.ResourceUtils;
  111. import org.springframework.util.StringUtils;
  112. import com.mongodb.ClientSessionOptions;
  113. import com.mongodb.MongoException;
  114. import com.mongodb.ReadPreference;
  115. import com.mongodb.WriteConcern;
  116. import com.mongodb.client.AggregateIterable;
  117. import com.mongodb.client.ClientSession;
  118. import com.mongodb.client.DistinctIterable;
  119. import com.mongodb.client.FindIterable;
  120. import com.mongodb.client.MapReduceIterable;
  121. import com.mongodb.client.MongoClient;
  122. import com.mongodb.client.MongoCollection;
  123. import com.mongodb.client.MongoCursor;
  124. import com.mongodb.client.MongoDatabase;
  125. import com.mongodb.client.MongoIterable;
  126. import com.mongodb.client.model.*;
  127. import com.mongodb.client.result.DeleteResult;
  128. import com.mongodb.client.result.UpdateResult;
  129. /**
  130. * Primary implementation of {@link MongoOperations}.
  131. *
  132. * @author Thomas Risberg
  133. * @author Graeme Rocher
  134. * @author Mark Pollack
  135. * @author Oliver Gierke
  136. * @author Amol Nayak
  137. * @author Patryk Wasik
  138. * @author Tobias Trelle
  139. * @author Sebastian Herold
  140. * @author Thomas Darimont
  141. * @author Chuong Ngo
  142. * @author Christoph Strobl
  143. * @author Doménique Tilleuil
  144. * @author Niko Schmuck
  145. * @author Mark Paluch
  146. * @author Laszlo Csontos
  147. * @author Maninder Singh
  148. * @author Borislav Rangelov
  149. * @author duozhilin
  150. * @author Andreas Zink
  151. * @author Cimon Lucas
  152. * @author Michael J. Simons
  153. * @author Roman Puchkovskiy
  154. * @author Yadhukrishna S Pai
  155. * @author Anton Barkan
  156. * @author Bartłomiej Mazur
  157. */
  158. public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider {
  159. private static final Logger LOGGER = LoggerFactory.getLogger(MongoTemplate.class);
  160. private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
  161. private final MongoConverter mongoConverter;
  162. private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
  163. private final MongoDatabaseFactory mongoDbFactory;
  164. private final PersistenceExceptionTranslator exceptionTranslator;
  165. private final QueryMapper queryMapper;
  166. private final UpdateMapper updateMapper;
  167. private final JsonSchemaMapper schemaMapper;
  168. private final SpelAwareProxyProjectionFactory projectionFactory;
  169. private final EntityOperations operations;
  170. private final PropertyOperations propertyOperations;
  171. private final QueryOperations queryOperations;
  172. private @Nullable WriteConcern writeConcern;
  173. private WriteConcernResolver writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
  174. private WriteResultChecking writeResultChecking = WriteResultChecking.NONE;
  175. private @Nullable ReadPreference readPreference;
  176. private @Nullable ApplicationEventPublisher eventPublisher;
  177. private @Nullable EntityCallbacks entityCallbacks;
  178. private @Nullable ResourceLoader resourceLoader;
  179. private @Nullable MongoPersistentEntityIndexCreator indexCreator;
  180. private SessionSynchronization sessionSynchronization = SessionSynchronization.ON_ACTUAL_TRANSACTION;
  181. /**
  182. * Constructor used for a basic template configuration.
  183. *
  184. * @param mongoClient must not be {@literal null}.
  185. * @param databaseName must not be {@literal null} or empty.
  186. * @since 2.1
  187. */
  188. public MongoTemplate(MongoClient mongoClient, String databaseName) {
  189. this(new SimpleMongoClientDatabaseFactory(mongoClient, databaseName), (MongoConverter) null);
  190. }
  191. /**
  192. * Constructor used for a basic template configuration.
  193. *
  194. * @param mongoDbFactory must not be {@literal null}.
  195. */
  196. public MongoTemplate(MongoDatabaseFactory mongoDbFactory) {
  197. this(mongoDbFactory, (MongoConverter) null);
  198. }
  199. /**
  200. * Constructor used for a basic template configuration.
  201. *
  202. * @param mongoDbFactory must not be {@literal null}.
  203. * @param mongoConverter
  204. */
  205. public MongoTemplate(MongoDatabaseFactory mongoDbFactory, @Nullable MongoConverter mongoConverter) {
  206. Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!");
  207. this.mongoDbFactory = mongoDbFactory;
  208. this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
  209. this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter;
  210. this.queryMapper = new QueryMapper(this.mongoConverter);
  211. this.updateMapper = new UpdateMapper(this.mongoConverter);
  212. this.schemaMapper = new MongoJsonSchemaMapper(this.mongoConverter);
  213. this.projectionFactory = new SpelAwareProxyProjectionFactory();
  214. this.operations = new EntityOperations(this.mongoConverter.getMappingContext());
  215. this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
  216. this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations,
  217. mongoDbFactory);
  218. // We always have a mapping context in the converter, whether it's a simple one or not
  219. mappingContext = this.mongoConverter.getMappingContext();
  220. // We create indexes based on mapping events
  221. if (mappingContext instanceof MongoMappingContext) {
  222. MongoMappingContext mappingContext = (MongoMappingContext) this.mappingContext;
  223. if (mappingContext.isAutoIndexCreation()) {
  224. indexCreator = new MongoPersistentEntityIndexCreator(mappingContext, this);
  225. eventPublisher = new MongoMappingEventPublisher(indexCreator);
  226. mappingContext.setApplicationEventPublisher(eventPublisher);
  227. }
  228. }
  229. }
  230. private MongoTemplate(MongoDatabaseFactory dbFactory, MongoTemplate that) {
  231. this.mongoDbFactory = dbFactory;
  232. this.exceptionTranslator = that.exceptionTranslator;
  233. this.sessionSynchronization = that.sessionSynchronization;
  234. // we need to (re)create the MappingMongoConverter as we need to have it use a DbRefResolver that operates within
  235. // the sames session. Otherwise loading referenced objects would happen outside of it.
  236. if (that.mongoConverter instanceof MappingMongoConverter) {
  237. this.mongoConverter = ((MappingMongoConverter) that.mongoConverter).with(dbFactory);
  238. } else {
  239. this.mongoConverter = that.mongoConverter;
  240. }
  241. this.queryMapper = that.queryMapper;
  242. this.updateMapper = that.updateMapper;
  243. this.schemaMapper = that.schemaMapper;
  244. this.projectionFactory = that.projectionFactory;
  245. this.mappingContext = that.mappingContext;
  246. this.operations = that.operations;
  247. this.propertyOperations = that.propertyOperations;
  248. this.queryOperations = that.queryOperations;
  249. }
  250. /**
  251. * Configures the {@link WriteResultChecking} to be used with the template. Setting {@literal null} will reset the
  252. * default of {@link #DEFAULT_WRITE_RESULT_CHECKING}.
  253. *
  254. * @param resultChecking
  255. */
  256. public void setWriteResultChecking(@Nullable WriteResultChecking resultChecking) {
  257. this.writeResultChecking = resultChecking == null ? DEFAULT_WRITE_RESULT_CHECKING : resultChecking;
  258. }
  259. /**
  260. * Configures the {@link WriteConcern} to be used with the template. If none is configured the {@link WriteConcern}
  261. * configured on the {@link MongoDatabaseFactory} will apply.
  262. *
  263. * @param writeConcern
  264. */
  265. public void setWriteConcern(@Nullable WriteConcern writeConcern) {
  266. this.writeConcern = writeConcern;
  267. }
  268. /**
  269. * Configures the {@link WriteConcernResolver} to be used with the template.
  270. *
  271. * @param writeConcernResolver
  272. */
  273. public void setWriteConcernResolver(@Nullable WriteConcernResolver writeConcernResolver) {
  274. this.writeConcernResolver = writeConcernResolver == null ? DefaultWriteConcernResolver.INSTANCE
  275. : writeConcernResolver;
  276. }
  277. /**
  278. * Used by @{link {@link #prepareCollection(MongoCollection)} to set the {@link ReadPreference} before any operations
  279. * are performed.
  280. *
  281. * @param readPreference
  282. */
  283. public void setReadPreference(@Nullable ReadPreference readPreference) {
  284. this.readPreference = readPreference;
  285. }
  286. /*
  287. * (non-Javadoc)
  288. * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
  289. */
  290. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  291. prepareIndexCreator(applicationContext);
  292. eventPublisher = applicationContext;
  293. if (entityCallbacks == null) {
  294. setEntityCallbacks(EntityCallbacks.create(applicationContext));
  295. }
  296. if (mappingContext instanceof ApplicationEventPublisherAware) {
  297. ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher);
  298. }
  299. resourceLoader = applicationContext;
  300. projectionFactory.setBeanFactory(applicationContext);
  301. projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
  302. }
  303. /**
  304. * Set the {@link EntityCallbacks} instance to use when invoking
  305. * {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link BeforeSaveCallback}.
  306. * <br />
  307. * Overrides potentially existing {@link EntityCallbacks}.
  308. *
  309. * @param entityCallbacks must not be {@literal null}.
  310. * @throws IllegalArgumentException if the given instance is {@literal null}.
  311. * @since 2.2
  312. */
  313. public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
  314. Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!");
  315. this.entityCallbacks = entityCallbacks;
  316. }
  317. /**
  318. * Inspects the given {@link ApplicationContext} for {@link MongoPersistentEntityIndexCreator} and those in turn if
  319. * they were registered for the current {@link MappingContext}. If no creator for the current {@link MappingContext}
  320. * can be found we manually add the internally created one as {@link ApplicationListener} to make sure indexes get
  321. * created appropriately for entity types persisted through this {@link MongoTemplate} instance.
  322. *
  323. * @param context must not be {@literal null}.
  324. */
  325. private void prepareIndexCreator(ApplicationContext context) {
  326. String[] indexCreators = context.getBeanNamesForType(MongoPersistentEntityIndexCreator.class);
  327. for (String creator : indexCreators) {
  328. MongoPersistentEntityIndexCreator creatorBean = context.getBean(creator, MongoPersistentEntityIndexCreator.class);
  329. if (creatorBean.isIndexCreatorFor(mappingContext)) {
  330. return;
  331. }
  332. }
  333. if (context instanceof ConfigurableApplicationContext && indexCreator != null) {
  334. ((ConfigurableApplicationContext) context).addApplicationListener(indexCreator);
  335. }
  336. }
  337. /**
  338. * Returns the default {@link org.springframework.data.mongodb.core.convert.MongoConverter}.
  339. *
  340. * @return
  341. */
  342. public MongoConverter getConverter() {
  343. return this.mongoConverter;
  344. }
  345. /*
  346. * (non-Javadoc)
  347. * @see org.springframework.data.mongodb.core.MongoOperations#executeAsStream(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
  348. */
  349. @Override
  350. public <T> CloseableIterator<T> stream(Query query, Class<T> entityType) {
  351. return stream(query, entityType, getCollectionName(entityType));
  352. }
  353. /*
  354. * (non-Javadoc)
  355. * @see org.springframework.data.mongodb.core.MongoOperations#stream(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
  356. */
  357. @Override
  358. public <T> CloseableIterator<T> stream(Query query, Class<T> entityType, String collectionName) {
  359. return doStream(query, entityType, collectionName, entityType);
  360. }
  361. @SuppressWarnings("ConstantConditions")
  362. protected <T> CloseableIterator<T> doStream(Query query, Class<?> entityType, String collectionName,
  363. Class<T> returnType) {
  364. Assert.notNull(query, "Query must not be null!");
  365. Assert.notNull(entityType, "Entity type must not be null!");
  366. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  367. Assert.notNull(returnType, "ReturnType must not be null!");
  368. return execute(collectionName, (CollectionCallback<CloseableIterator<T>>) collection -> {
  369. MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entityType);
  370. QueryContext queryContext = queryOperations.createQueryContext(query);
  371. Document mappedQuery = queryContext.getMappedQuery(persistentEntity);
  372. Document mappedFields = queryContext.getMappedFields(persistentEntity, returnType, projectionFactory);
  373. FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType).initiateFind(collection,
  374. col -> col.find(mappedQuery, Document.class).projection(mappedFields));
  375. return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator,
  376. new ProjectingReadCallback<>(mongoConverter, entityType, returnType, collectionName));
  377. });
  378. }
  379. @Override
  380. public String getCollectionName(Class<?> entityClass) {
  381. return this.operations.determineCollectionName(entityClass);
  382. }
  383. /*
  384. * (non-Javadoc)
  385. * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(java.lang.String)
  386. */
  387. @Override
  388. @SuppressWarnings("ConstantConditions")
  389. public Document executeCommand(String jsonCommand) {
  390. Assert.hasText(jsonCommand, "JsonCommand must not be null nor empty!");
  391. return execute(db -> db.runCommand(Document.parse(jsonCommand), Document.class));
  392. }
  393. /*
  394. * (non-Javadoc)
  395. * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(org.bson.Document)
  396. */
  397. @Override
  398. @SuppressWarnings("ConstantConditions")
  399. public Document executeCommand(Document command) {
  400. Assert.notNull(command, "Command must not be null!");
  401. return execute(db -> db.runCommand(command, Document.class));
  402. }
  403. /*
  404. * (non-Javadoc)
  405. * @see org.springframework.data.mongodb.core.MongoOperations#executeCommand(org.bson.Document, com.mongodb.ReadPreference)
  406. */
  407. @Override
  408. @SuppressWarnings("ConstantConditions")
  409. public Document executeCommand(Document command, @Nullable ReadPreference readPreference) {
  410. Assert.notNull(command, "Command must not be null!");
  411. return execute(db -> readPreference != null //
  412. ? db.runCommand(command, readPreference, Document.class) //
  413. : db.runCommand(command, Document.class));
  414. }
  415. /*
  416. * (non-Javadoc)
  417. * @see org.springframework.data.mongodb.core.MongoOperations#executeQuery(org.springframework.data.mongodb.core.query.Query, java.lang.String, org.springframework.data.mongodb.core.DocumentCallbackHandler)
  418. */
  419. @Override
  420. public void executeQuery(Query query, String collectionName, DocumentCallbackHandler dch) {
  421. executeQuery(query, collectionName, dch, new QueryCursorPreparer(query, null));
  422. }
  423. /**
  424. * Execute a MongoDB query and iterate over the query results on a per-document basis with a
  425. * {@link DocumentCallbackHandler} using the provided CursorPreparer.
  426. *
  427. * @param query the query class that specifies the criteria used to find a record and also an optional fields
  428. * specification, must not be {@literal null}.
  429. * @param collectionName name of the collection to retrieve the objects from
  430. * @param documentCallbackHandler the handler that will extract results, one document at a time
  431. * @param preparer allows for customization of the {@link FindIterable} used when iterating over the result set,
  432. * (apply limits, skips and so on).
  433. */
  434. protected void executeQuery(Query query, String collectionName, DocumentCallbackHandler documentCallbackHandler,
  435. @Nullable CursorPreparer preparer) {
  436. Assert.notNull(query, "Query must not be null!");
  437. Assert.notNull(collectionName, "CollectionName must not be null!");
  438. Assert.notNull(documentCallbackHandler, "DocumentCallbackHandler must not be null!");
  439. Document queryObject = queryMapper.getMappedObject(query.getQueryObject(), Optional.empty());
  440. Document sortObject = query.getSortObject();
  441. Document fieldsObject = query.getFieldsObject();
  442. if (LOGGER.isDebugEnabled()) {
  443. LOGGER.debug("Executing query: {} sort: {} fields: {} in collection: {}", serializeToJsonSafely(queryObject),
  444. sortObject, fieldsObject, collectionName);
  445. }
  446. this.executeQueryInternal(new FindCallback(queryObject, fieldsObject, null),
  447. preparer != null ? preparer : CursorPreparer.NO_OP_PREPARER, documentCallbackHandler, collectionName);
  448. }
  449. /*
  450. * (non-Javadoc)
  451. * @see org.springframework.data.mongodb.core.MongoOperations#execute(org.springframework.data.mongodb.core.DbCallback)
  452. */
  453. public <T> T execute(DbCallback<T> action) {
  454. Assert.notNull(action, "DbCallback must not be null!");
  455. try {
  456. MongoDatabase db = prepareDatabase(this.doGetDatabase());
  457. return action.doInDB(db);
  458. } catch (RuntimeException e) {
  459. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  460. }
  461. }
  462. /*
  463. * (non-Javadoc)
  464. * @see org.springframework.data.mongodb.core.MongoOperations#execute(java.lang.Class, org.springframework.data.mongodb.core.DbCallback)
  465. */
  466. public <T> T execute(Class<?> entityClass, CollectionCallback<T> callback) {
  467. Assert.notNull(entityClass, "EntityClass must not be null!");
  468. return execute(getCollectionName(entityClass), callback);
  469. }
  470. /*
  471. * (non-Javadoc)
  472. * @see org.springframework.data.mongodb.core.MongoOperations#execute(java.lang.String, org.springframework.data.mongodb.core.DbCallback)
  473. */
  474. public <T> T execute(String collectionName, CollectionCallback<T> callback) {
  475. Assert.notNull(collectionName, "CollectionName must not be null!");
  476. Assert.notNull(callback, "CollectionCallback must not be null!");
  477. try {
  478. MongoCollection<Document> collection = getAndPrepareCollection(doGetDatabase(), collectionName);
  479. return callback.doInCollection(collection);
  480. } catch (RuntimeException e) {
  481. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  482. }
  483. }
  484. /*
  485. * (non-Javadoc)
  486. * @see org.springframework.data.mongodb.core.MongoOperations#withSession(com.mongodb.ClientSessionOptions)
  487. */
  488. @Override
  489. public SessionScoped withSession(ClientSessionOptions options) {
  490. Assert.notNull(options, "ClientSessionOptions must not be null!");
  491. return withSession(() -> mongoDbFactory.getSession(options));
  492. }
  493. /*
  494. * (non-Javadoc)
  495. * @see org.springframework.data.mongodb.core.MongoOperations#withSession(com.mongodb.session.ClientSession)
  496. */
  497. @Override
  498. public MongoTemplate withSession(ClientSession session) {
  499. Assert.notNull(session, "ClientSession must not be null!");
  500. return new SessionBoundMongoTemplate(session, MongoTemplate.this);
  501. }
  502. /**
  503. * Define if {@link MongoTemplate} should participate in transactions. Default is set to
  504. * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION}.<br />
  505. * <strong>NOTE:</strong> MongoDB transactions require at least MongoDB 4.0.
  506. *
  507. * @since 2.1
  508. */
  509. public void setSessionSynchronization(SessionSynchronization sessionSynchronization) {
  510. this.sessionSynchronization = sessionSynchronization;
  511. }
  512. /*
  513. * (non-Javadoc)
  514. * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class)
  515. */
  516. public <T> MongoCollection<Document> createCollection(Class<T> entityClass) {
  517. return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
  518. }
  519. /*
  520. * (non-Javadoc)
  521. * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class, org.springframework.data.mongodb.core.CollectionOptions)
  522. */
  523. public <T> MongoCollection<Document> createCollection(Class<T> entityClass,
  524. @Nullable CollectionOptions collectionOptions) {
  525. Assert.notNull(entityClass, "EntityClass must not be null!");
  526. CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
  527. options = Optionals
  528. .firstNonEmpty(() -> Optional.ofNullable(collectionOptions).flatMap(CollectionOptions::getCollation),
  529. () -> operations.forType(entityClass).getCollation()) //
  530. .map(options::collation).orElse(options);
  531. return doCreateCollection(getCollectionName(entityClass), convertToDocument(options, entityClass));
  532. }
  533. /*
  534. * (non-Javadoc)
  535. * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.String)
  536. */
  537. public MongoCollection<Document> createCollection(String collectionName) {
  538. Assert.notNull(collectionName, "CollectionName must not be null!");
  539. return doCreateCollection(collectionName, new Document());
  540. }
  541. /*
  542. * (non-Javadoc)
  543. * @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.String, org.springframework.data.mongodb.core.CollectionOptions)
  544. */
  545. public MongoCollection<Document> createCollection(String collectionName,
  546. @Nullable CollectionOptions collectionOptions) {
  547. Assert.notNull(collectionName, "CollectionName must not be null!");
  548. return doCreateCollection(collectionName, convertToDocument(collectionOptions));
  549. }
  550. /*
  551. * (non-Javadoc)
  552. * @see org.springframework.data.mongodb.core.MongoOperations#getCollection(java.lang.String)
  553. */
  554. @SuppressWarnings("ConstantConditions")
  555. public MongoCollection<Document> getCollection(String collectionName) {
  556. Assert.notNull(collectionName, "CollectionName must not be null!");
  557. return execute(db -> db.getCollection(collectionName, Document.class));
  558. }
  559. /*
  560. * (non-Javadoc)
  561. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollection(java.lang.Class)
  562. */
  563. public <T> boolean collectionExists(Class<T> entityClass) {
  564. return collectionExists(getCollectionName(entityClass));
  565. }
  566. /*
  567. * (non-Javadoc)
  568. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollection(java.lang.String)
  569. */
  570. @SuppressWarnings("ConstantConditions")
  571. public boolean collectionExists(String collectionName) {
  572. Assert.notNull(collectionName, "CollectionName must not be null!");
  573. return execute(db -> {
  574. for (String name : db.listCollectionNames()) {
  575. if (name.equals(collectionName)) {
  576. return true;
  577. }
  578. }
  579. return false;
  580. });
  581. }
  582. /*
  583. * (non-Javadoc)
  584. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#dropCollection(java.lang.Class)
  585. */
  586. public <T> void dropCollection(Class<T> entityClass) {
  587. dropCollection(getCollectionName(entityClass));
  588. }
  589. /*
  590. * (non-Javadoc)
  591. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#dropCollection(java.lang.String)
  592. */
  593. public void dropCollection(String collectionName) {
  594. Assert.notNull(collectionName, "CollectionName must not be null!");
  595. execute(collectionName, (CollectionCallback<Void>) collection -> {
  596. collection.drop();
  597. if (LOGGER.isDebugEnabled()) {
  598. LOGGER.debug("Dropped collection [{}]",
  599. collection.getNamespace() != null ? collection.getNamespace().getCollectionName() : collectionName);
  600. }
  601. return null;
  602. });
  603. }
  604. @Override
  605. public IndexOperations indexOps(String collectionName) {
  606. return indexOps(collectionName, null);
  607. }
  608. /*
  609. * (non-Javadoc)
  610. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.String)
  611. */
  612. public IndexOperations indexOps(String collectionName, @Nullable Class<?> type) {
  613. return new DefaultIndexOperations(this, collectionName, type);
  614. }
  615. /*
  616. * (non-Javadoc)
  617. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#indexOps(java.lang.Class)
  618. */
  619. public IndexOperations indexOps(Class<?> entityClass) {
  620. return indexOps(getCollectionName(entityClass), entityClass);
  621. }
  622. /*
  623. * (non-Javadoc)
  624. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#bulkOps(org.springframework.data.mongodb.core.BulkMode, java.lang.String)
  625. */
  626. public BulkOperations bulkOps(BulkMode bulkMode, String collectionName) {
  627. return bulkOps(bulkMode, null, collectionName);
  628. }
  629. /*
  630. * (non-Javadoc)
  631. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#bulkOps(org.springframework.data.mongodb.core.BulkMode, java.lang.Class)
  632. */
  633. public BulkOperations bulkOps(BulkMode bulkMode, Class<?> entityClass) {
  634. return bulkOps(bulkMode, entityClass, getCollectionName(entityClass));
  635. }
  636. /*
  637. * (non-Javadoc)
  638. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#bulkOps(org.springframework.data.mongodb.core.BulkMode, java.lang.Class, java.lang.String)
  639. */
  640. public BulkOperations bulkOps(BulkMode mode, @Nullable Class<?> entityType, String collectionName) {
  641. Assert.notNull(mode, "BulkMode must not be null!");
  642. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  643. DefaultBulkOperations operations = new DefaultBulkOperations(this, collectionName,
  644. new BulkOperationContext(mode, Optional.ofNullable(getPersistentEntity(entityType)), queryMapper, updateMapper,
  645. eventPublisher, entityCallbacks));
  646. operations.setDefaultWriteConcern(writeConcern);
  647. return operations;
  648. }
  649. /*
  650. * (non-Javadoc)
  651. * @see org.springframework.data.mongodb.core.MongoOperations#scriptOps()
  652. */
  653. @Override
  654. public ScriptOperations scriptOps() {
  655. return new DefaultScriptOperations(this);
  656. }
  657. // Find methods that take a Query to express the query and that return a single object.
  658. @Nullable
  659. @Override
  660. public <T> T findOne(Query query, Class<T> entityClass) {
  661. return findOne(query, entityClass, getCollectionName(entityClass));
  662. }
  663. @Nullable
  664. @Override
  665. public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
  666. Assert.notNull(query, "Query must not be null!");
  667. Assert.notNull(entityClass, "EntityClass must not be null!");
  668. Assert.notNull(collectionName, "CollectionName must not be null!");
  669. if (ObjectUtils.isEmpty(query.getSortObject())) {
  670. return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(),
  671. new QueryCursorPreparer(query, entityClass), entityClass);
  672. } else {
  673. query.limit(1);
  674. List<T> results = find(query, entityClass, collectionName);
  675. return results.isEmpty() ? null : results.get(0);
  676. }
  677. }
  678. @Override
  679. public boolean exists(Query query, Class<?> entityClass) {
  680. return exists(query, entityClass, getCollectionName(entityClass));
  681. }
  682. @Override
  683. public boolean exists(Query query, String collectionName) {
  684. return exists(query, null, collectionName);
  685. }
  686. @Override
  687. @SuppressWarnings("ConstantConditions")
  688. public boolean exists(Query query, @Nullable Class<?> entityClass, String collectionName) {
  689. if (query == null) {
  690. throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null");
  691. }
  692. Assert.notNull(collectionName, "CollectionName must not be null!");
  693. QueryContext queryContext = queryOperations.createQueryContext(query);
  694. Document mappedQuery = queryContext.getMappedQuery(entityClass, this::getPersistentEntity);
  695. return execute(collectionName,
  696. new ExistsCallback(mappedQuery, queryContext.getCollation(entityClass).orElse(null)));
  697. }
  698. // Find methods that take a Query to express the query and that return a List of objects.
  699. /*
  700. * (non-Javadoc)
  701. * @see org.springframework.data.mongodb.core.MongoOperations#findOne(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
  702. */
  703. @Override
  704. public <T> List<T> find(Query query, Class<T> entityClass) {
  705. return find(query, entityClass, getCollectionName(entityClass));
  706. }
  707. /*
  708. * (non-Javadoc)
  709. * @see org.springframework.data.mongodb.core.MongoOperations#findOne(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
  710. */
  711. @Override
  712. public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
  713. Assert.notNull(query, "Query must not be null!");
  714. Assert.notNull(collectionName, "CollectionName must not be null!");
  715. Assert.notNull(entityClass, "EntityClass must not be null!");
  716. return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
  717. new QueryCursorPreparer(query, entityClass));
  718. }
  719. @Nullable
  720. @Override
  721. public <T> T findById(Object id, Class<T> entityClass) {
  722. return findById(id, entityClass, getCollectionName(entityClass));
  723. }
  724. @Nullable
  725. @Override
  726. public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
  727. Assert.notNull(id, "Id must not be null!");
  728. Assert.notNull(entityClass, "EntityClass must not be null!");
  729. Assert.notNull(collectionName, "CollectionName must not be null!");
  730. String idKey = operations.getIdPropertyName(entityClass);
  731. return doFindOne(collectionName, new Document(idKey, id), new Document(), entityClass);
  732. }
  733. /*
  734. * (non-Javadoc)
  735. * @see org.springframework.data.mongodb.core.MongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.Class, java.lang.Class)
  736. */
  737. @Override
  738. public <T> List<T> findDistinct(Query query, String field, Class<?> entityClass, Class<T> resultClass) {
  739. return findDistinct(query, field, getCollectionName(entityClass), entityClass, resultClass);
  740. }
  741. /*
  742. * (non-Javadoc)
  743. * @see org.springframework.data.mongodb.core.MongoOperations#findDistinct(org.springframework.data.mongodb.core.query.Query, java.lang.String, java.lang.String, java.lang.Class, java.lang.Class)
  744. */
  745. @Override
  746. @SuppressWarnings("unchecked")
  747. public <T> List<T> findDistinct(Query query, String field, String collectionName, Class<?> entityClass,
  748. Class<T> resultClass) {
  749. Assert.notNull(query, "Query must not be null!");
  750. Assert.notNull(field, "Field must not be null!");
  751. Assert.notNull(collectionName, "CollectionName must not be null!");
  752. Assert.notNull(entityClass, "EntityClass must not be null!");
  753. Assert.notNull(resultClass, "ResultClass must not be null!");
  754. MongoPersistentEntity<?> entity = entityClass != Object.class ? getPersistentEntity(entityClass) : null;
  755. DistinctQueryContext distinctQueryContext = queryOperations.distinctQueryContext(query, field);
  756. Document mappedQuery = distinctQueryContext.getMappedQuery(entity);
  757. String mappedFieldName = distinctQueryContext.getMappedFieldName(entity);
  758. Class<T> mongoDriverCompatibleType = distinctQueryContext.getDriverCompatibleClass(resultClass);
  759. MongoIterable<?> result = execute(collectionName, (collection) -> {
  760. if (LOGGER.isDebugEnabled()) {
  761. LOGGER.debug("Executing findDistinct using query {} for field: {} in collection: {}",
  762. serializeToJsonSafely(mappedQuery), field, collectionName);
  763. }
  764. QueryCursorPreparer preparer = new QueryCursorPreparer(query, entityClass);
  765. if (preparer.hasReadPreference()) {
  766. collection = collection.withReadPreference(preparer.getReadPreference());
  767. }
  768. DistinctIterable<T> iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
  769. distinctQueryContext.applyCollation(entityClass, iterable::collation);
  770. return iterable;
  771. });
  772. if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
  773. MongoConverter converter = getConverter();
  774. DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
  775. result = result.map((source) -> converter.mapValueToTargetType(source,
  776. distinctQueryContext.getMostSpecificConversionTargetType(resultClass, entityClass), dbRefResolver));
  777. }
  778. try {
  779. return (List<T>) result.into(new ArrayList<>());
  780. } catch (RuntimeException e) {
  781. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  782. }
  783. }
  784. @Override
  785. public <T> GeoResults<T> geoNear(NearQuery near, Class<T> entityClass) {
  786. return geoNear(near, entityClass, getCollectionName(entityClass));
  787. }
  788. @Override
  789. public <T> GeoResults<T> geoNear(NearQuery near, Class<T> domainType, String collectionName) {
  790. return geoNear(near, domainType, collectionName, domainType);
  791. }
  792. public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String collectionName, Class<T> returnType) {
  793. if (near == null) {
  794. throw new InvalidDataAccessApiUsageException("NearQuery must not be null!");
  795. }
  796. if (domainType == null) {
  797. throw new InvalidDataAccessApiUsageException("Entity class must not be null!");
  798. }
  799. Assert.notNull(collectionName, "CollectionName must not be null!");
  800. Assert.notNull(returnType, "ReturnType must not be null!");
  801. String collection = StringUtils.hasText(collectionName) ? collectionName : getCollectionName(domainType);
  802. String distanceField = operations.nearQueryDistanceFieldName(domainType);
  803. Aggregation $geoNear = TypedAggregation.newAggregation(domainType, Aggregation.geoNear(near, distanceField))
  804. .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
  805. AggregationResults<Document> results = aggregate($geoNear, collection, Document.class);
  806. DocumentCallback<GeoResult<T>> callback = new GeoNearResultDocumentCallback<>(distanceField,
  807. new ProjectingReadCallback<>(mongoConverter, domainType, returnType, collection), near.getMetric());
  808. List<GeoResult<T>> result = new ArrayList<>();
  809. BigDecimal aggregate = BigDecimal.ZERO;
  810. for (Document element : results) {
  811. GeoResult<T> geoResult = callback.doWith(element);
  812. aggregate = aggregate.add(BigDecimal.valueOf(geoResult.getDistance().getValue()));
  813. result.add(geoResult);
  814. }
  815. Distance avgDistance = new Distance(
  816. result.size() == 0 ? 0 : aggregate.divide(new BigDecimal(result.size()), RoundingMode.HALF_UP).doubleValue(),
  817. near.getMetric());
  818. return new GeoResults<>(result, avgDistance);
  819. }
  820. @Nullable
  821. @Override
  822. public <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass) {
  823. return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
  824. }
  825. @Nullable
  826. @Override
  827. public <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName) {
  828. return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName);
  829. }
  830. @Nullable
  831. @Override
  832. public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass) {
  833. return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
  834. }
  835. @Nullable
  836. @Override
  837. public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass,
  838. String collectionName) {
  839. Assert.notNull(query, "Query must not be null!");
  840. Assert.notNull(update, "Update must not be null!");
  841. Assert.notNull(options, "Options must not be null!");
  842. Assert.notNull(entityClass, "EntityClass must not be null!");
  843. Assert.notNull(collectionName, "CollectionName must not be null!");
  844. FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
  845. Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
  846. throw new IllegalArgumentException(
  847. "Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
  848. });
  849. if (!options.getCollation().isPresent()) {
  850. operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation);
  851. }
  852. return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
  853. getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
  854. }
  855. /*
  856. * (non-Javadoc)
  857. * @see org.springframework.data.mongodb.core.MongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions, java.lang.Class, java.lang.String, java.lang.Class)
  858. */
  859. @Override
  860. public <S, T> T findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
  861. String collectionName, Class<T> resultType) {
  862. Assert.notNull(query, "Query must not be null!");
  863. Assert.notNull(replacement, "Replacement must not be null!");
  864. Assert.notNull(options, "Options must not be null! Use FindAndReplaceOptions#empty() instead.");
  865. Assert.notNull(entityType, "EntityType must not be null!");
  866. Assert.notNull(collectionName, "CollectionName must not be null!");
  867. Assert.notNull(resultType, "ResultType must not be null! Use Object.class instead.");
  868. Assert.isTrue(query.getLimit() <= 1, "Query must not define a limit other than 1 ore none!");
  869. Assert.isTrue(query.getSkip() <= 0, "Query must not define skip.");
  870. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
  871. QueryContext queryContext = queryOperations.createQueryContext(query);
  872. Document mappedQuery = queryContext.getMappedQuery(entity);
  873. Document mappedFields = queryContext.getMappedFields(entity, resultType, projectionFactory);
  874. Document mappedSort = queryContext.getMappedSort(entity);
  875. replacement = maybeCallBeforeConvert(replacement, collectionName);
  876. Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
  877. maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName));
  878. maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
  879. T saved = doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
  880. queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, resultType);
  881. if (saved != null) {
  882. maybeEmitEvent(new AfterSaveEvent<>(saved, mappedReplacement, collectionName));
  883. return maybeCallAfterSave(saved, mappedReplacement, collectionName);
  884. }
  885. return saved;
  886. }
  887. // Find methods that take a Query to express the query and that return a single object that is also removed from the
  888. // collection in the database.
  889. @Nullable
  890. @Override
  891. public <T> T findAndRemove(Query query, Class<T> entityClass) {
  892. return findAndRemove(query, entityClass, getCollectionName(entityClass));
  893. }
  894. @Nullable
  895. @Override
  896. public <T> T findAndRemove(Query query, Class<T> entityClass, String collectionName) {
  897. Assert.notNull(query, "Query must not be null!");
  898. Assert.notNull(entityClass, "EntityClass must not be null!");
  899. Assert.notNull(collectionName, "CollectionName must not be null!");
  900. return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
  901. getMappedSortObject(query, entityClass), operations.forType(entityClass).getCollation(query).orElse(null),
  902. entityClass);
  903. }
  904. @Override
  905. public long count(Query query, Class<?> entityClass) {
  906. Assert.notNull(entityClass, "Entity class must not be null!");
  907. return count(query, entityClass, getCollectionName(entityClass));
  908. }
  909. @Override
  910. public long count(Query query, String collectionName) {
  911. return count(query, null, collectionName);
  912. }
  913. /*
  914. * (non-Javadoc)
  915. * @see org.springframework.data.mongodb.core.MongoOperations#count(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
  916. */
  917. public long count(Query query, @Nullable Class<?> entityClass, String collectionName) {
  918. Assert.notNull(query, "Query must not be null!");
  919. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  920. CountContext countContext = queryOperations.countQueryContext(query);
  921. CountOptions options = countContext.getCountOptions(entityClass);
  922. Document mappedQuery = countContext.getMappedQuery(entityClass, mappingContext::getPersistentEntity);
  923. return doCount(collectionName, mappedQuery, options);
  924. }
  925. @SuppressWarnings("ConstantConditions")
  926. protected long doCount(String collectionName, Document filter, CountOptions options) {
  927. if (LOGGER.isDebugEnabled()) {
  928. LOGGER.debug("Executing count: {} in collection: {}", serializeToJsonSafely(filter), collectionName);
  929. }
  930. return execute(collectionName,
  931. collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
  932. }
  933. /*
  934. * (non-Javadoc)
  935. * @see org.springframework.data.mongodb.core.MongoOperations#estimatedCount(java.lang.String)
  936. */
  937. @Override
  938. public long estimatedCount(String collectionName) {
  939. return doEstimatedCount(collectionName, new EstimatedDocumentCountOptions());
  940. }
  941. protected long doEstimatedCount(String collectionName, EstimatedDocumentCountOptions options) {
  942. return execute(collectionName, collection -> collection.estimatedDocumentCount(options));
  943. }
  944. /*
  945. * (non-Javadoc)
  946. * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object)
  947. */
  948. @Override
  949. public <T> T insert(T objectToSave) {
  950. Assert.notNull(objectToSave, "ObjectToSave must not be null!");
  951. ensureNotIterable(objectToSave);
  952. return insert(objectToSave, getCollectionName(ClassUtils.getUserClass(objectToSave)));
  953. }
  954. /*
  955. * (non-Javadoc)
  956. * @see org.springframework.data.mongodb.core.MongoOperations#insert(java.lang.Object, java.lang.String)
  957. */
  958. @Override
  959. @SuppressWarnings("unchecked")
  960. public <T> T insert(T objectToSave, String collectionName) {
  961. Assert.notNull(objectToSave, "ObjectToSave must not be null!");
  962. Assert.notNull(collectionName, "CollectionName must not be null!");
  963. ensureNotIterable(objectToSave);
  964. return (T) doInsert(collectionName, objectToSave, this.mongoConverter);
  965. }
  966. /**
  967. * Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or
  968. * {@link Iterator}.
  969. *
  970. * @param source can be {@literal null}.
  971. * @deprecated since 3.2. Call {@link #ensureNotCollectionLike(Object)} instead.
  972. */
  973. protected void ensureNotIterable(@Nullable Object source) {
  974. ensureNotCollectionLike(source);
  975. }
  976. /**
  977. * Ensure the given {@literal source} is not an {@link java.lang.reflect.Array}, {@link Collection} or
  978. * {@link Iterator}.
  979. *
  980. * @param source can be {@literal null}.
  981. * @since 3.2.
  982. */
  983. protected void ensureNotCollectionLike(@Nullable Object source) {
  984. if (EntityOperations.isCollectionLike(source)) {
  985. throw new IllegalArgumentException("Cannot use a collection here.");
  986. }
  987. }
  988. /**
  989. * Prepare the collection before any processing is done using it. This allows a convenient way to apply settings like
  990. * withCodecRegistry() etc. Can be overridden in sub-classes.
  991. *
  992. * @param collection
  993. */
  994. protected MongoCollection<Document> prepareCollection(MongoCollection<Document> collection) {
  995. if (this.readPreference != null) {
  996. collection = collection.withReadPreference(readPreference);
  997. }
  998. return collection;
  999. }
  1000. /**
  1001. * Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply custom
  1002. * settings in sub-classes. <br />
  1003. * In case of using MongoDB Java driver version 3 the returned {@link WriteConcern} will be defaulted to
  1004. * {@link WriteConcern#ACKNOWLEDGED} when {@link WriteResultChecking} is set to {@link WriteResultChecking#EXCEPTION}.
  1005. *
  1006. * @param mongoAction any MongoAction already configured or null
  1007. * @return The prepared WriteConcern or null
  1008. */
  1009. @Nullable
  1010. protected WriteConcern prepareWriteConcern(MongoAction mongoAction) {
  1011. WriteConcern wc = writeConcernResolver.resolve(mongoAction);
  1012. return potentiallyForceAcknowledgedWrite(wc);
  1013. }
  1014. @Nullable
  1015. private WriteConcern potentiallyForceAcknowledgedWrite(@Nullable WriteConcern wc) {
  1016. if (ObjectUtils.nullSafeEquals(WriteResultChecking.EXCEPTION, writeResultChecking)) {
  1017. if (wc == null || wc.getWObject() == null
  1018. || (wc.getWObject() instanceof Number && ((Number) wc.getWObject()).intValue() < 1)) {
  1019. return WriteConcern.ACKNOWLEDGED;
  1020. }
  1021. }
  1022. return wc;
  1023. }
  1024. protected <T> T doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
  1025. BeforeConvertEvent<T> event = new BeforeConvertEvent<>(objectToSave, collectionName);
  1026. T toConvert = maybeEmitEvent(event).getSource();
  1027. toConvert = maybeCallBeforeConvert(toConvert, collectionName);
  1028. AdaptibleEntity<T> entity = operations.forEntity(toConvert, mongoConverter.getConversionService());
  1029. entity.assertUpdateableIdIfNotSet();
  1030. T initialized = entity.initializeVersionProperty();
  1031. Document dbDoc = entity.toMappedDocument(writer).getDocument();
  1032. maybeEmitEvent(new BeforeSaveEvent<>(initialized, dbDoc, collectionName));
  1033. initialized = maybeCallBeforeSave(initialized, dbDoc, collectionName);
  1034. Object id = insertDocument(collectionName, dbDoc, initialized.getClass());
  1035. T saved = populateIdIfNecessary(initialized, id);
  1036. maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
  1037. return maybeCallAfterSave(saved, dbDoc, collectionName);
  1038. }
  1039. @Override
  1040. @SuppressWarnings("unchecked")
  1041. public <T> Collection<T> insert(Collection<? extends T> batchToSave, Class<?> entityClass) {
  1042. Assert.notNull(batchToSave, "BatchToSave must not be null!");
  1043. return (Collection<T>) doInsertBatch(getCollectionName(entityClass), batchToSave, this.mongoConverter);
  1044. }
  1045. @Override
  1046. @SuppressWarnings("unchecked")
  1047. public <T> Collection<T> insert(Collection<? extends T> batchToSave, String collectionName) {
  1048. Assert.notNull(batchToSave, "BatchToSave must not be null!");
  1049. Assert.notNull(collectionName, "CollectionName must not be null!");
  1050. return (Collection<T>) doInsertBatch(collectionName, batchToSave, this.mongoConverter);
  1051. }
  1052. @Override
  1053. @SuppressWarnings("unchecked")
  1054. public <T> Collection<T> insertAll(Collection<? extends T> objectsToSave) {
  1055. Assert.notNull(objectsToSave, "ObjectsToSave must not be null!");
  1056. return (Collection<T>) doInsertAll(objectsToSave, this.mongoConverter);
  1057. }
  1058. @SuppressWarnings("unchecked")
  1059. protected <T> Collection<T> doInsertAll(Collection<? extends T> listToSave, MongoWriter<T> writer) {
  1060. Map<String, List<T>> elementsByCollection = new HashMap<>();
  1061. List<T> savedObjects = new ArrayList<>(listToSave.size());
  1062. for (T element : listToSave) {
  1063. if (element == null) {
  1064. continue;
  1065. }
  1066. String collection = getCollectionName(ClassUtils.getUserClass(element));
  1067. List<T> collectionElements = elementsByCollection.get(collection);
  1068. if (null == collectionElements) {
  1069. collectionElements = new ArrayList<>();
  1070. elementsByCollection.put(collection, collectionElements);
  1071. }
  1072. collectionElements.add(element);
  1073. }
  1074. for (Map.Entry<String, List<T>> entry : elementsByCollection.entrySet()) {
  1075. savedObjects.addAll((Collection<T>) doInsertBatch(entry.getKey(), entry.getValue(), this.mongoConverter));
  1076. }
  1077. return savedObjects;
  1078. }
  1079. protected <T> Collection<T> doInsertBatch(String collectionName, Collection<? extends T> batchToSave,
  1080. MongoWriter<T> writer) {
  1081. Assert.notNull(writer, "MongoWriter must not be null!");
  1082. List<Document> documentList = new ArrayList<>();
  1083. List<T> initializedBatchToSave = new ArrayList<>(batchToSave.size());
  1084. for (T uninitialized : batchToSave) {
  1085. BeforeConvertEvent<T> event = new BeforeConvertEvent<>(uninitialized, collectionName);
  1086. T toConvert = maybeEmitEvent(event).getSource();
  1087. toConvert = maybeCallBeforeConvert(toConvert, collectionName);
  1088. AdaptibleEntity<T> entity = operations.forEntity(toConvert, mongoConverter.getConversionService());
  1089. entity.assertUpdateableIdIfNotSet();
  1090. T initialized = entity.initializeVersionProperty();
  1091. Document document = entity.toMappedDocument(writer).getDocument();
  1092. maybeEmitEvent(new BeforeSaveEvent<>(initialized, document, collectionName));
  1093. initialized = maybeCallBeforeSave(initialized, document, collectionName);
  1094. documentList.add(document);
  1095. initializedBatchToSave.add(initialized);
  1096. }
  1097. List<Object> ids = insertDocumentList(collectionName, documentList);
  1098. List<T> savedObjects = new ArrayList<>(documentList.size());
  1099. int i = 0;
  1100. for (T obj : initializedBatchToSave) {
  1101. if (i < ids.size()) {
  1102. T saved = populateIdIfNecessary(obj, ids.get(i));
  1103. Document doc = documentList.get(i);
  1104. maybeEmitEvent(new AfterSaveEvent<>(saved, doc, collectionName));
  1105. savedObjects.add(maybeCallAfterSave(saved, doc, collectionName));
  1106. } else {
  1107. savedObjects.add(obj);
  1108. }
  1109. i++;
  1110. }
  1111. return savedObjects;
  1112. }
  1113. @Override
  1114. public <T> T save(T objectToSave) {
  1115. Assert.notNull(objectToSave, "Object to save must not be null!");
  1116. return save(objectToSave, getCollectionName(ClassUtils.getUserClass(objectToSave)));
  1117. }
  1118. @Override
  1119. @SuppressWarnings("unchecked")
  1120. public <T> T save(T objectToSave, String collectionName) {
  1121. Assert.notNull(objectToSave, "Object to save must not be null!");
  1122. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  1123. ensureNotCollectionLike(objectToSave);
  1124. AdaptibleEntity<T> source = operations.forEntity(objectToSave, mongoConverter.getConversionService());
  1125. return source.isVersionedEntity() //
  1126. ? doSaveVersioned(source, collectionName) //
  1127. : (T) doSave(collectionName, objectToSave, this.mongoConverter);
  1128. }
  1129. @SuppressWarnings("unchecked")
  1130. private <T> T doSaveVersioned(AdaptibleEntity<T> source, String collectionName) {
  1131. if (source.isNew()) {
  1132. return (T) doInsert(collectionName, source.getBean(), this.mongoConverter);
  1133. }
  1134. // Create query for entity with the id and old version
  1135. Query query = source.getQueryForVersion();
  1136. // Bump version number
  1137. T toSave = source.incrementVersion();
  1138. toSave = maybeEmitEvent(new BeforeConvertEvent<T>(toSave, collectionName)).getSource();
  1139. toSave = maybeCallBeforeConvert(toSave, collectionName);
  1140. if (source.getBean() != toSave) {
  1141. source = operations.forEntity(toSave, mongoConverter.getConversionService());
  1142. }
  1143. source.assertUpdateableIdIfNotSet();
  1144. MappedDocument mapped = source.toMappedDocument(mongoConverter);
  1145. maybeEmitEvent(new BeforeSaveEvent<>(toSave, mapped.getDocument(), collectionName));
  1146. toSave = maybeCallBeforeSave(toSave, mapped.getDocument(), collectionName);
  1147. UpdateDefinition update = mapped.updateWithoutId();
  1148. UpdateResult result = doUpdate(collectionName, query, update, toSave.getClass(), false, false);
  1149. if (result.getModifiedCount() == 0) {
  1150. throw new OptimisticLockingFailureException(
  1151. String.format("Cannot save entity %s with version %s to collection %s. Has it been modified meanwhile?",
  1152. source.getId(), source.getVersion(), collectionName));
  1153. }
  1154. maybeEmitEvent(new AfterSaveEvent<>(toSave, mapped.getDocument(), collectionName));
  1155. return maybeCallAfterSave(toSave, mapped.getDocument(), collectionName);
  1156. }
  1157. protected <T> T doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
  1158. objectToSave = maybeEmitEvent(new BeforeConvertEvent<>(objectToSave, collectionName)).getSource();
  1159. objectToSave = maybeCallBeforeConvert(objectToSave, collectionName);
  1160. AdaptibleEntity<T> entity = operations.forEntity(objectToSave, mongoConverter.getConversionService());
  1161. entity.assertUpdateableIdIfNotSet();
  1162. MappedDocument mapped = entity.toMappedDocument(writer);
  1163. Document dbDoc = mapped.getDocument();
  1164. maybeEmitEvent(new BeforeSaveEvent<>(objectToSave, dbDoc, collectionName));
  1165. objectToSave = maybeCallBeforeSave(objectToSave, dbDoc, collectionName);
  1166. Object id = saveDocument(collectionName, dbDoc, objectToSave.getClass());
  1167. T saved = populateIdIfNecessary(objectToSave, id);
  1168. maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName));
  1169. return maybeCallAfterSave(saved, dbDoc, collectionName);
  1170. }
  1171. @SuppressWarnings("ConstantConditions")
  1172. protected Object insertDocument(String collectionName, Document document, Class<?> entityClass) {
  1173. if (LOGGER.isDebugEnabled()) {
  1174. LOGGER.debug("Inserting Document containing fields: {} in collection: {}", document.keySet(), collectionName);
  1175. }
  1176. return execute(collectionName, collection -> {
  1177. MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass,
  1178. document, null);
  1179. WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
  1180. if (writeConcernToUse == null) {
  1181. collection.insertOne(document);
  1182. } else {
  1183. collection.withWriteConcern(writeConcernToUse).insertOne(document);
  1184. }
  1185. return operations.forEntity(document).getId();
  1186. });
  1187. }
  1188. protected List<Object> insertDocumentList(String collectionName, List<Document> documents) {
  1189. if (documents.isEmpty()) {
  1190. return Collections.emptyList();
  1191. }
  1192. if (LOGGER.isDebugEnabled()) {
  1193. LOGGER.debug("Inserting list of Documents containing {} items", documents.size());
  1194. }
  1195. execute(collectionName, collection -> {
  1196. MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null,
  1197. null, null);
  1198. WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
  1199. if (writeConcernToUse == null) {
  1200. collection.insertMany(documents);
  1201. } else {
  1202. collection.withWriteConcern(writeConcernToUse).insertMany(documents);
  1203. }
  1204. return null;
  1205. });
  1206. return MappedDocument.toIds(documents);
  1207. }
  1208. protected Object saveDocument(String collectionName, Document dbDoc, Class<?> entityClass) {
  1209. if (LOGGER.isDebugEnabled()) {
  1210. LOGGER.debug("Saving Document containing fields: {}", dbDoc.keySet());
  1211. }
  1212. return execute(collectionName, collection -> {
  1213. MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass,
  1214. dbDoc, null);
  1215. WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
  1216. MappedDocument mapped = MappedDocument.of(dbDoc);
  1217. MongoCollection<Document> collectionToUse = writeConcernToUse == null //
  1218. ? collection //
  1219. : collection.withWriteConcern(writeConcernToUse);
  1220. if (!mapped.hasId()) {
  1221. collectionToUse.insertOne(dbDoc);
  1222. } else {
  1223. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
  1224. UpdateContext updateContext = queryOperations.replaceSingleContext(mapped, true);
  1225. Document replacement = updateContext.getMappedUpdate(entity);
  1226. Document filter = updateContext.getMappedQuery(entity);
  1227. if (updateContext.requiresShardKey(filter, entity)) {
  1228. if (entity.getShardKey().isImmutable()) {
  1229. filter = updateContext.applyShardKey(entity, filter, null);
  1230. } else {
  1231. filter = updateContext.applyShardKey(entity, filter,
  1232. collection.find(filter, Document.class).projection(updateContext.getMappedShardKey(entity)).first());
  1233. }
  1234. }
  1235. collectionToUse.replaceOne(filter, replacement, new ReplaceOptions().upsert(true));
  1236. }
  1237. return mapped.getId();
  1238. });
  1239. }
  1240. @Override
  1241. public UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass) {
  1242. return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false);
  1243. }
  1244. @Override
  1245. public UpdateResult upsert(Query query, UpdateDefinition update, String collectionName) {
  1246. return doUpdate(collectionName, query, update, null, true, false);
  1247. }
  1248. @Override
  1249. public UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
  1250. Assert.notNull(entityClass, "EntityClass must not be null!");
  1251. return doUpdate(collectionName, query, update, entityClass, true, false);
  1252. }
  1253. @Override
  1254. public UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass) {
  1255. return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false);
  1256. }
  1257. @Override
  1258. public UpdateResult updateFirst(Query query, UpdateDefinition update, String collectionName) {
  1259. return doUpdate(collectionName, query, update, null, false, false);
  1260. }
  1261. @Override
  1262. public UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
  1263. Assert.notNull(entityClass, "EntityClass must not be null!");
  1264. return doUpdate(collectionName, query, update, entityClass, false, false);
  1265. }
  1266. @Override
  1267. public UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?> entityClass) {
  1268. return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
  1269. }
  1270. @Override
  1271. public UpdateResult updateMulti(Query query, UpdateDefinition update, String collectionName) {
  1272. return doUpdate(collectionName, query, update, null, false, true);
  1273. }
  1274. @Override
  1275. public UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
  1276. Assert.notNull(entityClass, "EntityClass must not be null!");
  1277. return doUpdate(collectionName, query, update, entityClass, false, true);
  1278. }
  1279. @SuppressWarnings("ConstantConditions")
  1280. protected UpdateResult doUpdate(String collectionName, Query query, UpdateDefinition update,
  1281. @Nullable Class<?> entityClass, boolean upsert, boolean multi) {
  1282. Assert.notNull(collectionName, "CollectionName must not be null!");
  1283. Assert.notNull(query, "Query must not be null!");
  1284. Assert.notNull(update, "Update must not be null!");
  1285. if (query.isSorted() && LOGGER.isWarnEnabled()) {
  1286. LOGGER.warn("{} does not support sort ('{}'). Please use findAndModify() instead.",
  1287. upsert ? "Upsert" : "UpdateFirst", serializeToJsonSafely(query.getSortObject()));
  1288. }
  1289. MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
  1290. UpdateContext updateContext = multi ? queryOperations.updateContext(update, query, upsert)
  1291. : queryOperations.updateSingleContext(update, query, upsert);
  1292. updateContext.increaseVersionForUpdateIfNecessary(entity);
  1293. Document queryObj = updateContext.getMappedQuery(entity);
  1294. UpdateOptions opts = updateContext.getUpdateOptions(entityClass);
  1295. if (updateContext.isAggregationUpdate()) {
  1296. List<Document> pipeline = updateContext.getUpdatePipeline(entityClass);
  1297. MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass,
  1298. update.getUpdateObject(), queryObj);
  1299. WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
  1300. return execute(collectionName, collection -> {
  1301. if (LOGGER.isDebugEnabled()) {
  1302. LOGGER.debug("Calling update using query: {} and update: {} in collection: {}",
  1303. serializeToJsonSafely(queryObj), serializeToJsonSafely(pipeline), collectionName);
  1304. }
  1305. collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
  1306. return multi ? collection.updateMany(queryObj, pipeline, opts) : collection.updateOne(queryObj, pipeline, opts);
  1307. });
  1308. }
  1309. Document updateObj = updateContext.getMappedUpdate(entity);
  1310. MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass,
  1311. updateObj, queryObj);
  1312. WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
  1313. return execute(collectionName, collection -> {
  1314. if (LOGGER.isDebugEnabled()) {
  1315. LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", serializeToJsonSafely(queryObj),
  1316. serializeToJsonSafely(updateObj), collectionName);
  1317. }
  1318. collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
  1319. if (!UpdateMapper.isUpdateObject(updateObj)) {
  1320. Document filter = new Document(queryObj);
  1321. if (updateContext.requiresShardKey(filter, entity)) {
  1322. if (entity.getShardKey().isImmutable()) {
  1323. filter = updateContext.applyShardKey(entity, filter, null);
  1324. } else {
  1325. filter = updateContext.applyShardKey(entity, filter,
  1326. collection.find(filter, Document.class).projection(updateContext.getMappedShardKey(entity)).first());
  1327. }
  1328. }
  1329. ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
  1330. return collection.replaceOne(filter, updateObj, replaceOptions);
  1331. } else {
  1332. return multi ? collection.updateMany(queryObj, updateObj, opts)
  1333. : collection.updateOne(queryObj, updateObj, opts);
  1334. }
  1335. });
  1336. }
  1337. @Override
  1338. public DeleteResult remove(Object object) {
  1339. Assert.notNull(object, "Object must not be null!");
  1340. return remove(object, getCollectionName(object.getClass()));
  1341. }
  1342. @Override
  1343. public DeleteResult remove(Object object, String collectionName) {
  1344. Assert.notNull(object, "Object must not be null!");
  1345. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  1346. Query query = operations.forEntity(object).getRemoveByQuery();
  1347. return doRemove(collectionName, query, object.getClass(), false);
  1348. }
  1349. @Override
  1350. public DeleteResult remove(Query query, String collectionName) {
  1351. return doRemove(collectionName, query, null, true);
  1352. }
  1353. @Override
  1354. public DeleteResult remove(Query query, Class<?> entityClass) {
  1355. return remove(query, entityClass, getCollectionName(entityClass));
  1356. }
  1357. @Override
  1358. public DeleteResult remove(Query query, Class<?> entityClass, String collectionName) {
  1359. Assert.notNull(entityClass, "EntityClass must not be null!");
  1360. return doRemove(collectionName, query, entityClass, true);
  1361. }
  1362. @SuppressWarnings("ConstantConditions")
  1363. protected <T> DeleteResult doRemove(String collectionName, Query query, @Nullable Class<T> entityClass,
  1364. boolean multi) {
  1365. Assert.notNull(query, "Query must not be null!");
  1366. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  1367. MongoPersistentEntity<?> entity = getPersistentEntity(entityClass);
  1368. DeleteContext deleteContext = multi ? queryOperations.deleteQueryContext(query)
  1369. : queryOperations.deleteSingleContext(query);
  1370. Document queryObject = deleteContext.getMappedQuery(entity);
  1371. DeleteOptions options = deleteContext.getDeleteOptions(entityClass);
  1372. MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, entityClass,
  1373. null, queryObject);
  1374. WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
  1375. return execute(collectionName, collection -> {
  1376. maybeEmitEvent(new BeforeDeleteEvent<>(queryObject, entityClass, collectionName));
  1377. Document removeQuery = queryObject;
  1378. if (LOGGER.isDebugEnabled()) {
  1379. LOGGER.debug("Remove using query: {} in collection: {}.",
  1380. new Object[] { serializeToJsonSafely(removeQuery), collectionName });
  1381. }
  1382. if (query.getLimit() > 0 || query.getSkip() > 0) {
  1383. MongoCursor<Document> cursor = new QueryCursorPreparer(query, entityClass)
  1384. .prepare(collection.find(removeQuery).projection(MappedDocument.getIdOnlyProjection())) //
  1385. .iterator();
  1386. Set<Object> ids = new LinkedHashSet<>();
  1387. while (cursor.hasNext()) {
  1388. ids.add(MappedDocument.of(cursor.next()).getId());
  1389. }
  1390. removeQuery = MappedDocument.getIdIn(ids);
  1391. }
  1392. MongoCollection<Document> collectionToUse = writeConcernToUse != null
  1393. ? collection.withWriteConcern(writeConcernToUse)
  1394. : collection;
  1395. DeleteResult result = multi ? collectionToUse.deleteMany(removeQuery, options)
  1396. : collectionToUse.deleteOne(removeQuery, options);
  1397. maybeEmitEvent(new AfterDeleteEvent<>(queryObject, entityClass, collectionName));
  1398. return result;
  1399. });
  1400. }
  1401. @Override
  1402. public <T> List<T> findAll(Class<T> entityClass) {
  1403. return findAll(entityClass, getCollectionName(entityClass));
  1404. }
  1405. @Override
  1406. public <T> List<T> findAll(Class<T> entityClass, String collectionName) {
  1407. return executeFindMultiInternal(
  1408. new FindCallback(new Document(), new Document(),
  1409. operations.forType(entityClass).getCollation().map(Collation::toMongoCollation).orElse(null)),
  1410. CursorPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName),
  1411. collectionName);
  1412. }
  1413. @Override
  1414. public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,
  1415. Class<T> entityClass) {
  1416. return mapReduce(new Query(), inputCollectionName, mapFunction, reduceFunction,
  1417. new MapReduceOptions().outputTypeInline(), entityClass);
  1418. }
  1419. @Override
  1420. public <T> MapReduceResults<T> mapReduce(String inputCollectionName, String mapFunction, String reduceFunction,
  1421. @Nullable MapReduceOptions mapReduceOptions, Class<T> entityClass) {
  1422. return mapReduce(new Query(), inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass);
  1423. }
  1424. @Override
  1425. public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
  1426. String reduceFunction, Class<T> entityClass) {
  1427. return mapReduce(query, inputCollectionName, mapFunction, reduceFunction, new MapReduceOptions().outputTypeInline(),
  1428. entityClass);
  1429. }
  1430. @Override
  1431. public <T> MapReduceResults<T> mapReduce(Query query, String inputCollectionName, String mapFunction,
  1432. String reduceFunction, @Nullable MapReduceOptions mapReduceOptions, Class<T> entityClass) {
  1433. return new MapReduceResults<>(
  1434. mapReduce(query, entityClass, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass),
  1435. new Document());
  1436. }
  1437. /**
  1438. * @param query
  1439. * @param domainType
  1440. * @param inputCollectionName
  1441. * @param mapFunction
  1442. * @param reduceFunction
  1443. * @param mapReduceOptions
  1444. * @param resultType
  1445. * @return
  1446. * @since 2.1
  1447. */
  1448. public <T> List<T> mapReduce(Query query, Class<?> domainType, String inputCollectionName, String mapFunction,
  1449. String reduceFunction, @Nullable MapReduceOptions mapReduceOptions, Class<T> resultType) {
  1450. Assert.notNull(domainType, "Domain type must not be null!");
  1451. Assert.notNull(inputCollectionName, "Input collection name must not be null!");
  1452. Assert.notNull(resultType, "Result type must not be null!");
  1453. Assert.notNull(mapFunction, "Map function must not be null!");
  1454. Assert.notNull(reduceFunction, "Reduce function must not be null!");
  1455. String mapFunc = replaceWithResourceIfNecessary(mapFunction);
  1456. String reduceFunc = replaceWithResourceIfNecessary(reduceFunction);
  1457. MongoCollection<Document> inputCollection = getAndPrepareCollection(doGetDatabase(), inputCollectionName);
  1458. // MapReduceOp
  1459. MapReduceIterable<Document> mapReduce = inputCollection.mapReduce(mapFunc, reduceFunc, Document.class);
  1460. if (query.getLimit() > 0 && mapReduceOptions != null && mapReduceOptions.getLimit() == null) {
  1461. mapReduce = mapReduce.limit(query.getLimit());
  1462. }
  1463. if (query.getMeta().getMaxTimeMsec() != null) {
  1464. mapReduce = mapReduce.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
  1465. }
  1466. Document mappedSort = getMappedSortObject(query, domainType);
  1467. if (mappedSort != null && !mappedSort.isEmpty()) {
  1468. mapReduce = mapReduce.sort(getMappedSortObject(query, domainType));
  1469. }
  1470. mapReduce = mapReduce
  1471. .filter(queryMapper.getMappedObject(query.getQueryObject(), mappingContext.getPersistentEntity(domainType)));
  1472. Optional<Collation> collation = query.getCollation();
  1473. if (mapReduceOptions != null) {
  1474. Optionals.ifAllPresent(collation, mapReduceOptions.getCollation(), (l, r) -> {
  1475. throw new IllegalArgumentException(
  1476. "Both Query and MapReduceOptions define a collation. Please provide the collation only via one of the two.");
  1477. });
  1478. if (mapReduceOptions.getCollation().isPresent()) {
  1479. collation = mapReduceOptions.getCollation();
  1480. }
  1481. if (!CollectionUtils.isEmpty(mapReduceOptions.getScopeVariables())) {
  1482. mapReduce = mapReduce.scope(new Document(mapReduceOptions.getScopeVariables()));
  1483. }
  1484. if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit() > 0) {
  1485. mapReduce = mapReduce.limit(mapReduceOptions.getLimit());
  1486. }
  1487. if (mapReduceOptions.getFinalizeFunction().filter(StringUtils::hasText).isPresent()) {
  1488. mapReduce = mapReduce.finalizeFunction(mapReduceOptions.getFinalizeFunction().get());
  1489. }
  1490. if (mapReduceOptions.getJavaScriptMode() != null) {
  1491. mapReduce = mapReduce.jsMode(mapReduceOptions.getJavaScriptMode());
  1492. }
  1493. if (mapReduceOptions.getOutputSharded().isPresent()) {
  1494. mapReduce = mapReduce.sharded(mapReduceOptions.getOutputSharded().get());
  1495. }
  1496. if (StringUtils.hasText(mapReduceOptions.getOutputCollection()) && !mapReduceOptions.usesInlineOutput()) {
  1497. mapReduce = mapReduce.collectionName(mapReduceOptions.getOutputCollection())
  1498. .action(mapReduceOptions.getMapReduceAction());
  1499. if (mapReduceOptions.getOutputDatabase().isPresent()) {
  1500. mapReduce = mapReduce.databaseName(mapReduceOptions.getOutputDatabase().get());
  1501. }
  1502. }
  1503. }
  1504. if (!collation.isPresent()) {
  1505. collation = operations.forType(domainType).getCollation();
  1506. }
  1507. mapReduce = collation.map(Collation::toMongoCollation).map(mapReduce::collation).orElse(mapReduce);
  1508. List<T> mappedResults = new ArrayList<>();
  1509. DocumentCallback<T> callback = new ReadDocumentCallback<>(mongoConverter, resultType, inputCollectionName);
  1510. for (Document document : mapReduce) {
  1511. mappedResults.add(callback.doWith(document));
  1512. }
  1513. return mappedResults;
  1514. }
  1515. public <T> GroupByResults<T> group(String inputCollectionName, GroupBy groupBy, Class<T> entityClass) {
  1516. return group(null, inputCollectionName, groupBy, entityClass);
  1517. }
  1518. public <T> GroupByResults<T> group(@Nullable Criteria criteria, String inputCollectionName, GroupBy groupBy,
  1519. Class<T> entityClass) {
  1520. Document document = groupBy.getGroupByObject();
  1521. document.put("ns", inputCollectionName);
  1522. if (criteria == null) {
  1523. document.put("cond", null);
  1524. } else {
  1525. document.put("cond", queryMapper.getMappedObject(criteria.getCriteriaObject(), Optional.empty()));
  1526. }
  1527. // If initial document was a JavaScript string, potentially loaded by Spring's Resource abstraction, load it and
  1528. // convert to Document
  1529. if (document.containsKey("initial")) {
  1530. Object initialObj = document.get("initial");
  1531. if (initialObj instanceof String) {
  1532. String initialAsString = replaceWithResourceIfNecessary((String) initialObj);
  1533. document.put("initial", Document.parse(initialAsString));
  1534. }
  1535. }
  1536. if (document.containsKey("$reduce")) {
  1537. document.put("$reduce", replaceWithResourceIfNecessary(ObjectUtils.nullSafeToString(document.get("$reduce"))));
  1538. }
  1539. if (document.containsKey("$keyf")) {
  1540. document.put("$keyf", replaceWithResourceIfNecessary(ObjectUtils.nullSafeToString(document.get("$keyf"))));
  1541. }
  1542. if (document.containsKey("finalize")) {
  1543. document.put("finalize", replaceWithResourceIfNecessary(ObjectUtils.nullSafeToString(document.get("finalize"))));
  1544. }
  1545. Document commandObject = new Document("group", document);
  1546. if (LOGGER.isDebugEnabled()) {
  1547. LOGGER.debug("Executing Group with Document [{}]", serializeToJsonSafely(commandObject));
  1548. }
  1549. Document commandResult = executeCommand(commandObject, this.readPreference);
  1550. if (LOGGER.isDebugEnabled()) {
  1551. LOGGER.debug("Group command result = [{}]", commandResult);
  1552. }
  1553. @SuppressWarnings("unchecked")
  1554. Iterable<Document> resultSet = (Iterable<Document>) commandResult.get("retval");
  1555. List<T> mappedResults = new ArrayList<>();
  1556. DocumentCallback<T> callback = new ReadDocumentCallback<>(mongoConverter, entityClass, inputCollectionName);
  1557. for (Document resultDocument : resultSet) {
  1558. mappedResults.add(callback.doWith(resultDocument));
  1559. }
  1560. return new GroupByResults<>(mappedResults, commandResult);
  1561. }
  1562. /* (non-Javadoc)
  1563. * @see org.springframework.data.mongodb.core.MongoOperations#aggregate(org.springframework.data.mongodb.core.aggregation.TypedAggregation, java.lang.Class)
  1564. */
  1565. @Override
  1566. public <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, Class<O> outputType) {
  1567. return aggregate(aggregation, getCollectionName(aggregation.getInputType()), outputType);
  1568. }
  1569. /* (non-Javadoc)
  1570. * @see org.springframework.data.mongodb.core.MongoOperations#aggregate(org.springframework.data.mongodb.core.aggregation.TypedAggregation, java.lang.String, java.lang.Class)
  1571. */
  1572. @Override
  1573. public <O> AggregationResults<O> aggregate(TypedAggregation<?> aggregation, String inputCollectionName,
  1574. Class<O> outputType) {
  1575. Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
  1576. return aggregate(aggregation, inputCollectionName, outputType, null);
  1577. }
  1578. /* (non-Javadoc)
  1579. * @see org.springframework.data.mongodb.core.MongoOperations#aggregate(org.springframework.data.mongodb.core.aggregation.Aggregation, java.lang.Class, java.lang.Class)
  1580. */
  1581. @Override
  1582. public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
  1583. return aggregate(aggregation, getCollectionName(inputType), outputType,
  1584. queryOperations.createAggregation(aggregation, inputType).getAggregationOperationContext());
  1585. }
  1586. /* (non-Javadoc)
  1587. * @see org.springframework.data.mongodb.core.MongoOperations#aggregate(org.springframework.data.mongodb.core.aggregation.Aggregation, java.lang.String, java.lang.Class)
  1588. */
  1589. @Override
  1590. public <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType) {
  1591. return aggregate(aggregation, collectionName, outputType, null);
  1592. }
  1593. /* (non-Javadoc)
  1594. * @see org.springframework.data.mongodb.core.MongoOperations#aggregateStream(org.springframework.data.mongodb.core.aggregation.TypedAggregation, java.lang.String, java.lang.Class)
  1595. */
  1596. @Override
  1597. public <O> CloseableIterator<O> aggregateStream(TypedAggregation<?> aggregation, String inputCollectionName,
  1598. Class<O> outputType) {
  1599. Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
  1600. AggregationOperationContext context = new TypeBasedAggregationOperationContext(aggregation.getInputType(),
  1601. mappingContext, queryMapper);
  1602. return aggregateStream(aggregation, inputCollectionName, outputType, context);
  1603. }
  1604. /* (non-Javadoc)
  1605. * @see org.springframework.data.mongodb.core.MongoOperations#aggregateStream(org.springframework.data.mongodb.core.aggregation.TypedAggregation, java.lang.Class)
  1606. */
  1607. @Override
  1608. public <O> CloseableIterator<O> aggregateStream(TypedAggregation<?> aggregation, Class<O> outputType) {
  1609. return aggregateStream(aggregation, getCollectionName(aggregation.getInputType()), outputType);
  1610. }
  1611. /* (non-Javadoc)
  1612. * @see org.springframework.data.mongodb.core.MongoOperations#aggregateStream(org.springframework.data.mongodb.core.aggregation.Aggregation, java.lang.Class, java.lang.Class)
  1613. */
  1614. @Override
  1615. public <O> CloseableIterator<O> aggregateStream(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
  1616. return aggregateStream(aggregation, getCollectionName(inputType), outputType,
  1617. new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper));
  1618. }
  1619. /* (non-Javadoc)
  1620. * @see org.springframework.data.mongodb.core.MongoOperations#aggregateStream(org.springframework.data.mongodb.core.aggregation.Aggregation, java.lang.String, java.lang.Class)
  1621. */
  1622. @Override
  1623. public <O> CloseableIterator<O> aggregateStream(Aggregation aggregation, String collectionName, Class<O> outputType) {
  1624. return aggregateStream(aggregation, collectionName, outputType, null);
  1625. }
  1626. /* (non-Javadoc)
  1627. * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.String)
  1628. */
  1629. @Override
  1630. @SuppressWarnings("unchecked")
  1631. public <T> List<T> findAllAndRemove(Query query, String collectionName) {
  1632. return (List<T>) findAllAndRemove(query, Object.class, collectionName);
  1633. }
  1634. /* (non-Javadoc)
  1635. * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
  1636. */
  1637. @Override
  1638. public <T> List<T> findAllAndRemove(Query query, Class<T> entityClass) {
  1639. return findAllAndRemove(query, entityClass, getCollectionName(entityClass));
  1640. }
  1641. /* (non-Javadoc)
  1642. * @see org.springframework.data.mongodb.core.MongoOperations#findAllAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String)
  1643. */
  1644. @Override
  1645. public <T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName) {
  1646. return doFindAndDelete(collectionName, query, entityClass);
  1647. }
  1648. /**
  1649. * Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)}
  1650. * and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is
  1651. * constructed out of the find result.
  1652. *
  1653. * @param collectionName
  1654. * @param query
  1655. * @param entityClass
  1656. * @return
  1657. */
  1658. protected <T> List<T> doFindAndDelete(String collectionName, Query query, Class<T> entityClass) {
  1659. List<T> result = find(query, entityClass, collectionName);
  1660. if (!CollectionUtils.isEmpty(result)) {
  1661. Query byIdInQuery = operations.getByIdInQuery(result);
  1662. remove(byIdInQuery, entityClass, collectionName);
  1663. }
  1664. return result;
  1665. }
  1666. protected <O> AggregationResults<O> aggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
  1667. @Nullable AggregationOperationContext context) {
  1668. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  1669. Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
  1670. Assert.notNull(outputType, "Output type must not be null!");
  1671. return doAggregate(aggregation, collectionName, outputType,
  1672. queryOperations.createAggregation(aggregation, context));
  1673. }
  1674. private <O> AggregationResults<O> doAggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
  1675. AggregationDefinition context) {
  1676. return doAggregate(aggregation, collectionName, outputType, context.getAggregationOperationContext());
  1677. }
  1678. @SuppressWarnings("ConstantConditions")
  1679. protected <O> AggregationResults<O> doAggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
  1680. AggregationOperationContext context) {
  1681. ReadDocumentCallback<O> callback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName);
  1682. AggregationOptions options = aggregation.getOptions();
  1683. AggregationUtil aggregationUtil = new AggregationUtil(queryMapper, mappingContext);
  1684. if (options.isExplain()) {
  1685. Document command = aggregationUtil.createCommand(collectionName, aggregation, context);
  1686. if (LOGGER.isDebugEnabled()) {
  1687. LOGGER.debug("Executing aggregation: {}", serializeToJsonSafely(command));
  1688. }
  1689. Document commandResult = executeCommand(command);
  1690. return new AggregationResults<>(commandResult.get("results", new ArrayList<Document>(0)).stream()
  1691. .map(callback::doWith).collect(Collectors.toList()), commandResult);
  1692. }
  1693. List<Document> pipeline = aggregationUtil.createPipeline(aggregation, context);
  1694. if (LOGGER.isDebugEnabled()) {
  1695. LOGGER.debug("Executing aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
  1696. }
  1697. return execute(collectionName, collection -> {
  1698. List<Document> rawResult = new ArrayList<>();
  1699. Class<?> domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation<?>) aggregation).getInputType()
  1700. : null;
  1701. Optional<Collation> collation = Optionals.firstNonEmpty(options::getCollation,
  1702. () -> operations.forType(domainType) //
  1703. .getCollation());
  1704. AggregateIterable<Document> aggregateIterable = collection.aggregate(pipeline, Document.class) //
  1705. .collation(collation.map(Collation::toMongoCollation).orElse(null)) //
  1706. .allowDiskUse(options.isAllowDiskUse());
  1707. if (options.getCursorBatchSize() != null) {
  1708. aggregateIterable = aggregateIterable.batchSize(options.getCursorBatchSize());
  1709. }
  1710. options.getComment().ifPresent(aggregateIterable::comment);
  1711. options.getHint().ifPresent(aggregateIterable::hint);
  1712. if (options.hasExecutionTimeLimit()) {
  1713. aggregateIterable = aggregateIterable.maxTime(options.getMaxTime().toMillis(), TimeUnit.MILLISECONDS);
  1714. }
  1715. if (options.isSkipResults()) {
  1716. // toCollection only allowed for $out and $merge if those are the last stages
  1717. if (aggregation.getPipeline().isOutOrMerge()) {
  1718. aggregateIterable.toCollection();
  1719. } else {
  1720. aggregateIterable.first();
  1721. }
  1722. return new AggregationResults<>(Collections.emptyList(), new Document());
  1723. }
  1724. MongoIterable<O> iterable = aggregateIterable.map(val -> {
  1725. rawResult.add(val);
  1726. return callback.doWith(val);
  1727. });
  1728. return new AggregationResults<>(iterable.into(new ArrayList<>()),
  1729. new Document("results", rawResult).append("ok", 1.0D));
  1730. });
  1731. }
  1732. @SuppressWarnings("ConstantConditions")
  1733. protected <O> CloseableIterator<O> aggregateStream(Aggregation aggregation, String collectionName,
  1734. Class<O> outputType, @Nullable AggregationOperationContext context) {
  1735. Assert.hasText(collectionName, "Collection name must not be null or empty!");
  1736. Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
  1737. Assert.notNull(outputType, "Output type must not be null!");
  1738. Assert.isTrue(!aggregation.getOptions().isExplain(), "Can't use explain option with streaming!");
  1739. AggregationDefinition aggregationDefinition = queryOperations.createAggregation(aggregation, context);
  1740. AggregationOptions options = aggregation.getOptions();
  1741. List<Document> pipeline = aggregationDefinition.getAggregationPipeline();
  1742. if (LOGGER.isDebugEnabled()) {
  1743. LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);
  1744. }
  1745. ReadDocumentCallback<O> readCallback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName);
  1746. return execute(collectionName, (CollectionCallback<CloseableIterator<O>>) collection -> {
  1747. AggregateIterable<Document> cursor = collection.aggregate(pipeline, Document.class) //
  1748. .allowDiskUse(options.isAllowDiskUse());
  1749. if (options.getCursorBatchSize() != null) {
  1750. cursor = cursor.batchSize(options.getCursorBatchSize());
  1751. }
  1752. options.getComment().ifPresent(cursor::comment);
  1753. options.getHint().ifPresent(cursor::hint);
  1754. Class<?> domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType()
  1755. : null;
  1756. Optionals.firstNonEmpty(options::getCollation, //
  1757. () -> operations.forType(domainType).getCollation()) //
  1758. .map(Collation::toMongoCollation) //
  1759. .ifPresent(cursor::collation);
  1760. return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator, readCallback);
  1761. });
  1762. }
  1763. /*
  1764. * (non-Javadoc)
  1765. * @see org.springframework.data.mongodb.core.ExecutableFindOperation#query(java.lang.Class)
  1766. */
  1767. @Override
  1768. public <T> ExecutableFind<T> query(Class<T> domainType) {
  1769. return new ExecutableFindOperationSupport(this).query(domainType);
  1770. }
  1771. /*
  1772. * (non-Javadoc)
  1773. * @see org.springframework.data.mongodb.core.ExecutableUpdateOperation#update(java.lang.Class)
  1774. */
  1775. @Override
  1776. public <T> ExecutableUpdate<T> update(Class<T> domainType) {
  1777. return new ExecutableUpdateOperationSupport(this).update(domainType);
  1778. }
  1779. /*
  1780. * (non-Javadoc)
  1781. * @see org.springframework.data.mongodb.core.ExecutableRemoveOperation#remove(java.lang.Class)
  1782. */
  1783. @Override
  1784. public <T> ExecutableRemove<T> remove(Class<T> domainType) {
  1785. return new ExecutableRemoveOperationSupport(this).remove(domainType);
  1786. }
  1787. /*
  1788. * (non-Javadoc)
  1789. * @see org.springframework.data.mongodb.core.ExecutableAggregationOperation#aggregateAndReturn(java.lang.Class)
  1790. */
  1791. @Override
  1792. public <T> ExecutableAggregation<T> aggregateAndReturn(Class<T> domainType) {
  1793. return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType);
  1794. }
  1795. /*
  1796. * (non-Javadoc)
  1797. * @see org.springframework.data.mongodb.core.ExecutableAggregationOperation#aggregateAndReturn(java.lang.Class)
  1798. */
  1799. @Override
  1800. public <T> ExecutableMapReduce<T> mapReduce(Class<T> domainType) {
  1801. return new ExecutableMapReduceOperationSupport(this).mapReduce(domainType);
  1802. }
  1803. /*
  1804. * (non-Javadoc)
  1805. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#insert(java.lang.Class)
  1806. */
  1807. @Override
  1808. public <T> ExecutableInsert<T> insert(Class<T> domainType) {
  1809. return new ExecutableInsertOperationSupport(this).insert(domainType);
  1810. }
  1811. protected String replaceWithResourceIfNecessary(String function) {
  1812. String func = function;
  1813. if (this.resourceLoader != null && ResourceUtils.isUrl(function)) {
  1814. Resource functionResource = resourceLoader.getResource(func);
  1815. if (!functionResource.exists()) {
  1816. throw new InvalidDataAccessApiUsageException(String.format("Resource %s not found!", function));
  1817. }
  1818. Scanner scanner = null;
  1819. try {
  1820. scanner = new Scanner(functionResource.getInputStream());
  1821. return scanner.useDelimiter("\\A").next();
  1822. } catch (IOException e) {
  1823. throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e);
  1824. } finally {
  1825. if (scanner != null) {
  1826. scanner.close();
  1827. }
  1828. }
  1829. }
  1830. return func;
  1831. }
  1832. /*
  1833. * (non-Javadoc)
  1834. * @see org.springframework.data.mongodb.core.ExecutableInsertOperation#getCollectionNames()
  1835. */
  1836. @SuppressWarnings("ConstantConditions")
  1837. public Set<String> getCollectionNames() {
  1838. return execute(db -> {
  1839. Set<String> result = new LinkedHashSet<>();
  1840. for (String name : db.listCollectionNames()) {
  1841. result.add(name);
  1842. }
  1843. return result;
  1844. });
  1845. }
  1846. public MongoDatabase getDb() {
  1847. return doGetDatabase();
  1848. }
  1849. protected MongoDatabase doGetDatabase() {
  1850. return MongoDatabaseUtils.getDatabase(mongoDbFactory, sessionSynchronization);
  1851. }
  1852. protected MongoDatabase prepareDatabase(MongoDatabase database) {
  1853. return database;
  1854. }
  1855. protected <E extends MongoMappingEvent<T>, T> E maybeEmitEvent(E event) {
  1856. if (eventPublisher != null) {
  1857. eventPublisher.publishEvent(event);
  1858. }
  1859. return event;
  1860. }
  1861. protected <T> T maybeCallBeforeConvert(T object, String collection) {
  1862. if (entityCallbacks != null) {
  1863. return entityCallbacks.callback(BeforeConvertCallback.class, object, collection);
  1864. }
  1865. return object;
  1866. }
  1867. protected <T> T maybeCallBeforeSave(T object, Document document, String collection) {
  1868. if (entityCallbacks != null) {
  1869. return entityCallbacks.callback(BeforeSaveCallback.class, object, document, collection);
  1870. }
  1871. return object;
  1872. }
  1873. protected <T> T maybeCallAfterSave(T object, Document document, String collection) {
  1874. if (entityCallbacks != null) {
  1875. return entityCallbacks.callback(AfterSaveCallback.class, object, document, collection);
  1876. }
  1877. return object;
  1878. }
  1879. protected <T> T maybeCallAfterConvert(T object, Document document, String collection) {
  1880. if (entityCallbacks != null) {
  1881. return entityCallbacks.callback(AfterConvertCallback.class, object, document, collection);
  1882. }
  1883. return object;
  1884. }
  1885. /**
  1886. * Create the specified collection using the provided options
  1887. *
  1888. * @param collectionName
  1889. * @param collectionOptions
  1890. * @return the collection that was created
  1891. */
  1892. @SuppressWarnings("ConstantConditions")
  1893. protected MongoCollection<Document> doCreateCollection(String collectionName, Document collectionOptions) {
  1894. return execute(db -> {
  1895. CreateCollectionOptions co = new CreateCollectionOptions();
  1896. if (collectionOptions.containsKey("capped")) {
  1897. co.capped((Boolean) collectionOptions.get("capped"));
  1898. }
  1899. if (collectionOptions.containsKey("size")) {
  1900. co.sizeInBytes(((Number) collectionOptions.get("size")).longValue());
  1901. }
  1902. if (collectionOptions.containsKey("max")) {
  1903. co.maxDocuments(((Number) collectionOptions.get("max")).longValue());
  1904. }
  1905. if (collectionOptions.containsKey("collation")) {
  1906. co.collation(IndexConverters.fromDocument(collectionOptions.get("collation", Document.class)));
  1907. }
  1908. if (collectionOptions.containsKey("validator")) {
  1909. com.mongodb.client.model.ValidationOptions options = new com.mongodb.client.model.ValidationOptions();
  1910. if (collectionOptions.containsKey("validationLevel")) {
  1911. options.validationLevel(ValidationLevel.fromString(collectionOptions.getString("validationLevel")));
  1912. }
  1913. if (collectionOptions.containsKey("validationAction")) {
  1914. options.validationAction(ValidationAction.fromString(collectionOptions.getString("validationAction")));
  1915. }
  1916. options.validator(collectionOptions.get("validator", Document.class));
  1917. co.validationOptions(options);
  1918. }
  1919. if (collectionOptions.containsKey("timeseries")) {
  1920. Document timeSeries = collectionOptions.get("timeseries", Document.class);
  1921. com.mongodb.client.model.TimeSeriesOptions options = new com.mongodb.client.model.TimeSeriesOptions(
  1922. timeSeries.getString("timeField"));
  1923. if (timeSeries.containsKey("metaField")) {
  1924. options.metaField(timeSeries.getString("metaField"));
  1925. }
  1926. if (timeSeries.containsKey("granularity")) {
  1927. options.granularity(TimeSeriesGranularity.valueOf(timeSeries.getString("granularity").toUpperCase()));
  1928. }
  1929. co.timeSeriesOptions(options);
  1930. }
  1931. db.createCollection(collectionName, co);
  1932. MongoCollection<Document> coll = db.getCollection(collectionName, Document.class);
  1933. // TODO: Emit a collection created event
  1934. if (LOGGER.isDebugEnabled()) {
  1935. LOGGER.debug("Created collection [{}]",
  1936. coll.getNamespace() != null ? coll.getNamespace().getCollectionName() : collectionName);
  1937. }
  1938. return coll;
  1939. });
  1940. }
  1941. /**
  1942. * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
  1943. * The query document is specified as a standard {@link Document} and so is the fields specification.
  1944. *
  1945. * @param collectionName name of the collection to retrieve the objects from.
  1946. * @param query the query document that specifies the criteria used to find a record.
  1947. * @param fields the document that specifies the fields to be returned.
  1948. * @param entityClass the parameterized type of the returned list.
  1949. * @return the {@link List} of converted objects.
  1950. */
  1951. protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
  1952. return doFindOne(collectionName, query, fields, CursorPreparer.NO_OP_PREPARER, entityClass);
  1953. }
  1954. /**
  1955. * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
  1956. * The query document is specified as a standard {@link Document} and so is the fields specification.
  1957. *
  1958. * @param collectionName name of the collection to retrieve the objects from.
  1959. * @param query the query document that specifies the criteria used to find a record.
  1960. * @param fields the document that specifies the fields to be returned.
  1961. * @param entityClass the parameterized type of the returned list.
  1962. * @param preparer the preparer used to modify the cursor on execution.
  1963. * @return the {@link List} of converted objects.
  1964. * @since 2.2
  1965. */
  1966. @SuppressWarnings("ConstantConditions")
  1967. protected <T> T doFindOne(String collectionName, Document query, Document fields, CursorPreparer preparer,
  1968. Class<T> entityClass) {
  1969. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
  1970. QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
  1971. Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
  1972. Document mappedQuery = queryContext.getMappedQuery(entity);
  1973. if (LOGGER.isDebugEnabled()) {
  1974. LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query),
  1975. mappedFields, entityClass, collectionName);
  1976. }
  1977. return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, preparer),
  1978. new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
  1979. }
  1980. /**
  1981. * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. The
  1982. * query document is specified as a standard Document and so is the fields specification.
  1983. *
  1984. * @param collectionName name of the collection to retrieve the objects from
  1985. * @param query the query document that specifies the criteria used to find a record
  1986. * @param fields the document that specifies the fields to be returned
  1987. * @param entityClass the parameterized type of the returned list.
  1988. * @return the List of converted objects.
  1989. */
  1990. protected <T> List<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass) {
  1991. return doFind(collectionName, query, fields, entityClass, null,
  1992. new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName));
  1993. }
  1994. /**
  1995. * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. The object is
  1996. * converted from the MongoDB native representation using an instance of {@see MongoConverter}. The query document is
  1997. * specified as a standard Document and so is the fields specification.
  1998. *
  1999. * @param collectionName name of the collection to retrieve the objects from.
  2000. * @param query the query document that specifies the criteria used to find a record.
  2001. * @param fields the document that specifies the fields to be returned.
  2002. * @param entityClass the parameterized type of the returned list.
  2003. * @param preparer allows for customization of the {@link FindIterable} used when iterating over the result set,
  2004. * (apply limits, skips and so on).
  2005. * @return the {@link List} of converted objects.
  2006. */
  2007. protected <T> List<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass,
  2008. CursorPreparer preparer) {
  2009. return doFind(collectionName, query, fields, entityClass, preparer,
  2010. new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName));
  2011. }
  2012. protected <S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass,
  2013. @Nullable CursorPreparer preparer, DocumentCallback<T> objectCallback) {
  2014. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
  2015. QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
  2016. Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory);
  2017. Document mappedQuery = queryContext.getMappedQuery(entity);
  2018. if (LOGGER.isDebugEnabled()) {
  2019. LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}",
  2020. serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName);
  2021. }
  2022. return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null),
  2023. preparer != null ? preparer : CursorPreparer.NO_OP_PREPARER, objectCallback, collectionName);
  2024. }
  2025. /**
  2026. * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified targetClass while
  2027. * using sourceClass for mapping the query.
  2028. *
  2029. * @since 2.0
  2030. */
  2031. <S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> sourceClass,
  2032. Class<T> targetClass, CursorPreparer preparer) {
  2033. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
  2034. QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields));
  2035. Document mappedFields = queryContext.getMappedFields(entity, targetClass, projectionFactory);
  2036. Document mappedQuery = queryContext.getMappedQuery(entity);
  2037. if (LOGGER.isDebugEnabled()) {
  2038. LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}",
  2039. serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName);
  2040. }
  2041. return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer,
  2042. new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
  2043. }
  2044. /**
  2045. * Convert given {@link CollectionOptions} to a document and take the domain type information into account when
  2046. * creating a mapped schema for validation. <br />
  2047. * This method calls {@link #convertToDocument(CollectionOptions)} for backwards compatibility and potentially
  2048. * overwrites the validator with the mapped validator document. In the long run
  2049. * {@link #convertToDocument(CollectionOptions)} will be removed so that this one becomes the only source of truth.
  2050. *
  2051. * @param collectionOptions can be {@literal null}.
  2052. * @param targetType must not be {@literal null}. Use {@link Object} type instead.
  2053. * @return never {@literal null}.
  2054. * @since 2.1
  2055. */
  2056. protected Document convertToDocument(@Nullable CollectionOptions collectionOptions, Class<?> targetType) {
  2057. Document doc = convertToDocument(collectionOptions);
  2058. if (collectionOptions != null) {
  2059. collectionOptions.getValidationOptions().ifPresent(it -> it.getValidator() //
  2060. .ifPresent(val -> doc.put("validator", getMappedValidator(val, targetType))));
  2061. collectionOptions.getTimeSeriesOptions().map(operations.forType(targetType)::mapTimeSeriesOptions)
  2062. .ifPresent(it -> {
  2063. Document timeseries = new Document("timeField", it.getTimeField());
  2064. if (StringUtils.hasText(it.getMetaField())) {
  2065. timeseries.append("metaField", it.getMetaField());
  2066. }
  2067. if (!Granularity.DEFAULT.equals(it.getGranularity())) {
  2068. timeseries.append("granularity", it.getGranularity().name().toLowerCase());
  2069. }
  2070. doc.put("timeseries", timeseries);
  2071. });
  2072. }
  2073. return doc;
  2074. }
  2075. /**
  2076. * @param collectionOptions can be {@literal null}.
  2077. * @return never {@literal null}.
  2078. * @deprecated since 2.1 in favor of {@link #convertToDocument(CollectionOptions, Class)}.
  2079. */
  2080. @Deprecated
  2081. protected Document convertToDocument(@Nullable CollectionOptions collectionOptions) {
  2082. Document document = new Document();
  2083. if (collectionOptions != null) {
  2084. collectionOptions.getCapped().ifPresent(val -> document.put("capped", val));
  2085. collectionOptions.getSize().ifPresent(val -> document.put("size", val));
  2086. collectionOptions.getMaxDocuments().ifPresent(val -> document.put("max", val));
  2087. collectionOptions.getCollation().ifPresent(val -> document.append("collation", val.toDocument()));
  2088. collectionOptions.getValidationOptions().ifPresent(it -> {
  2089. it.getValidationLevel().ifPresent(val -> document.append("validationLevel", val.getValue()));
  2090. it.getValidationAction().ifPresent(val -> document.append("validationAction", val.getValue()));
  2091. it.getValidator().ifPresent(val -> document.append("validator", getMappedValidator(val, Object.class)));
  2092. });
  2093. }
  2094. return document;
  2095. }
  2096. Document getMappedValidator(Validator validator, Class<?> domainType) {
  2097. Document validationRules = validator.toDocument();
  2098. if (validationRules.containsKey("$jsonSchema")) {
  2099. return schemaMapper.mapSchema(validationRules, domainType);
  2100. }
  2101. return queryMapper.getMappedObject(validationRules, mappingContext.getPersistentEntity(domainType));
  2102. }
  2103. /**
  2104. * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
  2105. * The first document that matches the query is returned and also removed from the collection in the database.
  2106. * <br />
  2107. * The query document is specified as a standard Document and so is the fields specification.
  2108. *
  2109. * @param collectionName name of the collection to retrieve the objects from
  2110. * @param query the query document that specifies the criteria used to find a record
  2111. * @param entityClass the parameterized type of the returned list.
  2112. * @return the List of converted objects.
  2113. */
  2114. @SuppressWarnings("ConstantConditions")
  2115. protected <T> T doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
  2116. @Nullable Collation collation, Class<T> entityClass) {
  2117. EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
  2118. if (LOGGER.isDebugEnabled()) {
  2119. LOGGER.debug("findAndRemove using query: {} fields: {} sort: {} for class: {} in collection: {}",
  2120. serializeToJsonSafely(query), fields, sort, entityClass, collectionName);
  2121. }
  2122. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
  2123. return executeFindOneInternal(
  2124. new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
  2125. new ReadDocumentCallback<>(readerToUse, entityClass, collectionName), collectionName);
  2126. }
  2127. @SuppressWarnings("ConstantConditions")
  2128. protected <T> T doFindAndModify(String collectionName, Document query, Document fields, Document sort,
  2129. Class<T> entityClass, UpdateDefinition update, @Nullable FindAndModifyOptions options) {
  2130. EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
  2131. if (options == null) {
  2132. options = new FindAndModifyOptions();
  2133. }
  2134. MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
  2135. UpdateContext updateContext = queryOperations.updateSingleContext(update, query, false);
  2136. updateContext.increaseVersionForUpdateIfNecessary(entity);
  2137. Document mappedQuery = updateContext.getMappedQuery(entity);
  2138. Object mappedUpdate = updateContext.isAggregationUpdate() ? updateContext.getUpdatePipeline(entityClass)
  2139. : updateContext.getMappedUpdate(entity);
  2140. if (LOGGER.isDebugEnabled()) {
  2141. LOGGER.debug(
  2142. "findAndModify using query: {} fields: {} sort: {} for class: {} and update: {} " + "in collection: {}",
  2143. serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(mappedUpdate),
  2144. collectionName);
  2145. }
  2146. return executeFindOneInternal(
  2147. new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate,
  2148. update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
  2149. new ReadDocumentCallback<>(readerToUse, entityClass, collectionName), collectionName);
  2150. }
  2151. /**
  2152. * Customize this part for findAndReplace.
  2153. *
  2154. * @param collectionName The name of the collection to perform the operation in.
  2155. * @param mappedQuery the query to look up documents.
  2156. * @param mappedFields the fields to project the result to.
  2157. * @param mappedSort the sort to be applied when executing the query.
  2158. * @param collation collation settings for the query. Can be {@literal null}.
  2159. * @param entityType the source domain type.
  2160. * @param replacement the replacement {@link Document}.
  2161. * @param options applicable options.
  2162. * @param resultType the target domain type.
  2163. * @return {@literal null} if object does not exist, {@link FindAndReplaceOptions#isReturnNew() return new} is
  2164. * {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
  2165. */
  2166. @Nullable
  2167. protected <T> T doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
  2168. Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
  2169. Document replacement, FindAndReplaceOptions options, Class<T> resultType) {
  2170. if (LOGGER.isDebugEnabled()) {
  2171. LOGGER.debug(
  2172. "findAndReplace using query: {} fields: {} sort: {} for class: {} and replacement: {} " + "in collection: {}",
  2173. serializeToJsonSafely(mappedQuery), serializeToJsonSafely(mappedFields), serializeToJsonSafely(mappedSort),
  2174. entityType, serializeToJsonSafely(replacement), collectionName);
  2175. }
  2176. return executeFindOneInternal(
  2177. new FindAndReplaceCallback(mappedQuery, mappedFields, mappedSort, replacement, collation, options),
  2178. new ProjectingReadCallback<>(mongoConverter, entityType, resultType, collectionName), collectionName);
  2179. }
  2180. /**
  2181. * Populates the id property of the saved object, if it's not set already.
  2182. *
  2183. * @param savedObject
  2184. * @param id
  2185. */
  2186. protected <T> T populateIdIfNecessary(T savedObject, Object id) {
  2187. return operations.forEntity(savedObject, mongoConverter.getConversionService()) //
  2188. .populateIdIfNecessary(id);
  2189. }
  2190. private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, String collectionName) {
  2191. try {
  2192. MongoCollection<Document> collection = db.getCollection(collectionName, Document.class);
  2193. collection = prepareCollection(collection);
  2194. return collection;
  2195. } catch (RuntimeException e) {
  2196. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  2197. }
  2198. }
  2199. /**
  2200. * Internal method using callbacks to do queries against the datastore that requires reading a single object from a
  2201. * collection of objects. It will take the following steps
  2202. * <ol>
  2203. * <li>Execute the given {@link CollectionCallback} for a {@link Document}.</li>
  2204. * <li>Apply the given {@link DocumentCallback} to each of the {@link Document}s to obtain the result.</li>
  2205. * <ol>
  2206. *
  2207. * @param <T>
  2208. * @param collectionCallback the callback to retrieve the {@link Document} with
  2209. * @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
  2210. * @param collectionName the collection to be queried
  2211. * @return
  2212. */
  2213. @Nullable
  2214. private <T> T executeFindOneInternal(CollectionCallback<Document> collectionCallback,
  2215. DocumentCallback<T> documentCallback, String collectionName) {
  2216. try {
  2217. Document document = collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
  2218. return document != null ? documentCallback.doWith(document) : null;
  2219. } catch (RuntimeException e) {
  2220. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  2221. }
  2222. }
  2223. /**
  2224. * Internal method using callback to do queries against the datastore that requires reading a collection of objects.
  2225. * It will take the following steps
  2226. * <ol>
  2227. * <li>Execute the given {@link CollectionCallback} for a {@link FindIterable}.</li>
  2228. * <li>Prepare that {@link FindIterable} with the given {@link CursorPreparer} (will be skipped if
  2229. * {@link CursorPreparer} is {@literal null}</li>
  2230. * <li>Iterate over the {@link FindIterable} and applies the given {@link DocumentCallback} to each of the
  2231. * {@link Document}s collecting the actual result {@link List}.</li>
  2232. * <ol>
  2233. *
  2234. * @param <T>
  2235. * @param collectionCallback the callback to retrieve the {@link FindIterable} with
  2236. * @param preparer the {@link CursorPreparer} to potentially modify the {@link FindIterable} before iterating over it
  2237. * @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
  2238. * @param collectionName the collection to be queried
  2239. * @return
  2240. */
  2241. private <T> List<T> executeFindMultiInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
  2242. CursorPreparer preparer, DocumentCallback<T> documentCallback, String collectionName) {
  2243. try {
  2244. try (MongoCursor<Document> cursor = preparer
  2245. .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
  2246. .iterator()) {
  2247. List<T> result = new ArrayList<>();
  2248. while (cursor.hasNext()) {
  2249. Document object = cursor.next();
  2250. result.add(documentCallback.doWith(object));
  2251. }
  2252. return result;
  2253. }
  2254. } catch (RuntimeException e) {
  2255. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  2256. }
  2257. }
  2258. private void executeQueryInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
  2259. CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) {
  2260. try (MongoCursor<Document> cursor = preparer
  2261. .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
  2262. .iterator()) {
  2263. while (cursor.hasNext()) {
  2264. callbackHandler.processDocument(cursor.next());
  2265. }
  2266. } catch (RuntimeException e) {
  2267. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  2268. }
  2269. }
  2270. public PersistenceExceptionTranslator getExceptionTranslator() {
  2271. return exceptionTranslator;
  2272. }
  2273. @Nullable
  2274. private MongoPersistentEntity<?> getPersistentEntity(@Nullable Class<?> type) {
  2275. return type != null ? mappingContext.getPersistentEntity(type) : null;
  2276. }
  2277. private static MongoConverter getDefaultMongoConverter(MongoDatabaseFactory factory) {
  2278. DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
  2279. MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList());
  2280. MongoMappingContext mappingContext = new MongoMappingContext();
  2281. mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
  2282. mappingContext.afterPropertiesSet();
  2283. MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
  2284. converter.setCustomConversions(conversions);
  2285. converter.setCodecRegistryProvider(factory);
  2286. converter.afterPropertiesSet();
  2287. return converter;
  2288. }
  2289. private Document getMappedSortObject(Query query, Class<?> type) {
  2290. if (query == null || ObjectUtils.isEmpty(query.getSortObject())) {
  2291. return null;
  2292. }
  2293. return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
  2294. }
  2295. /**
  2296. * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
  2297. * exception if the conversation failed. Thus allows safe re-throwing of the return value.
  2298. *
  2299. * @param ex the exception to translate
  2300. * @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation
  2301. * @return
  2302. */
  2303. static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex,
  2304. PersistenceExceptionTranslator exceptionTranslator) {
  2305. RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
  2306. return resolved == null ? ex : resolved;
  2307. }
  2308. // Callback implementations
  2309. /**
  2310. * Simple {@link CollectionCallback} that takes a query {@link Document} plus an optional fields specification
  2311. * {@link Document} and executes that against the {@link MongoCollection}.
  2312. *
  2313. * @author Oliver Gierke
  2314. * @author Thomas Risberg
  2315. * @author Christoph Strobl
  2316. */
  2317. private static class FindOneCallback implements CollectionCallback<Document> {
  2318. private final Document query;
  2319. private final Optional<Document> fields;
  2320. private final CursorPreparer cursorPreparer;
  2321. FindOneCallback(Document query, Document fields, CursorPreparer preparer) {
  2322. this.query = query;
  2323. this.fields = Optional.of(fields).filter(it -> !ObjectUtils.isEmpty(fields));
  2324. this.cursorPreparer = preparer;
  2325. }
  2326. @Override
  2327. public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
  2328. FindIterable<Document> iterable = cursorPreparer.initiateFind(collection, col -> col.find(query, Document.class));
  2329. if (LOGGER.isDebugEnabled()) {
  2330. LOGGER.debug("findOne using query: {} fields: {} in db.collection: {}", serializeToJsonSafely(query),
  2331. serializeToJsonSafely(fields.orElseGet(Document::new)),
  2332. collection.getNamespace() != null ? collection.getNamespace().getFullName() : "n/a");
  2333. }
  2334. if (fields.isPresent()) {
  2335. iterable = iterable.projection(fields.get());
  2336. }
  2337. return iterable.first();
  2338. }
  2339. }
  2340. /**
  2341. * Simple {@link CollectionCallback} that takes a query {@link Document} plus an optional fields specification
  2342. * {@link Document} and executes that against the {@link MongoCollection}.
  2343. *
  2344. * @author Oliver Gierke
  2345. * @author Thomas Risberg
  2346. * @author Christoph Strobl
  2347. */
  2348. private static class FindCallback implements CollectionCallback<FindIterable<Document>> {
  2349. private final Document query;
  2350. private final Document fields;
  2351. private final @Nullable com.mongodb.client.model.Collation collation;
  2352. public FindCallback(Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation) {
  2353. Assert.notNull(query, "Query must not be null!");
  2354. Assert.notNull(fields, "Fields must not be null!");
  2355. this.query = query;
  2356. this.fields = fields;
  2357. this.collation = collation;
  2358. }
  2359. public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
  2360. throws MongoException, DataAccessException {
  2361. FindIterable<Document> findIterable = collection.find(query, Document.class).projection(fields);
  2362. if (collation != null) {
  2363. findIterable = findIterable.collation(collation);
  2364. }
  2365. return findIterable;
  2366. }
  2367. }
  2368. /**
  2369. * Optimized {@link CollectionCallback} that takes an already mapped query and a nullable
  2370. * {@link com.mongodb.client.model.Collation} to execute a count query limited to one element.
  2371. *
  2372. * @author Christoph Strobl
  2373. * @since 2.0
  2374. */
  2375. private class ExistsCallback implements CollectionCallback<Boolean> {
  2376. private final Document mappedQuery;
  2377. private final com.mongodb.client.model.Collation collation;
  2378. ExistsCallback(Document mappedQuery, com.mongodb.client.model.Collation collation) {
  2379. this.mappedQuery = mappedQuery;
  2380. this.collation = collation;
  2381. }
  2382. @Override
  2383. public Boolean doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
  2384. return doCount(collection.getNamespace().getCollectionName(), mappedQuery,
  2385. new CountOptions().limit(1).collation(collation)) > 0;
  2386. }
  2387. }
  2388. /**
  2389. * Simple {@link CollectionCallback} that takes a query {@link Document} plus an optional fields specification
  2390. * {@link Document} and executes that against the {@link MongoCollection}.
  2391. *
  2392. * @author Thomas Risberg
  2393. */
  2394. private static class FindAndRemoveCallback implements CollectionCallback<Document> {
  2395. private final Document query;
  2396. private final Document fields;
  2397. private final Document sort;
  2398. private final Optional<Collation> collation;
  2399. FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) {
  2400. this.query = query;
  2401. this.fields = fields;
  2402. this.sort = sort;
  2403. this.collation = Optional.ofNullable(collation);
  2404. }
  2405. public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
  2406. FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions().sort(sort).projection(fields);
  2407. collation.map(Collation::toMongoCollation).ifPresent(opts::collation);
  2408. return collection.findOneAndDelete(query, opts);
  2409. }
  2410. }
  2411. private static class FindAndModifyCallback implements CollectionCallback<Document> {
  2412. private final Document query;
  2413. private final Document fields;
  2414. private final Document sort;
  2415. private final Object update;
  2416. private final List<Document> arrayFilters;
  2417. private final FindAndModifyOptions options;
  2418. FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List<Document> arrayFilters,
  2419. FindAndModifyOptions options) {
  2420. this.query = query;
  2421. this.fields = fields;
  2422. this.sort = sort;
  2423. this.update = update;
  2424. this.arrayFilters = arrayFilters;
  2425. this.options = options;
  2426. }
  2427. public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
  2428. FindOneAndUpdateOptions opts = new FindOneAndUpdateOptions();
  2429. opts.sort(sort);
  2430. if (options.isUpsert()) {
  2431. opts.upsert(true);
  2432. }
  2433. opts.projection(fields);
  2434. if (options.isReturnNew()) {
  2435. opts.returnDocument(ReturnDocument.AFTER);
  2436. }
  2437. options.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
  2438. if (!arrayFilters.isEmpty()) {
  2439. opts.arrayFilters(arrayFilters);
  2440. }
  2441. if (update instanceof Document) {
  2442. return collection.findOneAndUpdate(query, (Document) update, opts);
  2443. } else if (update instanceof List) {
  2444. return collection.findOneAndUpdate(query, (List<Document>) update, opts);
  2445. }
  2446. throw new IllegalArgumentException(String.format("Using %s is not supported in findOneAndUpdate", update));
  2447. }
  2448. }
  2449. /**
  2450. * {@link CollectionCallback} specific for find and remove operation.
  2451. *
  2452. * @author Mark Paluch
  2453. * @author Christoph Strobl
  2454. * @since 2.1
  2455. */
  2456. private static class FindAndReplaceCallback implements CollectionCallback<Document> {
  2457. private final Document query;
  2458. private final Document fields;
  2459. private final Document sort;
  2460. private final Document update;
  2461. private final @Nullable com.mongodb.client.model.Collation collation;
  2462. private final FindAndReplaceOptions options;
  2463. FindAndReplaceCallback(Document query, Document fields, Document sort, Document update,
  2464. @Nullable com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) {
  2465. this.query = query;
  2466. this.fields = fields;
  2467. this.sort = sort;
  2468. this.update = update;
  2469. this.options = options;
  2470. this.collation = collation;
  2471. }
  2472. /*
  2473. * (non-Javadoc)
  2474. * @see org.springframework.data.mongodb.core.CollectionCallback#doInCollection(com.mongodb.client.MongoCollection)
  2475. */
  2476. @Override
  2477. public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
  2478. FindOneAndReplaceOptions opts = new FindOneAndReplaceOptions();
  2479. opts.sort(sort);
  2480. opts.collation(collation);
  2481. opts.projection(fields);
  2482. if (options.isUpsert()) {
  2483. opts.upsert(true);
  2484. }
  2485. if (options.isReturnNew()) {
  2486. opts.returnDocument(ReturnDocument.AFTER);
  2487. }
  2488. return collection.findOneAndReplace(query, update, opts);
  2489. }
  2490. }
  2491. /**
  2492. * Simple internal callback to allow operations on a {@link Document}.
  2493. *
  2494. * @author Oliver Gierke
  2495. * @author Thomas Darimont
  2496. */
  2497. interface DocumentCallback<T> {
  2498. T doWith(Document object);
  2499. }
  2500. /**
  2501. * Simple {@link DocumentCallback} that will transform {@link Document} into the given target type using the given
  2502. * {@link EntityReader}.
  2503. *
  2504. * @author Oliver Gierke
  2505. * @author Christoph Strobl
  2506. * @author Roman Puchkovskiy
  2507. */
  2508. private class ReadDocumentCallback<T> implements DocumentCallback<T> {
  2509. private final EntityReader<? super T, Bson> reader;
  2510. private final Class<T> type;
  2511. private final String collectionName;
  2512. ReadDocumentCallback(EntityReader<? super T, Bson> reader, Class<T> type, String collectionName) {
  2513. this.reader = reader;
  2514. this.type = type;
  2515. this.collectionName = collectionName;
  2516. }
  2517. public T doWith(Document document) {
  2518. maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
  2519. T entity = reader.read(type, document);
  2520. if (entity == null) {
  2521. throw new MappingException(String.format("EntityReader %s returned null", reader));
  2522. }
  2523. maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
  2524. entity = maybeCallAfterConvert(entity, document, collectionName);
  2525. return entity;
  2526. }
  2527. }
  2528. /**
  2529. * {@link DocumentCallback} transforming {@link Document} into the given {@code targetType} or decorating the
  2530. * {@code sourceType} with a {@literal projection} in case the {@code targetType} is an {@literal interface}.
  2531. *
  2532. * @param <S>
  2533. * @param <T>
  2534. * @since 2.0
  2535. */
  2536. private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
  2537. private final EntityReader<Object, Bson> reader;
  2538. private final Class<S> entityType;
  2539. private final Class<T> targetType;
  2540. private final String collectionName;
  2541. ProjectingReadCallback(EntityReader<Object, Bson> reader, Class<S> entityType, Class<T> targetType,
  2542. String collectionName) {
  2543. this.reader = reader;
  2544. this.entityType = entityType;
  2545. this.targetType = targetType;
  2546. this.collectionName = collectionName;
  2547. }
  2548. /*
  2549. * (non-Javadoc)
  2550. * @see org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback#doWith(org.bson.Document)
  2551. */
  2552. @SuppressWarnings("unchecked")
  2553. public T doWith(Document document) {
  2554. if (document == null) {
  2555. return null;
  2556. }
  2557. Class<?> typeToRead = targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType
  2558. : targetType;
  2559. maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));
  2560. Object entity = reader.read(typeToRead, document);
  2561. if (entity == null) {
  2562. throw new MappingException(String.format("EntityReader %s returned null", reader));
  2563. }
  2564. Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
  2565. maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
  2566. return (T) maybeCallAfterConvert(result, document, collectionName);
  2567. }
  2568. }
  2569. class QueryCursorPreparer implements CursorPreparer {
  2570. private final Query query;
  2571. private final @Nullable Class<?> type;
  2572. QueryCursorPreparer(Query query, @Nullable Class<?> type) {
  2573. this.query = query;
  2574. this.type = type;
  2575. }
  2576. /*
  2577. * (non-Javadoc)
  2578. * @see org.springframework.data.mongodb.core.CursorPreparer#prepare(com.mongodb.DBCursor)
  2579. */
  2580. public FindIterable<Document> prepare(FindIterable<Document> iterable) {
  2581. FindIterable<Document> cursorToUse = iterable;
  2582. operations.forType(type).getCollation(query) //
  2583. .map(Collation::toMongoCollation) //
  2584. .ifPresent(cursorToUse::collation);
  2585. Meta meta = query.getMeta();
  2586. if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
  2587. && !StringUtils.hasText(query.getHint()) && !meta.hasValues() && !query.getCollation().isPresent()) {
  2588. return cursorToUse;
  2589. }
  2590. try {
  2591. if (query.getSkip() > 0) {
  2592. cursorToUse = cursorToUse.skip((int) query.getSkip());
  2593. }
  2594. if (query.getLimit() > 0) {
  2595. cursorToUse = cursorToUse.limit(query.getLimit());
  2596. }
  2597. if (!ObjectUtils.isEmpty(query.getSortObject())) {
  2598. Document sort = type != null ? getMappedSortObject(query, type) : query.getSortObject();
  2599. cursorToUse = cursorToUse.sort(sort);
  2600. }
  2601. if (StringUtils.hasText(query.getHint())) {
  2602. String hint = query.getHint();
  2603. if (BsonUtils.isJsonDocument(hint)) {
  2604. cursorToUse = cursorToUse.hint(BsonUtils.parse(hint, mongoDbFactory));
  2605. } else {
  2606. cursorToUse = cursorToUse.hintString(hint);
  2607. }
  2608. }
  2609. if (meta.hasValues()) {
  2610. if (StringUtils.hasText(meta.getComment())) {
  2611. cursorToUse = cursorToUse.comment(meta.getComment());
  2612. }
  2613. if (meta.getMaxTimeMsec() != null) {
  2614. cursorToUse = cursorToUse.maxTime(meta.getMaxTimeMsec(), TimeUnit.MILLISECONDS);
  2615. }
  2616. if (meta.getCursorBatchSize() != null) {
  2617. cursorToUse = cursorToUse.batchSize(meta.getCursorBatchSize());
  2618. }
  2619. if (meta.getAllowDiskUse() != null) {
  2620. cursorToUse = cursorToUse.allowDiskUse(meta.getAllowDiskUse());
  2621. }
  2622. for (Meta.CursorOption option : meta.getFlags()) {
  2623. switch (option) {
  2624. case NO_TIMEOUT:
  2625. cursorToUse = cursorToUse.noCursorTimeout(true);
  2626. break;
  2627. case PARTIAL:
  2628. cursorToUse = cursorToUse.partial(true);
  2629. break;
  2630. case SECONDARY_READS:
  2631. case SLAVE_OK:
  2632. break;
  2633. default:
  2634. throw new IllegalArgumentException(String.format("%s is no supported flag.", option));
  2635. }
  2636. }
  2637. }
  2638. } catch (RuntimeException e) {
  2639. throw potentiallyConvertRuntimeException(e, exceptionTranslator);
  2640. }
  2641. return cursorToUse;
  2642. }
  2643. @Override
  2644. public ReadPreference getReadPreference() {
  2645. return (query.getMeta().getFlags().contains(CursorOption.SECONDARY_READS)
  2646. || query.getMeta().getFlags().contains(CursorOption.SLAVE_OK)) ? ReadPreference.primaryPreferred() : null;
  2647. }
  2648. }
  2649. /**
  2650. * {@link DocumentCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to
  2651. * a delegate and creates a {@link GeoResult} from the result.
  2652. *
  2653. * @author Oliver Gierke
  2654. * @author Christoph Strobl
  2655. */
  2656. static class GeoNearResultDocumentCallback<T> implements DocumentCallback<GeoResult<T>> {
  2657. private final String distanceField;
  2658. private final DocumentCallback<T> delegate;
  2659. private final Metric metric;
  2660. /**
  2661. * Creates a new {@link GeoNearResultDocumentCallback} using the given {@link DocumentCallback} delegate for
  2662. * {@link GeoResult} content unmarshalling.
  2663. *
  2664. * @param distanceField the field to read the distance from.
  2665. * @param delegate must not be {@literal null}.
  2666. * @param metric the {@link Metric} to apply to the result distance.
  2667. */
  2668. GeoNearResultDocumentCallback(String distanceField, DocumentCallback<T> delegate, Metric metric) {
  2669. Assert.notNull(delegate, "DocumentCallback must not be null!");
  2670. this.distanceField = distanceField;
  2671. this.delegate = delegate;
  2672. this.metric = metric;
  2673. }
  2674. public GeoResult<T> doWith(Document object) {
  2675. double distance = Double.NaN;
  2676. if (object.containsKey(distanceField)) {
  2677. distance = NumberUtils.convertNumberToTargetClass(object.get(distanceField, Number.class), Double.class);
  2678. }
  2679. T doWith = delegate.doWith(object);
  2680. return new GeoResult<>(doWith, new Distance(distance, metric));
  2681. }
  2682. }
  2683. /**
  2684. * A {@link CloseableIterator} that is backed by a MongoDB {@link MongoCollection}.
  2685. *
  2686. * @author Thomas Darimont
  2687. * @since 1.7
  2688. */
  2689. static class CloseableIterableCursorAdapter<T> implements CloseableIterator<T> {
  2690. private volatile @Nullable MongoCursor<Document> cursor;
  2691. private PersistenceExceptionTranslator exceptionTranslator;
  2692. private DocumentCallback<T> objectReadCallback;
  2693. /**
  2694. * Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link MongoCollection}.
  2695. */
  2696. CloseableIterableCursorAdapter(MongoIterable<Document> cursor, PersistenceExceptionTranslator exceptionTranslator,
  2697. DocumentCallback<T> objectReadCallback) {
  2698. this.cursor = cursor.iterator();
  2699. this.exceptionTranslator = exceptionTranslator;
  2700. this.objectReadCallback = objectReadCallback;
  2701. }
  2702. CloseableIterableCursorAdapter(MongoCursor<Document> cursor, PersistenceExceptionTranslator exceptionTranslator,
  2703. DocumentCallback<T> objectReadCallback) {
  2704. this.cursor = cursor;
  2705. this.exceptionTranslator = exceptionTranslator;
  2706. this.objectReadCallback = objectReadCallback;
  2707. }
  2708. @Override
  2709. public boolean hasNext() {
  2710. MongoCursor<Document> cursor = this.cursor;
  2711. if (cursor == null) {
  2712. return false;
  2713. }
  2714. try {
  2715. return cursor.hasNext();
  2716. } catch (RuntimeException ex) {
  2717. throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
  2718. }
  2719. }
  2720. @Nullable
  2721. @Override
  2722. public T next() {
  2723. if (cursor == null) {
  2724. return null;
  2725. }
  2726. try {
  2727. Document item = cursor.next();
  2728. return objectReadCallback.doWith(item);
  2729. } catch (RuntimeException ex) {
  2730. throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
  2731. }
  2732. }
  2733. @Override
  2734. public void close() {
  2735. MongoCursor<Document> c = cursor;
  2736. try {
  2737. if (c != null) {
  2738. c.close();
  2739. }
  2740. } catch (RuntimeException ex) {
  2741. throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
  2742. } finally {
  2743. cursor = null;
  2744. exceptionTranslator = null;
  2745. objectReadCallback = null;
  2746. }
  2747. }
  2748. }
  2749. /**
  2750. * @deprecated since 3.1.4. Use {@link #getMongoDatabaseFactory()} instead.
  2751. * @return the {@link MongoDatabaseFactory} in use.
  2752. */
  2753. @Deprecated
  2754. public MongoDatabaseFactory getMongoDbFactory() {
  2755. return getMongoDatabaseFactory();
  2756. }
  2757. /**
  2758. * @return the {@link MongoDatabaseFactory} in use.
  2759. * @since 3.1.4
  2760. */
  2761. public MongoDatabaseFactory getMongoDatabaseFactory() {
  2762. return mongoDbFactory;
  2763. }
  2764. /**
  2765. * {@link MongoTemplate} extension bound to a specific {@link ClientSession} that is applied when interacting with the
  2766. * server through the driver API.
  2767. * <br />
  2768. * The prepare steps for {@link MongoDatabase} and {@link MongoCollection} proxy the target and invoke the desired
  2769. * target method matching the actual arguments plus a {@link ClientSession}.
  2770. *
  2771. * @author Christoph Strobl
  2772. * @since 2.1
  2773. */
  2774. static class SessionBoundMongoTemplate extends MongoTemplate {
  2775. private final MongoTemplate delegate;
  2776. private final ClientSession session;
  2777. /**
  2778. * @param session must not be {@literal null}.
  2779. * @param that must not be {@literal null}.
  2780. */
  2781. SessionBoundMongoTemplate(ClientSession session, MongoTemplate that) {
  2782. super(that.getMongoDbFactory().withSession(session), that);
  2783. this.delegate = that;
  2784. this.session = session;
  2785. }
  2786. /*
  2787. * (non-Javadoc)
  2788. * @see org.springframework.data.mongodb.core.MongoTemplate#getCollection(java.lang.String)
  2789. */
  2790. @Override
  2791. public MongoCollection<Document> getCollection(String collectionName) {
  2792. // native MongoDB objects that offer methods with ClientSession must not be proxied.
  2793. return delegate.getCollection(collectionName);
  2794. }
  2795. /*
  2796. * (non-Javadoc)
  2797. * @see org.springframework.data.mongodb.core.MongoTemplate#getDb()
  2798. */
  2799. @Override
  2800. public MongoDatabase getDb() {
  2801. // native MongoDB objects that offer methods with ClientSession must not be proxied.
  2802. return delegate.getDb();
  2803. }
  2804. }
  2805. }