DATADOC-4 - Improved MongoTemplate exception handling

- moved exception translation from MongoDbUtils to MongoExceptionTranslator
- don't let MongoFactoryBean implement PersistenceExceptionTranslator
- heavily refactored MongoTemplate to let higher level methods use callbacks to centralize exception handling in the callback executing methods
- changed return type of MongoOperations.getCollectionNames to Set<String> as returning a List implies same order, that we can't guarantee actually
- polished JavaDoc of MongoOperations
- added assertions to methods
- changed methods taking a List<Object> to take a List<? extends Object> instead
- added abstract unit test for MongoOperations to define tests regarding expected exception behavior
- let MongoTemplateUnitTests extend MongoOperationsUnitTests
This commit is contained in:
Oliver Gierke
2011-01-04 16:56:35 +01:00
parent 47dde8ab4d
commit affbe71f93
8 changed files with 704 additions and 244 deletions

View File

@@ -18,18 +18,11 @@ package org.springframework.data.document.mongodb;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.document.UncategorizedDocumentStoreException;
import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.mongodb.DB; import com.mongodb.DB;
import com.mongodb.Mongo; import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.MongoException.DuplicateKey;
import com.mongodb.MongoException.Network;
/** /**
* Helper class featuring helper methods for internal MongoDb classes. * Helper class featuring helper methods for internal MongoDb classes.
@@ -53,36 +46,6 @@ abstract class MongoDbUtils {
} }
/**
* Convert the given runtime exception to an appropriate exception from the
* <code>org.springframework.dao</code> hierarchy.
* Return null if no translation is appropriate: any other exception may
* have resulted from user code, and should not be translated.
* @param ex runtime exception that occurred
* @return the corresponding DataAccessException instance,
* or <code>null</code> if the exception should not be translated
*/
public static DataAccessException translateMongoExceptionIfPossible(RuntimeException ex) {
// Check for well-known MongoException subclasses.
// All other MongoExceptions
if(ex instanceof DuplicateKey) {
return new DataIntegrityViolationException(ex.getMessage(),ex);
}
if(ex instanceof Network) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof MongoException) {
return new UncategorizedDocumentStoreException(ex.getMessage(), ex);
}
// If we get here, we have an exception that resulted from user code,
// rather than the persistence provider, so we return null to indicate
// that translation should not occur.
return null;
}
/** /**
* Obtains a {@link DB} connection for the given {@link Mongo} instance and database name * Obtains a {@link DB} connection for the given {@link Mongo} instance and database name
* *

View File

@@ -16,11 +16,22 @@
package org.springframework.data.document.mongodb; package org.springframework.data.document.mongodb;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.document.UncategorizedDocumentStoreException;
import com.mongodb.MongoException;
import com.mongodb.MongoException.DuplicateKey;
import com.mongodb.MongoException.Network;
/** /**
* Simple {@link PersistenceExceptionTranslator} for Mongo. * Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
* exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is
* appropriate: any other exception may have resulted from user code, and should not be translated.
* @param ex runtime exception that occurred
* @return the corresponding DataAccessException instance, or {@literal null} if the exception should not be translated
*
* *
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@@ -34,6 +45,22 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
*/ */
public DataAccessException translateExceptionIfPossible(RuntimeException ex) { public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
return MongoDbUtils.translateMongoExceptionIfPossible(ex); // Check for well-known MongoException subclasses.
// All other MongoExceptions
if (ex instanceof DuplicateKey) {
return new DataIntegrityViolationException(ex.getMessage(), ex);
}
if (ex instanceof Network) {
return new DataAccessResourceFailureException(ex.getMessage(), ex);
}
if (ex instanceof MongoException) {
return new UncategorizedDocumentStoreException(ex.getMessage(), ex);
}
// If we get here, we have an exception that resulted from user code,
// rather than the persistence provider, so we return null to indicate
// that translation should not occur.
return null;
} }
} }

View File

