DATADOC-114 - Fixes for updateFirst/updateMulti not converting POJOs correctly

This commit is contained in:
J. Brisbin
2011-05-05 15:07:49 -05:00
parent dfbc89c3b6
commit 62ecfc8416
9 changed files with 1340 additions and 1264 deletions

View File

@@ -136,7 +136,6 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher
*
* @param mongo
* @param databaseName
* @param defaultCollectionName
* @param mongoConverter
* @param writeConcern
* @param writeResultChecking
@@ -541,7 +540,7 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(o.getClass());
if (entity == null) {
throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " +
throw new InvalidDataAccessApiUsageException("No Persitent Entity information found for the class " +
o.getClass().getName());
}
String collection = entity.getCollection();
@@ -721,59 +720,79 @@ public class MongoTemplate implements MongoOperations, ApplicationEventPublisher
* @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(com.mongodb.DBObject, com.mongodb.DBObject)
*/
public WriteResult updateFirst(Class<?> entityClass, Query query, Update update) {
return updateFirst(determineCollectionName(entityClass), query, update);
return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, false);
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#updateFirst(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject)
*/
public WriteResult updateFirst(String collectionName, final Query query, final Update update) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("calling update using query: " + query.getQueryObject() + " and update: " + update.getUpdateObject(mongoConverter) + " in collection: " + collectionName);
}
return execute(collectionName, new CollectionCallback<WriteResult>() {
public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException {
DBObject updateObj = update.getUpdateObject(mongoConverter);
WriteResult wr;
if (writeConcern == null) {
wr = collection.update(query.getQueryObject(), updateObj);
} else {
wr = collection.update(query.getQueryObject(), updateObj, false, false, writeConcern);
}
handleAnyWriteResultErrors(wr, query.getQueryObject(), "update with '" + updateObj + "'");
return wr;
}
});
public WriteResult updateFirst(final String collectionName, final Query query, final Update update) {
return doUpdate(collectionName, query, update, null, false, false);
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(com.mongodb.DBObject, com.mongodb.DBObject)
*/
public WriteResult updateMulti(Class<?> entityClass, Query query, Update update) {
return updateMulti(determineCollectionName(entityClass), query, update);
return doUpdate(determineCollectionName(entityClass), query, update, entityClass, false, true);
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.MongoOperations#updateMulti(java.lang.String, com.mongodb.DBObject, com.mongodb.DBObject)
*/
public WriteResult updateMulti(String collectionName, final Query query, final Update update) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("calling updateMulti using query: " + query.getQueryObject() + " and update: " + update.getUpdateObject(mongoConverter) + " in collection: " + collectionName);
}
return doUpdate(collectionName, query, update, null, false, true);
}
private WriteResult doUpdate(final String collectionName,
final Query query,
final Update update,
final Class<?> entityClass,
final boolean upsert,
final boolean multi) {
return execute(collectionName, new CollectionCallback<WriteResult>() {
public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException {
DBObject updateObj = update.getUpdateObject(mongoConverter);
DBObject queryObj = query.getQueryObject();
DBObject updateObj = update.getUpdateObject();
String idProperty = "id";
if (null != entityClass) {
idProperty = getPersistentEntity(entityClass).getIdProperty().getName();
}
for (String key : queryObj.keySet()) {
if (idProperty.equals(key)) {
// This is an ID field
queryObj.put(ID, mongoConverter.maybeConvertObject(queryObj.get(key)));
queryObj.removeField(key);
} else {
queryObj.put(key, mongoConverter.maybeConvertObject(queryObj.get(key)));
}
}
for (String key : updateObj.keySet()) {
updateObj.put(key, mongoConverter.maybeConvertObject(updateObj.get(key)));
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("calling update using query: " + queryObj + " and update: " + updateObj + " in collection: " + collectionName);
}
WriteResult wr;
if (writeConcern == null) {
wr = collection.updateMulti(query.getQueryObject(), updateObj);
if (multi) {
wr = collection.updateMulti(queryObj, updateObj);
} else {
wr = collection.update(queryObj, updateObj);
}
} else {
wr = collection.update(query.getQueryObject(), updateObj, false, true, writeConcern);
wr = collection.update(queryObj, updateObj, upsert, multi, writeConcern);
}
handleAnyWriteResultErrors(wr, query.getQueryObject(), "update with '" + updateObj + "'");
handleAnyWriteResultErrors(wr, queryObj, "update with '" + updateObj + "'");
return wr;
}
});
}
/* (non-Javadoc)

View File

@@ -0,0 +1,103 @@
/*
* 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.document.mongodb.convert;
import static org.springframework.data.mapping.MappingBeanHelper.isSimpleType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
public abstract class AbstractMongoConverter implements MongoConverter {
public Object maybeConvertObject(Object obj) {
if (obj instanceof Enum<?>) {
return ((Enum<?>) obj).name();
}
if (null != obj && isSimpleType(obj.getClass())) {
// Doesn't need conversion
return obj;
}
if (obj instanceof BasicDBList) {
return maybeConvertList((BasicDBList) obj);
}
if (obj instanceof DBObject) {
DBObject newValueDbo = new BasicDBObject();
for (String vk : ((DBObject) obj).keySet()) {
Object o = ((DBObject) obj).get(vk);
newValueDbo.put(vk, maybeConvertObject(o));
}
return newValueDbo;
}
if (obj instanceof Map) {
Map<Object, Object> m = new HashMap<Object, Object>();
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) obj).entrySet()) {
m.put(entry.getKey(), maybeConvertObject(entry.getValue()));
}
return m;
}
if (obj instanceof List) {
List<?> l = (List<?>) obj;
List<Object> newList = new ArrayList<Object>();
for (Object o : l) {
newList.add(maybeConvertObject(o));
}
return newList;
}
if (obj.getClass().isArray()) {
return maybeConvertArray((Object[]) obj);
}
DBObject newDbo = new BasicDBObject();
this.write(obj, newDbo);
return newDbo;
}
public Object[] maybeConvertArray(Object[] src) {
Object[] newArr = new Object[src.length];
for (int i = 0; i < src.length; i++) {
newArr[i] = maybeConvertObject(src[i]);
}
return newArr;
}
public BasicDBList maybeConvertList(BasicDBList dbl) {
BasicDBList newDbl = new BasicDBList();
Iterator iter = dbl.iterator();
while (iter.hasNext()) {
Object o = iter.next();
newDbl.add(maybeConvertObject(o));
}
return newDbl;
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.data.document.mongodb.convert;
import static org.springframework.data.document.mongodb.convert.ObjectIdConverters.*;
import static org.springframework.data.mapping.MappingBeanHelper.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
@@ -75,7 +76,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
* @author Jon Brisbin <jbrisbin@vmware.com>
* @author Oliver Gierke
*/
public class MappingMongoConverter implements MongoConverter, ApplicationContextAware, InitializingBean {
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, InitializingBean {
private static final String CUSTOM_TYPE_KEY = "_class";
@SuppressWarnings({"unchecked"})
@@ -112,7 +113,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
if (null != converters) {
for (Converter<?, ?> c : converters) {
registerConverter(c);
}
}
}
@@ -130,20 +131,20 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
}
conversionService.addConverter(converter);
}
private Class<?> getCustomTarget(Class<?> source, Class<?> expectedTargetType) {
for (ConvertiblePair typePair : customTypeMapping) {
if (typePair.getSourceType().isAssignableFrom(source)) {
Class<?> targetType = typePair.getTargetType();
if (targetType.equals(expectedTargetType) || expectedTargetType == null) {
return targetType;
}
}
}
return null;
for (ConvertiblePair typePair : customTypeMapping) {
if (typePair.getSourceType().isAssignableFrom(source)) {
Class<?> targetType = typePair.getTargetType();
if (targetType.equals(expectedTargetType) || expectedTargetType == null) {
return targetType;
}
}
}
return null;
}
public MappingContext<BasicMongoPersistentEntity<?>> getMappingContext() {
@@ -174,16 +175,17 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
return conversionService.convert(id, ObjectId.class);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public <S extends Object> S read(Class<S> clazz, final DBObject dbo) {
if (null == dbo) {
return null;
}
Class<?> customTarget = getCustomTarget(clazz, DBObject.class);
if (customTarget != null) {
return conversionService.convert(dbo, clazz);
return conversionService.convert(dbo, clazz);
}
if ((clazz.isArray()
@@ -230,7 +232,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
}
final List<String> ctorParamNames = new ArrayList<String>();
final S instance = MappingBeanHelper.constructInstance(entity, new PreferredConstructor.ParameterValueProvider() {
final S instance = constructInstance(entity, new PreferredConstructor.ParameterValueProvider() {
public Object getParameterValue(PreferredConstructor.Parameter parameter) {
String name = parameter.getName();
Class<?> type = parameter.getType();
@@ -260,7 +262,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
if (dbo.containsField("_id") && null != idProperty) {
Object idObj = dbo.get("_id");
try {
MappingBeanHelper.setProperty(instance, idProperty, idObj, useFieldAccessOnly);
setProperty(instance, idProperty, idObj, useFieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
@@ -277,7 +279,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
Object obj = getValueInternal(prop, dbo, spelCtx, prop.getSpelExpression());
try {
MappingBeanHelper.setProperty(instance, prop, obj, useFieldAccessOnly);
setProperty(instance, prop, obj, useFieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
@@ -292,7 +294,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
PersistentProperty inverseProp = association.getInverse();
Object obj = getValueInternal(inverseProp, dbo, spelCtx, inverseProp.getSpelExpression());
try {
MappingBeanHelper.setProperty(instance, inverseProp, obj);
setProperty(instance, inverseProp, obj);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
@@ -308,13 +310,13 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
if (null == obj) {
return;
}
Class<?> customTarget = getCustomTarget(obj.getClass(), DBObject.class);
if (customTarget != null) {
DBObject result = conversionService.convert(obj, DBObject.class);
dbo.putAll(result);
return;
DBObject result = conversionService.convert(obj, DBObject.class);
dbo.putAll(result);
return;
}
PersistentEntity<?> entity = mappingContext.getPersistentEntity(obj.getClass());
@@ -336,7 +338,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
if (!dbo.containsField("_id") && null != idProperty) {
Object idObj;
try {
idObj = MappingBeanHelper.getProperty(obj, idProperty, Object.class, useFieldAccessOnly);
idObj = getProperty(obj, idProperty, Object.class, useFieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
@@ -359,14 +361,14 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
Class<?> type = prop.getType();
Object propertyObj;
try {
propertyObj = MappingBeanHelper.getProperty(obj, prop, type, useFieldAccessOnly);
propertyObj = getProperty(obj, prop, type, useFieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new MappingException(e.getMessage(), e);
}
if (null != propertyObj) {
if (!MappingBeanHelper.isSimpleType(propertyObj.getClass())) {
if (!isSimpleType(propertyObj.getClass())) {
writePropertyInternal(prop, propertyObj, dbo);
} else {
dbo.put(name, propertyObj);
@@ -381,7 +383,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
Class<?> type = inverseProp.getType();
Object propertyObj;
try {
propertyObj = MappingBeanHelper.getProperty(obj, inverseProp, type, useFieldAccessOnly);
propertyObj = getProperty(obj, inverseProp, type, useFieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
@@ -417,7 +419,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
conversionService.addConverter(BigIntegerToObjectIdConverter.INSTANCE);
}
MappingBeanHelper.setConversionService(conversionService);
setConversionService(conversionService);
}
@SuppressWarnings({"unchecked"})
@@ -442,7 +444,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
if (null != dbref) {
DBRef dbRef = createDBRef(propObjItem, dbref);
dbList.add(dbRef);
} else if (type.isArray() && MappingBeanHelper.isSimpleType(prop.getComponentType())) {
} else if (type.isArray() && isSimpleType(prop.getComponentType())) {
dbList.add(propObjItem);
} else if (propObjItem instanceof List) {
List<?> propObjColl = (List<?>) propObjItem;
@@ -450,7 +452,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
while (typeInfo.isCollectionLike()) {
typeInfo = new ClassTypeInformation(typeInfo.getComponentType().getType());
}
if (MappingBeanHelper.isSimpleType(typeInfo.getType())) {
if (isSimpleType(typeInfo.getType())) {
dbList.add(propObjColl);
} else {
BasicDBList propNestedDbList = new BasicDBList();
@@ -461,7 +463,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
}
dbList.add(propNestedDbList);
}
} else if (MappingBeanHelper.isSimpleType(propObjItem.getClass())) {
} else if (isSimpleType(propObjItem.getClass())) {
dbList.add(propObjItem);
} else {
BasicDBObject propDbObj = new BasicDBObject();
@@ -505,9 +507,9 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
for (Map.Entry<Object, Object> entry : obj.entrySet()) {
Object key = entry.getKey();
Object val = entry.getValue();
if (MappingBeanHelper.isSimpleType(key.getClass())) {
if (isSimpleType(key.getClass())) {
String simpleKey = conversionService.convert(key, String.class);
if (MappingBeanHelper.isSimpleType(val.getClass())) {
if (isSimpleType(val.getClass())) {
dbo.put(simpleKey, val);
} else {
DBObject newDbo = new BasicDBObject();
@@ -538,7 +540,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
PersistentProperty idProperty = targetEntity.getIdProperty();
ObjectId id = null;
try {
id = MappingBeanHelper.getProperty(target, idProperty, ObjectId.class, useFieldAccessOnly);
id = getProperty(target, idProperty, ObjectId.class, useFieldAccessOnly);
if (null == id) {
throw new MappingException("Cannot create a reference to an object with a NULL id.");
}
@@ -564,27 +566,27 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
@SuppressWarnings({"unchecked"})
protected Object getValueInternal(PersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx, String spelExpr) {
String name = prop.getName();
Class<?> propertyType = prop.getType();
String name = prop.getName();
Class<?> propertyType = prop.getType();
Object o;
if (null != spelExpr) {
Expression x = spelExpressionParser.parseExpression(spelExpr);
o = x.getValue(ctx);
} else {
Object dbObj = dbo.get(name);
if (dbObj == null) {
return null;
return null;
}
Class<?> customTarget = getCustomTarget(dbObj.getClass(), propertyType);
if (customTarget != null) {
return conversionService.convert(dbObj, propertyType);
return conversionService.convert(dbObj, propertyType);
}
if (dbObj instanceof DBRef) {
dbObj = ((DBRef) dbObj).fetch();
}

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.document.mongodb.convert;
import com.mongodb.BasicDBList;
import org.bson.types.ObjectId;
import org.springframework.data.document.mongodb.MongoReader;
import org.springframework.data.document.mongodb.MongoWriter;
@@ -43,4 +44,10 @@ public interface MongoConverter extends MongoWriter<Object>, MongoReader<Object>
public ObjectId convertObjectId(Object id);
MappingContext getMappingContext();
Object maybeConvertObject(Object obj);
Object[] maybeConvertArray(Object[] src);
BasicDBList maybeConvertList(BasicDBList dbl);
}

View File

@@ -16,10 +16,26 @@
package org.springframework.data.document.mongodb.convert;
import static org.springframework.data.document.mongodb.convert.ObjectIdConverters.*;
import java.lang.reflect.*;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.math.BigInteger;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;
import com.mongodb.BasicDBList;
@@ -48,506 +64,506 @@ import org.springframework.util.comparator.CompoundComparator;
/**
* Basic {@link MongoConverter} implementation to convert between domain classes and {@link DBObject}s.
*
*
* @author Mark Pollack
* @author Thomas Risberg
* @author Oliver Gierke
*/
public class SimpleMongoConverter implements MongoConverter, InitializingBean {
public class SimpleMongoConverter extends AbstractMongoConverter implements InitializingBean {
private static final Log LOG = LogFactory.getLog(SimpleMongoConverter.class);
@SuppressWarnings("unchecked")
private static final List<Class<?>> MONGO_TYPES = Arrays.asList(Number.class, Date.class, String.class, DBObject.class);
private static final Set<String> SIMPLE_TYPES;
private static final Log LOG = LogFactory.getLog(SimpleMongoConverter.class);
@SuppressWarnings("unchecked")
private static final List<Class<?>> MONGO_TYPES = Arrays.asList(Number.class, Date.class, String.class, DBObject.class);
private 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());
basics.add(Enum.class.getName());
SIMPLE_TYPES = Collections.unmodifiableSet(basics);
}
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());
basics.add(Enum.class.getName());
SIMPLE_TYPES = Collections.unmodifiableSet(basics);
}
private final GenericConversionService conversionService;
private final MappingContext<? extends MongoPersistentEntity<?>> mappingContext;
private final GenericConversionService conversionService;
private final MappingContext<? extends MongoPersistentEntity<?>> mappingContext;
/**
* Creates a {@link SimpleMongoConverter}.
*/
public SimpleMongoConverter() {
this.conversionService = ConversionServiceFactory.createDefaultConversionService();
this.conversionService.removeConvertible(Object.class, String.class);
this.mappingContext = new SimpleMongoMappingContext();
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.convert.MongoConverter#getMappingContext()
*/
public MappingContext<? extends MongoPersistentEntity<?>> getMappingContext() {
return mappingContext;
}
/**
* Creates a {@link SimpleMongoConverter}.
*/
public SimpleMongoConverter() {
this.conversionService = ConversionServiceFactory.createDefaultConversionService();
this.conversionService.removeConvertible(Object.class, String.class);
this.mappingContext = new SimpleMongoMappingContext();
}
/**
* Initializes additional converters that handle {@link ObjectId} conversion. Will register converters for supported
* id types if none are registered for those conversion already. {@link GenericConversionService} is configured.
*/
private void initializeConverters() {
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.convert.MongoConverter#getMappingContext()
*/
public MappingContext<? extends MongoPersistentEntity<?>> getMappingContext() {
return mappingContext;
}
if (!conversionService.canConvert(ObjectId.class, String.class)) {
conversionService.addConverter(ObjectIdToStringConverter.INSTANCE);
}
if (!conversionService.canConvert(String.class, ObjectId.class)) {
conversionService.addConverter(StringToObjectIdConverter.INSTANCE);
}
if (!conversionService.canConvert(ObjectId.class, BigInteger.class)) {
conversionService.addConverter(ObjectIdToBigIntegerConverter.INSTANCE);
}
if (!conversionService.canConvert(BigInteger.class, ObjectId.class)) {
conversionService.addConverter(BigIntegerToObjectIdConverter.INSTANCE);
}
}
/**
* Initializes additional converters that handle {@link ObjectId} conversion. Will register converters for supported
* id types if none are registered for those conversion already. {@link GenericConversionService} is configured.
*/
private void initializeConverters() {
/**
* Add custom {@link Converter} or {@link ConverterFactory} instances to be used that will take presidence over
* using object traversal to convert and object to/from DBObject
*
* @param converters
*/
public void setConverters(Set<?> converters) {
for (Object converter : converters) {
boolean added = false;
if (converter instanceof Converter) {
this.conversionService.addConverter((Converter<?, ?>) converter);
added = true;
}
if (converter instanceof ConverterFactory) {
this.conversionService.addConverterFactory((ConverterFactory<?, ?>) converter);
added = true;
}
if (!added) {
throw new IllegalArgumentException("Given set contains element that is neither Converter nor ConverterFactory!");
}
}
}
if (!conversionService.canConvert(ObjectId.class, String.class)) {
conversionService.addConverter(ObjectIdToStringConverter.INSTANCE);
}
if (!conversionService.canConvert(String.class, ObjectId.class)) {
conversionService.addConverter(StringToObjectIdConverter.INSTANCE);
}
if (!conversionService.canConvert(ObjectId.class, BigInteger.class)) {
conversionService.addConverter(ObjectIdToBigIntegerConverter.INSTANCE);
}
if (!conversionService.canConvert(BigInteger.class, ObjectId.class)) {
conversionService.addConverter(BigIntegerToObjectIdConverter.INSTANCE);
}
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.document.mongodb.MongoWriter#write(java.lang.Object, com.mongodb.DBObject)
*/
@SuppressWarnings("rawtypes")
public void write(Object obj, DBObject dbo) {
/**
* Add custom {@link Converter} or {@link ConverterFactory} instances to be used that will take presidence over
* using object traversal to convert and object to/from DBObject
*
* @param converters
*/
public void setConverters(Set<?> converters) {
for (Object converter : converters) {
boolean added = false;
if (converter instanceof Converter) {
this.conversionService.addConverter((Converter<?, ?>) converter);
added = true;
}
if (converter instanceof ConverterFactory) {
this.conversionService.addConverterFactory((ConverterFactory<?, ?>) converter);
added = true;
}
if (!added) {
throw new IllegalArgumentException("Given set contains element that is neither Converter nor ConverterFactory!");
}
}
}
MongoBeanWrapper beanWrapper = createWrapper(obj, false);
for (MongoPropertyDescriptor descriptor : beanWrapper.getDescriptors()) {
if (descriptor.isMappable()) {
Object value = beanWrapper.getValue(descriptor);
/*
* (non-Javadoc)
*
* @see org.springframework.data.document.mongodb.MongoWriter#write(java.lang.Object, com.mongodb.DBObject)
*/
@SuppressWarnings("rawtypes")
public void write(Object obj, DBObject dbo) {
if (value == null) {
continue;
}
MongoBeanWrapper beanWrapper = createWrapper(obj, false);
for (MongoPropertyDescriptor descriptor : beanWrapper.getDescriptors()) {
if (descriptor.isMappable()) {
Object value = beanWrapper.getValue(descriptor);
String keyToUse = descriptor.getKeyToMap();
if (descriptor.isEnum()) {
writeValue(dbo, keyToUse, ((Enum) value).name());
} else if (descriptor.isIdProperty() && descriptor.isOfIdType()) {
if (value instanceof String && ObjectId.isValid((String) value)) {
try {
writeValue(dbo, keyToUse, conversionService.convert(value, ObjectId.class));
} catch (ConversionFailedException iae) {
LOG.warn("Unable to convert the String " + value + " to an ObjectId");
writeValue(dbo, keyToUse, value);
}
} else {
// we can't convert this id - use as is
writeValue(dbo, keyToUse, value);
}
} else {
writeValue(dbo, keyToUse, value);
}
} else {
if (!"class".equals(descriptor.getName())) {
LOG.debug("Skipping property " + descriptor.getName() + " as it's not a mappable one.");
}
}
}
}
if (value == null) {
continue;
}
/**
* Writes the given value to the given {@link DBObject}. Will skip {@literal null} values.
*
* @param dbo
* @param keyToUse
* @param value
*/
private void writeValue(DBObject dbo, String keyToUse, Object value) {
String keyToUse = descriptor.getKeyToMap();
if (descriptor.isEnum()) {
writeValue(dbo, keyToUse, ((Enum) value).name());
} else if (descriptor.isIdProperty() && descriptor.isOfIdType()) {
if (value instanceof String && ObjectId.isValid((String) value)) {
try {
writeValue(dbo, keyToUse, conversionService.convert(value, ObjectId.class));
} catch (ConversionFailedException iae) {
LOG.warn("Unable to convert the String " + value + " to an ObjectId");
writeValue(dbo, keyToUse, value);
}
} else {
// we can't convert this id - use as is
writeValue(dbo, keyToUse, value);
}
} else {
writeValue(dbo, keyToUse, value);
}
} else {
if (!"class".equals(descriptor.getName())) {
LOG.debug("Skipping property " + descriptor.getName() + " as it's not a mappable one.");
}
}
}
}
if (!isSimpleType(value.getClass())) {
writeCompoundValue(dbo, keyToUse, value);
} else {
dbo.put(keyToUse, value);
}
}
/**
* Writes the given value to the given {@link DBObject}. Will skip {@literal null} values.
*
* @param dbo
* @param keyToUse
* @param value
*/
private void writeValue(DBObject dbo, String keyToUse, Object value) {
/**
* Writes the given {@link CompoundComparator} value to the given {@link DBObject}.
*
* @param dbo
* @param keyToUse
* @param value
*/
@SuppressWarnings("unchecked")
private void writeCompoundValue(DBObject dbo, String keyToUse, Object value) {
if (value instanceof Map) {
writeMap(dbo, keyToUse, (Map<String, Object>) value);
return;
}
if (value instanceof Collection) {
// Should write a collection!
writeArray(dbo, keyToUse, ((Collection<Object>) value).toArray());
return;
}
if (value instanceof Object[]) {
// Should write an array!
writeArray(dbo, keyToUse, (Object[]) value);
return;
}
Class<?> customTargetType = getCustomTargetType(value);
if (customTargetType != null) {
dbo.put(keyToUse, conversionService.convert(value, customTargetType));
return;
}
DBObject nestedDbo = new BasicDBObject();
write(value, nestedDbo);
dbo.put(keyToUse, nestedDbo);
}
/**
* Returns whether the {@link ConversionService} has a custom {@link Converter} registered that can convert the given
* object into one of the types supported by MongoDB.
*
* @param obj
* @return
*/
private Class<?> getCustomTargetType(Object obj) {
for (Class<?> mongoType : MONGO_TYPES) {
if (conversionService.canConvert(obj.getClass(), mongoType)) {
return mongoType;
}
}
return null;
}
if (!isSimpleType(value.getClass())) {
writeCompoundValue(dbo, keyToUse, value);
} else {
dbo.put(keyToUse, value);
}
}
/**
* Writes the given {@link Map} to the given {@link DBObject}.
*
* @param dbo
* @param mapKey
* @param map
*/
protected void writeMap(DBObject dbo, String mapKey, Map<String, Object> map) {
// TODO support non-string based keys as long as there is a Spring Converter obj->string and (optionally)
// string->obj
DBObject dboToPopulate = null;
/**
* Writes the given {@link CompoundComparator} value to the given {@link DBObject}.
*
* @param dbo
* @param keyToUse
* @param value
*/
@SuppressWarnings("unchecked")
private void writeCompoundValue(DBObject dbo, String keyToUse, Object value) {
if (value instanceof Map) {
writeMap(dbo, keyToUse, (Map<String, Object>) value);
return;
}
if (value instanceof Collection) {
// Should write a collection!
writeArray(dbo, keyToUse, ((Collection<Object>) value).toArray());
return;
}
if (value instanceof Object[]) {
// Should write an array!
writeArray(dbo, keyToUse, (Object[]) value);
return;
}
// TODO - Does that make sense? If we create a new object here it's content will never make it out of this
// method
if (mapKey != null) {
dboToPopulate = new BasicDBObject();
} else {
dboToPopulate = dbo;
}
if (map != null) {
for (Entry<String, Object> entry : map.entrySet()) {
Class<?> customTargetType = getCustomTargetType(value);
if (customTargetType != null) {
dbo.put(keyToUse, conversionService.convert(value, customTargetType));
return;
}
Object entryValue = entry.getValue();
String entryKey = entry.getKey();
DBObject nestedDbo = new BasicDBObject();
write(value, nestedDbo);
dbo.put(keyToUse, nestedDbo);
}
if (!isSimpleType(entryValue.getClass())) {
writeCompoundValue(dboToPopulate, entryKey, entryValue);
} else {
dboToPopulate.put(entryKey, entryValue);
}
}
dbo.put(mapKey, dboToPopulate);
}
}
/**
* Returns whether the {@link ConversionService} has a custom {@link Converter} registered that can convert the given
* object into one of the types supported by MongoDB.
*
* @param obj
* @return
*/
private Class<?> getCustomTargetType(Object obj) {
/**
* Writes the given array to the given {@link DBObject}.
*
* @param dbo
* @param keyToUse
* @param array
*/
protected void writeArray(DBObject dbo, String keyToUse, Object[] array) {
Object[] dboValues;
if (array != null) {
dboValues = new Object[array.length];
int i = 0;
for (Object o : array) {
if (!isSimpleType(o.getClass())) {
DBObject dboValue = new BasicDBObject();
write(o, dboValue);
dboValues[i] = dboValue;
} else {
dboValues[i] = o;
}
i++;
}
dbo.put(keyToUse, dboValues);
}
}
for (Class<?> mongoType : MONGO_TYPES) {
if (conversionService.canConvert(obj.getClass(), mongoType)) {
return mongoType;
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.document.mongodb.MongoReader#read(java.lang.Class, com.mongodb.DBObject)
*/
public <S> S read(Class<S> clazz, DBObject source) {
/**
* Writes the given {@link Map} to the given {@link DBObject}.
*
* @param dbo
* @param mapKey
* @param map
*/
protected void writeMap(DBObject dbo, String mapKey, Map<String, Object> map) {
// TODO support non-string based keys as long as there is a Spring Converter obj->string and (optionally)
// string->obj
DBObject dboToPopulate = null;
if (source == null) {
return null;
}
// TODO - Does that make sense? If we create a new object here it's content will never make it out of this
// method
if (mapKey != null) {
dboToPopulate = new BasicDBObject();
} else {
dboToPopulate = dbo;
}
if (map != null) {
for (Entry<String, Object> entry : map.entrySet()) {
Assert.notNull(clazz, "Mapped class was not specified");
S target = BeanUtils.instantiateClass(clazz);
MongoBeanWrapper bw = new MongoBeanWrapper(target, conversionService, true);
Object entryValue = entry.getValue();
String entryKey = entry.getKey();
for (MongoPropertyDescriptor descriptor : bw.getDescriptors()) {
String keyToUse = descriptor.getKeyToMap();
if (source.containsField(keyToUse)) {
if (descriptor.isMappable()) {
Object value = source.get(keyToUse);
if (!isSimpleType(value.getClass())) {
if (value instanceof Object[]) {
bw.setValue(descriptor, readCollection(descriptor, Arrays.asList((Object[]) value))
.toArray());
} else if (value instanceof BasicDBList) {
bw.setValue(descriptor, readCollection(descriptor, (BasicDBList) value));
} else if (value instanceof DBObject) {
bw.setValue(descriptor, readCompoundValue(descriptor, (DBObject) value));
} else {
LOG.warn("Unable to map compound DBObject field " + keyToUse + " to property "
+ descriptor.getName()
+ ". The field value should have been a 'DBObject.class' but was "
+ value.getClass().getName());
}
} else {
bw.setValue(descriptor, value);
}
} else {
LOG.warn("Unable to map DBObject field " + keyToUse + " to property " + descriptor.getName()
+ ". Skipping.");
}
}
}
if (!isSimpleType(entryValue.getClass())) {
writeCompoundValue(dboToPopulate, entryKey, entryValue);
} else {
dboToPopulate.put(entryKey, entryValue);
}
}
dbo.put(mapKey, dboToPopulate);
}
}
return target;
}
/**
* Writes the given array to the given {@link DBObject}.
*
* @param dbo
* @param keyToUse
* @param array
*/
protected void writeArray(DBObject dbo, String keyToUse, Object[] array) {
Object[] dboValues;
if (array != null) {
dboValues = new Object[array.length];
int i = 0;
for (Object o : array) {
if (!isSimpleType(o.getClass())) {
DBObject dboValue = new BasicDBObject();
write(o, dboValue);
dboValues[i] = dboValue;
} else {
dboValues[i] = o;
}
i++;
}
dbo.put(keyToUse, dboValues);
}
}
/**
* Reads the given collection values (that are {@link DBObject}s potentially) into a {@link Collection} of domain
* objects.
*
* @param descriptor
* @param values
* @return
*/
private Collection<Object> readCollection(MongoPropertyDescriptor descriptor, Collection<?> values) {
/*
* (non-Javadoc)
*
* @see org.springframework.data.document.mongodb.MongoReader#read(java.lang.Class, com.mongodb.DBObject)
*/
public <S> S read(Class<S> clazz, DBObject source) {
Class<?> targetCollectionType = descriptor.getPropertyType();
boolean targetIsArray = targetCollectionType.isArray();
if (source == null) {
return null;
}
@SuppressWarnings("unchecked")
Collection<Object> result = targetIsArray ? new ArrayList<Object>(values.size()) : CollectionFactory
.createCollection(targetCollectionType, values.size());
Assert.notNull(clazz, "Mapped class was not specified");
S target = BeanUtils.instantiateClass(clazz);
MongoBeanWrapper bw = new MongoBeanWrapper(target, conversionService, true);
for (Object o : values) {
if (o instanceof DBObject) {
Class<?> type;
if (targetIsArray) {
type = targetCollectionType.getComponentType();
} else {
type = getGenericParameters(descriptor.getTypeToSet()).get(0);
}
result.add(read(type, (DBObject) o));
} else {
result.add(o);
}
}
for (MongoPropertyDescriptor descriptor : bw.getDescriptors()) {
String keyToUse = descriptor.getKeyToMap();
if (source.containsField(keyToUse)) {
if (descriptor.isMappable()) {
Object value = source.get(keyToUse);
if (!isSimpleType(value.getClass())) {
if (value instanceof Object[]) {
bw.setValue(descriptor, readCollection(descriptor, Arrays.asList((Object[]) value))
.toArray());
} else if (value instanceof BasicDBList) {
bw.setValue(descriptor, readCollection(descriptor, (BasicDBList) value));
} else if (value instanceof DBObject) {
bw.setValue(descriptor, readCompoundValue(descriptor, (DBObject) value));
} else {
LOG.warn("Unable to map compound DBObject field " + keyToUse + " to property "
+ descriptor.getName()
+ ". The field value should have been a 'DBObject.class' but was "
+ value.getClass().getName());
}
} else {
bw.setValue(descriptor, value);
}
} else {
LOG.warn("Unable to map DBObject field " + keyToUse + " to property " + descriptor.getName()
+ ". Skipping.");
}
}
}
return result;
}
return target;
}
/**
* Reads a compound value from the given {@link DBObject} for the given property.
*
* @param pd
* @param dbo
* @return
*/
private Object readCompoundValue(MongoPropertyDescriptor pd, DBObject dbo) {
/**
* Reads the given collection values (that are {@link DBObject}s potentially) into a {@link Collection} of domain
* objects.
*
* @param descriptor
* @param values
* @return
*/
private Collection<Object> readCollection(MongoPropertyDescriptor descriptor, Collection<?> values) {
Assert.isTrue(!pd.isCollection(), "Collections not supported!");
Class<?> targetCollectionType = descriptor.getPropertyType();
boolean targetIsArray = targetCollectionType.isArray();
if (pd.isMap()) {
return readMap(pd, dbo, getGenericParameters(pd.getTypeToSet()).get(1));
} else {
return read(pd.getPropertyType(), dbo);
}
}
@SuppressWarnings("unchecked")
Collection<Object> result = targetIsArray ? new ArrayList<Object>(values.size()) : CollectionFactory
.createCollection(targetCollectionType, values.size());
/**
* Create a {@link Map} instance. Will return a {@link HashMap} by default. Subclasses might want to override this
* method to use a custom {@link Map} implementation.
*
* @return
*/
protected Map<String, Object> createMap() {
return new HashMap<String, Object>();
}
for (Object o : values) {
if (o instanceof DBObject) {
Class<?> type;
if (targetIsArray) {
type = targetCollectionType.getComponentType();
} else {
type = getGenericParameters(descriptor.getTypeToSet()).get(0);
}
result.add(read(type, (DBObject) o));
} else {
result.add(o);
}
}
/**
* Reads every key/value pair from the {@link DBObject} into a {@link Map} instance.
*
* @param pd
* @param dbo
* @param targetType
* @return
*/
protected Map<?, ?> readMap(MongoPropertyDescriptor pd, DBObject dbo, Class<?> targetType) {
Map<String, Object> map = createMap();
for (String key : dbo.keySet()) {
Object value = dbo.get(key);
if (!isSimpleType(value.getClass())) {
map.put(key, read(targetType, (DBObject) value));
// Can do some reflection tricks here -
// throw new RuntimeException("User types not supported yet as values for Maps");
} else {
map.put(key, conversionService.convert(value, targetType));
}
}
return map;
}
return result;
}
protected static boolean isSimpleType(Class<?> propertyType) {
if (propertyType == null) {
return false;
}
if (propertyType.isArray()) {
return isSimpleType(propertyType.getComponentType());
}
return SIMPLE_TYPES.contains(propertyType.getName());
}
/**
* Reads a compound value from the given {@link DBObject} for the given property.
*
* @param pd
* @param dbo
* @return
*/
private Object readCompoundValue(MongoPropertyDescriptor pd, DBObject dbo) {
/**
* Callback to allow customizing creation of a {@link MongoBeanWrapper}.
*
* @param target the target object to wrap
* @param fieldAccess whether to use field access or property access
* @return
*/
protected MongoBeanWrapper createWrapper(Object target, boolean fieldAccess) {
Assert.isTrue(!pd.isCollection(), "Collections not supported!");
return new MongoBeanWrapper(target, conversionService, fieldAccess);
}
if (pd.isMap()) {
return readMap(pd, dbo, getGenericParameters(pd.getTypeToSet()).get(1));
} else {
return read(pd.getPropertyType(), dbo);
}
}
public List<Class<?>> getGenericParameters(Type genericParameterType) {
/**
* Create a {@link Map} instance. Will return a {@link HashMap} by default. Subclasses might want to override this
* method to use a custom {@link Map} implementation.
*
* @return
*/
protected Map<String, Object> createMap() {
return new HashMap<String, Object>();
}
List<Class<?>> actualGenericParameterTypes = new ArrayList<Class<?>>();
/**
* Reads every key/value pair from the {@link DBObject} into a {@link Map} instance.
*
* @param pd
* @param dbo
* @param targetType
* @return
*/
protected Map<?, ?> readMap(MongoPropertyDescriptor pd, DBObject dbo, Class<?> targetType) {
Map<String, Object> map = createMap();
for (String key : dbo.keySet()) {
Object value = dbo.get(key);
if (!isSimpleType(value.getClass())) {
map.put(key, read(targetType, (DBObject) value));
// Can do some reflection tricks here -
// throw new RuntimeException("User types not supported yet as values for Maps");
} else {
map.put(key, conversionService.convert(value, targetType));
}
}
return map;
}
if (genericParameterType instanceof ParameterizedType) {
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for (Type parameterArgType : parameterArgTypes) {
if (parameterArgType instanceof GenericArrayType) {
Class<?> arrayType = (Class<?>) ((GenericArrayType) parameterArgType).getGenericComponentType();
actualGenericParameterTypes.add(Array.newInstance(arrayType, 0).getClass());
} else {
if (parameterArgType instanceof ParameterizedType) {
ParameterizedType paramTypeArgs = (ParameterizedType) parameterArgType;
actualGenericParameterTypes.add((Class<?>) paramTypeArgs.getRawType());
} else {
if (parameterArgType instanceof TypeVariable) {
throw new RuntimeException("Can not map " + ((TypeVariable<?>) parameterArgType).getName());
} else {
if (parameterArgType instanceof Class) {
actualGenericParameterTypes.add((Class<?>) parameterArgType);
} else {
throw new RuntimeException("Can not map " + parameterArgType);
}
}
}
}
}
}
protected static boolean isSimpleType(Class<?> propertyType) {
if (propertyType == null) {
return false;
}
if (propertyType.isArray()) {
return isSimpleType(propertyType.getComponentType());
}
return SIMPLE_TYPES.contains(propertyType.getName());
}
return actualGenericParameterTypes;
}
/**
* Callback to allow customizing creation of a {@link MongoBeanWrapper}.
*
* @param target the target object to wrap
* @param fieldAccess whether to use field access or property access
* @return
*/
protected MongoBeanWrapper createWrapper(Object target, boolean fieldAccess) {
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.convert.MongoConverter#convertObjectId(org.bson.types.ObjectId, java.lang.Class)
*/
public <T> T convertObjectId(ObjectId id, Class<T> targetType) {
return conversionService.convert(id, targetType);
}
return new MongoBeanWrapper(target, conversionService, fieldAccess);
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.convert.MongoConverter#convertObjectId(java.lang.Object)
*/
public ObjectId convertObjectId(Object id) {
return conversionService.convert(id, ObjectId.class);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() {
initializeConverters();
}
public List<Class<?>> getGenericParameters(Type genericParameterType) {
List<Class<?>> actualGenericParameterTypes = new ArrayList<Class<?>>();
if (genericParameterType instanceof ParameterizedType) {
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for (Type parameterArgType : parameterArgTypes) {
if (parameterArgType instanceof GenericArrayType) {
Class<?> arrayType = (Class<?>) ((GenericArrayType) parameterArgType).getGenericComponentType();
actualGenericParameterTypes.add(Array.newInstance(arrayType, 0).getClass());
} else {
if (parameterArgType instanceof ParameterizedType) {
ParameterizedType paramTypeArgs = (ParameterizedType) parameterArgType;
actualGenericParameterTypes.add((Class<?>) paramTypeArgs.getRawType());
} else {
if (parameterArgType instanceof TypeVariable) {
throw new RuntimeException("Can not map " + ((TypeVariable<?>) parameterArgType).getName());
} else {
if (parameterArgType instanceof Class) {
actualGenericParameterTypes.add((Class<?>) parameterArgType);
} else {
throw new RuntimeException("Can not map " + parameterArgType);
}
}
}
}
}
}
return actualGenericParameterTypes;
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.convert.MongoConverter#convertObjectId(org.bson.types.ObjectId, java.lang.Class)
*/
public <T> T convertObjectId(ObjectId id, Class<T> targetType) {
return conversionService.convert(id, targetType);
}
/* (non-Javadoc)
* @see org.springframework.data.document.mongodb.convert.MongoConverter#convertObjectId(java.lang.Object)
*/
public ObjectId convertObjectId(Object id) {
return conversionService.convert(id, ObjectId.class);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() {
initializeConverters();
}
}

View File

@@ -20,97 +20,92 @@ import java.util.Collections;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.util.JSON;
import org.springframework.data.document.mongodb.convert.MongoConverter;
public class BasicUpdate extends Update {
private DBObject updateObject = null;
private DBObject updateObject = null;
public BasicUpdate(String updateString) {
super();
this.updateObject = (DBObject) JSON.parse(updateString);
}
public BasicUpdate(String updateString) {
super();
this.updateObject = (DBObject) JSON.parse(updateString);
}
public BasicUpdate(DBObject updateObject) {
super();
this.updateObject = updateObject;
}
public BasicUpdate(DBObject updateObject) {
super();
this.updateObject = updateObject;
}
@Override
public Update set(String key, Object value) {
updateObject.put("$set", Collections.singletonMap(key, convertValueIfNecessary(value)));
return this;
}
@Override
public Update set(String key, Object value) {
updateObject.put("$set", Collections.singletonMap(key, value));
return this;
}
@Override
public Update unset(String key) {
updateObject.put("$unset", Collections.singletonMap(key, 1));
return this;
}
@Override
public Update unset(String key) {
updateObject.put("$unset", Collections.singletonMap(key, 1));
return this;
}
@Override
public Update inc(String key, Number inc) {
updateObject.put("$inc", Collections.singletonMap(key, inc));
return this;
}
@Override
public Update inc(String key, Number inc) {
updateObject.put("$inc", Collections.singletonMap(key, inc));
return this;
}
@Override
public Update push(String key, Object value) {
updateObject.put("$push", Collections.singletonMap(key, convertValueIfNecessary(value)));
return this;
}
@Override
public Update push(String key, Object value) {
updateObject.put("$push", Collections.singletonMap(key, value));
return this;
}
@Override
public Update pushAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = convertValueIfNecessary(values[i]);
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
updateObject.put("$pushAll", keyValue);
return this;
}
@Override
public Update pushAll(String key, Object[] values) {
DBObject keyValue = new BasicDBObject();
keyValue.put(key, values);
updateObject.put("$pushAll", keyValue);
return this;
}
@Override
public Update addToSet(String key, Object value) {
updateObject.put("$addToSet", Collections.singletonMap(key, convertValueIfNecessary(value)));
return this;
}
@Override
public Update addToSet(String key, Object value) {
updateObject.put("$addToSet", Collections.singletonMap(key, value));
return this;
}
@Override
public Update pop(String key, Position pos) {
updateObject.put("$pop", Collections.singletonMap(key, (pos == Position.FIRST ? -1 : 1)));
return this;
}
@Override
public Update pop(String key, Position pos) {
updateObject.put("$pop", Collections.singletonMap(key, (pos == Position.FIRST ? -1 : 1)));
return this;
}
@Override
public Update pull(String key, Object value) {
updateObject.put("$pull", Collections.singletonMap(key, convertValueIfNecessary(value)));
return this;
}
@Override
public Update pull(String key, Object value) {
updateObject.put("$pull", Collections.singletonMap(key, value));
return this;
}
@Override
public Update pullAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = convertValueIfNecessary(values[i]);
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
updateObject.put("$pullAll", keyValue);
return this;
}
@Override
public Update pullAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = values[i];
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
updateObject.put("$pullAll", keyValue);
return this;
}
@Override
public Update rename(String oldName, String newName) {
updateObject.put("$rename", Collections.singletonMap(oldName, newName));
return this;
}
@Override
public Update rename(String oldName, String newName) {
updateObject.put("$rename", Collections.singletonMap(oldName, newName));
return this;
}
@Override
public DBObject getUpdateObject() {
return updateObject;
}
@Override
public DBObject getUpdateObject() {
return updateObject;
}
}

View File

@@ -30,371 +30,368 @@ import org.springframework.util.Assert;
public class Criteria implements CriteriaDefinition {
private String key;
private String key;
private List<Criteria> criteriaChain;
private List<Criteria> criteriaChain;
private LinkedHashMap<String, Object> criteria = new LinkedHashMap<String, Object>();
private LinkedHashMap<String, Object> criteria = new LinkedHashMap<String, Object>();
private Object isValue = null;
private Object isValue = null;
public Criteria(String key) {
this.criteriaChain = new ArrayList<Criteria>();
this.criteriaChain.add(this);
this.key = key;
}
public Criteria(String key) {
this.criteriaChain = new ArrayList<Criteria>();
this.criteriaChain.add(this);
this.key = key;
}
protected Criteria(List<Criteria> criteriaChain, String key) {
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
this.key = key;
}
protected Criteria(List<Criteria> criteriaChain, String key) {
this.criteriaChain = criteriaChain;
this.criteriaChain.add(this);
this.key = key;
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key
* @return
*/
public static Criteria where(String key) {
return new Criteria(key);
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key
* @return
*/
public static Criteria where(String key) {
return new Criteria(key);
}
public static Criteria whereId() {
return new Criteria("id");
}
public static Criteria whereId() {
return new Criteria("id");
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key
* @return
*/
public Criteria and(String key) {
return new Criteria(this.criteriaChain, key);
}
/**
* Static factory method to create a Criteria using the provided key
*
* @param key
* @return
*/
public Criteria and(String key) {
return new Criteria(this.criteriaChain, key);
}
/**
* Creates a criterion using the $is operator
*
* @param o
* @return
*/
public Criteria is(Object o) {
if (isValue != null) {
throw new InvalidDocumentStoreApiUsageException("Multiple 'is' values declared.");
}
this.isValue = o;
return this;
}
/**
* Creates a criterion using the $is operator
*
* @param o
* @return
*/
public Criteria is(Object o) {
if (isValue != null) {
throw new InvalidDocumentStoreApiUsageException("Multiple 'is' values declared.");
}
this.isValue = o;
return this;
}
/**
* Creates a criterion using the $ne operator
*
* @param o
* @return
*/
public Criteria ne(Object o) {
criteria.put("$ne", o);
return this;
}
/**
* Creates a criterion using the $ne operator
*
* @param o
* @return
*/
public Criteria ne(Object o) {
criteria.put("$ne", o);
return this;
}
/**
* Creates a criterion using the $lt operator
*
* @param o
* @return
*/
public Criteria lt(Object o) {
criteria.put("$lt", o);
return this;
}
/**
* Creates a criterion using the $lt operator
*
* @param o
* @return
*/
public Criteria lt(Object o) {
criteria.put("$lt", o);
return this;
}
/**
* Creates a criterion using the $lte operator
*
* @param o
* @return
*/
public Criteria lte(Object o) {
criteria.put("$lte", o);
return this;
}
/**
* Creates a criterion using the $lte operator
*
* @param o
* @return
*/
public Criteria lte(Object o) {
criteria.put("$lte", o);
return this;
}
/**
* Creates a criterion using the $gt operator
*
* @param o
* @return
*/
public Criteria gt(Object o) {
criteria.put("$gt", o);
return this;
}
/**
* Creates a criterion using the $gt operator
*
* @param o
* @return
*/
public Criteria gt(Object o) {
criteria.put("$gt", o);
return this;
}
/**
* Creates a criterion using the $gte operator
*
* @param o
* @return
*/
public Criteria gte(Object o) {
criteria.put("$gte", o);
return this;
}
/**
* Creates a criterion using the $gte operator
*
* @param o
* @return
*/
public Criteria gte(Object o) {
criteria.put("$gte", o);
return this;
}
/**
* Creates a criterion using the $in operator
*
* @param o
* @return
*/
public Criteria in(Object... o) {
criteria.put("$in", o);
return this;
}
/**
* Creates a criterion using the $in operator
*
* @param o
* @return
*/
public Criteria in(Object... o) {
criteria.put("$in", o);
return this;
}
/**
* Creates a criterion using the $nin operator
*
* @param o
* @return
*/
public Criteria nin(Object... o) {
criteria.put("$nin", o);
return this;
}
/**
* Creates a criterion using the $nin operator
*
* @param o
* @return
*/
public Criteria nin(Object... o) {
criteria.put("$nin", o);
return this;
}
/**
* Creates a criterion using the $mod operator
*
* @param value
* @param remainder
* @return
*/
public Criteria mod(Number value, Number remainder) {
List<Object> l = new ArrayList<Object>();
l.add(value);
l.add(remainder);
criteria.put("$mod", l);
return this;
}
/**
* Creates a criterion using the $mod operator
*
* @param value
* @param remainder
* @return
*/
public Criteria mod(Number value, Number remainder) {
List<Object> l = new ArrayList<Object>();
l.add(value);
l.add(remainder);
criteria.put("$mod", l);
return this;
}
/**
* Creates a criterion using the $all operator
*
* @param o
* @return
*/
public Criteria all(Object o) {
criteria.put("$is", o);
return this;
}
/**
* Creates a criterion using the $all operator
*
* @param o
* @return
*/
public Criteria all(Object o) {
criteria.put("$is", o);
return this;
}
/**
* Creates a criterion using the $size operator
*
* @param s
* @return
*/
public Criteria size(int s) {
criteria.put("$size", s);
return this;
}
/**
* Creates a criterion using the $size operator
*
* @param s
* @return
*/
public Criteria size(int s) {
criteria.put("$size", s);
return this;
}
/**
* Creates a criterion using the $exists operator
*
* @param b
* @return
*/
public Criteria exists(boolean b) {
criteria.put("$exists", b);
return this;
}
/**
* Creates a criterion using the $exists operator
*
* @param b
* @return
*/
public Criteria exists(boolean b) {
criteria.put("$exists", b);
return this;
}
/**
* Creates a criterion using the $type operator
*
* @param t
* @return
*/
public Criteria type(int t) {
criteria.put("$type", t);
return this;
}
/**
* Creates a criterion using the $type operator
*
* @param t
* @return
*/
public Criteria type(int t) {
criteria.put("$type", t);
return this;
}
/**
* Creates a criterion using the $not meta operator which affects the clause directly following
*
* @return
*/
public Criteria not() {
criteria.put("$not", null);
return this;
}
/**
* Creates a criterion using the $not meta operator which affects the clause directly following
*
* @return
*/
public Criteria not() {
criteria.put("$not", null);
return this;
}
/**
* Creates a criterion using a $regex
*
* @param re
* @return
*/
public Criteria regex(String re) {
criteria.put("$regex", re);
return this;
}
/**
* Creates a criterion using a $regex
*
* @param re
* @return
*/
public Criteria regex(String re) {
criteria.put("$regex", re);
return this;
}
/**
* Creates a geospatial criterion using a $within $center operation
* @param circle must not be {@literal null}
* @return
*/
public Criteria withinCenter(Circle circle) {
Assert.notNull(circle);
LinkedList<Object> list = new LinkedList<Object>();
list.addLast(circle.getCenter().asArray());
list.add(circle.getRadius());
criteria.put("$within", new BasicDBObject("$center", list));
return this;
}
/**
* Creates a geospatial criterion using a $within $center operation. This is only available for Mongo 1.7 and higher.
* @param circle must not be {@literal null}
* @return
*/
public Criteria withinCenterSphere(Circle circle) {
Assert.notNull(circle);
LinkedList<Object> list = new LinkedList<Object>();
list.addLast(circle.getCenter().asArray());
list.add(circle.getRadius());
criteria.put("$within", new BasicDBObject("$centerSphere", list));
return this;
}
/**
* Creates a geospatial criterion using a $within $box operation
*
* @param circle
* @return
*/
public Criteria withinBox(Box box){
Assert.notNull(box);
LinkedList<double[]> list = new LinkedList<double[]>();
list.addLast(box.getLowerLeft().asArray());
list.addLast(box.getUpperRight().asArray());
criteria.put("$within", new BasicDBObject("$box", list));
return this;
}
/**
* Creates a geospatial criterion using a $near operation
* @param point must not be {@literal null}
* @return
*/
public Criteria near(Point point) {
Assert.notNull(point);
criteria.put("$near", point.asArray());
return this;
}
/**
* Creates a geospatial criterion using a $within $center operation
*
* @param circle must not be {@literal null}
* @return
*/
public Criteria withinCenter(Circle circle) {
Assert.notNull(circle);
LinkedList<Object> list = new LinkedList<Object>();
list.addLast(circle.getCenter().asArray());
list.add(circle.getRadius());
criteria.put("$within", new BasicDBObject("$center", list));
return this;
}
/**
* Creates a geospatial criterion using a $nearSphere operation. This is only available for Mongo 1.7 and higher.
* @param point must not be {@literal null}
* @return
*/
public Criteria nearSphere(Point point) {
Assert.notNull(point);
criteria.put("$nearSphere", point.asArray());
return this;
}
/**
* Creates a geospatial criterion using a $within $center operation. This is only available for Mongo 1.7 and higher.
*
* @param circle must not be {@literal null}
* @return
*/
public Criteria withinCenterSphere(Circle circle) {
Assert.notNull(circle);
LinkedList<Object> list = new LinkedList<Object>();
list.addLast(circle.getCenter().asArray());
list.add(circle.getRadius());
criteria.put("$within", new BasicDBObject("$centerSphere", list));
return this;
}
/**
* Creates a geospatical criterion using a $maxDistance operation, for use with $near
*
* @param maxDistance
* @return
*/
public Criteria maxDistance(double maxDistance) {
criteria.put("$maxDistance", maxDistance);
return this;
}
/**
* Creates a geospatial criterion using a $within $box operation
*
* @param box
* @return
*/
public Criteria withinBox(Box box) {
Assert.notNull(box);
LinkedList<double[]> list = new LinkedList<double[]>();
list.addLast(box.getLowerLeft().asArray());
list.addLast(box.getUpperRight().asArray());
criteria.put("$within", new BasicDBObject("$box", list));
return this;
}
/**
* Creates a criterion using the $elemMatch operator
*
* @param t
* @return
*/
public Criteria elemMatch(Criteria c) {
criteria.put("$elemMatch", c.getCriteriaObject());
return this;
}
/**
* Creates a geospatial criterion using a $near operation
*
* @param point must not be {@literal null}
* @return
*/
public Criteria near(Point point) {
Assert.notNull(point);
criteria.put("$near", point.asArray());
return this;
}
/**
* Creates an or query using the $or operator for all of the provided queries
*
* @param queries
*/
public void or(List<Query> queries) {
criteria.put("$or", queries);
}
/**
* Creates a geospatial criterion using a $nearSphere operation. This is only available for Mongo 1.7 and higher.
*
* @param point must not be {@literal null}
* @return
*/
public Criteria nearSphere(Point point) {
Assert.notNull(point);
criteria.put("$nearSphere", point.asArray());
return this;
}
public String getKey() {
return this.key;
}
/**
* Creates a geospatical criterion using a $maxDistance operation, for use with $near
*
* @param maxDistance
* @return
*/
public Criteria maxDistance(double maxDistance) {
criteria.put("$maxDistance", maxDistance);
return this;
}
/*
* (non-Javadoc)
*
* @see org.springframework.datastore.document.mongodb.query.Criteria#
* getCriteriaObject(java.lang.String)
*/
public DBObject getCriteriaObject() {
if (this.criteriaChain.size() == 1) {
return criteriaChain.get(0).getSingleCriteriaObject();
} else {
DBObject criteriaObject = new BasicDBObject();
for (Criteria c : this.criteriaChain) {
criteriaObject.putAll(c.getSingleCriteriaObject());
}
return criteriaObject;
}
}
/**
* Creates a criterion using the $elemMatch operator
*
* @param c
* @return
*/
public Criteria elemMatch(Criteria c) {
criteria.put("$elemMatch", c.getCriteriaObject());
return this;
}
protected DBObject getSingleCriteriaObject() {
DBObject dbo = new BasicDBObject();
boolean not = false;
for (String k : this.criteria.keySet()) {
if (not) {
DBObject notDbo = new BasicDBObject();
notDbo.put(k, convertValueIfNecessary(this.criteria.get(k)));
dbo.put("$not", notDbo);
not = false;
} else {
if ("$not".equals(k)) {
not = true;
} else {
dbo.put(k, convertValueIfNecessary(this.criteria.get(k)));
}
}
}
DBObject queryCriteria = new BasicDBObject();
if (isValue != null) {
queryCriteria.put(this.key, convertValueIfNecessary(this.isValue));
queryCriteria.putAll(dbo);
} else {
queryCriteria.put(this.key, dbo);
}
return queryCriteria;
}
/**
* Creates an or query using the $or operator for all of the provided queries
*
* @param queries
*/
public void or(List<Query> queries) {
criteria.put("$or", queries);
}
private Object convertValueIfNecessary(Object value) {
if (value instanceof Enum) {
return ((Enum<?>) value).name();
}
return value;
}
public String getKey() {
return this.key;
}
/*
* (non-Javadoc)
*
* @see org.springframework.datastore.document.mongodb.query.Criteria#
* getCriteriaObject(java.lang.String)
*/
public DBObject getCriteriaObject() {
if (this.criteriaChain.size() == 1) {
return criteriaChain.get(0).getSingleCriteriaObject();
} else {
DBObject criteriaObject = new BasicDBObject();
for (Criteria c : this.criteriaChain) {
criteriaObject.putAll(c.getSingleCriteriaObject());
}
return criteriaObject;
}
}
protected DBObject getSingleCriteriaObject() {
DBObject dbo = new BasicDBObject();
boolean not = false;
for (String k : this.criteria.keySet()) {
if (not) {
DBObject notDbo = new BasicDBObject();
notDbo.put(k, this.criteria.get(k));
dbo.put("$not", notDbo);
not = false;
} else {
if ("$not".equals(k)) {
not = true;
} else {
dbo.put(k, this.criteria.get(k));
}
}
}
DBObject queryCriteria = new BasicDBObject();
if (isValue != null) {
queryCriteria.put(this.key, this.isValue);
queryCriteria.putAll(dbo);
} else {
queryCriteria.put(this.key, dbo);
}
return queryCriteria;
}
}

View File

@@ -16,16 +16,11 @@
package org.springframework.data.document.mongodb.query;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.document.mongodb.convert.MongoConverter;
import org.springframework.data.mapping.MappingBeanHelper;
public class Update {
@@ -53,7 +48,7 @@ public class Update {
* @return
*/
public Update set(String key, Object value) {
addMultiFieldOperation("$set", key, convertValueIfNecessary(value));
addMultiFieldOperation("$set", key, value);
return this;
}
@@ -88,7 +83,7 @@ public class Update {
* @return
*/
public Update push(String key, Object value) {
addMultiFieldOperation("$push", key, convertValueIfNecessary(value));
addMultiFieldOperation("$push", key, value);
return this;
}
@@ -102,7 +97,7 @@ public class Update {
public Update pushAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = convertValueIfNecessary(values[i]);
convertedValues[i] = values[i];
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
@@ -118,7 +113,7 @@ public class Update {
* @return
*/
public Update addToSet(String key, Object value) {
addMultiFieldOperation("$addToSet", key, convertValueIfNecessary(value));
addMultiFieldOperation("$addToSet", key, value);
return this;
}
@@ -143,7 +138,7 @@ public class Update {
* @return
*/
public Update pull(String key, Object value) {
addMultiFieldOperation("$pull", key, convertValueIfNecessary(value));
addMultiFieldOperation("$pull", key, value);
return this;
}
@@ -157,7 +152,7 @@ public class Update {
public Update pullAll(String key, Object[] values) {
Object[] convertedValues = new Object[values.length];
for (int i = 0; i < values.length; i++) {
convertedValues[i] = convertValueIfNecessary(values[i]);
convertedValues[i] = values[i];
}
DBObject keyValue = new BasicDBObject();
keyValue.put(key, convertedValues);
@@ -177,23 +172,14 @@ public class Update {
return this;
}
public DBObject getUpdateObject(MongoConverter converter) {
public DBObject getUpdateObject() {
DBObject dbo = new BasicDBObject();
for (String k : modifierOps.keySet()) {
Object o = modifierOps.get(k);
if (null != converter) {
dbo.put(k, maybeConvertObject(o, converter));
} else {
dbo.put(k, o);
}
dbo.put(k, modifierOps.get(k));
}
return dbo;
}
public DBObject getUpdateObject() {
return getUpdateObject(null);
}
@SuppressWarnings("unchecked")
protected void addMultiFieldOperation(String operator, String key,
Object value) {
@@ -213,54 +199,4 @@ public class Update {
keyValueMap.put(key, value);
}
protected Object convertValueIfNecessary(Object value) {
if (value instanceof Enum) {
return ((Enum<?>) value).name();
}
return value;
}
@SuppressWarnings({"unchecked"})
protected Object maybeConvertObject(Object obj, MongoConverter converter) {
if (null != obj && MappingBeanHelper.isSimpleType(obj.getClass())) {
// Doesn't need conversion
return obj;
}
if (obj instanceof Map) {
Map<Object, Object> m = new HashMap<Object, Object>();
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) obj).entrySet()) {
m.put(entry.getKey(), maybeConvertObject(entry.getValue(), converter));
}
return m;
}
if (obj instanceof BasicDBList) {
return maybeConvertList((BasicDBList) obj, converter);
}
if (obj instanceof DBObject) {
DBObject newValueDbo = new BasicDBObject();
for (String vk : ((DBObject) obj).keySet()) {
Object o = ((DBObject) obj).get(vk);
newValueDbo.put(vk, maybeConvertObject(o, converter));
}
return newValueDbo;
}
DBObject newDbo = new BasicDBObject();
converter.write(obj, newDbo);
return newDbo;
}
protected BasicDBList maybeConvertList(BasicDBList dbl, MongoConverter converter) {
BasicDBList newDbl = new BasicDBList();
Iterator iter = dbl.iterator();
while (iter.hasNext()) {
Object o = iter.next();
newDbl.add(maybeConvertObject(o, converter));
}
return newDbl;
}
}

View File

@@ -21,6 +21,8 @@ import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.List;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
@@ -28,9 +30,7 @@ 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;
import org.springframework.data.document.mongodb.convert.AbstractMongoConverter;
import org.springframework.data.document.mongodb.convert.MongoConverter;
import org.springframework.data.mapping.model.MappingContext;
import org.springframework.data.mapping.model.PersistentEntity;
@@ -38,292 +38,293 @@ import org.springframework.data.mapping.model.PersistentEntity;
/**
* 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;
@Mock
CollectionCallback<Object> collectionCallback;
@Mock
DbCallback<Object> dbCallback;
MongoConverter converter;
Person person;
List<Person> persons;
MongoConverter converter;
Person person;
List<Person> persons;
@Before
public final void operationsSetUp() {
@Before
public final void operationsSetUp() {
person = new Person("Oliver");
persons = Arrays.asList(person);
person = new Person("Oliver");
persons = Arrays.asList(person);
converter = new MongoConverter() {
converter = new AbstractMongoConverter() {
public void write(Object t, DBObject dbo) {
dbo.put("firstName", person.getFirstName());
}
public void write(Object t, DBObject dbo) {
dbo.put("firstName", person.getFirstName());
}
@SuppressWarnings("unchecked")
public <S extends Object> S read(Class<S> clazz, DBObject dbo) {
return (S) person;
}
@SuppressWarnings("unchecked")
public <S extends Object> S read(Class<S> clazz, DBObject dbo) {
return (S) person;
}
public <T> T convertObjectId(ObjectId id, Class<T> targetType) {
return null;
}
public <T> T convertObjectId(ObjectId id, Class<T> targetType) {
return null;
}
public ObjectId convertObjectId(Object id) {
return null;
}
public MappingContext<PersistentEntity<?>> getMappingContext() {
return null;
}
};
}
public ObjectId convertObjectId(Object id) {
return null;
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({ "unchecked", "rawtypes" })
public void rejectsNullForCollectionCallback() {
public MappingContext<PersistentEntity<?>> getMappingContext() {
return null;
}
};
}
getOperations().execute("test", (CollectionCallback) null);
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public void rejectsNullForCollectionCallback() {
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({ "unchecked", "rawtypes" })
public void rejectsNullForCollectionCallback2() {
getOperations().execute("collection", (CollectionCallback) null);
}
getOperations().execute("test", (CollectionCallback) null);
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({ "unchecked", "rawtypes" })
public void rejectsNullForDbCallback() {
getOperations().execute((DbCallback) null);
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public void rejectsNullForCollectionCallback2() {
getOperations().execute("collection", (CollectionCallback) null);
}
@Test
public void convertsExceptionForCollectionExists() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.collectionExists("foo");
}
}.assertDataAccessException();
}
@Test(expected = IllegalArgumentException.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public void rejectsNullForDbCallback() {
getOperations().execute((DbCallback) null);
}
@Test
public void convertsExceptionForCreateCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.createCollection("foo");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForCollectionExists() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.collectionExists("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 convertsExceptionForCreateCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.createCollection("foo");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForDropCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.dropCollection("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 convertsExceptionForExecuteCollectionCallback() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute("test", collectionCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForDropCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.dropCollection("foo");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteDbCallback() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute(dbCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCollectionCallback() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute("test", collectionCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCollectionCallbackAndCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute("collection", collectionCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteDbCallback() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute(dbCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCommand() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeCommand(new BasicDBObject());
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCollectionCallbackAndCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.execute("collection", collectionCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteStringCommand() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeCommand("");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteCommand() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeCommand(new BasicDBObject());
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteInSession() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeInSession(dbCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteStringCommand() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeCommand("");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection(Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForExecuteInSession() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.executeInSession(dbCallback);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionName() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollection() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection(Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionNameAndType() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection", Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionName() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection");
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionNameTypeAndReader() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection", Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionNameAndType() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection", Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionNames() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollectionNames();
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionWithCollectionNameTypeAndReader() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollection("collection", Object.class);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert(person);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetCollectionNames() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.getCollectionNames();
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert2() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert("collection", person);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert(person);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsertList() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList(persons);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsert2() {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insert("collection", person);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForGetInsertList2() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList("collection", persons);
}
}.assertDataAccessException();
}
@Test
public void convertsExceptionForInsertList() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList(persons);
}
}.assertDataAccessException();
}
private abstract class Execution {
@Test
public void convertsExceptionForGetInsertList2() throws Exception {
new Execution() {
@Override
public void doWith(MongoOperations operations) {
operations.insertList("collection", persons);
}
}.assertDataAccessException();
}
public void assertDataAccessException() {
assertException(DataAccessException.class);
}
private abstract class Execution {
public void assertException(Class<? extends Exception> exception) {
public void assertDataAccessException() {
assertException(DataAccessException.class);
}
try {
doWith(getOperationsForExceptionHandling());
fail("Expected " + exception + " but completed without any!");
} catch (Exception e) {
assertTrue("Expected " + exception + " but got " + e, exception.isInstance(e));
}
}
public void assertException(Class<? extends Exception> exception) {
public abstract void doWith(MongoOperations operations);
}
try {
doWith(getOperationsForExceptionHandling());
fail("Expected " + exception + " but completed without any!");
} catch (Exception e) {
assertTrue("Expected " + exception + " but got " + e, exception.isInstance(e));
}
}
/**
* 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();
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();
/**
* Returns a plain {@link MongoOperations}.
*
* @return
*/
protected abstract MongoOperations getOperations();
}