From 62ecfc8416095b59d5ac9a6c6595d12add636c63 Mon Sep 17 00:00:00 2001 From: "J. Brisbin" Date: Thu, 5 May 2011 15:07:49 -0500 Subject: [PATCH] DATADOC-114 - Fixes for updateFirst/updateMulti not converting POJOs correctly --- .../data/document/mongodb/MongoTemplate.java | 77 +- .../convert/AbstractMongoConverter.java | 103 ++ .../convert/MappingMongoConverter.java | 98 +- .../mongodb/convert/MongoConverter.java | 7 + .../mongodb/convert/SimpleMongoConverter.java | 930 +++++++++--------- .../document/mongodb/query/BasicUpdate.java | 147 ++- .../data/document/mongodb/query/Criteria.java | 665 +++++++------ .../data/document/mongodb/query/Update.java | 80 +- .../mongodb/MongoOperationsUnitTests.java | 497 +++++----- 9 files changed, 1340 insertions(+), 1264 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/AbstractMongoConverter.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java index 012278ffa..87b7239e5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/MongoTemplate.java @@ -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() { - 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() { 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) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/AbstractMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/AbstractMongoConverter.java new file mode 100644 index 000000000..bd9d1c41e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/AbstractMongoConverter.java @@ -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 + */ +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 m = new HashMap(); + for (Map.Entry entry : ((Map) obj).entrySet()) { + m.put(entry.getKey(), maybeConvertObject(entry.getValue())); + } + return m; + } + + if (obj instanceof List) { + List l = (List) obj; + List newList = new ArrayList(); + 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; + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java index 4a3ecac2a..49911ad60 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MappingMongoConverter.java @@ -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 * @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> getMappingContext() { @@ -174,16 +175,17 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext return conversionService.convert(id, ObjectId.class); } + @SuppressWarnings({"unchecked", "rawtypes"}) public S read(Class 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 ctorParamNames = new ArrayList(); - 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 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(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java index 93ad33810..84ddb8e9c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/MongoConverter.java @@ -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, MongoReader public ObjectId convertObjectId(Object id); MappingContext getMappingContext(); + + Object maybeConvertObject(Object obj); + + Object[] maybeConvertArray(Object[] src); + + BasicDBList maybeConvertList(BasicDBList dbl); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java index f66d7b3db..658a2114f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/convert/SimpleMongoConverter.java @@ -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> MONGO_TYPES = Arrays.asList(Number.class, Date.class, String.class, DBObject.class); - private static final Set SIMPLE_TYPES; + private static final Log LOG = LogFactory.getLog(SimpleMongoConverter.class); + @SuppressWarnings("unchecked") + private static final List> MONGO_TYPES = Arrays.asList(Number.class, Date.class, String.class, DBObject.class); + private static final Set SIMPLE_TYPES; - static { - Set basics = new HashSet(); - 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 basics = new HashSet(); + 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> mappingContext; + private final GenericConversionService conversionService; + private final MappingContext> 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> 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> 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) value); - return; - } - if (value instanceof Collection) { - // Should write a collection! - writeArray(dbo, keyToUse, ((Collection) 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 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) value); + return; + } + if (value instanceof Collection) { + // Should write a collection! + writeArray(dbo, keyToUse, ((Collection) 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 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 read(Class 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 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 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 readCollection(MongoPropertyDescriptor descriptor, Collection values) { + /* + * (non-Javadoc) + * + * @see org.springframework.data.document.mongodb.MongoReader#read(java.lang.Class, com.mongodb.DBObject) + */ + public S read(Class clazz, DBObject source) { - Class targetCollectionType = descriptor.getPropertyType(); - boolean targetIsArray = targetCollectionType.isArray(); + if (source == null) { + return null; + } - @SuppressWarnings("unchecked") - Collection result = targetIsArray ? new ArrayList(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 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 result = targetIsArray ? new ArrayList(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 createMap() { - return new HashMap(); - } + 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 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> 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 createMap() { + return new HashMap(); + } - List> actualGenericParameterTypes = new ArrayList>(); + /** + * 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 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 convertObjectId(ObjectId id, Class 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> getGenericParameters(Type genericParameterType) { + + List> actualGenericParameterTypes = new ArrayList>(); + + 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 convertObjectId(ObjectId id, Class 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(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/BasicUpdate.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/BasicUpdate.java index cfb4ea118..2e71925a0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/BasicUpdate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/BasicUpdate.java @@ -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; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java index 3a5209064..4b0653d9c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Criteria.java @@ -30,371 +30,368 @@ import org.springframework.util.Assert; public class Criteria implements CriteriaDefinition { - private String key; + private String key; - private List criteriaChain; + private List criteriaChain; - private LinkedHashMap criteria = new LinkedHashMap(); + private LinkedHashMap criteria = new LinkedHashMap(); - private Object isValue = null; + private Object isValue = null; - public Criteria(String key) { - this.criteriaChain = new ArrayList(); - this.criteriaChain.add(this); - this.key = key; - } + public Criteria(String key) { + this.criteriaChain = new ArrayList(); + this.criteriaChain.add(this); + this.key = key; + } - protected Criteria(List criteriaChain, String key) { - this.criteriaChain = criteriaChain; - this.criteriaChain.add(this); - this.key = key; - } + protected Criteria(List 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 l = new ArrayList(); - 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 l = new ArrayList(); + 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 list = new LinkedList(); - 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 list = new LinkedList(); - 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 list = new LinkedList(); - 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 list = new LinkedList(); + 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 list = new LinkedList(); + 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 list = new LinkedList(); + 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 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 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; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Update.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Update.java index 2ebf20bf1..11b310b4b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Update.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/query/Update.java @@ -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 m = new HashMap(); - for (Map.Entry entry : ((Map) 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; - } - } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java index cf8ffe1f9..3fc35cc16 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/MongoOperationsUnitTests.java @@ -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 collectionCallback; - @Mock - DbCallback dbCallback; + @Mock + CollectionCallback collectionCallback; + @Mock + DbCallback dbCallback; - MongoConverter converter; - Person person; - List persons; + MongoConverter converter; + Person person; + List 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 read(Class clazz, DBObject dbo) { - return (S) person; - } + @SuppressWarnings("unchecked") + public S read(Class clazz, DBObject dbo) { + return (S) person; + } - public T convertObjectId(ObjectId id, Class targetType) { - return null; - } + public T convertObjectId(ObjectId id, Class targetType) { + return null; + } - public ObjectId convertObjectId(Object id) { - return null; - } - - public MappingContext> getMappingContext() { - return null; - } - }; - } + public ObjectId convertObjectId(Object id) { + return null; + } - @Test(expected = IllegalArgumentException.class) - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void rejectsNullForCollectionCallback() { + public MappingContext> 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 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 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(); }