@@ -22,8 +22,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.mongodb.Mongo; import com.mongodb.Mongo;
@@ -38,8 +36,7 @@ import com.mongodb.ServerAddress;
* *
* @since 1.0 * @since 1.0
*/ */
public class MongoFactoryBean implements FactoryBean<Mongo>, InitializingBean, public class MongoFactoryBean implements FactoryBean<Mongo>, InitializingBean {
PersistenceExceptionTranslator {
/** /**
@@ -120,10 +117,4 @@ public class MongoFactoryBean implements FactoryBean<Mongo>, InitializingBean,
} }
} }
} }
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
logger.debug("Translating " + ex);
return MongoDbUtils.translateMongoExceptionIfPossible(ex);
}
} }

View File

@@ -16,6 +16,7 @@
package org.springframework.data.document.mongodb; package org.springframework.data.document.mongodb;
import java.util.List; import java.util.List;
import java.util.Set;
import com.mongodb.DBCollection; import com.mongodb.DBCollection;
import com.mongodb.DBObject; import com.mongodb.DBObject;
@@ -27,7 +28,7 @@ import com.mongodb.DBObject;
* *
* @author Thomas Risberg * @author Thomas Risberg
* @author Mark Pollack * @author Mark Pollack
* * @author Oliver Gierke
*/ */
public interface MongoOperations { public interface MongoOperations {
@@ -38,7 +39,7 @@ public interface MongoOperations {
String getDefaultCollectionName(); String getDefaultCollectionName();
/** /**
* The default collection used by this template * The default collection used by this template.
* @return The default collection used by this template * @return The default collection used by this template
*/ */
DBCollection getDefaultCollection(); DBCollection getDefaultCollection();
@@ -117,17 +118,17 @@ public interface MongoOperations {
DBCollection createCollection(String collectionName); DBCollection createCollection(String collectionName);
/** /**
* Create a collect with the provided name and options * Create a collect with the provided name and options.
* @param collectionName name of the collection * @param collectionName name of the collection
* @param collectionOptions options to use when creating the collection. * @param collectionOptions options to use when creating the collection.
*/ */
void createCollection(String collectionName, CollectionOptions collectionOptions); void createCollection(String collectionName, CollectionOptions collectionOptions);
/** /**
* A list of collection names * A set of collection names.
* @return list of collection names * @return list of collection names
*/ */
List<String> getCollectionNames(); Set<String> getCollectionNames();
/** /**
* Get a collection by name, creating it if it doesn't exist. * Get a collection by name, creating it if it doesn't exist.
@@ -216,14 +217,14 @@ public interface MongoOperations {
* *
* @param listToSave the list of objects to save. * @param listToSave the list of objects to save.
*/ */
void insertList(List<Object> listToSave); void insertList(List<? extends Object> listToSave);
/** /**
* Insert a list of objects into the specified collection in a single batch write to the database. * Insert a list of objects into the specified collection in a single batch write to the database.
* @param collectionName name of the collection to store the object in * @param collectionName name of the collection to store the object in
* @param listToSave the list of objects to save. * @param listToSave the list of objects to save.
*/ */
void insertList(String collectionName, List<Object> listToSave); void insertList(String collectionName, List<? extends Object> listToSave);
/** /**
* Insert a list of objects into the specified collection using the provided MongoWriter instance * Insert a list of objects into the specified collection using the provided MongoWriter instance
@@ -233,7 +234,7 @@ public interface MongoOperations {
* @param listToSave the list of objects to save. * @param listToSave the list of objects to save.
* @param writer the writer to convert the object to save into a DBObject * @param writer the writer to convert the object to save into a DBObject
*/ */
<T> void insertList(String collectionName, List<T> listToSave, MongoWriter<T> writer); <T> void insertList(String collectionName, List<? extends T> listToSave, MongoWriter<T> writer);
/** /**
* Save the object to the default collection. This will perform an insert if the object is not already * Save the object to the default collection. This will perform an insert if the object is not already

View File

@@ -16,19 +16,20 @@
package org.springframework.data.document.mongodb; package org.springframework.data.document.mongodb;
import static java.lang.String.*;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory; import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jca.cci.core.ConnectionCallback;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObject;
@@ -39,7 +40,6 @@ import com.mongodb.DBCursor;
import com.mongodb.DBObject; import com.mongodb.DBObject;
import com.mongodb.Mongo; import com.mongodb.Mongo;
import com.mongodb.MongoException; import com.mongodb.MongoException;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON; import com.mongodb.util.JSON;
/** /**
@@ -53,10 +53,10 @@ import com.mongodb.util.JSON;
public class MongoTemplate implements InitializingBean, MongoOperations { public class MongoTemplate implements InitializingBean, MongoOperations {
private static final String ID = "_id"; private static final String ID = "_id";
private static final String COLLECTION_ERROR_TEMPLATE = "Error creating collection %s: %s";
private final MongoConverter mongoConverter; private final MongoConverter mongoConverter;
private final Mongo mongo; private final Mongo mongo;
private final MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
private String defaultCollectionName; private String defaultCollectionName;
private String databaseName; private String databaseName;
@@ -98,7 +98,7 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
} }
/** /**
* Sets the password to use to authenticate with the Mongo database * Sets the password to use to authenticate with the Mongo database.
* *
* @param password The password to use * @param password The password to use
*/ */
@@ -107,10 +107,20 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
this.password = password; this.password = password;
} }
/**
* Sets the name of the default collection to be used.
*
* @param defaultCollectionName
*/
public void setDefaultCollectionName(String defaultCollectionName) { public void setDefaultCollectionName(String defaultCollectionName) {
this.defaultCollectionName = defaultCollectionName; this.defaultCollectionName = defaultCollectionName;
} }
/**
* Sets the database name to be used.
*
* @param databaseName
*/
public void setDatabaseName(String databaseName) { public void setDatabaseName(String databaseName) {
Assert.notNull(databaseName); Assert.notNull(databaseName);
this.databaseName = databaseName; this.databaseName = databaseName;
@@ -127,7 +137,12 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
* @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollection() * @see org.springframework.data.document.mongodb.MongoOperations#getDefaultCollection()
*/ */
public DBCollection getDefaultCollection() { public DBCollection getDefaultCollection() {
return getDb().getCollection(getDefaultCollectionName());
return execute(new DBCallback<DBCollection>() {
public DBCollection doInDB(DB db) throws MongoException, DataAccessException {
return db.getCollection(getDefaultCollectionName());
}
});
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -140,33 +155,41 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#executeCommand(com.mongodb.DBObject) * @see org.springframework.data.document.mongodb.MongoOperations#executeCommand(com.mongodb.DBObject)
*/ */
public void executeCommand(DBObject command) { public void executeCommand(final DBObject command) {
CommandResult cr = getDb().command(command);
String err = cr.getErrorMessage(); CommandResult result = execute(new DBCallback<CommandResult>() {
if (err != null) { public CommandResult doInDB(DB db) throws MongoException, DataAccessException {
return db.command(command);
}
});
String error = result.getErrorMessage();
if (error != null) {
throw new InvalidDataAccessApiUsageException("Command execution of " + throw new InvalidDataAccessApiUsageException("Command execution of " +
command.toString() + " failed: " + err); command.toString() + " failed: " + error);
} }
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.DBCallback) * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.DBCallback)
*/ */
public <T> T execute(DBCallback<T> action) { public <T> T execute(DbCallback<T> action) {
DB db = getDb();
Assert.notNull(action);
try { try {
DB db = getDb();
return action.doInDB(db); return action.doInDB(db);
} catch (MongoException e) { } catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(e); throw potentiallyConvertRuntimeException(e);
} }
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback) * @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback)
*/ */
public <T> T execute(CollectionCallback<T> action) { public <T> T execute(CollectionCallback<T> callback) {
return execute(action, defaultCollectionName); return execute(callback, defaultCollectionName);
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -174,79 +197,126 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
*/ */
public <T> T execute(CollectionCallback<T> callback, String collectionName) { public <T> T execute(CollectionCallback<T> callback, String collectionName) {
Assert.notNull(callback);
try { try {
return callback.doInCollection(getCollection(collectionName)); DBCollection collection = getDb().getCollection(collectionName);
return callback.doInCollection(collection);
} catch (MongoException e) { } catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(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 <ol> <li>Execute the given {@link ConnectionCallback} for a
* {@link DBCursor}.</li> <li>Prepare that {@link DBCursor} with the given {@link CursorPreparer} (will be skipped
* if {@link CursorPreparer} is {@literal null}</li> <li>Iterate over the {@link DBCursor} and applies the given
* {@link DbObjectCallback} to each of the {@link DBObject}s collecting the actual result {@link List}.</li> <ol>
*
* @param <T>
* @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 <T> List<T> executeEach(CollectionCallback<DBCursor> collectionCallback, CursorPreparer preparer,
DbObjectCallback<T> objectCallback, String collectionName) {
try {
DBCursor cursor = collectionCallback.doInCollection(getCollection(collectionName));
if (preparer != null) {
preparer.prepare(cursor);
}
List<T> result = new ArrayList<T>();
for (DBObject object : cursor) {
result.add(objectCallback.doWith(object));
}
return result;
} catch (MongoException e) {
throw potentiallyConvertRuntimeException(e);
} }
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#executeInSession(org.springframework.data.document.mongodb.DBCallback) * @see org.springframework.data.document.mongodb.MongoOperations#executeInSession(org.springframework.data.document.mongodb.DBCallback)
*/ */
public <T> T executeInSession(DBCallback<T> action) { public <T> T executeInSession(final DBCallback<T> action) {
DB db = getDb();
db.requestStart(); return execute(new DBCallback<T>() {
public T doInDB(DB db) throws MongoException, DataAccessException {
try { try {
db.requestStart();
return action.doInDB(db); return action.doInDB(db);
} catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(e);
} finally { } finally {
db.requestDone(); db.requestDone();
} }
} }
});
}
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String) * @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String)
*/ */
public DBCollection createCollection(String collectionName) { public DBCollection createCollection(final String collectionName) {
try { return execute(new DBCallback<DBCollection>() {
return getDb().createCollection(collectionName, new BasicDBObject()); public DBCollection doInDB(DB db) throws MongoException, DataAccessException {
} catch (MongoException e) { return db.createCollection(collectionName, new BasicDBObject());
throw new InvalidDataAccessApiUsageException(format(COLLECTION_ERROR_TEMPLATE, collectionName, e.getMessage()), e);
} }
});
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String, org.springframework.data.document.mongodb.CollectionOptions) * @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String, org.springframework.data.document.mongodb.CollectionOptions)
*/ */
public void createCollection(String collectionName, CollectionOptions collectionOptions) { public void createCollection(final String collectionName, final CollectionOptions collectionOptions) {
try { execute(new DBCallback<Void>() {
getDb().createCollection(collectionName, convertToDbObject(collectionOptions)); public Void doInDB(DB db) throws MongoException, DataAccessException {
} catch (MongoException e) { db.createCollection(collectionName, convertToDbObject(collectionOptions));
throw new InvalidDataAccessApiUsageException(format(COLLECTION_ERROR_TEMPLATE, collectionName, e.getMessage()), e); return null;
} }
});
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String) * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String)
*/ */
public DBCollection getCollection(String collectionName) { public DBCollection getCollection(final String collectionName) {
try { return execute(new DBCallback<DBCollection>() {
return getDb().getCollection(collectionName); public DBCollection doInDB(DB db) throws MongoException, DataAccessException {
} catch (MongoException e) { return db.getCollection(collectionName);
throw new InvalidDataAccessApiUsageException(format(COLLECTION_ERROR_TEMPLATE, collectionName, e.getMessage()), e);
} }
});
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#collectionExists(java.lang.String) * @see org.springframework.data.document.mongodb.MongoOperations#collectionExists(java.lang.String)
*/ */
public boolean collectionExists(String collectionName) { public boolean collectionExists(final String collectionName) {
try { return execute(new DBCallback<Boolean>() {
return getDb().collectionExists(collectionName); public Boolean doInDB(DB db) throws MongoException, DataAccessException {
} catch (MongoException e) { return db.collectionExists(collectionName);
throw new InvalidDataAccessApiUsageException("Error creating collection " + collectionName + ": " + e.getMessage(), e);
} }
});
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#dropCollection(java.lang.String) * @see org.springframework.data.document.mongodb.MongoOperations#dropCollection(java.lang.String)
*/ */
public void dropCollection(String collectionName) { public void dropCollection(String collectionName) {
getDb().getCollection(collectionName)
.drop(); execute(new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.drop();
return null;
}
}, collectionName);
} }
@@ -287,21 +357,24 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.util.List) * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.util.List)
*/ */
public void insertList(List<Object> listToSave) { public void insertList(List<? extends Object> listToSave) {
insertList(getRequiredDefaultCollectionName(), listToSave); insertList(getRequiredDefaultCollectionName(), listToSave);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List) * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List)
*/ */
public void insertList(String collectionName, List<Object> listToSave) { public void insertList(String collectionName, List<? extends Object> listToSave) {
insertList(collectionName, listToSave, this.mongoConverter); insertList(collectionName, listToSave, this.mongoConverter);
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List, org.springframework.data.document.mongodb.MongoWriter) * @see org.springframework.data.document.mongodb.MongoOperations#insertList(java.lang.String, java.util.List, org.springframework.data.document.mongodb.MongoWriter)
*/ */
public <T> void insertList(String collectionName, List<T> listToSave, MongoWriter<T> writer) { public <T> void insertList(String collectionName, List<? extends T> listToSave, MongoWriter<T> writer) {
Assert.notNull(writer);
List<DBObject> dbObjectList = new ArrayList<DBObject>(); List<DBObject> dbObjectList = new ArrayList<DBObject>();
for (T o : listToSave) { for (T o : listToSave) {
BasicDBObject dbDoc = new BasicDBObject(); BasicDBObject dbDoc = new BasicDBObject();
@@ -341,51 +414,56 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
} }
protected Object insertDBObject(String collectionName, DBObject dbDoc) { protected Object insertDBObject(String collectionName, final DBObject dbDoc) {
if (dbDoc.keySet().size() > 0 ) {
WriteResult wr = null; if (dbDoc.keySet().isEmpty()) {
try {
wr = getDb().getCollection(collectionName).insert(dbDoc);
return dbDoc.get(ID);
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
}
else {
return null; return null;
} }
return execute(new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.insert(dbDoc);
return dbDoc.get(ID);
}
}, collectionName);
} }
protected List<Object> insertDBObjectList(String collectionName, List<DBObject> dbDocList) {
if (!dbDocList.isEmpty()) {
protected List<Object> insertDBObjectList(String collectionName, final List<DBObject> dbDocList) {
if (dbDocList.isEmpty()) {
return Collections.emptyList();
}
execute(new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.insert(dbDocList);
return null;
}
}, collectionName);
List<Object> ids = new ArrayList<Object>(); List<Object> ids = new ArrayList<Object>();
WriteResult wr = null;
try {
wr = getDb().getCollection(collectionName).insert(dbDocList);
for (DBObject dbo : dbDocList) { for (DBObject dbo : dbDocList) {
ids.add(dbo.get(ID)); ids.add(dbo.get(ID));
} }
return ids; return ids;
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
} else {
return null;
}
} }
protected Object saveDBObject(String collectionName, DBObject dbDoc) { protected Object saveDBObject(String collectionName, final DBObject dbDoc) {
if (dbDoc.keySet().size() > 0 ) {
WriteResult wr = null; if (dbDoc.keySet().isEmpty()) {
try {
wr = getDb().getCollection(collectionName).save(dbDoc);
return dbDoc.get(ID);
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
} else {
return null; return null;
} }
return execute(new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.save(dbDoc);
return dbDoc.get(ID);
}
}, collectionName);
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -398,13 +476,13 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject) * @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject)
*/ */
public void updateFirst(String collectionName, DBObject queryDoc, DBObject updateDoc) { public void updateFirst(String collectionName, final DBObject queryDoc, final DBObject updateDoc) {
execute(new CollectionCallback<Void>() {
try { public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
getDb().getCollection(collectionName).update(queryDoc, updateDoc); collection.update(queryDoc, updateDoc);
} catch (MongoException e) { return null;
throw new DataRetrievalFailureException("Error during update using " + queryDoc + ", " + updateDoc + "!", e);
} }
}, collectionName);
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -417,12 +495,13 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject) * @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject)
*/ */
public void updateMulti(String collectionName, DBObject queryDoc, DBObject updateDoc) { public void updateMulti(String collectionName, final DBObject queryDoc, final DBObject updateDoc) {
try { execute(new CollectionCallback<Void>() {
getDb().getCollection(collectionName).updateMulti(queryDoc, updateDoc); public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
} catch (MongoException e) { collection.updateMulti(queryDoc, updateDoc);
throw new DataRetrievalFailureException("Error during updateMulti using " + queryDoc + ", " + updateDoc + "!", e); return null;
} }
}, collectionName);
} }
/* (non-Javadoc) /* (non-Javadoc)
@@ -435,12 +514,13 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#remove(java.lang.String, com.mongodb.DBObject) * @see org.springframework.data.document.mongodb.MongoOperations#remove(java.lang.String, com.mongodb.DBObject)
*/ */
public void remove(String collectionName, DBObject queryDoc) { public void remove(String collectionName, final DBObject queryDoc) {
try { execute(new CollectionCallback<Void>() {
getDb().getCollection(collectionName).remove(queryDoc); public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
} catch (MongoException e) { collection.remove(queryDoc);
throw new DataRetrievalFailureException("Error during remove using " + queryDoc + "!", e); return null;
} }
}, collectionName);
} }
@@ -448,53 +528,35 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.Class) * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.Class)
*/ */
public <T> List<T> getCollection(Class<T> targetClass) { public <T> List<T> getCollection(Class<T> targetClass) {
return executeEach(new FindCallback(null), null, new ReadDbObjectCallback<T>(mongoConverter, targetClass),
List<T> results = new ArrayList<T>(); getDefaultCollectionName());
DBCollection collection = getDb().getCollection(getDefaultCollectionName());
for (DBObject dbo : collection.find()) {
Object obj = mongoConverter.read(targetClass, dbo);
//effectively acts as a query on the collection restricting it to elements of a specific type
if (targetClass.isInstance(obj)) {
results.add(targetClass.cast(obj));
}
}
return results;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String, java.lang.Class) * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String, java.lang.Class)
*/ */
public <T> List<T> getCollection(String collectionName, Class<T> targetClass) { public <T> List<T> getCollection(String collectionName, Class<T> targetClass) {
return executeEach(new FindCallback(null), null, new ReadDbObjectCallback<T>(mongoConverter, targetClass),
List<T> results = new ArrayList<T>(); collectionName);
DBCollection collection = getDb().getCollection(collectionName);
for (DBObject dbo : collection.find()) {
Object obj = mongoConverter.read(targetClass, dbo);
//effectively acts as a query on the collection restricting it to elements of a specific type
if (targetClass.isInstance(obj)) {
results.add(targetClass.cast(obj));
}
}
return results;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollectionNames() * @see org.springframework.data.document.mongodb.MongoOperations#getCollectionNames()
*/ */
public List<String> getCollectionNames() { public Set<String> getCollectionNames() {
return new ArrayList<String>(getDb().getCollectionNames()); return execute(new DBCallback<Set<String>>() {
public Set<String> doInDB(DB db) throws MongoException, DataAccessException {
return db.getCollectionNames();
}
});
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String, java.lang.Class, org.springframework.data.document.mongodb.MongoReader) * @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String, java.lang.Class, org.springframework.data.document.mongodb.MongoReader)
*/ */
public <T> List<T> getCollection(String collectionName, Class<T> targetClass, MongoReader<T> reader) { public <T> List<T> getCollection(String collectionName, Class<T> targetClass, MongoReader<T> reader) {
List<T> results = new ArrayList<T>(); return executeEach(new FindCallback(null), null, new ReadDbObjectCallback<T>(reader, targetClass),
DBCollection collection = getDb().getCollection(collectionName); collectionName);
for (DBObject dbo : collection.find()) {
results.add(reader.read(targetClass, dbo));
}
return results;
} }
// Queries that take JavaScript to express the query. // Queries that take JavaScript to express the query.
@@ -562,37 +624,18 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
* @see org.springframework.data.document.mongodb.MongoOperations#query(java.lang.String, com.mongodb.DBObject, java.lang.Class, org.springframework.data.document.mongodb.CursorPreparer) * @see org.springframework.data.document.mongodb.MongoOperations#query(java.lang.String, com.mongodb.DBObject, java.lang.Class, org.springframework.data.document.mongodb.CursorPreparer)
*/ */
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, CursorPreparer preparer) { public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, CursorPreparer preparer) {
DBCollection collection = getDb().getCollection(collectionName); return executeEach(new FindCallback(query), preparer, new ReadDbObjectCallback<T>(mongoConverter, targetClass),
List<T> results = new ArrayList<T>(); collectionName);
DBCursor cursor = collection.find(query);
if (preparer != null) {
preparer.prepare(cursor);
}
for (DBObject dbo : cursor) {
Object obj = mongoConverter.read(targetClass,dbo);
//effectively acts as a query on the collection restricting it to elements of a specific type
if (targetClass.isInstance(obj)) {
results.add(targetClass.cast(obj));
}
}
return results;
} }
/* (non-Javadoc) /* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#query(java.lang.String, com.mongodb.DBObject, java.lang.Class, org.springframework.data.document.mongodb.MongoReader) * @see org.springframework.data.document.mongodb.MongoOperations#query(java.lang.String, com.mongodb.DBObject, java.lang.Class, org.springframework.data.document.mongodb.MongoReader)
*/ */
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, MongoReader<T> reader) { public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, MongoReader<T> reader) {
DBCollection collection = getDb().getCollection(collectionName); return executeEach(new FindCallback(query), null, new ReadDbObjectCallback<T>(reader, targetClass),
List<T> results = new ArrayList<T>(); collectionName);
for (DBObject dbo : collection.find(query)) {
results.add(reader.read(targetClass, dbo));
}
return results;
} }
public RuntimeException convertMongoAccessException(RuntimeException ex) {
return MongoDbUtils.translateMongoExceptionIfPossible(ex);
}
public DB getDb() { public DB getDb() {
return MongoDbUtils.getDB(mongo, databaseName, username, password == null ? null : password.toCharArray()); return MongoDbUtils.getDB(mongo, databaseName, username, password == null ? null : password.toCharArray());
@@ -635,16 +678,81 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
} }
} }
/**
* 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;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/ */
public void afterPropertiesSet() { public void afterPropertiesSet() {
if (this.getDefaultCollectionName() != null) { if (this.getDefaultCollectionName() != null) {
DB db = getDb(); if (!collectionExists(getDefaultCollectionName())) {
if (! db.collectionExists(getDefaultCollectionName())) { createCollection(getDefaultCollectionName(), null);
db.createCollection(getDefaultCollectionName(), null);
} }
} }
} }
/**
* Simple {@link CollectionCallback} that takes a query {@link DBObject} and executes that against the
* {@link DBCollection}.
*
* @author Oliver Gierke
*/
private static class FindCallback implements CollectionCallback<DBCursor> {
private final DBObject query;
public FindCallback(DBObject query) {
this.query = query;
}
public DBCursor doInCollection(DBCollection collection) throws MongoException, DataAccessException {
return collection.find(query);
}
}
/**
* Simple internal callback to allow operations on a {@link DBObject}.
*
* @author Oliver Gierke
*/
private interface DbObjectCallback<T> {
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 static class ReadDbObjectCallback<T> implements DbObjectCallback<T> {
private final MongoReader<? super T> reader;
private final Class<T> type;
public ReadDbObjectCallback(MongoReader<? super T> reader, Class<T> type) {
this.reader = reader;
this.type = type;
}
@SuppressWarnings("unchecked")
public T doWith(DBObject object) {
return (T) reader.read(type, object);
}
}
} }

View File

@@ -0,0 +1,344 @@
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.document.mongodb;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.DataAccessException;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* Abstract base class for unit tests to specify behaviour we expect from {@link MongoOperations}. Subclasses return
* instances of their implementation and thus can see if it correctly implements the {@link MongoOperations} interface.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public abstract class MongoOperationsUnitTests {
@Mock
CollectionCallback<Object> collectionCallback;
@Mock
DbCallback<Object> dbCallback;
MongoConverter converter;
Person person;
List<Person> persons;
@Before
public final void operationsSetUp() {
person = new Person("Oliver");
persons = Arrays.asList(person);
converter = new MongoConverter() {
public Object read(Class<? extends Object> clazz, DBObject dbo) {
return person;
}
public void write(Object t, DBObject dbo) {
dbo.put("firstName", person.getFirstName());
}
};
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public void rejectsNullForCollectionCallback() {
getOperations().execute((CollectionCallback) null);
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public void rejectsNullForCollectionCallback2() {
getOperations().execute((CollectionCallback) null, "collection");
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public void rejectsNullForDbCallback() {
getOperations().execute((DbCallback) null);
}
@Test
public void convertsExceptionForCollectionExists() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.collectionExists("foo");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForCreateCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.createCollection("foo");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForCreateCollection2() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.createCollection("foo", new CollectionOptions(1, 1, true));
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForDropCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.dropCollection("foo");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCollectionCallback() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute(collectionCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteDbCallback() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute(dbCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCollectionCallbackAndCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute(collectionCallback, "collection");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCommand() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeCommand(new BasicDBObject());
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteStringCommand() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeCommand("");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteInSession() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeInSession(dbCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection(Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionName() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionNameAndType() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection", Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionNameTypeAndReader() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection", Object.class, converter);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionNames() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollectionNames();
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetDefaultCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getDefaultCollection();
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert(person);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert2() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert("collection", person);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert3() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert("collection", person, converter);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsertList() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList(persons);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetInsertList2() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList("collection", persons);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetInsertList3() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList("collection", persons, converter);
}
}.assertDataAccessException();
}
private abstract class Execution {
public void assertDataAccessException() {
assertException(DataAccessException.class);
}
public void assertException(Class<? extends Exception> exception) {
try {
doWith(getOperationsForExceptionHandling());
fail("Expected " + exception + " but completed without any!");
} catch (Exception e) {
assertTrue("Expected " + exception + " but got " + e, exception.isInstance(e));
}
}
public abstract void doWith(MongoOperations operations);
}
/**
* Expects an {@link MongoOperations} instance that will be used to check that invoking methods on it will only
* cause {@link DataAccessException}s.
*
* @return
*/
protected abstract MongoOperations getOperationsForExceptionHandling();
/**
* Returns a plain {@link MongoOperations}.
*
* @return
*/
protected abstract MongoOperations getOperations();
}

View File

@@ -23,7 +23,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.DataAccessException;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.DB; import com.mongodb.DB;
@@ -36,7 +36,7 @@ import com.mongodb.MongoException;
* @author Oliver Gierke * @author Oliver Gierke
*/ */
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class MongoTemplateUnitTests { public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
MongoTemplate template; MongoTemplate template;
@@ -48,7 +48,7 @@ public class MongoTemplateUnitTests {
@Before @Before
public void setUp() { public void setUp() {
this.template = new MongoTemplate(mongo, "database"); this.template = new MongoTemplate(mongo, "database", "default");
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
@@ -61,7 +61,7 @@ public class MongoTemplateUnitTests {
new MongoTemplate(null, "database"); new MongoTemplate(null, "database");
} }
@Test(expected = DataRetrievalFailureException.class) @Test(expected = DataAccessException.class)
public void removeHandlesMongoExceptionProperly() throws Exception { public void removeHandlesMongoExceptionProperly() throws Exception {
MongoTemplate template = mockOutGetDb(); MongoTemplate template = mockOutGetDb();
when(db.getCollection("collection")).thenThrow(new MongoException("Exception!")); when(db.getCollection("collection")).thenThrow(new MongoException("Exception!"));
@@ -87,4 +87,22 @@ public class MongoTemplateUnitTests {
stub(template.getDb()).toReturn(db); stub(template.getDb()).toReturn(db);
return template; return template;
} }
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperationsUnitTests#getOperations()
*/
@Override
protected MongoOperations getOperationsForExceptionHandling() {
MongoTemplate template = spy(this.template);
stub(template.getDb()).toThrow(new MongoException("Error!"));
return template;
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperationsUnitTests#getOperations()
*/
@Override
protected MongoOperations getOperations() {
return this.template;
}
} }

View File

@@ -21,6 +21,14 @@ public class Person {
private Person friend; private Person friend;
public Person() {
}
public Person(String firstname) {
this.firstName = firstname;
}
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;