diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java new file mode 100644 index 000000000..a20cddffe --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoAction.java @@ -0,0 +1,96 @@ +/* + * 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.mongodb.core; + +import com.mongodb.DBObject; +import com.mongodb.WriteConcern; + +/** + * Represents an action taken against the collection. Used by {@link WriteConcernResolver} to determine a custom + * WriteConcern based on this information. + * + * Properties that will always be not-null are collectionName and defaultWriteConcern. + * The EntityClass is null only for the MongoActionOperaton.INSERT_LIST. + * + * INSERT, SAVE have null query, + * REMOVE has null document + * INSERT_LIST has null entityClass, document, and query. + * + * @author Mark Pollack + * + */ +public class MongoAction { + + private String collectionName; + + private WriteConcern defaultWriteConcern; + + private Class entityClass; + + private MongoActionOperation mongoActionOperation; + + private DBObject query; + + private DBObject document; + + /** + * Create an instance of a MongoAction + * @param defaultWriteConcern the default write concern + * @param mongoActionOperation action being taken against the collection + * @param collectionName the collection name + * @param entityClass the POJO that is being operated against + * @param document the converted DBObject from the POJO or Spring Update object + * @param query the converted DBOjbect from the Spring Query object + */ + public MongoAction(WriteConcern defaultWriteConcern, MongoActionOperation mongoActionOperation, + String collectionName, Class entityClass, DBObject document, DBObject query) { + super(); + this.defaultWriteConcern = defaultWriteConcern; + this.mongoActionOperation = mongoActionOperation; + this.collectionName = collectionName; + this.entityClass = entityClass; + this.query = query; + this.document = document; + } + + public String getCollectionName() { + return collectionName; + } + + public WriteConcern getDefaultWriteConcern() { + return defaultWriteConcern; + } + + public Class getEntityClass() { + return entityClass; + } + + public MongoActionOperation getMongoActionOperation() { + return mongoActionOperation; + } + + public DBObject getQuery() { + return query; + } + + public DBObject getDocument() { + return document; + } + + + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java new file mode 100644 index 000000000..e79326d07 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoActionOperation.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2011 the original author or authors. + * + * Licensed under t +import org.springframework.data.convert.EntityReader; +he 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.mongodb.core; + +/** + * Enumeration for operations on a collection. Used with {@link MongoAction} to help determine the + * WriteConcern to use for a given mutating operation + * + * @author Mark Pollack + * @see MongoAction + * + */ +public enum MongoActionOperation { + + REMOVE, + UPDATE, + INSERT, + INSERT_LIST, + SAVE +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 45f40de94..5c3a295c4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -41,6 +41,7 @@ import com.mongodb.MapReduceCommand; import com.mongodb.MapReduceOutput; import com.mongodb.Mongo; import com.mongodb.MongoException; +import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; import com.mongodb.WriteResult; import com.mongodb.util.JSON; @@ -124,6 +125,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * the DB or Collection. */ private WriteConcern writeConcern = null; + + private WriteConcernResolver writeConcernResolver = new DefaultWriteConcernResolver(); /* * WriteResultChecking to be used for write operations if it has been @@ -131,10 +134,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { */ private WriteResultChecking writeResultChecking = WriteResultChecking.NONE; - /* - * Flag used to indicate use of slaveOk() for any operations on collections. + /** + * Set the ReadPreference when operating on a collection. See {@link #prepareCollection(DBCollection)} */ - private boolean slaveOk = false; + private ReadPreference readPreference = null; private final MongoConverter mongoConverter; private final MappingContext, MongoPersistentProperty> mappingContext; @@ -223,12 +226,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { } /** - * TODO: document properly + * Configures the {@link WriteConcernResolver} to be used with the template. * - * @param slaveOk + * @param writeConcernResolver */ - public void setSlaveOk(boolean slaveOk) { - this.slaveOk = slaveOk; + public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) { + this.writeConcernResolver = writeConcernResolver; + } + + /** + * Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are performed. + * @param readPreference + */ + public void setReadPreference(ReadPreference readPreference) { + this.readPreference = readPreference; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -578,8 +589,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * @param collection */ protected void prepareCollection(DBCollection collection) { - if (this.slaveOk) { - collection.slaveOk(); + if (this.readPreference != null) { + collection.setReadPreference(readPreference); } } @@ -590,8 +601,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { * @param writeConcern any WriteConcern already configured or null * @return The prepared WriteConcern or null */ - protected WriteConcern prepareWriteConcern(WriteConcern writeConcern) { - return writeConcern; + protected WriteConcern prepareWriteConcern(MongoAction mongoAction) { + return writeConcernResolver.resolve(mongoAction); } protected void doInsert(String collectionName, T objectToSave, MongoWriter writer) { @@ -601,7 +612,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { writer.write(objectToSave, dbDoc); maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc)); - Object id = insertDBObject(collectionName, dbDoc); + Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass()); populateIdIfNecessary(objectToSave, id); maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc)); @@ -685,19 +696,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { writer.write(objectToSave, dbDoc); maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc)); - Object id = saveDBObject(collectionName, dbDoc); + Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass()); populateIdIfNecessary(objectToSave, id); maybeEmitEvent(new AfterSaveEvent(objectToSave, dbDoc)); } - protected Object insertDBObject(String collectionName, final DBObject dbDoc) { + protected Object insertDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + " in collection: " + collectionName); } return execute(collectionName, new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass, dbDoc, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (writeConcernToUse == null) { collection.insert(dbDoc); } else { @@ -708,7 +720,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { }); } - protected List insertDBObjectList(String collectionName, final List dbDocList) { + protected List insertDBObjectList(final String collectionName, final List dbDocList) { if (dbDocList.isEmpty()) { return Collections.emptyList(); } @@ -718,7 +730,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { } execute(collectionName, new CollectionCallback() { public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, null, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (writeConcernToUse == null) { collection.insert(dbDocList); } else { @@ -741,13 +754,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { return ids; } - protected Object saveDBObject(String collectionName, final DBObject dbDoc) { + protected Object saveDBObject(final String collectionName, final DBObject dbDoc, final Class entityClass) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("save DBObject containing fields: " + dbDoc.keySet()); } return execute(collectionName, new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, dbDoc, null); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (writeConcernToUse == null) { collection.save(dbDoc); } else { @@ -796,7 +810,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { } WriteResult wr; - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass, updateObj, queryObj); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (writeConcernToUse == null) { if (multi) { wr = collection.updateMulti(queryObj, updateObj); @@ -869,7 +884,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { doRemove(determineCollectionName(entityClass), query, entityClass); } - protected void doRemove(String collectionName, final Query query, Class entityClass) { + protected void doRemove(final String collectionName, final Query query, final Class entityClass) { if (query == null) { throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null"); } @@ -879,7 +894,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException { DBObject dboq = mapper.getMappedObject(queryObject, entity); WriteResult wr = null; - WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern); + MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, entityClass, null, queryObject); + WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction); if (LOGGER.isDebugEnabled()) { LOGGER.debug("remove using query: " + queryObject + " in collection: " + collection.getName()); } @@ -1412,8 +1428,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { /** * Checks and handles any errors. *

- * TODO: current implementation logs errors - will be configurable to log warning, errors or throw exception in later - * versions + * Current implementation logs errors. Future version may make this configurable to log warning, errors or throw exception. */ private void handleAnyWriteResultErrors(WriteResult wr, DBObject query, String operation) { if (WriteResultChecking.NONE == this.writeResultChecking) { @@ -1587,6 +1602,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { return source; } } + + private class DefaultWriteConcernResolver implements WriteConcernResolver { + + public WriteConcern resolve(MongoAction action) { + return action.getDefaultWriteConcern(); + } + + } /** * {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java new file mode 100644 index 000000000..94e4e40d6 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/WriteConcernResolver.java @@ -0,0 +1,37 @@ +/* + * 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.mongodb.core; + +import com.mongodb.WriteConcern; + +/** + * A strategy interface to determine the WriteConcern to use for a given MongoDbAction. + * + * Return the passed in default WriteConcern (a property on MongoAction) if no determination can be made. + * + * @author Mark Pollack + * + */ +public interface WriteConcernResolver { + + /** + * Resolve the WriteConcern given the MongoAction + * @param action describes the context of the Mongo action. Contains a default WriteConcern to use if one should not be resolved. + * @return a WriteConcern based on the passed in MongoAction value, maybe null + */ + WriteConcern resolve(MongoAction action); +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index 5275f3c67..836679b8d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -61,6 +61,8 @@ import com.mongodb.DBObject; import com.mongodb.DBRef; import com.mongodb.Mongo; import com.mongodb.MongoException; +import com.mongodb.ReadPreference; +import com.mongodb.WriteConcern; import com.mongodb.WriteResult; /** @@ -774,8 +776,8 @@ public class MongoTemplateTests { @Test - public void testUsingSlaveOk() throws Exception { - this.template.execute("slaveOkTest", new CollectionCallback() { + public void testUsingReadPreference() throws Exception { + this.template.execute("readPref", new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { assertThat(collection.getOptions(), is(0)); assertThat(collection.getDB().getOptions(), is(0)); @@ -783,10 +785,10 @@ public class MongoTemplateTests { } }); MongoTemplate slaveTemplate = new MongoTemplate(factory); - slaveTemplate.setSlaveOk(true); - slaveTemplate.execute("slaveOkTest", new CollectionCallback() { + slaveTemplate.setReadPreference(ReadPreference.SECONDARY); + slaveTemplate.execute("readPref", new CollectionCallback() { public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException { - assertThat(collection.getOptions(), is(4)); + assertThat(collection.getReadPreference(), is(ReadPreference.SECONDARY)); assertThat(collection.getDB().getOptions(), is(0)); return null; } @@ -821,6 +823,55 @@ public class MongoTemplateTests { assertThat(result.getId(), is(person.getId())); assertThat(result.getFirstName(), is("Carter")); } + + @Test + public void testWriteConcernResolver() { + + PersonWithIdPropertyOfTypeObjectId person = new PersonWithIdPropertyOfTypeObjectId(); + person.setId(new ObjectId()); + person.setFirstName("Dave"); + + template.setWriteConcern(WriteConcern.NONE); + template.save(person); + WriteResult result = template.updateFirst(query(where("id").is(person.getId())), update("firstName", "Carter"), + PersonWithIdPropertyOfTypeObjectId.class); + WriteConcern lastWriteConcern = result.getLastConcern(); + assertThat(lastWriteConcern, equalTo(WriteConcern.NONE)); + + FsyncSafeWriteConcernResolver resolver = new FsyncSafeWriteConcernResolver(); + template.setWriteConcernResolver(resolver); + Query q = query(where("_id").is(person.getId())); + Update u = update("firstName", "Carter"); + result = template.updateFirst(q, u, PersonWithIdPropertyOfTypeObjectId.class); + lastWriteConcern = result.getLastConcern(); + assertThat(lastWriteConcern, equalTo(WriteConcern.FSYNC_SAFE)); + + MongoAction lastMongoAction = resolver.getMongoAction(); + assertThat(lastMongoAction.getCollectionName(), is("personWithIdPropertyOfTypeObjectId")); + assertThat(lastMongoAction.getDefaultWriteConcern(), equalTo(WriteConcern.NONE)); + assertThat(lastMongoAction.getDocument(), notNullValue()); + assertThat(lastMongoAction.getEntityClass().toString(), is(PersonWithIdPropertyOfTypeObjectId.class.toString())); + assertThat(lastMongoAction.getMongoActionOperation(), is(MongoActionOperation.UPDATE)); + assertThat(lastMongoAction.getQuery(), equalTo(q.getQueryObject())); + assertThat(lastMongoAction.getDocument(), equalTo(u.getUpdateObject())); + + } + + private class FsyncSafeWriteConcernResolver implements WriteConcernResolver { + + private MongoAction mongoAction; + + public WriteConcern resolve(MongoAction action) { + this.mongoAction = action; + return WriteConcern.FSYNC_SAFE; + } + + public MongoAction getMongoAction() { + return mongoAction; + } + } + + /** * @see DATADOC-246