From c274bef3c88b2b0c3a4531dd9b3a657a81d321cb Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Wed, 6 Apr 2011 10:44:09 -0500 Subject: [PATCH] Removed some obsolete code, changed the way the converter handles _id types. --- .../data/document/mongodb/MongoTemplate.java | 2484 ++++++++--------- .../convert/MappingMongoConverter.java | 7 +- .../mongodb/mapping/MappingTests.java | 235 +- .../mongodb/mapping/PersonCustomIdName.java | 27 +- 4 files changed, 1367 insertions(+), 1386 deletions(-) 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 684523d3f..aacdb8dd9 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 @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -22,9 +22,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; import com.mongodb.BasicDBObject; import com.mongodb.CommandResult; @@ -40,13 +37,9 @@ import com.mongodb.util.JSON; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.types.ObjectId; -import org.springframework.beans.BeansException; import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.PropertyAccessorFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.convert.ConversionFailedException; @@ -80,1254 +73,1231 @@ import org.springframework.util.Assert; * @author Mark Pollack * @author Oliver Gierke */ -public class MongoTemplate implements InitializingBean, MongoOperations, ApplicationContextAware, ApplicationEventPublisherAware, MappingContextAware { - - private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class); - - private static final String ID = "_id"; - - /* - * 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; - - private MongoConverter mongoConverter; - private MappingContext mappingContext; - private final Mongo mongo; - private final MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator(); - - private String defaultCollectionName; - private String databaseName; - private String username; - private String password; - private ApplicationContext applicationContext; - private ApplicationEventPublisher eventPublisher; - private ExecutorService eventPublishers = Executors.newCachedThreadPool(); - private LinkedBlockingQueue eventQueue = new LinkedBlockingQueue(); - - /** - * Constructor used for a basic template configuration - * - * @param mongo - * @param databaseName - */ - public MongoTemplate(Mongo mongo, String databaseName) { - this(mongo, databaseName, null, null, null, null); - } - - /** - * Constructor used for a basic template configuration with a specific {@link com.mongodb.WriteConcern} - * to be used for all database write operations - * - * @param mongo - * @param databaseName - * @param writeConcern - */ - public MongoTemplate(Mongo mongo, String databaseName, WriteConcern writeConcern, WriteResultChecking writeResultChecking) { - this(mongo, databaseName, null, null, writeConcern, writeResultChecking); - } - - /** - * Constructor used for a basic template configuration with a default collection name - * - * @param mongo - * @param databaseName - * @param defaultCollectionName - */ - public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName) { - this(mongo, databaseName, defaultCollectionName, null, null, null); - } - - /** - * Constructor used for a basic template configuration with a default collection name and - * with a specific {@link com.mongodb.WriteConcern} to be used for all database write operations - * - * @param mongo - * @param databaseName - * @param defaultCollectionName - * @param writeConcern - */ - public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName, WriteConcern writeConcern, WriteResultChecking writeResultChecking) { - this(mongo, databaseName, defaultCollectionName, null, writeConcern, writeResultChecking); - } - - /** - * Constructor used for a template configuration with a default collection name and a custom {@link org.springframework.data.document.mongodb.convert.MongoConverter} - * - * @param mongo - * @param databaseName - * @param defaultCollectionName - * @param mongoConverter - */ - public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName, MongoConverter mongoConverter) { - this(mongo, databaseName, defaultCollectionName, mongoConverter, null, null); - } - - /** - * Constructor used for a template configuration with a default collection name and a custom {@link MongoConverter} - * and with a specific {@link com.mongodb.WriteConcern} to be used for all database write operations - * - * @param mongo - * @param databaseName - * @param defaultCollectionName - * @param mongoConverter - * @param writeConcern - * @param writeResultChecking - */ - public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName, MongoConverter mongoConverter, WriteConcern writeConcern, WriteResultChecking writeResultChecking) { - - Assert.notNull(mongo); - Assert.notNull(databaseName); - - this.defaultCollectionName = defaultCollectionName; - this.mongo = mongo; - this.databaseName = databaseName; - this.writeConcern = writeConcern; - if (writeResultChecking != null) { - this.writeResultChecking = writeResultChecking; - } - setMongoConverter(mongoConverter == null ? new SimpleMongoConverter() : mongoConverter); - } - - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.eventPublisher = applicationEventPublisher; - } - - public void setMappingContext(MappingContext mappingContext) { - this.mappingContext = mappingContext; - } - - /** - * Sets the username to use to connect to the Mongo database - * - * @param username The username to use - */ - public void setUsername(String username) { - this.username = username; - } - - /** - * Sets the password to use to authenticate with the Mongo database. - * - * @param password The password to use - */ - public void setPassword(String password) { - - this.password = password; - } - - /** - * Sets the name of the default collection to be used. - * - * @param defaultCollectionName - */ - public void setDefaultCollectionName(String defaultCollectionName) { - this.defaultCollectionName = defaultCollectionName; - } - - /** - * Sets the database name to be used. - * - * @param databaseName - */ - public void setDatabaseName(String databaseName) { - Assert.notNull(databaseName); - this.databaseName = databaseName; - } - - /** - * Returns the default {@link org.springframework.data.document.mongodb.convert.MongoConverter}. - * - * @return - */ - public MongoConverter getConverter() { - return this.mongoConverter; - } - - public void setConverter(MongoConverter converter) { - this.mongoConverter = converter; - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollectionName() - */ - public String getDefaultCollectionName() { - return defaultCollectionName; - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollection() - */ - public DBCollection getDefaultCollection() { - - return execute(new DbCallback() { - public DBCollection doInDB(DB db) throws MongoException, DataAccessException { - return db.getCollection(getDefaultCollectionName()); - } - }); - } - - /* (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 = getDb(); - return action.doInDB(db); - } catch (MongoException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback) - */ - public T execute(CollectionCallback callback) { - return execute(getDefaultCollectionName(), 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 = getDb().getCollection(collectionName); - return callback.doInCollection(collection); - } catch (MongoException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /** - * Central callback executing method 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. Apply the given - * {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
    1. - * - * @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 execute(CollectionCallback collectionCallback, - DbObjectCallback objectCallback, String collectionName) { - - try { - T result = objectCallback.doWith(collectionCallback.doInCollection(getCollection(collectionName))); - return result; - } catch (MongoException e) { - throw potentiallyConvertRuntimeException(e); - } - } - - /** - * Central callback executing method 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. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped - * if {@link CursorPreparer} is {@literal null}
      3. Iterate over the {@link DBCursor} and applies the given - * {@link DbObjectCallback} to each of the {@link DBObject}s collecting the actual result {@link List}.
        1. - * - * @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 executeEach(CollectionCallback collectionCallback, CursorPreparer preparer, - DbObjectCallback objectCallback, String collectionName) { - - try { - DBCursor cursor = collectionCallback.doInCollection(getCollection(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); - } - } - - /* (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(); - return null; - } - }); - } - - // Indexing methods - - public void ensureIndex(IndexDefinition indexDefinition) { - ensureIndex(getDefaultCollectionName(), 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(getDefaultCollectionName(), query, targetClass); - } - - public T findOne(Query query, Class targetClass, - MongoReader reader) { - return findOne(getDefaultCollectionName(), query, targetClass, reader); - } - - public T findOne(String collectionName, Query query, - Class targetClass) { - return findOne(collectionName, query, targetClass, null); - } - - public T findOne(String collectionName, Query query, - Class targetClass, MongoReader reader) { - return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, reader); - } - - // 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(getEntityCollection(targetClass), query, targetClass); - } - - public List find(Query query, Class targetClass, MongoReader reader) { - return find(getEntityCollection(targetClass), query, targetClass, reader); - } - - 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 (MongoException e) { - throw potentiallyConvertRuntimeException(e); - } - return cursorToUse; - } - }; - } - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, cursorPreparer); - } - - public List find(String collectionName, Query query, Class targetClass, MongoReader reader) { - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, reader); - } - - public List find(String collectionName, Query query, - Class targetClass, CursorPreparer preparer) { - return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, preparer); - } - - // 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(getDefaultCollectionName(), query, targetClass); - } - - public T findAndRemove(Query query, Class targetClass, - MongoReader reader) { - return findAndRemove(getDefaultCollectionName(), query, targetClass, reader); - } - - public T findAndRemove(String collectionName, Query query, - Class targetClass) { - return findAndRemove(collectionName, query, targetClass, null); - } - - public T findAndRemove(String collectionName, Query query, - Class targetClass, MongoReader reader) { - return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(), targetClass, reader); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.Object) - */ - public void insert(Object objectToSave) { - insert(getEntityCollection(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) { - insert(collectionName, objectToSave, this.mongoConverter); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insert(T, org.springframework.data.document.mongodb.MongoWriter) - */ - public void insert(T objectToSave, MongoWriter writer) { - insert(getEntityCollection(objectToSave), objectToSave, writer); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.String, T, org.springframework.data.document.mongodb.MongoWriter) - */ - public void insert(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) { - insertList(getRequiredDefaultCollectionName(), listToSave); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List) - */ - public void insertList(String collectionName, List listToSave) { - insertList(collectionName, listToSave, this.mongoConverter); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.util.List, org.springframework.data.document.mongodb.MongoWriter) - */ - public void insertList(List listToSave, MongoWriter writer) { - insertList(getDefaultCollectionName(), listToSave, writer); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List, org.springframework.data.document.mongodb.MongoWriter) - */ - public void insertList(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(getEntityCollection(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) { - save(collectionName, objectToSave, this.mongoConverter); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#save(T, org.springframework.data.document.mongodb.MongoWriter) - */ - public void save(T objectToSave, MongoWriter writer) { - save(getDefaultCollectionName(), objectToSave, writer); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#save(java.lang.String, T, org.springframework.data.document.mongodb.MongoWriter) - */ - public void save(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 (dbDoc.keySet().isEmpty()) { - return null; - } - - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (writeConcern == null) { - collection.insert(dbDoc); - } else { - collection.insert(dbDoc, writeConcern); - } - return dbDoc.get(ID); - } - }); - } - - protected List insertDBObjectList(String collectionName, final List dbDocList) { - - if (dbDocList.isEmpty()) { - return Collections.emptyList(); - } - - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (writeConcern == null) { - collection.insert(dbDocList); - } else { - collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcern); - } - 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 (dbDoc.keySet().isEmpty()) { - return null; - } - - return execute(collectionName, new CollectionCallback() { - public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - if (writeConcern == null) { - collection.save(dbDoc); - } else { - collection.save(dbDoc, writeConcern); - } - 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) { - return updateFirst(getRequiredDefaultCollectionName(), query, update); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject) - */ - public WriteResult updateFirst(String collectionName, final Query query, final Update update) { - return execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteResult wr; - if (writeConcern == null) { - wr = collection.update(query.getQueryObject(), update.getUpdateObject()); - } else { - wr = collection.update(query.getQueryObject(), update.getUpdateObject(), false, false, writeConcern); - } - handleAnyWriteResultErrors(wr, query.getQueryObject(), "update with '" + update.getUpdateObject() + "'"); - return wr; - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(com.mongodb.DBObject, com.mongodb.DBObject) - */ - public WriteResult updateMulti(Query query, Update update) { - return updateMulti(getRequiredDefaultCollectionName(), query, update); - } - - /* (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 execute(collectionName, new CollectionCallback() { - public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteResult wr = null; - if (writeConcern == null) { - wr = collection.updateMulti(query.getQueryObject(), update.getUpdateObject()); - } else { - wr = collection.update(query.getQueryObject(), update.getUpdateObject(), false, true, writeConcern); - } - handleAnyWriteResultErrors(wr, query.getQueryObject(), "update with '" + update.getUpdateObject() + "'"); - return wr; - } - }); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#remove(com.mongodb.DBObject) - */ - public void remove(Query query) { - remove(getRequiredDefaultCollectionName(), query); - } - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#remove(java.lang.String, com.mongodb.DBObject) - */ - public void remove(String collectionName, final Query query) { - execute(collectionName, new CollectionCallback() { - public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteResult wr = null; - if (writeConcern == null) { - wr = collection.remove(query.getQueryObject()); - } else { - wr = collection.remove(query.getQueryObject(), writeConcern); - } - handleAnyWriteResultErrors(wr, query.getQueryObject(), "remove"); - return null; - } - }); - } - - - /* (non-Javadoc) - * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.Class) - */ - public List getCollection(Class targetClass) { - return executeEach(new FindCallback(null), null, new ReadDbObjectCallback(mongoConverter, targetClass), - getDefaultCollectionName()); - } - - public List getCollection(String collectionName, Class targetClass) { - return executeEach(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 List getCollection(String collectionName, Class targetClass, MongoReader reader) { - return executeEach(new FindCallback(null), null, new ReadDbObjectCallback(reader, targetClass), - collectionName); - } - - public DB getDb() { - return MongoDbUtils.getDB(mongo, databaseName, username, password == null ? null : password.toCharArray()); - } - - 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 - return coll; - } - }); - } - - /** - * Map the results of an ad-hoc query on the default MongoDB collection to an object using the provided MongoReader - *

          - * 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. - * @param reader the MongoReader to convert from DBObject to an object. - * @return the List of converted objects. - */ - protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class targetClass, MongoReader reader) { - MongoReader readerToUse = reader; - if (readerToUse == null) { - readerToUse = this.mongoConverter; - } - substituteMappedIdIfNecessary(query, targetClass, readerToUse); - return execute(new FindOneCallback(query, 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) { - substituteMappedIdIfNecessary(query, targetClass, mongoConverter); - return executeEach(new FindCallback(query, 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 provided MongoReader - *

          - * 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. - * @param reader the MongoReader to convert from DBObject to an object. - * @return the List of converted objects. - */ - protected List doFind(String collectionName, DBObject query, DBObject fields, Class targetClass, MongoReader reader) { - substituteMappedIdIfNecessary(query, targetClass, reader); - return executeEach(new FindCallback(query, fields), null, new ReadDbObjectCallback(reader, 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 provided MongoReader - * 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. - * @param reader the MongoReader to convert from DBObject to an object. - * @return the List of converted objects. - */ - protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, Class targetClass, MongoReader reader) { - MongoReader readerToUse = reader; - if (readerToUse == null) { - readerToUse = this.mongoConverter; - } - substituteMappedIdIfNecessary(query, targetClass, readerToUse); - return execute(new FindAndRemoveCallback(query, fields, sort), new ReadDbObjectCallback(readerToUse, targetClass), - collectionName); - } - - /** - * 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; - } - - ConfigurablePropertyAccessor bw = PropertyAccessorFactory.forDirectFieldAccess(savedObject); - MongoPropertyDescriptor idDescriptor = new MongoPropertyDescriptors(savedObject.getClass()).getIdDescriptor(); - - if (idDescriptor == null) { - return; - } - - if (bw.getPropertyValue(idDescriptor.getName()) == null) { - Object target = null; - if (id instanceof ObjectId) { - target = this.mongoConverter.convertObjectId((ObjectId) id, idDescriptor.getPropertyType()); - } else { - target = id; - } - bw.setPropertyValue(idDescriptor.getName(), target); - } - } - - /** - * Substitutes the id key if it is found in he query. Any 'id' keys will be replaced with '_id' and the value converted - * to an ObjectId if possible. This conversion should match the way that the id fields are converted during read - * operations. - * - * @param query - * @param targetClass - * @param reader - */ - protected void substituteMappedIdIfNecessary(DBObject query, Class targetClass, MongoReader reader) { - MongoConverter converter = null; - if (reader instanceof SimpleMongoConverter) { - converter = (MongoConverter) reader; - } else { - return; - } - String idKey = null; - if (query.containsField("id")) { - idKey = "id"; - } - if (query.containsField("_id")) { - idKey = "_id"; - } - if (idKey == null) { - // no ids in this query - return; - } - final MongoPropertyDescriptor descriptor; - try { - descriptor = new MongoPropertyDescriptor(new PropertyDescriptor(idKey, targetClass), targetClass); - } catch (IntrospectionException e) { - // no property descriptor for this key - return; - } - if (descriptor.isIdProperty() && descriptor.isOfIdType()) { - Object value = query.get(idKey); - 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"); - } - query.removeField(idKey); - if (newValue != null) { - query.put(MongoPropertyDescriptor.ID_KEY, newValue); - } else { - query.put(MongoPropertyDescriptor.ID_KEY, value); - } - } - } - - - private String getRequiredDefaultCollectionName() { - String name = getDefaultCollectionName(); - if (name == null) { - throw new IllegalStateException( - "No 'defaultCollection' or 'defaultCollectionName' specified. Check configuration of MongoTemplate."); - } - return name; - } - - private String getEntityCollection(T obj) { - if (null != obj) { - return getEntityCollection(obj.getClass()); - } - - return null; - } - - private String getEntityCollection(Class clazz) { - if (null != mappingContext) { - PersistentEntity entity = mappingContext.getPersistentEntity(clazz); - if (entity == null) { - mappingContext.addPersistentEntity(clazz); - entity = mappingContext.getPersistentEntity(clazz); - } - if (null != entity && entity instanceof MongoPersistentEntity) { - return ((MongoPersistentEntity) entity).getCollection(); - } - } - // Otherwise, return the default for this template. - return getRequiredDefaultCollectionName(); - } - - /** - * 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; - } - - private void initializeMappingMongoConverter(MappingMongoConverter converter) { - converter.setMongo(mongo); - converter.setDefaultDatabase(databaseName); - } - - /* - * (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ - public void afterPropertiesSet() { - if (this.getDefaultCollectionName() != null) { - if (!collectionExists(getDefaultCollectionName())) { - createCollection(getDefaultCollectionName(), null); - } - } - if (null != applicationContext) { - eventPublishers.submit(new Runnable() { - public void run() { - while (true) { - ApplicationEvent event = null; - try { - event = eventQueue.take(); - applicationContext.publishEvent(event); - } catch (InterruptedException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - } - }); - } - } - - - /** - * 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) { - return collection.findOne(query); - } else { - 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 setMongoConverter(MongoConverter converter) { - this.mongoConverter = converter; - if (null != converter && converter instanceof MappingMongoConverter) { - initializeMappingMongoConverter((MappingMongoConverter) mongoConverter); - } - } - - public void setWriteResultChecking(WriteResultChecking resultChecking) { - this.writeResultChecking = resultChecking; - } - - public void setWriteConcern(WriteConcern writeConcern) { - this.writeConcern = writeConcern; - } +public class MongoTemplate implements InitializingBean, MongoOperations, ApplicationEventPublisherAware, MappingContextAware { + + private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class); + + private static final String ID = "_id"; + + /* + * 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; + + private MongoConverter mongoConverter; + private MappingContext mappingContext; + private final Mongo mongo; + private final MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator(); + + private String defaultCollectionName; + private String databaseName; + private String username; + private String password; + private ApplicationEventPublisher eventPublisher; + + /** + * Constructor used for a basic template configuration + * + * @param mongo + * @param databaseName + */ + public MongoTemplate(Mongo mongo, String databaseName) { + this(mongo, databaseName, null, null, null, null); + } + + /** + * Constructor used for a basic template configuration with a specific {@link com.mongodb.WriteConcern} + * to be used for all database write operations + * + * @param mongo + * @param databaseName + * @param writeConcern + */ + public MongoTemplate(Mongo mongo, String databaseName, WriteConcern writeConcern, WriteResultChecking writeResultChecking) { + this(mongo, databaseName, null, null, writeConcern, writeResultChecking); + } + + /** + * Constructor used for a basic template configuration with a default collection name + * + * @param mongo + * @param databaseName + * @param defaultCollectionName + */ + public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName) { + this(mongo, databaseName, defaultCollectionName, null, null, null); + } + + /** + * Constructor used for a basic template configuration with a default collection name and + * with a specific {@link com.mongodb.WriteConcern} to be used for all database write operations + * + * @param mongo + * @param databaseName + * @param defaultCollectionName + * @param writeConcern + */ + public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName, WriteConcern writeConcern, WriteResultChecking writeResultChecking) { + this(mongo, databaseName, defaultCollectionName, null, writeConcern, writeResultChecking); + } + + /** + * Constructor used for a template configuration with a default collection name and a custom {@link org.springframework.data.document.mongodb.convert.MongoConverter} + * + * @param mongo + * @param databaseName + * @param defaultCollectionName + * @param mongoConverter + */ + public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName, MongoConverter mongoConverter) { + this(mongo, databaseName, defaultCollectionName, mongoConverter, null, null); + } + + /** + * Constructor used for a template configuration with a default collection name and a custom {@link MongoConverter} + * and with a specific {@link com.mongodb.WriteConcern} to be used for all database write operations + * + * @param mongo + * @param databaseName + * @param defaultCollectionName + * @param mongoConverter + * @param writeConcern + * @param writeResultChecking + */ + public MongoTemplate(Mongo mongo, String databaseName, String defaultCollectionName, MongoConverter mongoConverter, WriteConcern writeConcern, WriteResultChecking writeResultChecking) { + + Assert.notNull(mongo); + Assert.notNull(databaseName); + + this.defaultCollectionName = defaultCollectionName; + this.mongo = mongo; + this.databaseName = databaseName; + this.writeConcern = writeConcern; + if (writeResultChecking != null) { + this.writeResultChecking = writeResultChecking; + } + setMongoConverter(mongoConverter == null ? new SimpleMongoConverter() : mongoConverter); + } + + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.eventPublisher = applicationEventPublisher; + } + + public void setMappingContext(MappingContext mappingContext) { + this.mappingContext = mappingContext; + } + + /** + * Sets the username to use to connect to the Mongo database + * + * @param username The username to use + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Sets the password to use to authenticate with the Mongo database. + * + * @param password The password to use + */ + public void setPassword(String password) { + + this.password = password; + } + + /** + * Sets the name of the default collection to be used. + * + * @param defaultCollectionName + */ + public void setDefaultCollectionName(String defaultCollectionName) { + this.defaultCollectionName = defaultCollectionName; + } + + /** + * Sets the database name to be used. + * + * @param databaseName + */ + public void setDatabaseName(String databaseName) { + Assert.notNull(databaseName); + this.databaseName = databaseName; + } + + /** + * Returns the default {@link org.springframework.data.document.mongodb.convert.MongoConverter}. + * + * @return + */ + public MongoConverter getConverter() { + return this.mongoConverter; + } + + public void setConverter(MongoConverter converter) { + this.mongoConverter = converter; + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollectionName() + */ + public String getDefaultCollectionName() { + return defaultCollectionName; + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollection() + */ + public DBCollection getDefaultCollection() { + + return execute(new DbCallback() { + public DBCollection doInDB(DB db) throws MongoException, DataAccessException { + return db.getCollection(getDefaultCollectionName()); + } + }); + } + + /* (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 = getDb(); + return action.doInDB(db); + } catch (MongoException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback) + */ + public T execute(CollectionCallback callback) { + return execute(getDefaultCollectionName(), 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 = getDb().getCollection(collectionName); + return callback.doInCollection(collection); + } catch (MongoException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /** + * Central callback executing method 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. Apply the given + * {@link DbObjectCallback} to each of the {@link DBObject}s to obtain the result.
            1. + * + * @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 execute(CollectionCallback collectionCallback, + DbObjectCallback objectCallback, String collectionName) { + + try { + T result = objectCallback.doWith(collectionCallback.doInCollection(getCollection(collectionName))); + return result; + } catch (MongoException e) { + throw potentiallyConvertRuntimeException(e); + } + } + + /** + * Central callback executing method 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. Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped + * if {@link CursorPreparer} is {@literal null}
              3. Iterate over the {@link DBCursor} and applies the given + * {@link DbObjectCallback} to each of the {@link DBObject}s collecting the actual result {@link List}.
                1. + * + * @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 executeEach(CollectionCallback collectionCallback, CursorPreparer preparer, + DbObjectCallback objectCallback, String collectionName) { + + try { + DBCursor cursor = collectionCallback.doInCollection(getCollection(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); + } + } + + /* (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(); + return null; + } + }); + } + + // Indexing methods + + public void ensureIndex(IndexDefinition indexDefinition) { + ensureIndex(getDefaultCollectionName(), 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(getDefaultCollectionName(), query, targetClass); + } + + public T findOne(Query query, Class targetClass, + MongoReader reader) { + return findOne(getDefaultCollectionName(), query, targetClass, reader); + } + + public T findOne(String collectionName, Query query, + Class targetClass) { + return findOne(collectionName, query, targetClass, null); + } + + public T findOne(String collectionName, Query query, + Class targetClass, MongoReader reader) { + return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, reader); + } + + // 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(getEntityCollection(targetClass), query, targetClass); + } + + public List find(Query query, Class targetClass, MongoReader reader) { + return find(getEntityCollection(targetClass), query, targetClass, reader); + } + + 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 (MongoException e) { + throw potentiallyConvertRuntimeException(e); + } + return cursorToUse; + } + }; + } + return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, cursorPreparer); + } + + public List find(String collectionName, Query query, Class targetClass, MongoReader reader) { + return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, reader); + } + + public List find(String collectionName, Query query, + Class targetClass, CursorPreparer preparer) { + return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), targetClass, preparer); + } + + // 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(getDefaultCollectionName(), query, targetClass); + } + + public T findAndRemove(Query query, Class targetClass, + MongoReader reader) { + return findAndRemove(getDefaultCollectionName(), query, targetClass, reader); + } + + public T findAndRemove(String collectionName, Query query, + Class targetClass) { + return findAndRemove(collectionName, query, targetClass, null); + } + + public T findAndRemove(String collectionName, Query query, + Class targetClass, MongoReader reader) { + return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(), targetClass, reader); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.Object) + */ + public void insert(Object objectToSave) { + insert(getEntityCollection(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) { + insert(collectionName, objectToSave, this.mongoConverter); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#insert(T, org.springframework.data.document.mongodb.MongoWriter) + */ + public void insert(T objectToSave, MongoWriter writer) { + insert(getEntityCollection(objectToSave), objectToSave, writer); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.String, T, org.springframework.data.document.mongodb.MongoWriter) + */ + public void insert(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) { + insertList(getRequiredDefaultCollectionName(), listToSave); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List) + */ + public void insertList(String collectionName, List listToSave) { + insertList(collectionName, listToSave, this.mongoConverter); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.util.List, org.springframework.data.document.mongodb.MongoWriter) + */ + public void insertList(List listToSave, MongoWriter writer) { + insertList(getDefaultCollectionName(), listToSave, writer); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List, org.springframework.data.document.mongodb.MongoWriter) + */ + public void insertList(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(getEntityCollection(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) { + save(collectionName, objectToSave, this.mongoConverter); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#save(T, org.springframework.data.document.mongodb.MongoWriter) + */ + public void save(T objectToSave, MongoWriter writer) { + save(getEntityCollection(objectToSave), objectToSave, writer); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#save(java.lang.String, T, org.springframework.data.document.mongodb.MongoWriter) + */ + public void save(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 (dbDoc.keySet().isEmpty()) { + return null; + } + + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + if (writeConcern == null) { + collection.insert(dbDoc); + } else { + collection.insert(dbDoc, writeConcern); + } + return dbDoc.get(ID); + } + }); + } + + protected List insertDBObjectList(String collectionName, final List dbDocList) { + + if (dbDocList.isEmpty()) { + return Collections.emptyList(); + } + + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + if (writeConcern == null) { + collection.insert(dbDocList); + } else { + collection.insert(dbDocList.toArray((DBObject[]) new BasicDBObject[dbDocList.size()]), writeConcern); + } + 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 (dbDoc.keySet().isEmpty()) { + return null; + } + + return execute(collectionName, new CollectionCallback() { + public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { + if (writeConcern == null) { + collection.save(dbDoc); + } else { + collection.save(dbDoc, writeConcern); + } + 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) { + return updateFirst(getRequiredDefaultCollectionName(), query, update); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject) + */ + public WriteResult updateFirst(String collectionName, final Query query, final Update update) { + return execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { + WriteResult wr; + if (writeConcern == null) { + wr = collection.update(query.getQueryObject(), update.getUpdateObject()); + } else { + wr = collection.update(query.getQueryObject(), update.getUpdateObject(), false, false, writeConcern); + } + handleAnyWriteResultErrors(wr, query.getQueryObject(), "update with '" + update.getUpdateObject() + "'"); + return wr; + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(com.mongodb.DBObject, com.mongodb.DBObject) + */ + public WriteResult updateMulti(Query query, Update update) { + return updateMulti(getRequiredDefaultCollectionName(), query, update); + } + + /* (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 execute(collectionName, new CollectionCallback() { + public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException { + WriteResult wr = null; + if (writeConcern == null) { + wr = collection.updateMulti(query.getQueryObject(), update.getUpdateObject()); + } else { + wr = collection.update(query.getQueryObject(), update.getUpdateObject(), false, true, writeConcern); + } + handleAnyWriteResultErrors(wr, query.getQueryObject(), "update with '" + update.getUpdateObject() + "'"); + return wr; + } + }); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#remove(com.mongodb.DBObject) + */ + public void remove(Query query) { + remove(getRequiredDefaultCollectionName(), query); + } + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#remove(java.lang.String, com.mongodb.DBObject) + */ + public void remove(String collectionName, final Query query) { + execute(collectionName, new CollectionCallback() { + public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { + WriteResult wr = null; + if (writeConcern == null) { + wr = collection.remove(query.getQueryObject()); + } else { + wr = collection.remove(query.getQueryObject(), writeConcern); + } + handleAnyWriteResultErrors(wr, query.getQueryObject(), "remove"); + return null; + } + }); + } + + + /* (non-Javadoc) + * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.Class) + */ + public List getCollection(Class targetClass) { + return executeEach(new FindCallback(null), null, new ReadDbObjectCallback(mongoConverter, targetClass), + getDefaultCollectionName()); + } + + public List getCollection(String collectionName, Class targetClass) { + return executeEach(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 List getCollection(String collectionName, Class targetClass, MongoReader reader) { + return executeEach(new FindCallback(null), null, new ReadDbObjectCallback(reader, targetClass), + collectionName); + } + + public DB getDb() { + return MongoDbUtils.getDB(mongo, databaseName, username, password == null ? null : password.toCharArray()); + } + + 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 + return coll; + } + }); + } + + /** + * Map the results of an ad-hoc query on the default MongoDB collection to an object using the provided MongoReader + *

                  + * 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. + * @param reader the MongoReader to convert from DBObject to an object. + * @return the List of converted objects. + */ + protected T doFindOne(String collectionName, DBObject query, DBObject fields, Class targetClass, MongoReader reader) { + MongoReader readerToUse = reader; + if (readerToUse == null) { + readerToUse = this.mongoConverter; + } + substituteMappedIdIfNecessary(query, targetClass, readerToUse); + return execute(new FindOneCallback(query, 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) { + substituteMappedIdIfNecessary(query, targetClass, mongoConverter); + return executeEach(new FindCallback(query, 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 provided MongoReader + *

                  + * 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. + * @param reader the MongoReader to convert from DBObject to an object. + * @return the List of converted objects. + */ + protected List doFind(String collectionName, DBObject query, DBObject fields, Class targetClass, MongoReader reader) { + substituteMappedIdIfNecessary(query, targetClass, reader); + return executeEach(new FindCallback(query, fields), null, new ReadDbObjectCallback(reader, 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 provided MongoReader + * 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. + * @param reader the MongoReader to convert from DBObject to an object. + * @return the List of converted objects. + */ + protected T doFindAndRemove(String collectionName, DBObject query, DBObject fields, DBObject sort, Class targetClass, MongoReader reader) { + MongoReader readerToUse = reader; + if (readerToUse == null) { + readerToUse = this.mongoConverter; + } + substituteMappedIdIfNecessary(query, targetClass, readerToUse); + return execute(new FindAndRemoveCallback(query, fields, sort), new ReadDbObjectCallback(readerToUse, targetClass), + collectionName); + } + + /** + * 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; + } + + ConfigurablePropertyAccessor bw = PropertyAccessorFactory.forDirectFieldAccess(savedObject); + MongoPropertyDescriptor idDescriptor = new MongoPropertyDescriptors(savedObject.getClass()).getIdDescriptor(); + + if (idDescriptor == null) { + return; + } + + if (bw.getPropertyValue(idDescriptor.getName()) == null) { + Object target = null; + if (id instanceof ObjectId) { + target = this.mongoConverter.convertObjectId((ObjectId) id, idDescriptor.getPropertyType()); + } else { + target = id; + } + bw.setPropertyValue(idDescriptor.getName(), target); + } + } + + /** + * Substitutes the id key if it is found in he query. Any 'id' keys will be replaced with '_id' and the value converted + * to an ObjectId if possible. This conversion should match the way that the id fields are converted during read + * operations. + * + * @param query + * @param targetClass + * @param reader + */ + protected void substituteMappedIdIfNecessary(DBObject query, Class targetClass, MongoReader reader) { + MongoConverter converter = null; + if (reader instanceof SimpleMongoConverter) { + converter = (MongoConverter) reader; + } else { + return; + } + String idKey = null; + if (query.containsField("id")) { + idKey = "id"; + } + if (query.containsField("_id")) { + idKey = "_id"; + } + if (idKey == null) { + // no ids in this query + return; + } + final MongoPropertyDescriptor descriptor; + try { + descriptor = new MongoPropertyDescriptor(new PropertyDescriptor(idKey, targetClass), targetClass); + } catch (IntrospectionException e) { + // no property descriptor for this key + return; + } + if (descriptor.isIdProperty() && descriptor.isOfIdType()) { + Object value = query.get(idKey); + 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"); + } + query.removeField(idKey); + if (newValue != null) { + query.put(MongoPropertyDescriptor.ID_KEY, newValue); + } else { + query.put(MongoPropertyDescriptor.ID_KEY, value); + } + } + } + + + private String getRequiredDefaultCollectionName() { + String name = getDefaultCollectionName(); + if (name == null) { + throw new IllegalStateException( + "No 'defaultCollection' or 'defaultCollectionName' specified. Check configuration of MongoTemplate."); + } + return name; + } + + private String getEntityCollection(T obj) { + if (null != obj) { + return getEntityCollection(obj.getClass()); + } + + return null; + } + + private String getEntityCollection(Class clazz) { + if (null != mappingContext) { + PersistentEntity entity = mappingContext.getPersistentEntity(clazz); + if (entity == null) { + entity = mappingContext.addPersistentEntity(clazz); + } + if (null != entity && entity instanceof MongoPersistentEntity) { + return ((MongoPersistentEntity) entity).getCollection(); + } + } + // Otherwise, return the default for this template. + return getRequiredDefaultCollectionName(); + } + + /** + * 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; + } + + private void initializeMappingMongoConverter(MappingMongoConverter converter) { + converter.setMongo(mongo); + converter.setDefaultDatabase(databaseName); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() { + if (this.getDefaultCollectionName() != null) { + if (!collectionExists(getDefaultCollectionName())) { + createCollection(getDefaultCollectionName(), null); + } + } + } + + + /** + * 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) { + return collection.findOne(query); + } else { + 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 setMongoConverter(MongoConverter converter) { + this.mongoConverter = converter; + if (null != converter && converter instanceof MappingMongoConverter) { + initializeMappingMongoConverter((MappingMongoConverter) mongoConverter); + } + } + + public void setWriteResultChecking(WriteResultChecking resultChecking) { + this.writeResultChecking = resultChecking; + } + + public void setWriteConcern(WriteConcern writeConcern) { + this.writeConcern = writeConcern; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java index 84ae4ca3f..49ffb6d64 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java @@ -73,6 +73,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext private static final String CUSTOM_TYPE_KEY = "_class"; @SuppressWarnings({"unchecked"}) private static final List> MONGO_TYPES = Arrays.asList(Number.class, Date.class, String.class, DBObject.class); + private static final List> VALID_ID_TYPES = Arrays.asList(new Class[]{ObjectId.class, String.class, BigInteger.class, byte[].class}); protected static final Log log = LogFactory.getLog(MappingMongoConverter.class); protected final GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService(); @@ -306,7 +307,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (!dbo.containsField("_id") && null != idProperty) { Object idObj = null; try { - idObj = MappingBeanHelper.getProperty(obj, idProperty, ObjectId.class, useFieldAccessOnly); + idObj = MappingBeanHelper.getProperty(obj, idProperty, Object.class, useFieldAccessOnly); } catch (IllegalAccessException e) { throw new MappingException(e.getMessage(), e); } catch (InvocationTargetException e) { @@ -315,6 +316,10 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (null != idObj) { dbo.put("_id", idObj); + } else { + if (!VALID_ID_TYPES.contains(idProperty.getType())) { + throw new MappingException("Invalid data type " + idProperty.getType().getName() + " for Id property. Should be one of " + VALID_ID_TYPES); + } } } 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 a2d9841af..e7e485ced 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 @@ -36,154 +36,159 @@ import org.springframework.data.document.mongodb.MongoDbUtils; import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.query.Criteria; import org.springframework.data.document.mongodb.query.Query; +import org.springframework.data.mapping.model.MappingException; /** * @author Jon Brisbin */ public class MappingTests { - private static final Log LOGGER = LogFactory.getLog(MongoDbUtils.class); - private final String[] collectionsToDrop = new String[]{"person", "personmapproperty", "personpojo", "personcustomidname", "account"}; + private static final Log LOGGER = LogFactory.getLog(MongoDbUtils.class); + private final String[] collectionsToDrop = new String[]{"person", "personmapproperty", "personpojo", "personcustomidname", "account"}; - ApplicationContext applicationContext; - MongoTemplate template; - MongoMappingContext mappingContext; + ApplicationContext applicationContext; + MongoTemplate template; + MongoMappingContext mappingContext; - @Before - public void setUp() throws Exception { - Mongo mongo = new Mongo(); - DB db = mongo.getDB("database"); - for (String coll : collectionsToDrop) { - db.getCollection(coll).drop(); - } - applicationContext = new ClassPathXmlApplicationContext("/mapping.xml"); - template = applicationContext.getBean(MongoTemplate.class); - mappingContext = applicationContext.getBean(MongoMappingContext.class); - } + @Before + public void setUp() throws Exception { + Mongo mongo = new Mongo(); + DB db = mongo.getDB("database"); + for (String coll : collectionsToDrop) { + db.getCollection(coll).drop(); + } + applicationContext = new ClassPathXmlApplicationContext("/mapping.xml"); + template = applicationContext.getBean(MongoTemplate.class); + mappingContext = applicationContext.getBean(MongoMappingContext.class); + } - @Test - public void testPersonPojo() throws Exception { - // POJOs aren't auto-detected, have to add manually - mappingContext.addPersistentEntity(PersonPojo.class); + @Test + public void testPersonPojo() throws Exception { + // POJOs aren't auto-detected, have to add manually + mappingContext.addPersistentEntity(PersonPojo.class); - LOGGER.info("about to create new personpojo"); - PersonPojo p = new PersonPojo(12345, "Person", "Pojo"); - LOGGER.info("about to insert"); - template.insert(p); - LOGGER.info("done inserting"); - assertNotNull(p.getId()); + LOGGER.info("about to create new personpojo"); + PersonPojo p = new PersonPojo(12345, "Person", "Pojo"); + LOGGER.info("about to insert"); + template.insert(p); + LOGGER.info("done inserting"); + assertNotNull(p.getId()); - List result = template.find( - new Query(Criteria.where("ssn").is(12345)), PersonPojo.class); - assertThat(result.size(), is(1)); - assertThat(result.get(0).getSsn(), is(12345)); - } + List result = template.find(new Query(Criteria.where("ssn").is(12345)), PersonPojo.class); + assertThat(result.size(), is(1)); + assertThat(result.get(0).getSsn(), is(12345)); + } - @Test - public void testPersonWithCustomIdName() { - // POJOs aren't auto-detected, have to add manually - mappingContext.addPersistentEntity(PersonCustomIdName.class); + @Test + public void testPersonWithCustomIdName() { + // POJOs aren't auto-detected, have to add manually + mappingContext.addPersistentEntity(PersonCustomIdName.class); - PersonCustomIdName p = new PersonCustomIdName(123456, "Custom", "Id"); - template.insert(p); + PersonCustomIdName p = new PersonCustomIdName(123456, "Custom Id"); + template.insert(p); - List result = template.find( - new Query(Criteria.where("ssn").is(123456)), PersonCustomIdName.class); - assertThat(result.size(), is(1)); - assertNotNull(result.get(0).getCustomId()); - } + List result = template.find(new Query(Criteria.where("ssn").is(123456)), PersonCustomIdName.class); + assertThat(result.size(), is(1)); + assertNotNull(result.get(0).getLastName()); + } - @Test - public void testPersonMapProperty() { - PersonMapProperty p = new PersonMapProperty(1234567, "Map", "Property"); - Map accounts = new HashMap(); + @Test(expected = MappingException.class) + public void testPersonWithInvalidCustomIdName() { + // POJOs aren't auto-detected, have to add manually + mappingContext.addPersistentEntity(PersonInvalidId.class); - AccountPojo checking = new AccountPojo("checking", 1000.0f); - AccountPojo savings = new AccountPojo("savings", 10000.0f); + PersonInvalidId p = new PersonInvalidId(); + template.insert(p); + } - accounts.put("checking", checking); - accounts.put("savings", savings); - p.setAccounts(accounts); + @Test + public void testPersonMapProperty() { + PersonMapProperty p = new PersonMapProperty(1234567, "Map", "Property"); + Map accounts = new HashMap(); - template.insert(p); - assertNotNull(p.getId()); + AccountPojo checking = new AccountPojo("checking", 1000.0f); + AccountPojo savings = new AccountPojo("savings", 10000.0f); - List result = template.find( - new Query(Criteria.where("ssn").is(1234567)), PersonMapProperty.class); - assertThat(result.size(), is(1)); - assertThat(result.get(0).getAccounts().size(), is(2)); - assertThat(result.get(0).getAccounts().get("checking").getBalance(), - is(1000.0f)); - } + accounts.put("checking", checking); + accounts.put("savings", savings); + p.setAccounts(accounts); - @Test - @SuppressWarnings({"unchecked"}) - public void testWriteEntity() { + template.insert(p); + assertNotNull(p.getId()); - Address addr = new Address(); - addr.setLines(new String[]{"1234 W. 1st Street", "Apt. 12"}); - addr.setCity("Anytown"); - addr.setPostalCode(12345); - addr.setCountry("USA"); + List result = template.find(new Query(Criteria.where("ssn").is(1234567)), PersonMapProperty.class); + assertThat(result.size(), is(1)); + assertThat(result.get(0).getAccounts().size(), is(2)); + assertThat(result.get(0).getAccounts().get("checking").getBalance(), + is(1000.0f)); + } - Account acct = new Account(); - acct.setBalance(1000.00f); - template.insert("account", acct); + @Test + @SuppressWarnings({"unchecked"}) + public void testWriteEntity() { - List accounts = new ArrayList(); - accounts.add(acct); + Address addr = new Address(); + addr.setLines(new String[]{"1234 W. 1st Street", "Apt. 12"}); + addr.setCity("Anytown"); + addr.setPostalCode(12345); + addr.setCountry("USA"); - Person p = new Person(123456789, "John", "Doe", 37, addr); - p.setAccounts(accounts); - template.insert("person", p); + Account acct = new Account(); + acct.setBalance(1000.00f); + template.insert("account", acct); - Account newAcct = new Account(); - newAcct.setBalance(10000.00f); - template.insert("account", newAcct); + List accounts = new ArrayList(); + accounts.add(acct); - accounts.add(newAcct); - template.save("person", p); + Person p = new Person(123456789, "John", "Doe", 37, addr); + p.setAccounts(accounts); + template.insert("person", p); - assertNotNull(p.getId()); + Account newAcct = new Account(); + newAcct.setBalance(10000.00f); + template.insert("account", newAcct); - List result = template.find( - new Query(Criteria.where("ssn").is(123456789)), Person.class); - assertThat(result.size(), is(1)); - assertThat(result.get(0).getAddress().getCountry(), is("USA")); - assertThat(result.get(0).getAccounts(), notNullValue()); - } + accounts.add(newAcct); + template.save("person", p); - @SuppressWarnings({"unchecked"}) - @Test - public void testUniqueIndex() { - Address addr = new Address(); - addr.setLines(new String[]{"1234 W. 1st Street", "Apt. 12"}); - addr.setCity("Anytown"); - addr.setPostalCode(12345); - addr.setCountry("USA"); + assertNotNull(p.getId()); - Person p1 = new Person(1234567890, "John", "Doe", 37, addr); - Person p2 = new Person(1234567890, "John", "Doe", 37, addr); + List result = template.find(new Query(Criteria.where("ssn").is(123456789)), Person.class); + assertThat(result.size(), is(1)); + assertThat(result.get(0).getAddress().getCountry(), is("USA")); + assertThat(result.get(0).getAccounts(), notNullValue()); + } - template.insert("person", p1); - template.insert("person", p2); + @SuppressWarnings({"unchecked"}) + @Test + public void testUniqueIndex() { + Address addr = new Address(); + addr.setLines(new String[]{"1234 W. 1st Street", "Apt. 12"}); + addr.setCity("Anytown"); + addr.setPostalCode(12345); + addr.setCountry("USA"); - List result = template.find( - new Query(Criteria.where("ssn").is(1234567890)), Person.class); - assertThat(result.size(), is(1)); - } + Person p1 = new Person(1234567890, "John", "Doe", 37, addr); + Person p2 = new Person(1234567890, "John", "Doe", 37, addr); - @Test - public void testPrimitivesAndCustomCollectionName() { - Location loc = new Location( - new double[]{1.0, 2.0}, - new int[]{1, 2, 3, 4}, - new float[]{1.0f, 2.0f} - ); - template.insert(loc); + template.insert("person", p1); + template.insert("person", p2); - List result = template.find("places", new Query(Criteria.where("_id").is(loc.getId())), Location.class); - assertThat(result.size(), is(1)); - } + List result = template.find(new Query(Criteria.where("ssn").is(1234567890)), Person.class); + assertThat(result.size(), is(1)); + } + + @Test + public void testPrimitivesAndCustomCollectionName() { + Location loc = new Location( + new double[]{1.0, 2.0}, + new int[]{1, 2, 3, 4}, + new float[]{1.0f, 2.0f} + ); + template.insert(loc); + + List result = template.find("places", new Query(Criteria.where("_id").is(loc.getId())), Location.class); + assertThat(result.size(), is(1)); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/PersonCustomIdName.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/PersonCustomIdName.java index 2711610ed..5fdcb4815 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/PersonCustomIdName.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/PersonCustomIdName.java @@ -16,7 +16,6 @@ package org.springframework.data.document.mongodb.mapping; -import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; /** @@ -24,19 +23,21 @@ import org.springframework.data.annotation.Id; */ public class PersonCustomIdName extends BasePerson { - @Id - private ObjectId customId; + @Id + private String lastName; - public PersonCustomIdName(Integer ssn, String firstName, String lastName) { - super(ssn, firstName, lastName); - } + public PersonCustomIdName(Integer ssn, String firstName) { + this.ssn = ssn; + this.firstName = firstName; + } - public ObjectId getCustomId() { - return customId; - } - - public void setCustomId(ObjectId customId) { - this.customId = customId; - } + @Override + public String getLastName() { + return this.lastName; + } + @Override + public void setLastName(String lastName) { + this.lastName = lastName; + } }