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.
This commit is contained in:
Oliver Gierke
2011-08-24 09:16:06 +02:00
parent 213963f2ff
commit 54377031bb
9 changed files with 243 additions and 93 deletions

View File

@@ -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;
}

View File

@@ -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 <jbrisbin@vmware.com>
* {@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();
}
}

View File

@@ -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<Object> serverAddresses = new ManagedList<Object>();
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);
}
}
}

View File

@@ -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;

View File

@@ -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<Mongo>, 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 {

View File

@@ -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();
}
}

View File

@@ -87,6 +87,7 @@ The password to use when connecting to a MongoDB server.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="writeConcern" />
</xsd:complexType>
</xsd:element>

View File

@@ -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<PropertyValue> 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");
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<mongo:db-factory id="first" write-concern="SAFE"/>
</beans>