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.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.util.Assert;
import com.mongodb.DB;
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.
@@ -52,36 +45,6 @@ abstract class MongoDbUtils {
private 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

View File

@@ -16,24 +16,51 @@
package org.springframework.data.document.mongodb;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DataIntegrityViolationException;
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
*/
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
/*
* (non-Javadoc)
*
* @see org.springframework.dao.support.PersistenceExceptionTranslator#
* translateExceptionIfPossible(java.lang.RuntimeException)
*/
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
/*
* (non-Javadoc)
*
* @see org.springframework.dao.support.PersistenceExceptionTranslator#
* translateExceptionIfPossible(java.lang.RuntimeException)
*/
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.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
import com.mongodb.Mongo;
@@ -38,8 +36,7 @@ import com.mongodb.ServerAddress;
*
* @since 1.0
*/
public class MongoFactoryBean implements FactoryBean<Mongo>, InitializingBean,
PersistenceExceptionTranslator {
public class MongoFactoryBean implements FactoryBean<Mongo>, InitializingBean {
/**
@@ -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;
import java.util.List;
import java.util.Set;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
@@ -27,7 +28,7 @@ import com.mongodb.DBObject;
*
* @author Thomas Risberg
* @author Mark Pollack
*
* @author Oliver Gierke
*/
public interface MongoOperations {
@@ -38,7 +39,7 @@ public interface MongoOperations {
String getDefaultCollectionName();
/**
* The default collection used by this template
* The default collection used by this template.
* @return The default collection used by this template
*/
DBCollection getDefaultCollection();
@@ -117,17 +118,17 @@ public interface MongoOperations {
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 collectionOptions options to use when creating the collection.
*/
void createCollection(String collectionName, CollectionOptions collectionOptions);
/**
* A list of collection names
* A set 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.
@@ -216,14 +217,14 @@ public interface MongoOperations {
*
* @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.
* @param collectionName name of the collection to store the object in
* @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
@@ -233,7 +234,7 @@ public interface MongoOperations {
* @param listToSave the list of objects to save.
* @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

View File

@@ -16,19 +16,20 @@
package org.springframework.data.document.mongodb;
import static java.lang.String.*;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jca.cci.core.ConnectionCallback;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject;
@@ -39,7 +40,6 @@ import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON;
/**
@@ -53,10 +53,10 @@ import com.mongodb.util.JSON;
public class MongoTemplate implements InitializingBean, MongoOperations {
private static final String ID = "_id";
private static final String COLLECTION_ERROR_TEMPLATE = "Error creating collection %s: %s";
private final MongoConverter mongoConverter;
private final Mongo mongo;
private final MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
private String defaultCollectionName;
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
*/
@@ -107,10 +107,20 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
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;
@@ -127,7 +137,12 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
* @see org.springframework.data.document.mongodb.MongoOperations#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)
@@ -140,33 +155,41 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#executeCommand(com.mongodb.DBObject)
*/
public void executeCommand(DBObject command) {
CommandResult cr = getDb().command(command);
String err = cr.getErrorMessage();
if (err != null) {
throw new InvalidDataAccessApiUsageException("Command execution of " +
command.toString() + " failed: " + err);
public void executeCommand(final DBObject command) {
CommandResult result = execute(new DBCallback<CommandResult>() {
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 " +
command.toString() + " failed: " + error);
}
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.DBCallback)
*/
public <T> T execute(DBCallback<T> action) {
DB db = getDb();
public <T> T execute(DbCallback<T> action) {
Assert.notNull(action);
try {
DB db = getDb();
return action.doInDB(db);
} catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(e);
throw potentiallyConvertRuntimeException(e);
}
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#execute(org.springframework.data.document.mongodb.CollectionCallback)
*/
public <T> T execute(CollectionCallback<T> action) {
return execute(action, defaultCollectionName);
public <T> T execute(CollectionCallback<T> callback) {
return execute(callback, defaultCollectionName);
}
/* (non-Javadoc)
@@ -174,79 +197,126 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
*/
public <T> T execute(CollectionCallback<T> callback, String collectionName) {
Assert.notNull(callback);
try {
return callback.doInCollection(getCollection(collectionName));
DBCollection collection = getDb().getCollection(collectionName);
return callback.doInCollection(collection);
} 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)
* @see org.springframework.data.document.mongodb.MongoOperations#executeInSession(org.springframework.data.document.mongodb.DBCallback)
*/
public <T> T executeInSession(DBCallback<T> action) {
DB db = getDb();
db.requestStart();
try {
return action.doInDB(db);
} catch (MongoException e) {
throw MongoDbUtils.translateMongoExceptionIfPossible(e);
} finally {
db.requestDone();
}
public <T> T executeInSession(final DBCallback<T> action) {
return execute(new DBCallback<T>() {
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(String collectionName) {
try {
return getDb().createCollection(collectionName, new BasicDBObject());
} catch (MongoException e) {
throw new InvalidDataAccessApiUsageException(format(COLLECTION_ERROR_TEMPLATE, collectionName, e.getMessage()), e);
}
public DBCollection createCollection(final String collectionName) {
return execute(new DBCallback<DBCollection>() {
public DBCollection doInDB(DB db) throws MongoException, DataAccessException {
return db.createCollection(collectionName, new BasicDBObject());
}
});
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#createCollection(java.lang.String, org.springframework.data.document.mongodb.CollectionOptions)
*/
public void createCollection(String collectionName, CollectionOptions collectionOptions) {
try {
getDb().createCollection(collectionName, convertToDbObject(collectionOptions));
} catch (MongoException e) {
throw new InvalidDataAccessApiUsageException(format(COLLECTION_ERROR_TEMPLATE, collectionName, e.getMessage()), e);
}
public void createCollection(final String collectionName, final CollectionOptions collectionOptions) {
execute(new DBCallback<Void>() {
public Void doInDB(DB db) throws MongoException, DataAccessException {
db.createCollection(collectionName, convertToDbObject(collectionOptions));
return null;
}
});
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String)
*/
public DBCollection getCollection(String collectionName) {
try {
return getDb().getCollection(collectionName);
} catch (MongoException e) {
throw new InvalidDataAccessApiUsageException(format(COLLECTION_ERROR_TEMPLATE, collectionName, e.getMessage()), e);
}
public DBCollection getCollection(final String collectionName) {
return execute(new DBCallback<DBCollection>() {
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(String collectionName) {
try {
return getDb().collectionExists(collectionName);
} catch (MongoException e) {
throw new InvalidDataAccessApiUsageException("Error creating collection " + collectionName + ": " + e.getMessage(), e);
}
public boolean collectionExists(final String collectionName) {
return execute(new DBCallback<Boolean>() {
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) {
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)
* @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);
}
/* (non-Javadoc)
* @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);
}
/* (non-Javadoc)
* @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>();
for (T o : listToSave) {
BasicDBObject dbDoc = new BasicDBObject();
@@ -341,51 +414,56 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
}
protected Object insertDBObject(String collectionName, DBObject dbDoc) {
if (dbDoc.keySet().size() > 0 ) {
WriteResult wr = null;
try {
wr = getDb().getCollection(collectionName).insert(dbDoc);
return dbDoc.get(ID);
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
}
else {
protected Object insertDBObject(String collectionName, final DBObject dbDoc) {
if (dbDoc.keySet().isEmpty()) {
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, 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>();
for (DBObject dbo : dbDocList) {
ids.add(dbo.get(ID));
}
return ids;
}
protected List<Object> insertDBObjectList(String collectionName, List<DBObject> dbDocList) {
if (!dbDocList.isEmpty()) {
List<Object> ids = new ArrayList<Object>();
WriteResult wr = null;
try {
wr = getDb().getCollection(collectionName).insert(dbDocList);
for (DBObject dbo : dbDocList) {
ids.add(dbo.get(ID));
}
return ids;
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
} else {
protected Object saveDBObject(String collectionName, final DBObject dbDoc) {
if (dbDoc.keySet().isEmpty()) {
return null;
}
}
return execute(new CollectionCallback<Object>() {
protected Object saveDBObject(String collectionName, DBObject dbDoc) {
if (dbDoc.keySet().size() > 0 ) {
WriteResult wr = null;
try {
wr = getDb().getCollection(collectionName).save(dbDoc);
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.save(dbDoc);
return dbDoc.get(ID);
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
} else {
return null;
}
}, collectionName);
}
/* (non-Javadoc)
@@ -398,13 +476,13 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc)
* @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) {
try {
getDb().getCollection(collectionName).update(queryDoc, updateDoc);
} catch (MongoException e) {
throw new DataRetrievalFailureException("Error during update using " + queryDoc + ", " + updateDoc + "!", e);
}
public void updateFirst(String collectionName, final DBObject queryDoc, final DBObject updateDoc) {
execute(new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.update(queryDoc, updateDoc);
return null;
}
}, collectionName);
}
/* (non-Javadoc)
@@ -417,12 +495,13 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc)
* @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) {
try {
getDb().getCollection(collectionName).updateMulti(queryDoc, updateDoc);
} catch (MongoException e) {
throw new DataRetrievalFailureException("Error during updateMulti using " + queryDoc + ", " + updateDoc + "!", e);
}
public void updateMulti(String collectionName, final DBObject queryDoc, final DBObject updateDoc) {
execute(new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.updateMulti(queryDoc, updateDoc);
return null;
}
}, collectionName);
}
/* (non-Javadoc)
@@ -435,12 +514,13 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#remove(java.lang.String, com.mongodb.DBObject)
*/
public void remove(String collectionName, DBObject queryDoc) {
try {
getDb().getCollection(collectionName).remove(queryDoc);
} catch (MongoException e) {
throw new DataRetrievalFailureException("Error during remove using " + queryDoc + "!", e);
}
public void remove(String collectionName, final DBObject queryDoc) {
execute(new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.remove(queryDoc);
return null;
}
}, collectionName);
}
@@ -448,53 +528,35 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.Class)
*/
public <T> List<T> getCollection(Class<T> targetClass) {
List<T> results = new ArrayList<T>();
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;
return executeEach(new FindCallback(null), null, new ReadDbObjectCallback<T>(mongoConverter, targetClass),
getDefaultCollectionName());
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollection(java.lang.String, java.lang.Class)
*/
public <T> List<T> getCollection(String collectionName, Class<T> targetClass) {
List<T> results = new ArrayList<T>();
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;
return executeEach(new FindCallback(null), null, new ReadDbObjectCallback<T>(mongoConverter, targetClass),
collectionName);
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#getCollectionNames()
*/
public List<String> getCollectionNames() {
return new ArrayList<String>(getDb().getCollectionNames());
public Set<String> getCollectionNames() {
return execute(new DBCallback<Set<String>>() {
public Set<String> doInDB(DB db) throws MongoException, DataAccessException {
return db.getCollectionNames();
}
});
}
/* (non-Javadoc)
* @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) {
List<T> results = new ArrayList<T>();
DBCollection collection = getDb().getCollection(collectionName);
for (DBObject dbo : collection.find()) {
results.add(reader.read(targetClass, dbo));
}
return results;
public <T> List<T> getCollection(String collectionName, Class<T> targetClass, MongoReader<T> reader) {
return executeEach(new FindCallback(null), null, new ReadDbObjectCallback<T>(reader, targetClass),
collectionName);
}
// Queries that take JavaScript to express the query.
@@ -504,7 +566,7 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
*/
public <T> List<T> queryUsingJavaScript(String query, Class<T> targetClass) {
return query(getDefaultCollectionName(), (DBObject)JSON.parse(query), targetClass); //
}
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#queryUsingJavaScript(java.lang.String, java.lang.Class, org.springframework.data.document.mongodb.MongoReader)
@@ -554,7 +616,7 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#query(java.lang.String, com.mongodb.DBObject, java.lang.Class)
*/
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass) {
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass) {
return query(collectionName, query, targetClass, (CursorPreparer) null);
}
@@ -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)
*/
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, CursorPreparer preparer) {
DBCollection collection = getDb().getCollection(collectionName);
List<T> results = new ArrayList<T>();
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;
return executeEach(new FindCallback(query), preparer, new ReadDbObjectCallback<T>(mongoConverter, targetClass),
collectionName);
}
/* (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)
*/
public <T> List<T> query(String collectionName, DBObject query, Class<T> targetClass, MongoReader<T> reader) {
DBCollection collection = getDb().getCollection(collectionName);
List<T> results = new ArrayList<T>();
for (DBObject dbo : collection.find(query)) {
results.add(reader.read(targetClass, dbo));
}
return results;
return executeEach(new FindCallback(query), null, new ReadDbObjectCallback<T>(reader, targetClass),
collectionName);
}
public RuntimeException convertMongoAccessException(RuntimeException ex) {
return MongoDbUtils.translateMongoExceptionIfPossible(ex);
}
public DB getDb() {
return MongoDbUtils.getDB(mongo, databaseName, username, password == null ? null : password.toCharArray());
@@ -603,7 +646,7 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
DBObject dbo = new BasicDBObject();
if (collectionOptions != null) {
if (collectionOptions.getCapped() != null) {
dbo.put("capped", collectionOptions.getCapped().booleanValue());
dbo.put("capped", collectionOptions.getCapped().booleanValue());
}
if (collectionOptions.getSize() != null) {
dbo.put("size", collectionOptions.getSize().intValue());
@@ -634,6 +677,19 @@ 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)
@@ -641,10 +697,62 @@ public class MongoTemplate implements InitializingBean, MongoOperations {
*/
public void afterPropertiesSet() {
if (this.getDefaultCollectionName() != null) {
DB db = getDb();
if (! db.collectionExists(getDefaultCollectionName())) {
db.createCollection(getDefaultCollectionName(), null);
if (!collectionExists(getDefaultCollectionName())) {
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.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.DB;
@@ -36,7 +36,7 @@ import com.mongodb.MongoException;
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoTemplateUnitTests {
public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
MongoTemplate template;
@@ -48,7 +48,7 @@ public class MongoTemplateUnitTests {
@Before
public void setUp() {
this.template = new MongoTemplate(mongo, "database");
this.template = new MongoTemplate(mongo, "database", "default");
}
@Test(expected = IllegalArgumentException.class)
@@ -61,7 +61,7 @@ public class MongoTemplateUnitTests {
new MongoTemplate(null, "database");
}
@Test(expected = DataRetrievalFailureException.class)
@Test(expected = DataAccessException.class)
public void removeHandlesMongoExceptionProperly() throws Exception {
MongoTemplate template = mockOutGetDb();
when(db.getCollection("collection")).thenThrow(new MongoException("Exception!"));
@@ -87,4 +87,22 @@ public class MongoTemplateUnitTests {
stub(template.getDb()).toReturn(db);
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

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