From 131a2912e9ffeb36febd487f9feeb37963560ed3 Mon Sep 17 00:00:00 2001 From: Mark Pollack Date: Tue, 31 May 2011 15:55:13 -0400 Subject: [PATCH] DATADOC-157 - MongoTemplate updateFirst/updateMulti methods to take java.lang.Class parameter as last in method param list to be consistent with other usage --- .../document/mongodb/MongoOperations.java | 58 +- .../data/document/mongodb/MongoTemplate.java | 2764 +++++++++-------- .../document/mongodb/MongoTemplateTests.java | 4 +- .../mongodb/mapping/MappingTests.java | 12 +- 4 files changed, 1547 insertions(+), 1291 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoOperations.java index c26fcbeaf..70f86df9f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoOperations.java @@ -190,11 +190,11 @@ public interface MongoOperations { * If your collection does not contain a homogeneous collection of types, this operation will not be an efficient way * to map objects since the test for class type is done in the client and not on the server. * - * @param targetClass + * @param entityClass * the parameterized type of the returned list * @return the converted collection */ - List getCollection(Class targetClass); + List getCollection(Class entityClass); /** * Query for a list of objects of type T from the specified collection. @@ -207,11 +207,11 @@ public interface MongoOperations { * * @param collectionName * name of the collection to retrieve the objects from - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the converted collection */ - List getCollection(String collectionName, Class targetClass); + List getCollection(String collectionName, Class entityClass); /** * Ensure that an index for the provided {@link IndexDefinition} exists for the default collection. If not it will be @@ -244,11 +244,11 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the converted object */ - T findOne(Query query, Class targetClass); + T findOne(Query query, Class entityClass); /** * Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified @@ -265,11 +265,11 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the converted object */ - T findOne(String collectionName, Query query, Class targetClass); + T findOne(String collectionName, Query query, Class entityClass); /** * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. @@ -283,11 +283,11 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the List of converted objects */ - List find(Query query, Class targetClass); + List find(Query query, Class entityClass); /** * Map the results of an ad-hoc query on the specified collection to a List of the specified type. @@ -303,11 +303,11 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the List of converted objects */ - List find(String collectionName, Query query, Class targetClass); + List find(String collectionName, Query query, Class entityClass); /** * Map the results of an ad-hoc query on the specified collection to a List of the specified type. @@ -323,14 +323,14 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @param preparer * allows for customization of the DBCursor used when iterating over the result set, (apply limits, skips and * so on). * @return the List of converted objects. */ - List find(String collectionName, Query query, Class targetClass, CursorPreparer preparer); + List find(String collectionName, Query query, Class entityClass, CursorPreparer preparer); /** * Returns a document with the given id mapped onto the given class. The collection the query is ran against will be @@ -338,10 +338,10 @@ public interface MongoOperations { * * @param * @param id the id of the document to return. - * @param targetClass the type the document shall be converted into. + * @param entityClass the type the document shall be converted into. * @return the document with the given id mapped onto the given target class. */ - T findById(Object id, Class targetClass); + T findById(Object id, Class entityClass); /** * Returns the document with the given id from the given collection mapped onto the given target class. @@ -349,10 +349,10 @@ public interface MongoOperations { * @param * @param collectionName the collection to query for the document * @param id the id of the document to return - * @param targetClass the type to convert the document to + * @param entityClass the type to convert the document to * @return */ - T findById(String collectionName, Object id, Class targetClass); + T findById(String collectionName, Object id, Class entityClass); /** * Map the results of an ad-hoc query on the default MongoDB collection to a single instance of an object of the @@ -368,11 +368,11 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the converted object */ - T findAndRemove(Query query, Class targetClass); + T findAndRemove(Query query, Class entityClass); /** * Map the results of an ad-hoc query on the specified collection to a single instance of an object of the specified @@ -389,11 +389,11 @@ public interface MongoOperations { * @param query * the query class that specifies the criteria used to find a record and also an optional fields * specification - * @param targetClass + * @param entityClass * the parameterized type of the returned list. * @return the converted object */ - T findAndRemove(String collectionName, Query query, Class targetClass); + T findAndRemove(String collectionName, Query query, Class entityClass); /** * Insert the object into the default collection. @@ -489,7 +489,6 @@ public interface MongoOperations { /** * Updates the first object that is found in the default collection that matches the query document with the provided * updated document. - * * @param entityClass * class that determines the collection to use * @param queryDoc @@ -497,7 +496,7 @@ public interface MongoOperations { * @param updateDoc * the update document that contains the updated object or $ operators to manipulate the existing object. */ - WriteResult updateFirst(Class entityClass, Query query, Update update); + WriteResult updateFirst(Query query, Update update, Class entityClass); /** * Updates the first object that is found in the specified collection that matches the query document criteria with @@ -515,7 +514,6 @@ public interface MongoOperations { /** * Updates all objects that are found in the default collection that matches the query document criteria with the * provided updated document. - * * @param entityClass * class that determines the collection to use * @param queryDoc @@ -523,7 +521,7 @@ public interface MongoOperations { * @param updateDoc * the update document that contains the updated object or $ operators to manipulate the existing object. */ - WriteResult updateMulti(Class entityClass, Query query, Update update); + WriteResult updateMulti(Query query, Update update, Class entityClass); /** * Updates all objects that are found in the specified collection that matches the query document criteria with the @@ -559,9 +557,9 @@ public interface MongoOperations { * * @param * @param query - * @param targetClass + * @param entityClass */ - void remove(Query query, Class targetClass); + void remove(Query query, Class entityClass); /** * Remove all documents from the specified collection that match the provided query document criteria. @@ -579,8 +577,8 @@ public interface MongoOperations { * * @param collectionName * @param query - * @param targetClass + * @param entityClass */ - void remove(String collectionName, Query query, Class targetClass); + void remove(String collectionName, Query query, Class entityClass); } \ No newline at end of file diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java index c3255e199..5eb76308d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java @@ -79,7 +79,7 @@ import org.springframework.util.Assert; /** * Primary implementation of {@link MongoOperations}. - * + * * @author Thomas Risberg * @author Graeme Rocher * @author Mark Pollack @@ -87,1257 +87,1515 @@ import org.springframework.util.Assert; */ public class MongoTemplate implements MongoOperations, ApplicationContextAware { - private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class); - - private static final String ID = "_id"; - private static final List ITERABLE_CLASSES = new ArrayList() {{ - add(List.class.getName()); - add(Collection.class.getName()); - add(Iterator.class.getName()); - }}; - - /* - * WriteConcern to be used for write operations if it has been specified. Otherwise - * we should not use a WriteConcern defaulting to the one set for the DB or Collection. - */ - private WriteConcern writeConcern = null; - - /* - * WriteResultChecking to be used for write operations if it has been specified. Otherwise - * we should not do any checking. - */ - private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; - - /* - * Flag used to indicate use of slaveOk() for any operations on collections. - */ - private boolean slaveOk = false; - - private MongoConverter mongoConverter; - private MappingContext, MongoPersistentProperty> mappingContext; - private MongoDbFactory mongoDbFactory; - private MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator(); - private QueryMapper mapper; - - private ApplicationEventPublisher eventPublisher; - private MongoPersistentEntityIndexCreator indexCreator; - - /** - * Constructor used for a basic template configuration - * - * @param mongo - * @param databaseName - */ - public MongoTemplate(Mongo mongo, String databaseName) { - this(new SimpleMongoDbFactory(mongo, databaseName), null, null, null); - } - - /** - * Constructor used for a template configuration with user credentials in the form of - * {@link org.springframework.data.authentication.UserCredentials} - * - * @param mongo - * @param databaseName - * @param userCredentials - */ - public MongoTemplate(Mongo mongo, String databaseName, UserCredentials userCredentials) { - this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials)); - } - - /** - * Constructor used for a basic template configuration - * - * @param mongoDbFactory - */ - public MongoTemplate(MongoDbFactory mongoDbFactory) { - this(mongoDbFactory, null, null, null); - } - - /** - * Constructor used for a basic template configuration - * - * @param mongoDbFactory - * @param mongoConverter - */ - public MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { - this(mongoDbFactory, mongoConverter, null, null); - } - - /** - * Constructor used for a template configuration with a custom {@link MongoConverter} and with a specific - * {@link com.mongodb.WriteConcern} to be used for all database write operations - * - * @param mongoDbFactory - * @param mongoConverter - * @param writeConcern - * @param writeResultChecking - */ - MongoTemplate(MongoDbFactory mongoDbFactory, - MongoConverter mongoConverter, - WriteConcern writeConcern, - WriteResultChecking writeResultChecking) { - Assert.notNull(mongoDbFactory); - // Always need a MongoDbFactory for obtaining instances of DB - this.mongoDbFactory = mongoDbFactory; - // Conversion of DBObject to POJO handled either custom or by default (MappingMongoConverter) - if (null == mongoConverter) { - this.mongoConverter = getDefaultMongoConverter(); - } else { - this.mongoConverter = mongoConverter; - } - // We always have a mapping context in the converter, whether it's a simple one or not - mappingContext = this.mongoConverter.getMappingContext(); - // We create indexes based on mapping events - if (null != mappingContext && mappingContext instanceof MongoMappingContext) { - indexCreator = new MongoPersistentEntityIndexCreator((MongoMappingContext) mappingContext, mongoDbFactory); - eventPublisher = new MongoMappingEventPublisher(indexCreator); - if (mappingContext instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); - } - } - // WriteConcern - this.writeConcern = writeConcern; - // For converting ID names and values throughout Query objects - mapper = new QueryMapper(this.mongoConverter); - // Track WriteResults? - if (writeResultChecking != null) { - this.writeResultChecking = writeResultChecking; - } - - } - - private final MongoConverter getDefaultMongoConverter() { - //ToDo: maybe add some additional configurations to this very basic one - MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory, new MongoMappingContext()); - converter.afterPropertiesSet(); - return converter; - } - - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - String[] beans = applicationContext.getBeanNamesForType(MongoPersistentEntityIndexCreator.class); - if ((null == beans || beans.length == 0) && applicationContext instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) applicationContext).addApplicationListener(indexCreator); - } - eventPublisher = applicationContext; - if (mappingContext instanceof ApplicationEventPublisherAware) { - ((ApplicationEventPublisherAware) mappingContext).setApplicationEventPublisher(eventPublisher); - } - } - - /** - * Returns the default {@link org.springframework.data.document.mongodb.convert.MongoConverter}. - * - * @return - */ - public MongoConverter getConverter() { - return this.mongoConverter; - } - - /** - * Returns the {@link org.springframework.data.document.mongodb.MongoDbFactory}. - * - * @return - */ - public MongoDbFactory getDbFactory() { - return this.mongoDbFactory; - } - - public MappingContext, MongoPersistentProperty> getMappingContext() { - return mappingContext; - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollectionName() - */ - public String getCollectionName(Class clazz) { - return this.determineCollectionName(clazz); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#executeCommand(java.lang.String) - */ - public CommandResult executeCommand(String jsonCommand) { - return executeCommand((DBObject) JSON.parse(jsonCommand)); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#executeCommand(com.mongodb.DBObject) - */ - public CommandResult executeCommand(final DBObject command) { - - CommandResult result = execute(new DbCallback() { - public CommandResult doInDB(DB db) throws MongoException, DataAccessException { - return db.command(command); - } - }); - - String error = result.getErrorMessage(); - if (error != null) { - // TODO: allow configuration of logging level / throw - // throw new InvalidDataAccessApiUsageException("Command execution of " + - // command.toString() + " failed: " + error); - LOGGER.warn("Command execution of " + command.toString() + " failed: " + error); - } - return result; - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.DBCallback) - */ - public T execute(DbCallback action) { - - Assert.notNull(action); - - try { - DB db = this.getDb(); - return action.doInDB(db); - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback) - */ - public T execute(Class entityClass, CollectionCallback callback) { - return execute(determineCollectionName(entityClass), callback); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback, java.lang.String) - */ - public T execute(String collectionName, CollectionCallback callback) { - - Assert.notNull(callback); - - try { - DBCollection collection = getAndPrepareCollection(getDb(), collectionName); - return callback.doInCollection(collection); - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#executeInSession(org.springframework.data.document.mongodb.DBCallback) - */ - public T executeInSession(final DbCallback action) { - - return execute(new DbCallback() { - public T doInDB(DB db) throws MongoException, DataAccessException { - try { - db.requestStart(); - return action.doInDB(db); - } finally { - db.requestDone(); - } - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String) - */ - public DBCollection createCollection(final String collectionName) { - return doCreateCollection(collectionName, new BasicDBObject()); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String, org.springframework.data.document.mongodb.CollectionOptions) - */ - public DBCollection createCollection(final String collectionName, final CollectionOptions collectionOptions) { - return doCreateCollection(collectionName, convertToDbObject(collectionOptions)); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String) - */ - public DBCollection getCollection(final String collectionName) { - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollection(collectionName); - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#collectionExists(java.lang.String) - */ - public boolean collectionExists(final String collectionName) { - return execute(new DbCallback() { - public Boolean doInDB(DB db) throws MongoException, DataAccessException { - return db.collectionExists(collectionName); - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#dropCollection(java.lang.String) - */ - public void dropCollection(String collectionName) { - - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - collection.drop(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Dropped collection [" + collection.getFullName() + "]"); - } - return null; - } - }); - } - - // Indexing methods - - public void ensureIndex(Class entityClass, IndexDefinition indexDefinition) { - ensureIndex(determineCollectionName(entityClass), indexDefinition); - } - - public void ensureIndex(String collectionName, final IndexDefinition indexDefinition) { - execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - DBObject indexOptions = indexDefinition.getIndexOptions(); - if (indexOptions != null) { - collection.ensureIndex(indexDefinition.getIndexKeys(), indexOptions); - } else { - collection.ensureIndex(indexDefinition.getIndexKeys()); - } - return null; - } - }); - } - - // Find methods that take a Query to express the query and that return a single object. - - public T findOne(Query query, Class targetClass) { - return findOne(determineCollectionName(targetClass), query, targetClass); - } - - public T findOne(String collectionName, Query query, Class targetClass) { - return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass); - } - - // Find methods that take a Query to express the query and that return a List of objects. - - public List find(Query query, Class targetClass) { - return find(determineCollectionName(targetClass), query, targetClass); - } - - public List find(String collectionName, final Query query, Class targetClass) { - CursorPreparer cursorPreparer = null; - if (query.getSkip() > 0 || query.getLimit() > 0 || query.getSortObject() != null) { - cursorPreparer = new CursorPreparer() { - - public DBCursor prepare(DBCursor cursor) { - DBCursor cursorToUse = cursor; - try { - if (query.getSkip() > 0) { - cursorToUse = cursorToUse.skip(query.getSkip()); - } - if (query.getLimit() > 0) { - cursorToUse = cursorToUse.limit(query.getLimit()); - } - if (query.getSortObject() != null) { - cursorToUse = cursorToUse.sort(query.getSortObject()); - } - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e); - } - return cursorToUse; - } - }; - } - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, cursorPreparer); - } - - public List find(String collectionName, Query query, Class targetClass, CursorPreparer preparer) { - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, preparer); - } - - public T findById(Object id, Class targetClass) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(targetClass); - return findById(persistentEntity.getCollection(), id, targetClass); - } - - public T findById(String collectionName, Object id, Class targetClass) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(targetClass); - MongoPersistentProperty idProperty = persistentEntity.getIdProperty(); - String idKey = idProperty == null ? ID : idProperty.getName(); - return doFindOne(collectionName, new BasicDBObject(idKey, id), null, targetClass); - } - - // Find methods that take a Query to express the query and that return a single object that is - // also removed from the collection in the database. - - public T findAndRemove(Query query, Class targetClass) { - return findAndRemove(determineCollectionName(targetClass), query, targetClass); - } - - public T findAndRemove(String collectionName, Query query, Class targetClass) { - return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(), - targetClass); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.Object) - */ - public void insert(Object objectToSave) { - ensureNotIterable(objectToSave); - insert(determineEntityCollectionName(objectToSave), objectToSave); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.String, java.lang.Object) - */ - public void insert(String collectionName, Object objectToSave) { - ensureNotIterable(objectToSave); - doInsert(collectionName, objectToSave, this.mongoConverter); - } - - protected void ensureNotIterable(Object o) { - if (null != o) { - if (o.getClass().isArray() || - ITERABLE_CLASSES.contains(o.getClass().getName())) { - throw new IllegalArgumentException("Cannot use a collection here."); - } - } - } - - /** - * Prepare the collection before any processing is done using it. This allows a convenient way to apply - * settings like slaveOk() etc. Can be overridden in sub-classes. - * - * @param collection - */ - protected void prepareCollection(DBCollection collection) { - if (this.slaveOk) { - collection.slaveOk(); - } - } - - /** - * Prepare the WriteConcern before any processing is done using it. This allows a convenient way to apply - * custom settings in sub-classes. - * - * @param writeConcern any WriteConcern already configured or null - * @return The prepared WriteConcern or null - */ - protected WriteConcern prepareWriteConcern(WriteConcern writeConcern) { - return writeConcern; - } - - protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { - BasicDBObject dbDoc = new BasicDBObject(); - - maybeEmitEvent(new BeforeConvertEvent(objectToSave)); - writer.write(objectToSave, dbDoc); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc)); - Object id = insertDBObject(collectionName, dbDoc); - - populateIdIfNecessary(objectToSave, id); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc)); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.util.List) - */ - public void insertList(List listToSave) { - doInsertList(listToSave, mongoConverter); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List) - */ - public void insertList(String collectionName, List listToSave) { - doInsertList(collectionName, listToSave, this.mongoConverter); - } - - protected void doInsertList(List listToSave, MongoWriter writer) { - Map> objs = new HashMap>(); - - for (T o : listToSave) { - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(o.getClass()); - if (entity == null) { - throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " - + o.getClass().getName()); - } - String collection = entity.getCollection(); - - List objList = objs.get(collection); - if (null == objList) { - objList = new ArrayList(); - objs.put(collection, objList); - } - objList.add(o); - - } - - for (Map.Entry> entry : objs.entrySet()) { - doInsertList(entry.getKey(), entry.getValue(), this.mongoConverter); - } - } - - protected void doInsertList(String collectionName, List listToSave, MongoWriter writer) { - - Assert.notNull(writer); - - List dbObjectList = new ArrayList(); - for (T o : listToSave) { - BasicDBObject dbDoc = new BasicDBObject(); - - maybeEmitEvent(new BeforeConvertEvent(o)); - writer.write(o, dbDoc); - - maybeEmitEvent(new BeforeSaveEvent(o, dbDoc)); - dbObjectList.add(dbDoc); - } - List ids = insertDBObjectList(collectionName, dbObjectList); - for (int i = 0; i < listToSave.size(); i++) { - if (i < ids.size()) { - T obj = listToSave.get(i); - populateIdIfNecessary(obj, ids.get(i)); - maybeEmitEvent(new AfterSaveEvent(obj, dbObjectList.get(i))); - } - } - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#save(java.lang.Object) - */ - public void save(Object objectToSave) { - save(determineEntityCollectionName(objectToSave), objectToSave); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#save(java.lang.String, java.lang.Object) - */ - public void save(String collectionName, Object objectToSave) { - doSave(collectionName, objectToSave, this.mongoConverter); - } - - protected void doSave(String collectionName, T objectToSave, MongoWriter writer) { - BasicDBObject dbDoc = new BasicDBObject(); - - maybeEmitEvent(new BeforeConvertEvent(objectToSave)); - writer.write(objectToSave, dbDoc); - - maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc)); - Object id = saveDBObject(collectionName, dbDoc); - - populateIdIfNecessary(objectToSave, id); - maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc)); - } - - protected Object insertDBObject(String collectionName, final DBObject dbDoc) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + " in collection: " + collectionName); - } - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); - if (writeConcernToUse == null) { - collection.insert(dbDoc); - } else { - collection.insert(dbDoc, writeConcernToUse); - } - return dbDoc.get(ID); - } - }); - } - - protected List insertDBObjectList(String collectionName, final List dbDocList) { - if (dbDocList.isEmpty()) { - return Collections.emptyList(); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("insert list of DBObjects containing " + dbDocList.size() + " items"); - } - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); - if (writeConcernToUse == null) { - collection.insert(dbDocList); - } else { - collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcernToUse); - } - return null; - } - }); - - List ids = new ArrayList(); - for (DBObject dbo : dbDocList) { - Object id = dbo.get(ID); - if (id instanceof ObjectId) { - ids.add((ObjectId) id); - } else { - // no id was generated - ids.add(null); - } - } - return ids; - } - - protected Object saveDBObject(String collectionName, final DBObject dbDoc) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("save DBObject containing fields: " + dbDoc.keySet()); - } - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); - if (writeConcernToUse == null) { - collection.save(dbDoc); - } else { - collection.save(dbDoc, writeConcernToUse); - } - return dbDoc.get(ID); - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(com.mongodb.DBObject, com.mongodb.DBObject) - */ - public WriteResult updateFirst(Class entityClass, Query query, Update update) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject) - */ - public WriteResult updateFirst(final String collectionName, final Query query, final Update update) { - return doUpdate(collectionName, query, update, null, false, false); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(com.mongodb.DBObject, com.mongodb.DBObject) - */ - public WriteResult updateMulti(Class entityClass, Query query, Update update) { - return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject) - */ - public WriteResult updateMulti(String collectionName, final Query query, final Update update) { - return doUpdate(collectionName, query, update, null, false, true); - } - - protected WriteResult doUpdate(final String collectionName, final Query query, final Update update, - final Class entityClass, final boolean upsert, final boolean multi) { - - return execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - DBObject queryObj = query.getQueryObject(); - DBObject updateObj = update.getUpdateObject(); - - String idProperty = "id"; - if (null != entityClass) { - idProperty = getPersistentEntity(entityClass).getIdProperty().getName(); - } - for (String key : queryObj.keySet()) { - if (idProperty.equals(key)) { - // This is an ID field - queryObj.put(ID, mongoConverter.maybeConvertObject(queryObj.get(key))); - queryObj.removeField(key); - } else { - queryObj.put(key, mongoConverter.maybeConvertObject(queryObj.get(key))); - } - } - - for (String key : updateObj.keySet()) { - updateObj.put(key, mongoConverter.maybeConvertObject(updateObj.get(key))); - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("calling update using query: " + queryObj + " and update: " + updateObj + " in collection: " - + collectionName); - } - - WriteResult wr; - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); - if (writeConcernToUse == null) { - if (multi) { - wr = collection.updateMulti(queryObj, updateObj); - } else { - wr = collection.update(queryObj, updateObj); - } - } else { - wr = collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse); - } - handleAnyWriteResultErrors(wr, queryObj, "update with '" + updateObj + "'"); - return wr; - } - }); - - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#remove(com.mongodb.DBObject) - */ - public void remove(Query query) { - remove(query, null); - } - - public void remove(Object object) { - remove(new Query(where(getIdPropertyName(object)).is(getIdValue(object))), object.getClass()); - } - - public void remove(Query query, Class targetClass) { - Assert.notNull(query); - remove(determineCollectionName(targetClass), query, targetClass); - } - - public void remove(String collectionName, final Query query, Class targetClass) { - if (query == null) { - throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null"); - } - final DBObject queryObject = query.getQueryObject(); - final MongoPersistentEntity entity = getPersistentEntity(targetClass); - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - DBObject dboq = mapper.getMappedObject(queryObject, entity); - WriteResult wr = null; - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("remove using query: " + queryObject + " in collection: " + collection.getName()); - } - if (writeConcernToUse == null) { - wr = collection.remove(dboq); - } else { - wr = collection.remove(dboq, writeConcernToUse); - } - handleAnyWriteResultErrors(wr, dboq, "remove"); - return null; - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#remove(java.lang.String, com.mongodb.DBObject) - */ - public void remove(String collectionName, final Query query) { - remove(collectionName, query, null); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.Class) - */ - public List getCollection(Class targetClass) { - return executeFindMultiInternal(new FindCallback(null), null, new ReadDbObjectCallback(mongoConverter, targetClass), - determineCollectionName(targetClass)); - } - - public List getCollection(String collectionName, Class targetClass) { - return executeFindMultiInternal(new FindCallback(null), null, new ReadDbObjectCallback(mongoConverter, targetClass), - collectionName); - } - - public Set getCollectionNames() { - return execute(new DbCallback>() { - public Set doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollectionNames(); - } - }); - } - - public DB getDb() { - return mongoDbFactory.getDb(); - } - - protected void maybeEmitEvent(MongoMappingEvent event) { - if (null != eventPublisher) { - eventPublisher.publishEvent(event); - } - } - - /** - * Create the specified collection using the provided options - * - * @param collectionName - * @param collectionOptions - * @return the collection that was created - */ - protected DBCollection doCreateCollection(final String collectionName, final DBObject collectionOptions) { - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - DBCollection coll = db.createCollection(collectionName, collectionOptions); - // TODO: Emit a collection created event - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Created collection [" + coll.getFullName() + "]"); - } - return coll; - } - }); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter - *

- * The query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param fields the document that specifies the fields to be returned - * @param targetClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class targetClass) { - MongoReader readerToUse = this.mongoConverter; - MongoPersistentEntity entity = mappingContext.getPersistentEntity(targetClass); - DBObject mappedQuery = mapper.getMappedObject(query, entity); - - return executeFindOneInternal(new FindOneCallback(mappedQuery, fields), new ReadDbObjectCallback(readerToUse, targetClass), - collectionName); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to a List of the specified type. - *

- * The object is converted from the MongoDB native representation using an instance of {@see MongoConverter}. Unless - * configured otherwise, an instance of SimpleMongoConverter will be used. - *

- * The query document is specified as a standard DBObject and so is the fields specification. - *

- * Can be overridden by subclasses. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param fields the document that specifies the fields to be returned - * @param targetClass the parameterized type of the returned list. - * @param preparer allows for customization of the DBCursor used when iterating over the result set, (apply limits, skips and - * so on). - * @return the List of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class targetClass, - CursorPreparer preparer) { - MongoPersistentEntity entity = mappingContext.getPersistentEntity(targetClass); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + targetClass - + " in collection: " + collectionName); - } - return executeFindMultiInternal(new FindCallback(mapper.getMappedObject(query, entity), fields), preparer, - new ReadDbObjectCallback(mongoConverter, targetClass), collectionName); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to a List using the template's converter. - *

