development on converter infrastructure

This commit is contained in:
Mark Pollack
2010-10-12 12:02:13 -04:00
parent 259e3c478e
commit dca1e6dcf9
21 changed files with 2684 additions and 93 deletions

View File

@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" path="src/test/java"/>
<classpathentry kind="src" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,12 @@
package org.springframework.datastore.document.mongodb;
import org.springframework.dao.DataAccessException;
import com.mongodb.DBCollection;
import com.mongodb.MongoException;
public interface CollectionCallback<T> {
T doInCollection(DBCollection collection) throws MongoException, DataAccessException;
}

View File

@@ -97,13 +97,20 @@ public class MongoBeanPropertyDocumentSource implements DocumentSource<DBObject>
protected void initialize(Object source) {
this.source = source;
this.mappedClass = source.getClass();
this.mappedFields = new HashMap<String, PropertyDescriptor>();
this.mappedProperties = new HashSet<String>();
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
this.mappedFields.put(pd.getName(), pd);
this.mappedProperties.add(pd.getName());
if (mappedClass.getClass().equals("java.util.Map")) {
} else {
this.mappedFields = new HashMap<String, PropertyDescriptor>();
this.mappedProperties = new HashSet<String>();
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
this.mappedFields.put(pd.getName(), pd);
this.mappedProperties.add(pd.getName());
}
}
if (mappedProperties.size() == 0) {
logger.warn("No properties mapped for object [" + source + "], type = [" + mappedClass + "]");
}
}
}

View File

@@ -0,0 +1,6 @@
package org.springframework.datastore.document.mongodb;
public interface MongoConverter extends MongoWriter<Object>, MongoReader<Object> {
}

View File

@@ -0,0 +1,5 @@
package org.springframework.datastore.document.mongodb;
public interface MongoDocumentWriter {
}

View File

@@ -0,0 +1,9 @@
package org.springframework.datastore.document.mongodb;
import com.mongodb.DBObject;
public interface MongoReader<T> {
T read(Class<? extends T> clazz, DBObject dbo);
//T read(DBObject dbo);
}

View File

@@ -0,0 +1,5 @@
package org.springframework.datastore.document.mongodb;
public interface MongoReaderWriter<T> extends MongoWriter<T>, MongoReader<T> {
}

View File

