From 54377031bbe97e4febb8cba980c39ba0f1fbe887 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 24 Aug 2011 09:16:06 +0200 Subject: [PATCH] DATADOC-216 - Added ability to configure a WriteConcern on DB level. Added WriteConcern property to MongoDbFactory and expose it through the db-factory namespace element. --- .../data/mongodb/MongoDbFactory.java | 19 ++- .../mongodb/config/MongoDbFactoryParser.java | 128 ++++++++++-------- .../data/mongodb/config/ParsingUtils.java | 36 +++-- .../data/mongodb/core/MongoDbUtils.java | 1 - .../data/mongodb/core/MongoFactoryBean.java | 3 +- .../mongodb/core/SimpleMongoDbFactory.java | 88 ++++++++---- .../mongodb/config/spring-mongo-1.0.xsd | 1 + .../MongoDbFactoryParserIntegrationTests.java | 50 +++++++ .../resources/namespace/db-factory-bean.xml | 10 ++ 9 files changed, 243 insertions(+), 93 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java create mode 100644 spring-data-mongodb/src/test/resources/namespace/db-factory-bean.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java index 742a36230..1db0daf12 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java @@ -4,10 +4,27 @@ import org.springframework.dao.DataAccessException; import com.mongodb.DB; +/** + * Interface for factories creating {@link DB} instances. + * + * @author Mark Pollack + */ public interface MongoDbFactory { + /** + * Creates a default {@link DB} instance. + * + * @return + * @throws DataAccessException + */ DB getDb() throws DataAccessException; + /** + * Creates a {@link DB} instance to access the database with the given name. + * + * @param dbName must not be {@literal null} or empty. + * @return + * @throws DataAccessException + */ DB getDb(String dbName) throws DataAccessException; - } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java index d800d36ec..3366f0f8f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/MongoDbFactoryParser.java @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.mongodb.config; import static org.springframework.data.mongodb.config.BeanNames.*; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.data.authentication.UserCredentials; import org.springframework.data.mongodb.core.MongoFactoryBean; @@ -31,13 +32,19 @@ import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import org.springframework.util.StringUtils; import org.w3c.dom.Element; +import com.mongodb.Mongo; + /** - * @author Jon Brisbin + * {@link BeanDefinitionParser} to parse {@code db-factory} elements into {@link BeanDefinition}s. + * + * @author Jon Brisbin + * @author Oliver Gierke */ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser { @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { String id = element.getAttribute("id"); if (!StringUtils.hasText(id)) { id = DB_FACTORY; @@ -47,55 +54,70 @@ public class MongoDbFactoryParser extends AbstractBeanDefinitionParser { @Override protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { - BeanDefinitionBuilder dbFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleMongoDbFactory.class); - - // UserCredentials - BeanDefinitionBuilder userCredentialsBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserCredentials.class); - String username = element.getAttribute("username"); - if (StringUtils.hasText(username)) { - userCredentialsBuilder.addConstructorArgValue(username); - } else { - userCredentialsBuilder.addConstructorArgValue(null); - } - String password = element.getAttribute("password"); - if (StringUtils.hasText(password)) { - userCredentialsBuilder.addConstructorArgValue(password); - } else { - userCredentialsBuilder.addConstructorArgValue(null); - } - - // host and port - String host = element.getAttribute("host"); - if (!StringUtils.hasText(host)) { - host = "localhost"; - } - String port = element.getAttribute("port"); - if (!StringUtils.hasText(port)) { - port = "27017"; - } - - // Database name - String dbname = element.getAttribute("dbname"); - if (!StringUtils.hasText(dbname)) { - dbname = "db"; - } - - // com.mongodb.Mongo object - String mongoRef = element.getAttribute("mongo-ref"); - - if (!StringUtils.hasText(mongoRef)) { - //Create implicit com.mongodb.Mongo object and register under generated name - BeanDefinitionBuilder mongoBuilder = BeanDefinitionBuilder.genericBeanDefinition(MongoFactoryBean.class); - mongoBuilder.addPropertyValue("host", host); - mongoBuilder.addPropertyValue("port", port); - mongoRef = BeanDefinitionReaderUtils.registerWithGeneratedName(mongoBuilder.getBeanDefinition(), parserContext.getRegistry()); - } - dbFactoryBuilder.addConstructorArgValue(new RuntimeBeanReference(mongoRef)); - dbFactoryBuilder.addConstructorArgValue(dbname); - dbFactoryBuilder.addConstructorArgValue(userCredentialsBuilder.getBeanDefinition()); - - return dbFactoryBuilder.getRawBeanDefinition(); - } - + String mongoRef = element.getAttribute("mongo-ref"); + if (!StringUtils.hasText(mongoRef)) { + mongoRef = registerMongoBeanDefinition(element, parserContext); + } + + // Database name + String dbname = element.getAttribute("dbname"); + if (!StringUtils.hasText(dbname)) { + dbname = "db"; + } + + BeanDefinitionBuilder dbFactoryBuilder = BeanDefinitionBuilder.genericBeanDefinition(SimpleMongoDbFactory.class); + dbFactoryBuilder.addConstructorArgValue(new RuntimeBeanReference(mongoRef)); + dbFactoryBuilder.addConstructorArgValue(dbname); + + BeanDefinition userCredentials = getUserCredentialsBeanDefinition(element); + if (userCredentials != null) { + dbFactoryBuilder.addConstructorArgValue(userCredentials); + } + + ParsingUtils.setPropertyValue(element, dbFactoryBuilder, "write-concern", "writeConcern"); + + return dbFactoryBuilder.getBeanDefinition(); + + } + + /** + * Registers a default {@link BeanDefinition} of a {@link Mongo} instance and returns the name under which the + * {@link Mongo} instance was registered under. + * + * @param element must not be {@literal null}. + * @param parserContext must not be {@literal null}. + * @return + */ + private String registerMongoBeanDefinition(Element element, ParserContext parserContext) { + + BeanDefinitionBuilder mongoBuilder = BeanDefinitionBuilder.genericBeanDefinition(MongoFactoryBean.class); + ParsingUtils.setPropertyValue(element, mongoBuilder, "host"); + ParsingUtils.setPropertyValue(element, mongoBuilder, "port"); + + return BeanDefinitionReaderUtils.registerWithGeneratedName(mongoBuilder.getBeanDefinition(), + parserContext.getRegistry()); + } + + /** + * Returns a {@link BeanDefinition} for a {@link UserCredentials} object. + * + * @param element + * @return the {@link BeanDefinition} or {@literal null} if neither username nor password given. + */ + private BeanDefinition getUserCredentialsBeanDefinition(Element element) { + + String username = element.getAttribute("username"); + String password = element.getAttribute("password"); + + if (!StringUtils.hasText(username) && !StringUtils.hasText(password)) { + return null; + } + + BeanDefinitionBuilder userCredentialsBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserCredentials.class); + userCredentialsBuilder.addConstructorArgValue(StringUtils.hasText(username) ? username : null); + userCredentialsBuilder.addConstructorArgValue(StringUtils.hasText(password) ? password : null); + + return userCredentialsBuilder.getBeanDefinition(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java index 7c3b42606..1cfe5ed3d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/ParsingUtils.java @@ -29,7 +29,7 @@ import com.mongodb.ServerAddress; abstract class ParsingUtils { /** - * Parses the mongo replica-set element. + * Parses the mongo replica-set element. * @param parserContext the parser context * @param element the mongo element * @param mongoBuilder the bean definition builder to populate @@ -42,17 +42,17 @@ abstract class ParsingUtils { if (StringUtils.hasText(replicaSetString)) { ManagedList serverAddresses = new ManagedList(); String[] replicaSetStringArray = StringUtils.commaDelimitedListToStringArray(replicaSetString); - for (int i = 0; i < replicaSetStringArray.length; i++) { - String[] hostAndPort = StringUtils.delimitedListToStringArray(replicaSetStringArray[i], ":"); + for (String element2 : replicaSetStringArray) { + String[] hostAndPort = StringUtils.delimitedListToStringArray(element2, ":"); BeanDefinitionBuilder defBuilder = BeanDefinitionBuilder.genericBeanDefinition(ServerAddress.class); defBuilder.addConstructorArgValue(hostAndPort[0]); defBuilder.addConstructorArgValue(hostAndPort[1]); serverAddresses.add(defBuilder.getBeanDefinition()); - } + } if (!serverAddresses.isEmpty()) { mongoBuilder.addPropertyValue("replicaSetSeeds", serverAddresses); } - } + } return true; } @@ -63,8 +63,9 @@ abstract class ParsingUtils { */ static boolean parseMongoOptions(ParserContext parserContext, Element element, BeanDefinitionBuilder mongoBuilder) { Element optionsElement = DomUtils.getChildElementByTagName(element, "options"); - if (optionsElement == null) - return false; + if (optionsElement == null) { + return false; + } BeanDefinitionBuilder optionsDefBuilder = BeanDefinitionBuilder .genericBeanDefinition(MongoOptionsFactoryBean.class); @@ -75,12 +76,12 @@ abstract class ParsingUtils { setPropertyValue(optionsElement, optionsDefBuilder, "max-wait-time", "maxWaitTime"); setPropertyValue(optionsElement, optionsDefBuilder, "connect-timeout", "connectTimeout"); setPropertyValue(optionsElement, optionsDefBuilder, "socket-timeout", "socketTimeout"); - setPropertyValue(optionsElement, optionsDefBuilder, "socket-keep-alive", "socketKeepAlive"); + setPropertyValue(optionsElement, optionsDefBuilder, "socket-keep-alive", "socketKeepAlive"); setPropertyValue(optionsElement, optionsDefBuilder, "auto-connect-retry", "autoConnectRetry"); - setPropertyValue(optionsElement, optionsDefBuilder, "write-number", "writeNumber"); + setPropertyValue(optionsElement, optionsDefBuilder, "write-number", "writeNumber"); setPropertyValue(optionsElement, optionsDefBuilder, "write-timeout", "writeTimeout"); setPropertyValue(optionsElement, optionsDefBuilder, "write-fsync", "writeFsync"); - setPropertyValue(optionsElement, optionsDefBuilder, "slave-ok", "slaveOk"); + setPropertyValue(optionsElement, optionsDefBuilder, "slave-ok", "slaveOk"); @@ -94,4 +95,19 @@ abstract class ParsingUtils { builder.addPropertyValue(propertyName, attr); } } + + /** + * Sets the property with the given attribute name on the given {@link BeanDefinitionBuilder} to the value of the + * attribute with the given name. + * + * @param element must not be {@literal null}. + * @param builder must not be {@literal null}. + * @param attrName must not be {@literal null} or empty. + */ + static void setPropertyValue(Element element, BeanDefinitionBuilder builder, String attrName) { + String attr = element.getAttribute(attrName); + if (StringUtils.hasText(attr)) { + builder.addPropertyValue(attrName, attr); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java index a3610d6f8..501b57a91 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoDbUtils.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.mongodb.core; import com.mongodb.DB; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoFactoryBean.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoFactoryBean.java index 7d18d2472..5445aa020 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoFactoryBean.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoFactoryBean.java @@ -24,7 +24,6 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.CannotGetMongoDbConnectionException; - import com.mongodb.Mongo; import com.mongodb.MongoOptions; import com.mongodb.ServerAddress; @@ -93,7 +92,7 @@ public class MongoFactoryBean implements FactoryBean, PersistenceExceptio if (host == null) { - logger.warn("Property host not specified. Using default configuration"); + logger.debug("Property host not specified. Using default configuration"); mongo = new Mongo(); } else { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java index 034253459..cf4c5cf54 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/SimpleMongoDbFactory.java @@ -1,3 +1,18 @@ +/* + * 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 org.apache.commons.logging.Log; @@ -10,25 +25,29 @@ import org.springframework.util.Assert; import com.mongodb.DB; import com.mongodb.Mongo; +import com.mongodb.WriteConcern; +/** + * Factory to create {@link DB} instances from a {@link Mongo} instance. + * + * @author Mark Pollack + * @author Oliver Gierke + */ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { - /** - * Logger, available to subclasses. - */ protected final Log logger = LogFactory.getLog(getClass()); - private Mongo mongo; - private String databaseName; + private final Mongo mongo; + private final String databaseName; private String username; private String password; + private WriteConcern writeConcern; /** - * Create an instance of SimpleMongoDbFactory given the Mongo instance and - * database name + * Create an instance of {@link SimpleMongoDbFactory} given the {@link Mongo} instance and database name. * - * @param mongo Mongo instance, not null - * @param databaseName Database name, not null + * @param mongo Mongo instance, must not be {@literal null}. + * @param databaseName database name, not be {@literal null}. */ public SimpleMongoDbFactory(Mongo mongo, String databaseName) { Assert.notNull(mongo, "Mongo must not be null"); @@ -38,32 +57,50 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { } /** - * Create an instance of SimpleMongoDbFactory given the Mongo instance, - * database name, and username/password + * Create an instance of SimpleMongoDbFactory given the Mongo instance, database name, and username/password * - * @param mongo Mongo instance, not null - * @param databaseName Database name, not null - * @param userCredentials username and password + * @param mongo Mongo instance, must not be {@literal null}. + * @param databaseName Database name, must not be {@literal null}. + * @param userCredentials username and password must not be {@literal null}. */ - public SimpleMongoDbFactory(Mongo mongo, String databaseName, - UserCredentials userCredentials) { + public SimpleMongoDbFactory(Mongo mongo, String databaseName, UserCredentials userCredentials) { this(mongo, databaseName); this.username = userCredentials.getUsername(); this.password = userCredentials.getPassword(); } - public DB getDb() throws DataAccessException { - Assert.notNull(mongo, "Mongo must not be null"); - Assert.hasText(databaseName, "Database name must not be empty"); - return MongoDbUtils.getDB(mongo, databaseName, username, - password == null ? null : password.toCharArray()); + /** + * Configures the {@link WriteConcern} to be used on the {@link DB} instance being created. + * + * @param writeConcern the writeConcern to set + */ + public void setWriteConcern(WriteConcern writeConcern) { + this.writeConcern = writeConcern; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.MongoDbFactory#getDb() + */ + public DB getDb() throws DataAccessException { + return getDb(databaseName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.MongoDbFactory#getDb(java.lang.String) + */ public DB getDb(String dbName) throws DataAccessException { - Assert.notNull(mongo, "Mongo must not be null"); - Assert.hasText(dbName, "Database name must not be empty"); - return MongoDbUtils.getDB(mongo, dbName, username, - password == null ? null : password.toCharArray()); + + Assert.hasText(dbName, "Database name must not be empty."); + + DB db = MongoDbUtils.getDB(mongo, dbName, username, password == null ? null : password.toCharArray()); + + if (writeConcern != null) { + db.setWriteConcern(writeConcern); + } + + return db; } /** @@ -72,5 +109,4 @@ public class SimpleMongoDbFactory implements DisposableBean, MongoDbFactory { public void destroy() throws Exception { mongo.close(); } - } diff --git a/spring-data-mongodb/src/main/resources/org/springframework/data/document/mongodb/config/spring-mongo-1.0.xsd b/spring-data-mongodb/src/main/resources/org/springframework/data/document/mongodb/config/spring-mongo-1.0.xsd index d87c36523..131e83274 100644 --- a/spring-data-mongodb/src/main/resources/org/springframework/data/document/mongodb/config/spring-mongo-1.0.xsd +++ b/spring-data-mongodb/src/main/resources/org/springframework/data/document/mongodb/config/spring-mongo-1.0.xsd @@ -87,6 +87,7 @@ The password to use when connecting to a MongoDB server. ]]> + diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java new file mode 100644 index 000000000..a45afaa20 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/config/MongoDbFactoryParserIntegrationTests.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011 by the original author(s). + * + * 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.config; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.xml.XmlBeanFactory; +import org.springframework.core.io.ClassPathResource; + +/** + * Integration tests for {@link MongoDbFactoryParser}. + * + * @author Oliver Gierke + */ +public class MongoDbFactoryParserIntegrationTests { + + @Test + public void parsesWriteConcern() { + XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("namespace/db-factory-bean.xml")); + BeanDefinition definition = factory.getBeanDefinition("first"); + + List values = definition.getPropertyValues().getPropertyValueList(); + assertThat(values, hasItem(new PropertyValue("writeConcern", "SAFE"))); + } + + @Test + public void createsDbFactoryBean() { + XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("namespace/db-factory-bean.xml")); + factory.getBean("first"); + } +} diff --git a/spring-data-mongodb/src/test/resources/namespace/db-factory-bean.xml b/spring-data-mongodb/src/test/resources/namespace/db-factory-bean.xml new file mode 100644 index 000000000..50dd407ca --- /dev/null +++ b/spring-data-mongodb/src/test/resources/namespace/db-factory-bean.xml @@ -0,0 +1,10 @@ + + + + + +