- * The query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param fields the document that specifies the fields to be returned - * @param targetClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class targetClass) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("find using query: " + query + " fields: " + fields + " for class: " + targetClass - + " in collection: " + collectionName); - } - MongoReader readerToUse = this.mongoConverter; - MongoPersistentEntity entity = mappingContext.getPersistentEntity(targetClass); - return executeFindMultiInternal(new FindCallback(mapper.getMappedObject(query, entity), fields), null, - new ReadDbObjectCallback(readerToUse, targetClass), collectionName); - } - - protected DBObject convertToDbObject(CollectionOptions collectionOptions) { - DBObject dbo = new BasicDBObject(); - if (collectionOptions != null) { - if (collectionOptions.getCapped() != null) { - dbo.put("capped", collectionOptions.getCapped().booleanValue()); - } - if (collectionOptions.getSize() != null) { - dbo.put("size", collectionOptions.getSize().intValue()); - } - if (collectionOptions.getMaxDocuments() != null) { - dbo.put("max", collectionOptions.getMaxDocuments().intValue()); - } - } - return dbo; - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter. - * The first document that matches the query is returned and also removed from the collection in the database. - *

- * The query document is specified as a standard DBObject and so is the fields specification. - * - * @param collectionName name of the collection to retrieve the objects from - * @param query the query document that specifies the criteria used to find a record - * @param targetClass the parameterized type of the returned list. - * @return the List of converted objects. - */ - protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, - Class targetClass) { - MongoReader readerToUse = this.mongoConverter; - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findAndRemove using query: " + query + " fields: " + fields + " sort: " + sort + " for class: " - + targetClass + " in collection: " + collectionName); - } - MongoPersistentEntity entity = mappingContext.getPersistentEntity(targetClass); - return executeFindOneInternal(new FindAndRemoveCallback(mapper.getMappedObject(query, entity), fields, sort), - new ReadDbObjectCallback(readerToUse, targetClass), collectionName); - } - - protected Object getIdValue(Object object) { - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(object.getClass()); - MongoPersistentProperty idProp = entity.getIdProperty(); - - if (idProp == null) { - throw new MappingException("No id property found for object of type " + entity.getType().getName()); - } - - ConversionService service = mongoConverter.getConversionService(); - - try { - return BeanWrapper.create(object, service).getProperty(idProp, Object.class, true); - } catch (IllegalAccessException e) { - throw new MappingException(e.getMessage(), e); - } catch (InvocationTargetException e) { - throw new MappingException(e.getMessage(), e); - } - } - - protected String getIdPropertyName(Object object) { - MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(object.getClass()); - MongoPersistentProperty idProperty = persistentEntity.getIdProperty(); - return idProperty == null ? ID : idProperty.getName(); - } - - /** - * Populates the id property of the saved object, if it's not set already. - * - * @param savedObject - * @param id - */ - protected void populateIdIfNecessary(Object savedObject, Object id) { - - if (id == null) { - return; - } - - MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass()); - - if (idProp == null) { - return; - } - - try { - BeanWrapper.create(savedObject, mongoConverter.getConversionService()).setProperty(idProp, id); - return; - } catch (IllegalAccessException e) { - throw new MappingException(e.getMessage(), e); - } catch (InvocationTargetException e) { - throw new MappingException(e.getMessage(), e); - } - } - - private DBCollection getAndPrepareCollection(DB db, String collectionName) { - try { - DBCollection collection = db.getCollection(collectionName); - prepareCollection(collection); - return collection; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /** - * Internal method using callbacks to do queries against the datastore that requires reading a single object from a - * collection of objects. It will take the following steps - *

    - *
  1. Execute the given {@link ConnectionCallback} for a {@link DBObject}.
  2. - *
  3. Apply the given {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
  4. - *
      - * - * @param - * @param collectionCallback the callback to retrieve the {@link DBObject} with - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried - * @return - */ - private T executeFindOneInternal(CollectionCallback collectionCallback, DbObjectCallback objectCallback, - String collectionName) { - - try { - T result = objectCallback.doWith(collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName))); - return result; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /** - * Internal method using callback to do queries against the datastore that requires reading a collection of - * objects. It will take the following steps - *
        - *
      1. Execute the given {@link ConnectionCallback} for a {@link DBCursor}.
      2. - *
      3. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped if {@link CursorPreparer} - * is {@literal null}
      4. - *
      5. Iterate over the {@link DBCursor} and applies the given {@link DbObjectCallback} to each of the - * {@link DBObject}s collecting the actual result {@link List}.
      6. - *
          - * - * @param - * @param collectionCallback the callback to retrieve the {@link DBCursor} with - * @param preparer the {@link CursorPreparer} to potentially modify the {@link DBCursor} before ireating over it - * @param objectCallback the {@link DbObjectCallback} to transform {@link DBObject}s into the actual domain type - * @param collectionName the collection to be queried - * @return - */ - private List executeFindMultiInternal(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { - - try { - DBCursor cursor = collectionCallback.doInCollection(getAndPrepareCollection(getDb(), collectionName)); - - if (preparer != null) { - cursor = preparer.prepare(cursor); - } - - List result = new ArrayList(); - - for (DBObject object : cursor) { - result.add(objectCallback.doWith(object)); - } - - return result; - } catch (RuntimeException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - private MongoPersistentEntity getPersistentEntity(Class type) { - return type == null ? null : mappingContext.getPersistentEntity(type); - } - - private MongoPersistentProperty getIdPropertyFor(Class type) { - return mappingContext.getPersistentEntity(type).getIdProperty(); - } - - private ObjectId convertIdValue(MongoConverter converter, Object value) { - ObjectId newValue = null; - try { - if (value instanceof String && ObjectId.isValid((String) value)) { - newValue = converter.convertObjectId(value); - } - } catch (ConversionFailedException iae) { - LOGGER.warn("Unable to convert the String " + value + " to an ObjectId"); - } - return newValue; - } - - private String determineEntityCollectionName(T obj) { - if (null != obj) { - return determineCollectionName(obj.getClass()); - } - - return null; - } - - private String determineCollectionName(Class clazz) { - - if (clazz == null) { - throw new InvalidDataAccessApiUsageException( - "No class parameter provided, entity collection can't be determined for " + clazz); - } - - MongoPersistentEntity entity = mappingContext.getPersistentEntity(clazz); - if (entity == null) { - throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " - + clazz.getName()); - } - return entity.getCollection(); - } - - /** - * Checks and handles any errors. - *

          - * TODO: current implementation logs errors - will be configurable to log warning, errors or throw exception in later - * versions - */ - private void handleAnyWriteResultErrors(WriteResult wr, DBObject query, String operation) { - if (WriteResultChecking.NONE == this.writeResultChecking) { - return; - } - String error = wr.getError(); - int n = wr.getN(); - if (error != null) { - String message = "Execution of '" + operation + (query == null ? "" : "' using '" + query.toString() + "' query") - + " failed: " + error; - if (WriteResultChecking.EXCEPTION == this.writeResultChecking) { - throw new DataIntegrityViolationException(message); - } else { - LOGGER.error(message); - } - } else if (n == 0) { - String message = "Execution of '" + operation + (query == null ? "" : "' using '" + query.toString() + "' query") - + " did not succeed: 0 documents updated"; - if (WriteResultChecking.EXCEPTION == this.writeResultChecking) { - throw new DataIntegrityViolationException(message); - } else { - LOGGER.warn(message); - } - } - - } - - /** - * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original - * exception if the conversation failed. Thus allows safe rethrowing of the return value. - * - * @param ex - * @return - */ - private RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) { - RuntimeException resolved = this.exceptionTranslator.translateExceptionIfPossible(ex); - return resolved == null ? ex : resolved; - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Oliver Gierke - * @author Thomas Risberg - */ - private static class FindOneCallback implements CollectionCallback { - - private final DBObject query; - - private final DBObject fields; - - public FindOneCallback(DBObject query, DBObject fields) { - this.query = query; - this.fields = fields; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (fields == null) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: " + query + " in db.collection: " + collection.getFullName()); - } - return collection.findOne(query); - } else { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("findOne using query: " + query + " fields: " + fields + " in db.collection: " - + collection.getFullName()); - } - return collection.findOne(query, fields); - } - } - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Oliver Gierke - * @author Thomas Risberg - */ - private static class FindCallback implements CollectionCallback { - - private final DBObject query; - - private final DBObject fields; - - public FindCallback(DBObject query) { - this(query, null); - } - - public FindCallback(DBObject query, DBObject fields) { - this.query = query; - this.fields = fields; - } - - public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (fields == null) { - return collection.find(query); - } else { - return collection.find(query, fields); - } - } - } - - /** - * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus an optional fields specification - * {@link DBObject} and executes that against the {@link DBCollection}. - * - * @author Thomas Risberg - */ - private static class FindAndRemoveCallback implements CollectionCallback { - - private final DBObject query; - - private final DBObject fields; - - private final DBObject sort; - - public FindAndRemoveCallback(DBObject query, DBObject fields, DBObject sort) { - this.query = query; - this.fields = fields; - this.sort = sort; - } - - public DBObject doInCollection(DBCollection collection) throws MongoException, DataAccessException { - return collection.findAndModify(query, fields, sort, true, null, false, false); - } - } - - /** - * Simple internal callback to allow operations on a {@link DBObject}. - * - * @author Oliver Gierke - */ - - private interface DbObjectCallback { - - T doWith(DBObject object); - } - - /** - * Simple {@link DbObjectCallback} that will transform {@link DBObject} into the given target type using the given - * {@link MongoReader}. - * - * @author Oliver Gierke - */ - private class ReadDbObjectCallback implements DbObjectCallback { - - private final MongoReader reader; - private final Class type; - - public ReadDbObjectCallback(MongoReader reader, Class type) { - this.reader = reader; - this.type = type; - } - - public T doWith(DBObject object) { - if (null != object) { - maybeEmitEvent(new AfterLoadEvent(object)); - } - T source = reader.read(type, object); - if (null != source) { - maybeEmitEvent(new AfterConvertEvent(object, source)); - } - return source; - } - } - - public void setWriteResultChecking(WriteResultChecking resultChecking) { - this.writeResultChecking = resultChecking; - } - - public void setWriteConcern(WriteConcern writeConcern) { - this.writeConcern = writeConcern; - } - - public void setSlaveOk(boolean slaveOk) { - this.slaveOk = slaveOk; - } + private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class); + + private static final String ID = "_id"; + private static final List ITERABLE_CLASSES = new ArrayList() { + { + add(List.class.getName()); + add(Collection.class.getName()); + add(Iterator.class.getName()); + } + }; + + /* + * WriteConcern to be used for write operations if it has been specified. + * Otherwise we should not use a WriteConcern defaulting to the one set for + * the DB or Collection. + */ + private WriteConcern writeConcern = null; + + /* + * WriteResultChecking to be used for write operations if it has been + * specified. Otherwise we should not do any checking. + */ + private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; + + /* + * Flag used to indicate use of slaveOk() for any operations on collections. + */ + private boolean slaveOk = false; + + private MongoConverter mongoConverter; + private MappingContext, MongoPersistentProperty> mappingContext; + private MongoDbFactory mongoDbFactory; + private MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator(); + private QueryMapper mapper; + + private ApplicationEventPublisher eventPublisher; + private MongoPersistentEntityIndexCreator indexCreator; + + /** + * Constructor used for a basic template configuration + * + * @param mongo + * @param databaseName + */ + public MongoTemplate(Mongo mongo, String databaseName) { + this(new SimpleMongoDbFactory(mongo, databaseName), null, null, null); + } + + /** + * Constructor used for a template configuration with user credentials in the + * form of {@link org.springframework.data.authentication.UserCredentials} + * + * @param mongo + * @param databaseName + * @param userCredentials + */ + public MongoTemplate(Mongo mongo, String databaseName, + UserCredentials userCredentials) { + this(new SimpleMongoDbFactory(mongo, databaseName, userCredentials)); + } + + /** + * Constructor used for a basic template configuration + * + * @param mongoDbFactory + */ + public MongoTemplate(MongoDbFactory mongoDbFactory) { + this(mongoDbFactory, null, null, null); + } + + /** + * Constructor used for a basic template configuration + * + * @param mongoDbFactory + * @param mongoConverter + */ + public MongoTemplate(MongoDbFactory mongoDbFactory, + MongoConverter mongoConverter) { + this(mongoDbFactory, mongoConverter, null, null); + } + + /** + * Constructor used for a template configuration with a custom + * {@link MongoConverter} and with a specific {@link com.mongodb.WriteConcern} + * to be used for all database write operations + * + * @param mongoDbFactory + * @param mongoConverter + * @param writeConcern + * @param writeResultChecking + */ + MongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter, + WriteConcern writeConcern, WriteResultChecking writeResultChecking) { + Assert.notNull(mongoDbFactory); + // Always need a MongoDbFactory for obtaining instances of DB + this.mongoDbFactory = mongoDbFactory; + // Conversion of DBObject to POJO handled either custom or by default + // (MappingMongoConverter) + if (null == mongoConverter) { + this.mongoConverter = getDefaultMongoConverter(); + } else { + this.mongoConverter = mongoConverter; + } + // We always have a mapping context in the converter, whether it's a simple + // one or not + mappingContext = this.mongoConverter.getMappingContext(); + // We create indexes based on mapping events + if (null != mappingContext && mappingContext instanceof MongoMappingContext) { + indexCreator = new MongoPersistentEntityIndexCreator( + (MongoMappingContext) mappingContext, mongoDbFactory); + eventPublisher = new MongoMappingEventPublisher(indexCreator); + if (mappingContext instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) mappingContext) + .setApplicationEventPublisher(eventPublisher); + } + } + // WriteConcern + this.writeConcern = writeConcern; + // For converting ID names and values throughout Query objects + mapper = new QueryMapper(this.mongoConverter); + // Track WriteResults? + if (writeResultChecking != null) { + this.writeResultChecking = writeResultChecking; + } + + } + + private final MongoConverter getDefaultMongoConverter() { + // ToDo: maybe add some additional configurations to this very basic one + MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory, + new MongoMappingContext()); + converter.afterPropertiesSet(); + return converter; + } + + public void setApplicationContext(ApplicationContext applicationContext) + throws BeansException { + String[] beans = applicationContext + .getBeanNamesForType(MongoPersistentEntityIndexCreator.class); + if ((null == beans || beans.length == 0) + && applicationContext instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) applicationContext) + .addApplicationListener(indexCreator); + } + eventPublisher = applicationContext; + if (mappingContext instanceof ApplicationEventPublisherAware) { + ((ApplicationEventPublisherAware) mappingContext) + .setApplicationEventPublisher(eventPublisher); + } + } + + /** + * Returns the default + * {@link org.springframework.data.document.mongodb.convert.MongoConverter}. + * + * @return + */ + public MongoConverter getConverter() { + return this.mongoConverter; + } + + /** + * Returns the + * {@link org.springframework.data.document.mongodb.MongoDbFactory}. + * + * @return + */ + public MongoDbFactory getDbFactory() { + return this.mongoDbFactory; + } + + public MappingContext, MongoPersistentProperty> getMappingContext() { + return mappingContext; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoOperations# + * getDefaultCollectionName() + */ + public String getCollectionName(Class clazz) { + return this.determineCollectionName(clazz); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#executeCommand + * (java.lang.String) + */ + public CommandResult executeCommand(String jsonCommand) { + return executeCommand((DBObject) JSON.parse(jsonCommand)); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#executeCommand + * (com.mongodb.DBObject) + */ + public CommandResult executeCommand(final DBObject command) { + + CommandResult result = execute(new DbCallback() { + public CommandResult doInDB(DB db) throws MongoException, + DataAccessException { + return db.command(command); + } + }); + + String error = result.getErrorMessage(); + if (error != null) { + // TODO: allow configuration of logging level / throw + // throw new InvalidDataAccessApiUsageException("Command execution of " + + // command.toString() + " failed: " + error); + LOGGER.warn("Command execution of " + command.toString() + " failed: " + + error); + } + return result; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoOperations#execute(org. + * springframework.data.document.mongodb.DBCallback) + */ + public T execute(DbCallback action) { + + Assert.notNull(action); + + try { + DB db = this.getDb(); + return action.doInDB(db); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoOperations#execute(org. + * springframework.data.document.mongodb.CollectionCallback) + */ + public T execute(Class entityClass, CollectionCallback callback) { + return execute(determineCollectionName(entityClass), callback); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoOperations#execute(org. + * springframework.data.document.mongodb.CollectionCallback, java.lang.String) + */ + public T execute(String collectionName, CollectionCallback callback) { + + Assert.notNull(callback); + + try { + DBCollection collection = getAndPrepareCollection(getDb(), collectionName); + return callback.doInCollection(collection); + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#executeInSession + * (org.springframework.data.document.mongodb.DBCallback) + */ + public T executeInSession(final DbCallback action) { + + return execute(new DbCallback() { + public T doInDB(DB db) throws MongoException, DataAccessException { + try { + db.requestStart(); + return action.doInDB(db); + } finally { + db.requestDone(); + } + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#createCollection + * (java.lang.String) + */ + public DBCollection createCollection(final String collectionName) { + return doCreateCollection(collectionName, new BasicDBObject()); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#createCollection + * (java.lang.String, + * org.springframework.data.document.mongodb.CollectionOptions) + */ + public DBCollection createCollection(final String collectionName, + final CollectionOptions collectionOptions) { + return doCreateCollection(collectionName, + convertToDbObject(collectionOptions)); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#getCollection + * (java.lang.String) + */ + public DBCollection getCollection(final String collectionName) { + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, + DataAccessException { + return db.getCollection(collectionName); + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#collectionExists + * (java.lang.String) + */ + public boolean collectionExists(final String collectionName) { + return execute(new DbCallback() { + public Boolean doInDB(DB db) throws MongoException, DataAccessException { + return db.collectionExists(collectionName); + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#dropCollection + * (java.lang.String) + */ + public void dropCollection(String collectionName) { + + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + collection.drop(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Dropped collection [" + collection.getFullName() + "]"); + } + return null; + } + }); + } + + // Indexing methods + + public void ensureIndex(Class entityClass, IndexDefinition indexDefinition) { + ensureIndex(determineCollectionName(entityClass), indexDefinition); + } + + public void ensureIndex(String collectionName, + final IndexDefinition indexDefinition) { + execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + DBObject indexOptions = indexDefinition.getIndexOptions(); + if (indexOptions != null) { + collection.ensureIndex(indexDefinition.getIndexKeys(), indexOptions); + } else { + collection.ensureIndex(indexDefinition.getIndexKeys()); + } + return null; + } + }); + } + + // Find methods that take a Query to express the query and that return a + // single object. + + public T findOne(Query query, Class entityClass) { + return findOne(determineCollectionName(entityClass), query, entityClass); + } + + public T findOne(String collectionName, Query query, Class entityClass) { + return doFindOne(collectionName, query.getQueryObject(), + query.getFieldsObject(), entityClass); + } + + // Find methods that take a Query to express the query and that return a List + // of objects. + + public List find(Query query, Class entityClass) { + return find(determineCollectionName(entityClass), query, entityClass); + } + + public List find(String collectionName, final Query query, + Class entityClass) { + CursorPreparer cursorPreparer = null; + if (query.getSkip() > 0 || query.getLimit() > 0 + || query.getSortObject() != null) { + cursorPreparer = new CursorPreparer() { + + public DBCursor prepare(DBCursor cursor) { + DBCursor cursorToUse = cursor; + try { + if (query.getSkip() > 0) { + cursorToUse = cursorToUse.skip(query.getSkip()); + } + if (query.getLimit() > 0) { + cursorToUse = cursorToUse.limit(query.getLimit()); + } + if (query.getSortObject() != null) { + cursorToUse = cursorToUse.sort(query.getSortObject()); + } + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + return cursorToUse; + } + }; + } + return doFind(collectionName, query.getQueryObject(), + query.getFieldsObject(), entityClass, cursorPreparer); + } + + public List find(String collectionName, Query query, + Class entityClass, CursorPreparer preparer) { + return doFind(collectionName, query.getQueryObject(), + query.getFieldsObject(), entityClass, preparer); + } + + public T findById(Object id, Class entityClass) { + MongoPersistentEntity persistentEntity = mappingContext + .getPersistentEntity(entityClass); + return findById(persistentEntity.getCollection(), id, entityClass); + } + + public T findById(String collectionName, Object id, Class entityClass) { + MongoPersistentEntity persistentEntity = mappingContext + .getPersistentEntity(entityClass); + MongoPersistentProperty idProperty = persistentEntity.getIdProperty(); + String idKey = idProperty == null ? ID : idProperty.getName(); + return doFindOne(collectionName, new BasicDBObject(idKey, id), null, + entityClass); + } + + // Find methods that take a Query to express the query and that return a + // single object that is + // also removed from the collection in the database. + + public T findAndRemove(Query query, Class entityClass) { + return findAndRemove(determineCollectionName(entityClass), query, + entityClass); + } + + public T findAndRemove(String collectionName, Query query, + Class entityClass) { + return doFindAndRemove(collectionName, query.getQueryObject(), + query.getFieldsObject(), query.getSortObject(), entityClass); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#insert(java.lang + * .Object) + */ + public void insert(Object objectToSave) { + ensureNotIterable(objectToSave); + insert(determineEntityCollectionName(objectToSave), objectToSave); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#insert(java.lang + * .String, java.lang.Object) + */ + public void insert(String collectionName, Object objectToSave) { + ensureNotIterable(objectToSave); + doInsert(collectionName, objectToSave, this.mongoConverter); + } + + protected void ensureNotIterable(Object o) { + if (null != o) { + if (o.getClass().isArray() + || ITERABLE_CLASSES.contains(o.getClass().getName())) { + throw new IllegalArgumentException("Cannot use a collection here."); + } + } + } + + /** + * Prepare the collection before any processing is done using it. This allows + * a convenient way to apply settings like slaveOk() etc. Can be overridden in + * sub-classes. + * + * @param collection + */ + protected void prepareCollection(DBCollection collection) { + if (this.slaveOk) { + collection.slaveOk(); + } + } + + /** + * Prepare the WriteConcern before any processing is done using it. This + * allows a convenient way to apply custom settings in sub-classes. + * + * @param writeConcern + * any WriteConcern already configured or null + * @return The prepared WriteConcern or null + */ + protected WriteConcern prepareWriteConcern(WriteConcern writeConcern) { + return writeConcern; + } + + protected void doInsert(String collectionName, T objectToSave, + MongoWriter writer) { + BasicDBObject dbDoc = new BasicDBObject(); + + maybeEmitEvent(new BeforeConvertEvent(objectToSave)); + writer.write(objectToSave, dbDoc); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc)); + Object id = insertDBObject(collectionName, dbDoc); + + populateIdIfNecessary(objectToSave, id); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc)); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#insertList(java + * .util.List) + */ + public void insertList(List listToSave) { + doInsertList(listToSave, mongoConverter); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#insertList(java + * .lang.String, java.util.List) + */ + public void insertList(String collectionName, + List listToSave) { + doInsertList(collectionName, listToSave, this.mongoConverter); + } + + protected void doInsertList(List listToSave, + MongoWriter writer) { + Map> objs = new HashMap>(); + + for (T o : listToSave) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(o + .getClass()); + if (entity == null) { + throw new InvalidDataAccessApiUsageException( + "No Persitent Entity information found for the class " + + o.getClass().getName()); + } + String collection = entity.getCollection(); + + List objList = objs.get(collection); + if (null == objList) { + objList = new ArrayList(); + objs.put(collection, objList); + } + objList.add(o); + + } + + for (Map.Entry> entry : objs.entrySet()) { + doInsertList(entry.getKey(), entry.getValue(), this.mongoConverter); + } + } + + protected void doInsertList(String collectionName, + List listToSave, MongoWriter writer) { + + Assert.notNull(writer); + + List dbObjectList = new ArrayList(); + for (T o : listToSave) { + BasicDBObject dbDoc = new BasicDBObject(); + + maybeEmitEvent(new BeforeConvertEvent(o)); + writer.write(o, dbDoc); + + maybeEmitEvent(new BeforeSaveEvent(o, dbDoc)); + dbObjectList.add(dbDoc); + } + List ids = insertDBObjectList(collectionName, dbObjectList); + for (int i = 0; i < listToSave.size(); i++) { + if (i < ids.size()) { + T obj = listToSave.get(i); + populateIdIfNecessary(obj, ids.get(i)); + maybeEmitEvent(new AfterSaveEvent(obj, dbObjectList.get(i))); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#save(java.lang + * .Object) + */ + public void save(Object objectToSave) { + save(determineEntityCollectionName(objectToSave), objectToSave); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#save(java.lang + * .String, java.lang.Object) + */ + public void save(String collectionName, Object objectToSave) { + doSave(collectionName, objectToSave, this.mongoConverter); + } + + protected void doSave(String collectionName, T objectToSave, + MongoWriter writer) { + BasicDBObject dbDoc = new BasicDBObject(); + + maybeEmitEvent(new BeforeConvertEvent(objectToSave)); + writer.write(objectToSave, dbDoc); + + maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc)); + Object id = saveDBObject(collectionName, dbDoc); + + populateIdIfNecessary(objectToSave, id); + maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc)); + } + + protected Object insertDBObject(String collectionName, final DBObject dbDoc) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + + " in collection: " + collectionName); + } + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + if (writeConcernToUse == null) { + collection.insert(dbDoc); + } else { + collection.insert(dbDoc, writeConcernToUse); + } + return dbDoc.get(ID); + } + }); + } + + protected List insertDBObjectList(String collectionName, + final List dbDocList) { + if (dbDocList.isEmpty()) { + return Collections.emptyList(); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("insert list of DBObjects containing " + dbDocList.size() + + " items"); + } + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + if (writeConcernToUse == null) { + collection.insert(dbDocList); + } else { + collection.insert(dbDocList + .toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), + writeConcernToUse); + } + return null; + } + }); + + List ids = new ArrayList(); + for (DBObject dbo : dbDocList) { + Object id = dbo.get(ID); + if (id instanceof ObjectId) { + ids.add((ObjectId) id); + } else { + // no id was generated + ids.add(null); + } + } + return ids; + } + + protected Object saveDBObject(String collectionName, final DBObject dbDoc) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("save DBObject containing fields: " + dbDoc.keySet()); + } + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + if (writeConcernToUse == null) { + collection.save(dbDoc); + } else { + collection.save(dbDoc, writeConcernToUse); + } + return dbDoc.get(ID); + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#updateFirst(com + * .mongodb.DBObject, com.mongodb.DBObject) + */ + public WriteResult updateFirst(Query query, Update update, + Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, + entityClass, false, false); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#updateFirst(java + * .lang.String, com.mongodb.DBObject, com.mongodb.DBObject) + */ + public WriteResult updateFirst(final String collectionName, + final Query query, final Update update) { + return doUpdate(collectionName, query, update, null, false, false); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#updateMulti(com + * .mongodb.DBObject, com.mongodb.DBObject) + */ + public WriteResult updateMulti(Query query, Update update, + Class entityClass) { + return doUpdate(determineCollectionName(entityClass), query, update, + entityClass, false, true); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#updateMulti(java + * .lang.String, com.mongodb.DBObject, com.mongodb.DBObject) + */ + public WriteResult updateMulti(String collectionName, final Query query, + final Update update) { + return doUpdate(collectionName, query, update, null, false, true); + } + + protected WriteResult doUpdate(final String collectionName, + final Query query, final Update update, final Class entityClass, + final boolean upsert, final boolean multi) { + + return execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + DBObject queryObj = query.getQueryObject(); + DBObject updateObj = update.getUpdateObject(); + + String idProperty = "id"; + if (null != entityClass) { + idProperty = getPersistentEntity(entityClass).getIdProperty() + .getName(); + } + for (String key : queryObj.keySet()) { + if (idProperty.equals(key)) { + // This is an ID field + queryObj.put(ID, + mongoConverter.maybeConvertObject(queryObj.get(key))); + queryObj.removeField(key); + } else { + queryObj.put(key, + mongoConverter.maybeConvertObject(queryObj.get(key))); + } + } + + for (String key : updateObj.keySet()) { + updateObj.put(key, + mongoConverter.maybeConvertObject(updateObj.get(key))); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("calling update using query: " + queryObj + + " and update: " + updateObj + " in collection: " + + collectionName); + } + + WriteResult wr; + WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + if (writeConcernToUse == null) { + if (multi) { + wr = collection.updateMulti(queryObj, updateObj); + } else { + wr = collection.update(queryObj, updateObj); + } + } else { + wr = collection.update(queryObj, updateObj, upsert, multi, + writeConcernToUse); + } + handleAnyWriteResultErrors(wr, queryObj, "update with '" + updateObj + + "'"); + return wr; + } + }); + + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#remove(com.mongodb + * .DBObject) + */ + public void remove(Query query) { + remove(query, null); + } + + public void remove(Object object) { + remove(new Query(where(getIdPropertyName(object)).is(getIdValue(object))), + object.getClass()); + } + + public void remove(Query query, Class entityClass) { + Assert.notNull(query); + remove(determineCollectionName(entityClass), query, entityClass); + } + + public void remove(String collectionName, final Query query, + Class entityClass) { + if (query == null) { + throw new InvalidDataAccessApiUsageException( + "Query passed in to remove can't be null"); + } + final DBObject queryObject = query.getQueryObject(); + final MongoPersistentEntity entity = getPersistentEntity(entityClass); + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + DBObject dboq = mapper.getMappedObject(queryObject, entity); + WriteResult wr = null; + WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("remove using query: " + queryObject + + " in collection: " + collection.getName()); + } + if (writeConcernToUse == null) { + wr = collection.remove(dboq); + } else { + wr = collection.remove(dboq, writeConcernToUse); + } + handleAnyWriteResultErrors(wr, dboq, "remove"); + return null; + } + }); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#remove(java.lang + * .String, com.mongodb.DBObject) + */ + public void remove(String collectionName, final Query query) { + remove(collectionName, query, null); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.data.document.mongodb.MongoOperations#getCollection + * (java.lang.Class) + */ + public List getCollection(Class entityClass) { + return executeFindMultiInternal(new FindCallback(null), null, + new ReadDbObjectCallback(mongoConverter, entityClass), + determineCollectionName(entityClass)); + } + + public List getCollection(String collectionName, Class entityClass) { + return executeFindMultiInternal(new FindCallback(null), null, + new ReadDbObjectCallback(mongoConverter, entityClass), + collectionName); + } + + public Set getCollectionNames() { + return execute(new DbCallback>() { + public Set doInDB(DB db) throws MongoException, + DataAccessException { + return db.getCollectionNames(); + } + }); + } + + public DB getDb() { + return mongoDbFactory.getDb(); + } + + protected void maybeEmitEvent(MongoMappingEvent event) { + if (null != eventPublisher) { + eventPublisher.publishEvent(event); + } + } + + /** + * Create the specified collection using the provided options + * + * @param collectionName + * @param collectionOptions + * @return the collection that was created + */ + protected DBCollection doCreateCollection(final String collectionName, + final DBObject collectionOptions) { + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, + DataAccessException { + DBCollection coll = db.createCollection(collectionName, + collectionOptions); + // TODO: Emit a collection created event + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Created collection [" + coll.getFullName() + "]"); + } + return coll; + } + }); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an + * object using the template's converter + *

          + * The query document is specified as a standard DBObject and so is the fields + * specification. + * + * @param collectionName + * name of the collection to retrieve the objects from + * @param query + * the query document that specifies the criteria used to find a + * record + * @param fields + * the document that specifies the fields to be returned + * @param entityClass + * the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected T doFindOne(String collectionName, DBObject query, + DBObject fields, Class entityClass) { + MongoReader readerToUse = this.mongoConverter; + MongoPersistentEntity entity = mappingContext + .getPersistentEntity(entityClass); + DBObject mappedQuery = mapper.getMappedObject(query, entity); + + return executeFindOneInternal(new FindOneCallback(mappedQuery, fields), + new ReadDbObjectCallback(readerToUse, entityClass), collectionName); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a + * List of the specified type. + *

          + * The object is converted from the MongoDB native representation using an + * instance of {@see MongoConverter}. Unless configured otherwise, an instance + * of SimpleMongoConverter will be used. + *

          + * The query document is specified as a standard DBObject and so is the fields + * specification. + *

          + * Can be overridden by subclasses. + * + * @param collectionName + * name of the collection to retrieve the objects from + * @param query + * the query document that specifies the criteria used to find a + * record + * @param fields + * the document that specifies the fields to be returned + * @param entityClass + * the parameterized type of the returned list. + * @param preparer + * allows for customization of the DBCursor used when iterating over + * the result set, (apply limits, skips and so on). + * @return the List of converted objects. + */ + protected List doFind(String collectionName, DBObject query, + DBObject fields, Class entityClass, CursorPreparer preparer) { + MongoPersistentEntity entity = mappingContext + .getPersistentEntity(entityClass); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("find using query: " + query + " fields: " + fields + + " for class: " + entityClass + " in collection: " + collectionName); + } + return executeFindMultiInternal( + new FindCallback(mapper.getMappedObject(query, entity), fields), + preparer, new ReadDbObjectCallback(mongoConverter, entityClass), + collectionName); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to a + * List using the template's converter. + *

          + * The query document is specified as a standard DBObject and so is the fields + * specification. + * + * @param collectionName + * name of the collection to retrieve the objects from + * @param query + * the query document that specifies the criteria used to find a + * record + * @param fields + * the document that specifies the fields to be returned + * @param entityClass + * the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected List doFind(String collectionName, DBObject query, + DBObject fields, Class entityClass) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("find using query: " + query + " fields: " + fields + + " for class: " + entityClass + " in collection: " + collectionName); + } + MongoReader readerToUse = this.mongoConverter; + MongoPersistentEntity entity = mappingContext + .getPersistentEntity(entityClass); + return executeFindMultiInternal( + new FindCallback(mapper.getMappedObject(query, entity), fields), null, + new ReadDbObjectCallback(readerToUse, entityClass), collectionName); + } + + protected DBObject convertToDbObject(CollectionOptions collectionOptions) { + DBObject dbo = new BasicDBObject(); + if (collectionOptions != null) { + if (collectionOptions.getCapped() != null) { + dbo.put("capped", collectionOptions.getCapped().booleanValue()); + } + if (collectionOptions.getSize() != null) { + dbo.put("size", collectionOptions.getSize().intValue()); + } + if (collectionOptions.getMaxDocuments() != null) { + dbo.put("max", collectionOptions.getMaxDocuments().intValue()); + } + } + return dbo; + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an + * object using the template's converter. The first document that matches the + * query is returned and also removed from the collection in the database. + *

          + * The query document is specified as a standard DBObject and so is the fields + * specification. + * + * @param collectionName + * name of the collection to retrieve the objects from + * @param query + * the query document that specifies the criteria used to find a + * record + * @param entityClass + * the parameterized type of the returned list. + * @return the List of converted objects. + */ + protected T doFindAndRemove(String collectionName, DBObject query, + DBObject fields, DBObject sort, Class entityClass) { + MongoReader readerToUse = this.mongoConverter; + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findAndRemove using query: " + query + " fields: " + fields + + " sort: " + sort + " for class: " + entityClass + + " in collection: " + collectionName); + } + MongoPersistentEntity entity = mappingContext + .getPersistentEntity(entityClass); + return executeFindOneInternal( + new FindAndRemoveCallback(mapper.getMappedObject(query, entity), + fields, sort), + new ReadDbObjectCallback(readerToUse, entityClass), collectionName); + } + + protected Object getIdValue(Object object) { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(object + .getClass()); + MongoPersistentProperty idProp = entity.getIdProperty(); + + if (idProp == null) { + throw new MappingException("No id property found for object of type " + + entity.getType().getName()); + } + + ConversionService service = mongoConverter.getConversionService(); + + try { + return BeanWrapper.create(object, service).getProperty(idProp, + Object.class, true); + } catch (IllegalAccessException e) { + throw new MappingException(e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new MappingException(e.getMessage(), e); + } + } + + protected String getIdPropertyName(Object object) { + MongoPersistentEntity persistentEntity = mappingContext + .getPersistentEntity(object.getClass()); + MongoPersistentProperty idProperty = persistentEntity.getIdProperty(); + return idProperty == null ? ID : idProperty.getName(); + } + + /** + * Populates the id property of the saved object, if it's not set already. + * + * @param savedObject + * @param id + */ + protected void populateIdIfNecessary(Object savedObject, Object id) { + + if (id == null) { + return; + } + + MongoPersistentProperty idProp = getIdPropertyFor(savedObject.getClass()); + + if (idProp == null) { + return; + } + + try { + BeanWrapper.create(savedObject, mongoConverter.getConversionService()) + .setProperty(idProp, id); + return; + } catch (IllegalAccessException e) { + throw new MappingException(e.getMessage(), e); + } catch (InvocationTargetException e) { + throw new MappingException(e.getMessage(), e); + } + } + + private DBCollection getAndPrepareCollection(DB db, String collectionName) { + try { + DBCollection collection = db.getCollection(collectionName); + prepareCollection(collection); + return collection; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /** + * Internal method using callbacks to do queries against the datastore that + * requires reading a single object from a collection of objects. It will take + * the following steps + *

            + *
          1. Execute the given {@link ConnectionCallback} for a {@link DBObject}.
          2. + *
          3. Apply the given {@link DbObjectCallback} to each of the + * {@link DBObject}s to obtain the result.
          4. + *
              + * + * @param + * @param collectionCallback + * the callback to retrieve the {@link DBObject} with + * @param objectCallback + * the {@link DbObjectCallback} to transform {@link DBObject}s into + * the actual domain type + * @param collectionName + * the collection to be queried + * @return + */ + private T executeFindOneInternal( + CollectionCallback collectionCallback, + DbObjectCallback objectCallback, String collectionName) { + + try { + T result = objectCallback.doWith(collectionCallback + .doInCollection(getAndPrepareCollection(getDb(), collectionName))); + return result; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /** + * Internal method using callback to do queries against the datastore that + * requires reading a collection of objects. It will take the following steps + *
                + *
              1. Execute the given {@link ConnectionCallback} for a {@link DBCursor}.
              2. + *
              3. Prepare that {@link DBCursor} with the given {@link CursorPreparer} + * (will be skipped if {@link CursorPreparer} is {@literal null}
              4. + *
              5. Iterate over the {@link DBCursor} and applies the given + * {@link DbObjectCallback} to each of the {@link DBObject}s collecting the + * actual result {@link List}.
              6. + *
                  + * + * @param + * @param collectionCallback + * the callback to retrieve the {@link DBCursor} with + * @param preparer + * the {@link CursorPreparer} to potentially modify the + * {@link DBCursor} before ireating over it + * @param objectCallback + * the {@link DbObjectCallback} to transform {@link DBObject}s into + * the actual domain type + * @param collectionName + * the collection to be queried + * @return + */ + private List executeFindMultiInternal( + CollectionCallback collectionCallback, CursorPreparer preparer, + DbObjectCallback objectCallback, String collectionName) { + + try { + DBCursor cursor = collectionCallback + .doInCollection(getAndPrepareCollection(getDb(), collectionName)); + + if (preparer != null) { + cursor = preparer.prepare(cursor); + } + + List result = new ArrayList(); + + for (DBObject object : cursor) { + result.add(objectCallback.doWith(object)); + } + + return result; + } catch (RuntimeException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + private MongoPersistentEntity getPersistentEntity(Class type) { + return type == null ? null : mappingContext.getPersistentEntity(type); + } + + private MongoPersistentProperty getIdPropertyFor(Class type) { + return mappingContext.getPersistentEntity(type).getIdProperty(); + } + + private ObjectId convertIdValue(MongoConverter converter, Object value) { + ObjectId newValue = null; + try { + if (value instanceof String && ObjectId.isValid((String) value)) { + newValue = converter.convertObjectId(value); + } + } catch (ConversionFailedException iae) { + LOGGER.warn("Unable to convert the String " + value + " to an ObjectId"); + } + return newValue; + } + + private String determineEntityCollectionName(T obj) { + if (null != obj) { + return determineCollectionName(obj.getClass()); + } + + return null; + } + + private String determineCollectionName(Class clazz) { + + if (clazz == null) { + throw new InvalidDataAccessApiUsageException( + "No class parameter provided, entity collection can't be determined for " + + clazz); + } + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(clazz); + if (entity == null) { + throw new InvalidDataAccessApiUsageException( + "No Persitent Entity information found for the class " + + clazz.getName()); + } + return entity.getCollection(); + } + + /** + * Checks and handles any errors. + *

                  + * TODO: current implementation logs errors - will be configurable to log + * warning, errors or throw exception in later versions + */ + private void handleAnyWriteResultErrors(WriteResult wr, DBObject query, + String operation) { + if (WriteResultChecking.NONE == this.writeResultChecking) { + return; + } + String error = wr.getError(); + int n = wr.getN(); + if (error != null) { + String message = "Execution of '" + operation + + (query == null ? "" : "' using '" + query.toString() + "' query") + + " failed: " + error; + if (WriteResultChecking.EXCEPTION == this.writeResultChecking) { + throw new DataIntegrityViolationException(message); + } else { + LOGGER.error(message); + } + } else if (n == 0) { + String message = "Execution of '" + operation + + (query == null ? "" : "' using '" + query.toString() + "' query") + + " did not succeed: 0 documents updated"; + if (WriteResultChecking.EXCEPTION == this.writeResultChecking) { + throw new DataIntegrityViolationException(message); + } else { + LOGGER.warn(message); + } + } + + } + + /** + * Tries to convert the given {@link RuntimeException} into a + * {@link DataAccessException} but returns the original exception if the + * conversation failed. Thus allows safe rethrowing of the return value. + * + * @param ex + * @return + */ + private RuntimeException potentiallyConvertRuntimeException( + RuntimeException ex) { + RuntimeException resolved = this.exceptionTranslator + .translateExceptionIfPossible(ex); + return resolved == null ? ex : resolved; + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus + * an optional fields specification {@link DBObject} and executes that against + * the {@link DBCollection}. + * + * @author Oliver Gierke + * @author Thomas Risberg + */ + private static class FindOneCallback implements CollectionCallback { + + private final DBObject query; + + private final DBObject fields; + + public FindOneCallback(DBObject query, DBObject fields) { + this.query = query; + this.fields = fields; + } + + public DBObject doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + if (fields == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: " + query + " in db.collection: " + + collection.getFullName()); + } + return collection.findOne(query); + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("findOne using query: " + query + " fields: " + fields + + " in db.collection: " + collection.getFullName()); + } + return collection.findOne(query, fields); + } + } + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus + * an optional fields specification {@link DBObject} and executes that against + * the {@link DBCollection}. + * + * @author Oliver Gierke + * @author Thomas Risberg + */ + private static class FindCallback implements CollectionCallback { + + private final DBObject query; + + private final DBObject fields; + + public FindCallback(DBObject query) { + this(query, null); + } + + public FindCallback(DBObject query, DBObject fields) { + this.query = query; + this.fields = fields; + } + + public DBCursor doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + if (fields == null) { + return collection.find(query); + } else { + return collection.find(query, fields); + } + } + } + + /** + * Simple {@link CollectionCallback} that takes a query {@link DBObject} plus + * an optional fields specification {@link DBObject} and executes that against + * the {@link DBCollection}. + * + * @author Thomas Risberg + */ + private static class FindAndRemoveCallback implements + CollectionCallback { + + private final DBObject query; + + private final DBObject fields; + + private final DBObject sort; + + public FindAndRemoveCallback(DBObject query, DBObject fields, DBObject sort) { + this.query = query; + this.fields = fields; + this.sort = sort; + } + + public DBObject doInCollection(DBCollection collection) + throws MongoException, DataAccessException { + return collection.findAndModify(query, fields, sort, true, null, false, + false); + } + } + + /** + * Simple internal callback to allow operations on a {@link DBObject}. + * + * @author Oliver Gierke + */ + + private interface DbObjectCallback { + + T doWith(DBObject object); + } + + /** + * Simple {@link DbObjectCallback} that will transform {@link DBObject} into + * the given target type using the given {@link MongoReader}. + * + * @author Oliver Gierke + */ + private class ReadDbObjectCallback implements DbObjectCallback { + + private final MongoReader reader; + private final Class type; + + public ReadDbObjectCallback(MongoReader reader, Class type) { + this.reader = reader; + this.type = type; + } + + public T doWith(DBObject object) { + if (null != object) { + maybeEmitEvent(new AfterLoadEvent(object)); + } + T source = reader.read(type, object); + if (null != source) { + maybeEmitEvent(new AfterConvertEvent(object, source)); + } + return source; + } + } + + public void setWriteResultChecking(WriteResultChecking resultChecking) { + this.writeResultChecking = resultChecking; + } + + public void setWriteConcern(WriteConcern writeConcern) { + this.writeConcern = writeConcern; + } + + public void setSlaveOk(boolean slaveOk) { + this.slaveOk = slaveOk; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java index 88e78b761..fe2605dff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoTemplateTests.java @@ -123,7 +123,7 @@ public class MongoTemplateTests { Update u = new Update().set("firstName", "Sven"); thrown.expect(DataIntegrityViolationException.class); thrown.expectMessage(endsWith("0 documents updated")); - mongoTemplate.updateFirst(Person.class, q, u); + mongoTemplate.updateFirst(q, u, Person.class); } @@ -419,7 +419,7 @@ public class MongoTemplateTests { Update u = new Update().set("firstName", "Bob").set("age", 10); - WriteResult wr = template.updateMulti(PersonWithIdPropertyOfTypeObjectId.class, new Query(), u); + WriteResult wr = template.updateMulti(new Query(), u, PersonWithIdPropertyOfTypeObjectId.class); assertThat(wr.getN(), is(2)); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java index dff70e4b7..1427d26a2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java @@ -343,7 +343,7 @@ public class MappingTests { template.insert(p); addr.setCity("New Town"); - template.updateFirst(Person.class, query(where("ssn").is(1111)), update("address", addr)); + template.updateFirst(query(where("ssn").is(1111)), update("address", addr), Person.class); Person p2 = template.findOne(query(where("ssn").is(1111)), Person.class); assertThat(p2.getAddress().getCity(), is("New Town")); @@ -380,7 +380,7 @@ public class MappingTests { public void testNoMappingAnnotationsUsingIntAsId() { PersonPojoIntId p = new PersonPojoIntId(1, "Text"); template.insert(p); - template.updateFirst(PersonPojoIntId.class, query(where("id").is(1)), update("text", "New Text")); + template.updateFirst(query(where("id").is(1)), update("text", "New Text"), PersonPojoIntId.class); PersonPojoIntId p2 = template.findOne(query(where("id").is(1)), PersonPojoIntId.class); assertEquals("New Text", p2.getText()); @@ -397,8 +397,8 @@ public class MappingTests { public void testNoMappingAnnotationsUsingLongAsId() { PersonPojoLongId p = new PersonPojoLongId(1, "Text"); template.insert(p); - template.updateFirst(PersonPojoLongId.class, query(where("id").is(1)), - update("text", "New Text")); + template.updateFirst(query(where("id").is(1)), update("text", "New Text"), + PersonPojoLongId.class); PersonPojoLongId p2 = template.findOne(query(where("id").is(1)), PersonPojoLongId.class); @@ -419,8 +419,8 @@ public class MappingTests { //Assign the String Id in code PersonPojoStringId p = new PersonPojoStringId("1", "Text"); template.insert(p); - template.updateFirst(PersonPojoLongId.class, query(where("id").is("1")), - update("text", "New Text")); + template.updateFirst(query(where("id").is("1")), update("text", "New Text"), + PersonPojoLongId.class); PersonPojoStringId p2 = template.findOne(query(where("id").is("1")), PersonPojoStringId.class);