@@ -24,8 +24,6 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.datastore.document.AbstractDocumentStoreTemplate;
import org.springframework.datastore.document.DocumentMapper;
import org.springframework.datastore.document.DocumentSource;
import org.springframework.datastore.document.mongodb.query.Query;
import com.mongodb.BasicDBObject;
@@ -43,12 +41,11 @@ public class MongoTemplate extends AbstractDocumentStoreTemplate<DB> implements
private String defaultCollectionName;
private MongoConverter mongoConverter;
//TODO expose configuration...
private CollectionOptions defaultCollectionOptions;
// public MongoTemplate() {
// super();
// }
public MongoTemplate(DB db) {
super();
@@ -60,24 +57,16 @@ public class MongoTemplate extends AbstractDocumentStoreTemplate<DB> implements
public String getDefaultCollectionName() {
return defaultCollectionName;
}
//TODO would one ever consider passing in a DBCollection object?
public void setDefaultCollectionName(String defaultCollection) {
this.defaultCollectionName = defaultCollection;
}
public void execute(String command) {
execute((DBObject)JSON.parse(command));
public void executeCommand(String jsonCommand) {
executeCommand((DBObject)JSON.parse(jsonCommand));
}
public void execute(DocumentSource<DBObject> command) {
execute(command.getDocument());
}
public void execute(DBObject command) {
public void executeCommand(DBObject command) {
CommandResult cr = getConnection().command(command);
String err = cr.getErrorMessage();
if (err != null) {
@@ -114,10 +103,7 @@ public class MongoTemplate extends AbstractDocumentStoreTemplate<DB> implements
getConnection().getCollection(collectionName)
.drop();
}
public void saveObject(Object object) {
saveObject(getRequiredDefaultCollectionName(), object);
}
private String getRequiredDefaultCollectionName() {
String name = getDefaultCollectionName();
@@ -128,69 +114,104 @@ public class MongoTemplate extends AbstractDocumentStoreTemplate<DB> implements
return name;
}
public void saveObject(String collectionName, Object source) {
MongoBeanPropertyDocumentSource docSrc = new MongoBeanPropertyDocumentSource(source);
save(collectionName, docSrc);
public void save(Object objectToSave) {
save(getRequiredDefaultCollectionName(), objectToSave);
}
public void save(String collectionName, DocumentSource<DBObject> documentSource) {
DBObject dbDoc = documentSource.getDocument();
WriteResult wr = null;
try {
wr = getConnection().getCollection(collectionName).save(dbDoc);
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
public void save(String collectionName, Object objectToSave) {
BasicDBObject dbDoc = new BasicDBObject();
this.mongoConverter.write(objectToSave, dbDoc);
saveDBObject(collectionName, dbDoc);
}
public <T> void save(String collectionName, T objectToSave, MongoWriter<T> writer) {
BasicDBObject dbDoc = new BasicDBObject();
this.mongoConverter.write(objectToSave, dbDoc);
saveDBObject(collectionName, dbDoc);
}
protected void saveDBObject(String collectionName, BasicDBObject dbDoc) {
if (dbDoc.keySet().size() > 0 ) {
WriteResult wr = null;
try {
wr = getConnection().getCollection(collectionName).save(dbDoc);
} catch (MongoException e) {
throw new DataRetrievalFailureException(wr.getLastError().getErrorMessage(), e);
}
}
}
public <T> List<T> queryForCollection(String collectionName, Class<T> targetClass) {
DocumentMapper<DBObject, T> mapper = MongoBeanPropertyDocumentMapper.newInstance(targetClass);
return queryForCollection(collectionName, mapper);
}
public <T> List<T> queryForCollection(String collectionName, DocumentMapper<DBObject, T> mapper) {
List<T> results = new ArrayList<T>();
DBCollection collection = getConnection().getCollection(collectionName);
for (DBObject dbo : collection.find()) {
results.add(mapper.mapDocument(dbo));
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;
}
public <T> List<T> queryForCollection(String collectionName, Class<T> targetClass, MongoReader<T> reader) {
List<T> results = new ArrayList<T>();
DBCollection collection = getConnection().getCollection(collectionName);
for (DBObject dbo : collection.find()) {
results.add(reader.read(targetClass, dbo));
}
return results;
}
public <T> List<T> queryForList(String collectionName, Query query, Class<T> targetClass) {
DocumentMapper<DBObject, T> mapper = MongoBeanPropertyDocumentMapper.newInstance(targetClass);
return queryForList(collectionName, query, mapper);
return queryForList(collectionName, query.getQueryObject(), targetClass);
}
public <T> List<T> queryForList(String collectionName, Query query, DocumentMapper<DBObject, T> mapper) {
return queryForList(collectionName, query.getQueryObject(), mapper);
public <T> List<T> queryForList(String collectionName, Query query, Class<T> targetClass, MongoReader<T> reader) {
return queryForList(collectionName, query.getQueryObject(), targetClass, reader);
}
public <T> List<T> queryForList(String collectionName, String query, Class<T> targetClass) {
DocumentMapper<DBObject, T> mapper = MongoBeanPropertyDocumentMapper.newInstance(targetClass);
return queryForList(collectionName, query, mapper);
return queryForList(collectionName, (DBObject)JSON.parse(query), targetClass);
}
public <T> List<T> queryForList(String collectionName, String query, DocumentMapper<DBObject, T> mapper) {
return queryForList(collectionName, (DBObject)JSON.parse(query), mapper);
public <T> List<T> queryForList(String collectionName, String query, Class<T> targetClass, MongoReader<T> reader) {
return queryForList(collectionName, (DBObject)JSON.parse(query), targetClass, reader);
}
public <T> List<T> queryForList(String collectionName, DBObject query, Class<T> targetClass) {
DocumentMapper<DBObject, T> mapper = MongoBeanPropertyDocumentMapper.newInstance(targetClass);
return queryForList(collectionName, query, mapper);
}
public <T> List<T> queryForList(String collectionName, DBObject query, DocumentMapper<DBObject, T> mapper) {
public <T> List<T> queryForList(String collectionName, DBObject query, Class<T> targetClass) {
DBCollection collection = getConnection().getCollection(collectionName);
List<T> results = new ArrayList<T>();
for (DBObject dbo : collection.find(query)) {
results.add(mapper.mapDocument(dbo));
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;
}
public RuntimeException translateIfNecessary(RuntimeException ex) {
public <T> List<T> queryForList(String collectionName, DBObject query, Class<T> targetClass, MongoReader<T> reader) {
DBCollection collection = getConnection().getCollection(collectionName);
List<T> results = new ArrayList<T>();
for (DBObject dbo : collection.find(query)) {
results.add(reader.read(targetClass, dbo));
}
return results;
}
public RuntimeException convertMongoAccessException(RuntimeException ex) {
return MongoDbUtils.translateMongoExceptionIfPossible(ex);
}

View File

@@ -0,0 +1,8 @@
package org.springframework.datastore.document.mongodb;
import com.mongodb.DBObject;
public interface MongoWriter<T> {
void write(T t, DBObject dbo);
}

View File

@@ -0,0 +1,190 @@
package org.springframework.datastore.document.mongodb;
import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.types.CodeWScope;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.Assert;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
public class SimpleMongoConverter implements MongoConverter {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
public static final Set<String> SIMPLE_TYPES;
static {
Set<String> basics = new HashSet<String>();
basics.add(boolean.class.getName());
basics.add(long.class.getName());
basics.add(short.class.getName());
basics.add(int.class.getName());
basics.add(byte.class.getName());
basics.add(float.class.getName());
basics.add(double.class.getName());
basics.add(char.class.getName());
basics.add(Boolean.class.getName());
basics.add(Long.class.getName());
basics.add(Short.class.getName());
basics.add(Integer.class.getName());
basics.add(Byte.class.getName());
basics.add(Float.class.getName());
basics.add(Double.class.getName());
basics.add(Character.class.getName());
basics.add(String.class.getName());
basics.add(java.util.Date.class.getName());
// basics.add(Time.class.getName());
// basics.add(Timestamp.class.getName());
// basics.add(java.sql.Date.class.getName());
// basics.add(BigDecimal.class.getName());
// basics.add(BigInteger.class.getName());
basics.add(Locale.class.getName());
// basics.add(Calendar.class.getName());
// basics.add(GregorianCalendar.class.getName());
// basics.add(java.util.Currency.class.getName());
// basics.add(TimeZone.class.getName());
// basics.add(Object.class.getName());
basics.add(Class.class.getName());
// basics.add(byte[].class.getName());
// basics.add(Byte[].class.getName());
// basics.add(char[].class.getName());
// basics.add(Character[].class.getName());
// basics.add(Blob.class.getName());
// basics.add(Clob.class.getName());
// basics.add(Serializable.class.getName());
// basics.add(URI.class.getName());
// basics.add(URL.class.getName());
basics.add(DBRef.class.getName());
basics.add(Pattern.class.getName());
basics.add(CodeWScope.class.getName());
basics.add(ObjectId.class.getName());
// TODO check on enums.. basics.add(Enum.class.getName());
SIMPLE_TYPES = Collections.unmodifiableSet(basics);
}
protected GenericConversionService conversionService = new GenericConversionService();
public SimpleMongoConverter() {
initializeConverters();
}
protected void initializeConverters() {
conversionService.addConverter(new Converter<ObjectId, String>() {
public String convert(ObjectId id) {
return id.toString();
}
});
}
public SimpleMongoConverter(GenericConversionService conversionService) {
super();
this.conversionService = conversionService;
}
public void write(Object obj, DBObject dbo) {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(obj);
initBeanWrapper(bw);
PropertyDescriptor[] propertyDescriptors = BeanUtils
.getPropertyDescriptors(obj.getClass());
for (PropertyDescriptor pd : propertyDescriptors) {
if (isSimpleType(pd.getPropertyType())) {
Object value = bw.getPropertyValue(pd.getName());
String keyToUse = ("id".equals(pd.getName()) ? "_id" : pd
.getName());
if (isValidProperty(pd)) {
//TODO validate Enums...
// This will leverage the conversion service.
dbo.put(keyToUse, value);
} else {
logger.warn("Unable to map property " + pd.getName() + ". Skipping.");
}
}
}
}
public Object read(Class<? extends Object> clazz, DBObject dbo) {
Assert.state(clazz != null, "Mapped class was not specified");
Object mappedObject = BeanUtils.instantiate(clazz);
BeanWrapper bw = PropertyAccessorFactory
.forBeanPropertyAccess(mappedObject);
initBeanWrapper(bw);
// Iterate over properties of the object.
PropertyDescriptor[] propertyDescriptors = BeanUtils
.getPropertyDescriptors(clazz);
for (PropertyDescriptor pd : propertyDescriptors) {
if (isSimpleType(pd.getPropertyType())) {
if (dbo.containsField(pd.getName())) {
Object value = dbo.get(pd.getName());
if (value instanceof ObjectId) {
setObjectIdOnObject(bw, pd, (ObjectId) value);
} else {
if (isValidProperty(pd)) {
// This will leverage the conversion service.
bw.setPropertyValue(pd.getName(),
dbo.get(pd.getName()));
} else {
logger.warn("Unable to map DBObject field "
+ pd.getName() + " to property "
+ pd.getName() + ". Skipping.");
}
}
}
}
}
return mappedObject;
}
protected void setObjectIdOnObject(BeanWrapper bw, PropertyDescriptor pd,
ObjectId value) {
// TODO strategy for setting the id field. suggest looking for public
// property 'Id' or private field id or _id;
}
protected boolean isValidProperty(PropertyDescriptor descriptor) {
return (descriptor.getReadMethod() != null && descriptor
.getWriteMethod() != null);
}
protected boolean isSimpleType(Class propertyType) {
if (propertyType == null)
return false;
if (propertyType.isArray()) {
return isSimpleType(propertyType.getComponentType());
}
return SIMPLE_TYPES.contains(propertyType.getName());
}
protected void initBeanWrapper(BeanWrapper bw) {
bw.setConversionService(conversionService);
}
}

View File

@@ -0,0 +1,13 @@
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.category.org.apache.activemq=ERROR
log4j.category.org.springframework.batch=DEBUG
log4j.category.org.springframework.transaction=INFO
log4j.category.org.hibernate.SQL=DEBUG
# for debugging datasource initialization
# log4j.category.test.jdbc=DEBUG

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:/META-INF/spring/app-context.xml"/>
</beans>