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 bf21a77ea..3db661b1c 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 @@ -19,6 +19,7 @@ package org.springframework.data.document.mongodb.convert; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mongodb.DBRef; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.types.ObjectId; @@ -26,12 +27,14 @@ import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.CollectionFactory; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.document.mongodb.mapping.MappingException; import org.springframework.data.document.mongodb.mapping.MappingIntrospector; import org.springframework.data.document.mongodb.mapping.MongoMappingContext; +import org.springframework.data.mapping.model.MappingInstantiationException; import org.springframework.expression.Expression; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -45,6 +48,8 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static org.springframework.data.document.mongodb.mapping.MappingIntrospector.isSimpleType; + /** * @author Jon Brisbin */ @@ -103,6 +108,10 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } public S read(Class clazz, final DBObject dbo) { + if (null == dbo) { + return null; + } + final StandardEvaluationContext ctx = new StandardEvaluationContext(); if (null != applicationContext) { ctx.setBeanResolver(new BeanFactoryResolver(applicationContext)); @@ -113,7 +122,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } try { - if ((clazz.isArray() || clazz.isAssignableFrom(Collection.class)) && dbo instanceof BasicDBList) { + if ((clazz.isArray() || (clazz.isAssignableFrom(Collection.class) || clazz.isAssignableFrom(List.class))) && dbo instanceof BasicDBList) { List l = new ArrayList(); BasicDBList dbList = (BasicDBList) dbo; for (Object o : dbList) { @@ -136,22 +145,49 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext final S instance = introspector.createInstance(new MappingIntrospector.ParameterValueProvider() { public T getParameterValue(String name, Class type, Expression spelExpr) { Object o = getValueInternal(name, type, dbo, ctx, spelExpr); - if (null != o && o.getClass().isAssignableFrom(type)) { + if (o instanceof DBRef) { cparamNames.add(name); - return (T) o; + return read(type, ((DBRef) o).fetch()); + } else if (null != o && o.getClass().isAssignableFrom(type)) { + if (type == Object.class && dbo.containsField("_class")) { + try { + cparamNames.add(name); + return (T) conversionService.convert(o, Class.forName(dbo.get("_class").toString())); + } catch (ClassNotFoundException e) { + throw new MappingInstantiationException(e.getMessage(), e); + } + } else { + cparamNames.add(name); + return (T) o; + } } else { cparamNames.add(name); return conversionService.convert(o, type); } } }); + + // The id is important. Set it first. + if (dbo.containsField("_id")) { + introspector.setValue(introspector.getIdField().getName(), instance, convertObjectId((ObjectId) dbo.get("_id"), introspector.getIdField().getType())); + } + introspector.doWithProperties(new MappingIntrospector.PropertyHandler() { public void doWithProperty(PropertyDescriptor descriptor, Field field, Expression spelExpr) { String name = descriptor.getName(); if (!cparamNames.contains(name)) { Object o = getValueInternal(name, descriptor.getPropertyType(), dbo, ctx, spelExpr); try { - introspector.setValue(name, instance, o); + if (null != descriptor.getWriteMethod()) { + Class requiredType = descriptor.getWriteMethod().getParameterTypes()[0]; + if (null != o && o.getClass().isAssignableFrom(requiredType)) { + introspector.setValue(name, instance, o); + } else { + introspector.setValue(name, instance, conversionService.convert(o, requiredType)); + } + } else { + introspector.setValue(name, instance, o); + } } catch (MappingException e) { log.error(e.getMessage(), e); } @@ -159,8 +195,42 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext } }); + // Handle mapping-specific stuff + if (introspector.isMappable()) { + introspector.doWithAssociations(new MappingIntrospector.AssociationHandler() { + public void doWithAssociation(MappingIntrospector.Association association) { + String name = association.getDescriptor().getName(); + Object targetObj = dbo.get(name); + if (null != targetObj) { + if (targetObj instanceof DBRef) { + targetObj = read(association.getTargetClass(), ((DBRef) targetObj).fetch()); + } else if (targetObj instanceof BasicDBList) { + BasicDBList dbList = (BasicDBList) targetObj; + Collection targets = CollectionFactory.createCollection(association.getField().getType(), dbList.size()); + for (Object tgtDbObj : dbList) { + if (tgtDbObj instanceof DBRef) { + targets.add(read(association.getTargetClass(), ((DBRef) tgtDbObj).fetch())); + } else if (tgtDbObj instanceof DBObject) { + targets.add(read(association.getTargetClass(), (DBObject) tgtDbObj)); + } else if (tgtDbObj.getClass().isAssignableFrom(association.getTargetClass())) { + targets.add(tgtDbObj); + } else { + targets.add(conversionService.convert(tgtDbObj, association.getTargetClass())); + } + } + targetObj = targets; + } + introspector.setValue(name, instance, targetObj); + } + } + }); + } + introspector.maybeAutowire(instance, applicationContext, autowirePersistentBeans); + if (log.isDebugEnabled()) { + log.debug("Handing back configured object: " + instance); + } return instance; } catch (MappingException e) { throw new RuntimeException(e); @@ -170,6 +240,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext public void write(final Object o, final DBObject dbo) { try { final MappingIntrospector introspector = MappingIntrospector.getInstance(o.getClass()); + //final PersistentEntity persistentEntity = mappingContext.getPersistentEntity(o.getClass().getName()); Field idFld = introspector.getIdField(); if (null != idFld) { @@ -185,7 +256,7 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext try { Object newObj = introspector.getFieldValue(name, o); if (null != newObj) { - if (MappingIntrospector.isSimpleType(newObj.getClass())) { + if (isSimpleType(newObj.getClass())) { dbo.put(name, newObj); } else { if (newObj.getClass().isArray() || newObj.getClass().isAssignableFrom(Collection.class)) { @@ -213,7 +284,29 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (introspector.isMappable()) { introspector.doWithAssociations(new MappingIntrospector.AssociationHandler() { public void doWithAssociation(MappingIntrospector.Association association) { - log.info("HANDLE ASSOCIATION: " + association); + String name = association.getDescriptor().getName(); + Object targetObj = introspector.getFieldValue(name, o); + if (null != targetObj) { + if (isSimpleType(targetObj.getClass())) { + dbo.put(name, targetObj); + } else if (targetObj instanceof Collection || targetObj instanceof List) { + // We're doing a ToMany + BasicDBList dbRefList = new BasicDBList(); + for (Object depObj : (targetObj instanceof Collection ? (Collection) targetObj : (List) targetObj)) { + DBObject depDbRef = createDBRef(depObj, depObj.getClass()); + if (null != depDbRef) { + dbo.put(name, depDbRef); + } + dbRefList.add(depDbRef); + } + dbo.put(name, dbRefList); + } else { + DBObject depDbRef = createDBRef(targetObj, association.getTargetClass()); + if (null != depDbRef) { + dbo.put(name, depDbRef); + } + } + } } }); } @@ -247,7 +340,11 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext if (null != spelExpr) { o = spelExpr.getValue(ctx); } else { - Object dbObj = dbo.get(name); + DBObject from = dbo; + if (dbo instanceof DBRef) { + from = ((DBRef) dbo).fetch(); + } + Object dbObj = from.get(name); if (dbObj instanceof DBObject) { // It's a complex object, have to read it in o = read(type, (DBObject) dbObj); @@ -258,6 +355,21 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext return o; } + protected DBObject createDBRef(Object obj, Class targetClass) { + if (null != obj) { + MappingIntrospector introspector = MappingIntrospector.getInstance(obj.getClass()); + if (introspector.isMappable()) { + String idName = introspector.getIdField().getName(); + Object idVal = introspector.getFieldValue(idName, obj); + BasicDBObject dbRefDbo = new BasicDBObject(); + dbRefDbo.put("$ref", targetClass.getSimpleName().toLowerCase()); + dbRefDbo.put("$id", convertObjectId(idVal)); + return dbRefDbo; + } + } + return null; + } + /** * Simple singleton to convert {@link ObjectId}s to their {@link String} representation. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java index 565044d4d..9dfaed9a8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingException.java @@ -19,17 +19,27 @@ package org.springframework.data.document.mongodb.mapping; /** * @author Jon Brisbin */ -public class MappingException extends Throwable { +public class MappingException extends RuntimeException { private final Object source; - public MappingException(Throwable throwable, Object source) { - super(String.format("Error encountered mapping object: %s", source), throwable); + public MappingException(String s) { + super(s); + this.source = null; + } + + public MappingException(String s, Object source) { + super(String.format("Error encountered mapping object: %s", source)); + this.source = source; + } + + public MappingException(String s, Throwable throwable, Object source) { + super(s, throwable); this.source = source; } public Object getSource() { return source; } - + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java index 11286726c..8642800e4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MappingIntrospector.java @@ -27,6 +27,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.data.mapping.annotation.*; +import org.springframework.data.mapping.model.MappingInstantiationException; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -91,9 +92,16 @@ public class MappingIntrospector { private final Map setters; private final Map getters; private Field idField = null; + private PropertyDescriptor idPropertyDescriptor = null; private List ignoredProperties = new ArrayList() {{ add("class"); }}; + private List> validIdTypes = new ArrayList>() {{ + add(ObjectId.class); + add(String.class); + add(BigInteger.class); + }}; + private SpelExpressionParser parser = new SpelExpressionParser(); private Map expressions = new HashMap(); private PreferredConstructor preferredConstructor = null; @@ -104,7 +112,7 @@ public class MappingIntrospector { try { this.beanInfo = Introspector.getBeanInfo(clazz); } catch (IntrospectionException e) { - throw new MappingException(e, clazz); + throw new MappingException(e.getMessage(), e, clazz); } Map properties = new HashMap(); Map fields = new HashMap(); @@ -122,24 +130,43 @@ public class MappingIntrospector { if (fld.isAnnotationPresent(Id.class)) { if (null == idField) { idField = fld; + idPropertyDescriptor = descriptor; } else { log.warn("Only the first field found with the @Id annotation will be considered the ID. Ignoring " + idField); } continue; - } else if (null == idField && fldType.equals(ObjectId.class)) { - // Respect fields of the MongoDB ObjectId type - idField = fld; - continue; } else if (null == idField - && (fldType.equals(String.class) || fldType.equals(BigInteger.class)) + && validIdTypes.contains(fldType) && ("id".equals(name) || "_id".equals(name))) { - // Strings and BigIntegers named "id"|"_id" are also valid ID fields idField = fld; + idPropertyDescriptor = descriptor; continue; - } else if (fld.isAnnotationPresent(OneToMany.class) + } else if (fld.isAnnotationPresent(Reference.class) + || fld.isAnnotationPresent(OneToMany.class) || fld.isAnnotationPresent(OneToOne.class) || fld.isAnnotationPresent(ManyToMany.class)) { - associations.add(new Association(descriptor, fld)); + Class targetClass = fld.getType(); + if (fld.isAnnotationPresent(Reference.class)) { + Reference ref = fld.getAnnotation(Reference.class); + if (ref.targetClass() != Object.class) { + targetClass = ref.targetClass(); + } + } + if (fldType.isAssignableFrom(Collection.class) || fldType.isAssignableFrom(List.class)) { + Type t = fld.getGenericType(); + if (t instanceof ParameterizedType) { + ParameterizedType ptype = (ParameterizedType) t; + Type[] paramTypes = ptype.getActualTypeArguments(); + if (paramTypes.length > 0) { + if (paramTypes[0] instanceof TypeVariable) { + targetClass = Object.class; + } else { + targetClass = (Class) paramTypes[0]; + } + } + } + } + associations.add(new Association(descriptor, fld, targetClass)); continue; } else if (fld.isAnnotationPresent(Value.class)) { // @Value fields are evaluated at runtime and are the same transient fields @@ -158,6 +185,21 @@ public class MappingIntrospector { } } } + + if (null == this.idField) { + // ID might be in a private field + for (Field f : fields.values()) { + Class type = f.getType(); + if (f.isAnnotationPresent(Id.class)) { + this.idField = f; + break; + } else if (validIdTypes.contains(type) && (f.getName().equals("id") || f.getName().equals("_id"))) { + this.idField = f; + break; + } + } + } + this.properties = Collections.unmodifiableMap(properties); this.fields = Collections.unmodifiableMap(fields); this.associations = Collections.unmodifiableSet(associations); @@ -168,7 +210,7 @@ public class MappingIntrospector { for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterTypes().length != 0) { // Non-no-arg constructor - if (null == preferredConstructor) { + if (null == preferredConstructor || constructor.isAnnotationPresent(PersistenceConstructor.class)) { String[] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(constructor); Type[] paramTypes = constructor.getGenericParameterTypes(); Class[] paramClassTypes = new Class[paramTypes.length]; @@ -178,7 +220,12 @@ public class MappingIntrospector { ParameterizedType ptype = (ParameterizedType) paramTypes[i]; Type[] types = ptype.getActualTypeArguments(); if (types.length == 1) { - targetType = (Class) types[0]; + if (types[0] instanceof TypeVariable) { + // Placeholder type + targetType = Object.class; + } else { + targetType = (Class) types[0]; + } } else { targetType = (Class) ptype.getRawType(); } @@ -188,6 +235,10 @@ public class MappingIntrospector { paramClassTypes[i] = targetType; } preferredConstructor = new PreferredConstructor((Constructor) constructor, paramNames, paramClassTypes); + if (constructor.isAnnotationPresent(PersistenceConstructor.class)) { + // We're done + break; + } } } } @@ -224,11 +275,15 @@ public class MappingIntrospector { return idField; } - public T createInstance() throws MappingException { + public PropertyDescriptor getIdPropertyDescriptor() { + return idPropertyDescriptor; + } + + public T createInstance() { return createInstance(null); } - public T createInstance(ParameterValueProvider provider) throws MappingException { + public T createInstance(ParameterValueProvider provider) { try { if (null == preferredConstructor || null == provider) { return clazz.newInstance(); @@ -245,53 +300,78 @@ public class MappingIntrospector { return preferredConstructor.constructor.newInstance(params.toArray()); } } catch (InvocationTargetException e) { - throw new MappingException(e, clazz); + throw new MappingInstantiationException(e.getMessage(), e); } catch (InstantiationException e) { - throw new MappingException(e, clazz); + throw new MappingInstantiationException(e.getMessage(), e); } catch (IllegalAccessException e) { - throw new MappingException(e, clazz); - } - } - - public Object getFieldValue(PropertyDescriptor descriptor, Object from) throws MappingException { - try { - Method getter = descriptor.getReadMethod(); - if (null != getter) { - return getter.invoke(from); - } else { - Field f = fields.get(descriptor.getName()); - return f.get(from); - } - } catch (IllegalAccessException e) { - throw new MappingException(e, from); - } catch (InvocationTargetException e) { - throw new MappingException(e, from); + throw new MappingInstantiationException(e.getMessage(), e); } } public Object getFieldValue(String name, Object from) throws MappingException { - PropertyDescriptor descriptor = properties.get(name); - if (null != descriptor) { - return getFieldValue(descriptor, from); + return getFieldValue(name, from, null); + } + + public Object getFieldValue(String name, Object from, Object defaultObj) throws MappingException { + try { + if (properties.containsKey(name) && null != getters.get(name)) { + return getters.get(name).invoke(from); + } else { + if (fields.containsKey(name)) { + Field f = fields.get(name); + return f.get(from); + } else { + for (Association assoc : associations) { + if (assoc.getField().getName().equals(name)) { + return assoc.getField().get(from); + } + } + for (Field f : clazz.getDeclaredFields()) { + // Lastly, check for any private fields + if (f.getName().equals(name)) { + f.setAccessible(true); + return f.get(from); + } + } + } + } + } catch (IllegalAccessException e) { + throw new MappingException(e.getMessage(), e, from); + } catch (InvocationTargetException e) { + throw new MappingException(e.getMessage(), e, from); } - return null; + return defaultObj; } public void setValue(String name, Object on, Object value) throws MappingException { Field f = fields.get(name); - if (null != f) { - Method setter = setters.get(name); - try { + try { + if (null != f) { + Method setter = setters.get(name); if (null != setter) { setter.invoke(on, value); } else { f.set(on, value); } - } catch (IllegalAccessException e) { - throw new MappingException(e, value); - } catch (InvocationTargetException e) { - throw new MappingException(e, value); + } else { + for (Association assoc : associations) { + if (assoc.getField().getName().equals(name)) { + assoc.getField().set(on, value); + return; + } + } + for (Field privFld : clazz.getDeclaredFields()) { + if (privFld.getName().equals(name)) { + privFld.setAccessible(true); + privFld.set(on, value); + return; + } + } } + } catch (IllegalAccessException e) { + throw new MappingException(e.getMessage(), e, value); + } catch (InvocationTargetException e) { + throw new MappingException(e.getMessage(), e, value); } } @@ -305,7 +385,7 @@ public class MappingIntrospector { try { ((InitializingBean) obj).afterPropertiesSet(); } catch (Exception e) { - throw new MappingException(e, obj); + throw new MappingException(e.getMessage(), e, obj); } } } @@ -346,10 +426,12 @@ public class MappingIntrospector { private final PropertyDescriptor descriptor; private final Field field; + private final Class targetClass; - public Association(PropertyDescriptor descriptor, Field field) { + public Association(PropertyDescriptor descriptor, Field field, Class targetClass) { this.descriptor = descriptor; this.field = field; + this.targetClass = targetClass; } public PropertyDescriptor getDescriptor() { @@ -360,6 +442,9 @@ public class MappingIntrospector { return field; } + public Class getTargetClass() { + return targetClass; + } } private class PreferredConstructor { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java index 18c008f3e..47f6b99a3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/document/mongodb/mapping/MongoMappingConfigurationStrategy.java @@ -5,18 +5,23 @@ import com.mongodb.DBCollection; import com.mongodb.DBObject; import com.mongodb.MongoException; import com.mongodb.util.JSON; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.dao.DataAccessException; +import org.springframework.data.document.mongodb.CollectionCallback; +import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.index.CompoundIndex; import org.springframework.data.document.mongodb.index.CompoundIndexes; import org.springframework.data.document.mongodb.index.IndexDirection; import org.springframework.data.document.mongodb.index.Indexed; -import org.springframework.data.document.mongodb.CollectionCallback; -import org.springframework.data.document.mongodb.MongoTemplate; -import org.springframework.data.mapping.annotation.*; +import org.springframework.data.mapping.annotation.IdentifiedBy; +import org.springframework.data.mapping.annotation.OneToOne; +import org.springframework.data.mapping.annotation.PersistenceStrategy; +import org.springframework.data.mapping.annotation.Persistent; import org.springframework.data.mapping.model.*; import org.springframework.data.mapping.model.types.Association; -import org.springframework.data.mapping.reflect.ClassPropertyFetcher; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -37,6 +42,8 @@ import java.util.concurrent.ConcurrentHashMap; */ public class MongoMappingConfigurationStrategy implements MappingConfigurationStrategy { + protected static final Log log = LogFactory.getLog(MongoMappingConfigurationStrategy.class); + protected MongoTemplate mongo; protected MongoMappingFactory mappingFactory; protected SpelExpressionParser expressionParser = new SpelExpressionParser(); @@ -84,16 +91,21 @@ public class MongoMappingConfigurationStrategy implements MappingConfigurationSt public List getPersistentProperties(Class aClass, MappingContext mappingContext, ClassMapping classMapping) { - PersistenceDescriptor pd = getPersistenceDescriptor(aClass, mappingContext, classMapping); + PersistenceDescriptor pd = null; + try { + pd = getPersistenceDescriptor(aClass, mappingContext, classMapping); + } catch (MappingException e) { + log.error(e.getMessage(), e); + } return (null != pd ? pd.getProperties() : null); } - public PersistentProperty getIdentity(Class aClass, MappingContext mappingContext) { + public PersistentProperty getIdentity(Class aClass, MappingContext mappingContext) throws MappingException { PersistenceDescriptor idPd = getPersistenceDescriptor(aClass, mappingContext, null); return (null != idPd ? idPd.getIdProperty() : null); } - public IdentityMapping getDefaultIdentityMapping(final ClassMapping classMapping) { + public IdentityMapping getDefaultIdentityMapping(final ClassMapping classMapping) throws MappingException { final PersistentProperty prop = getPersistenceDescriptor(classMapping.getEntity().getJavaClass(), classMapping.getEntity().getMappingContext(), classMapping).getIdProperty(); @@ -117,15 +129,15 @@ public class MongoMappingConfigurationStrategy implements MappingConfigurationSt return owners.get(aClass); } - protected PersistenceDescriptor getPersistenceDescriptor(Class javaClass, - MappingContext context, - ClassMapping mapping) { + protected PersistenceDescriptor getPersistenceDescriptor(final Class javaClass, + final MappingContext context, + final ClassMapping mapping) throws MappingException { PersistenceDescriptor descriptor = descriptors.get(javaClass); if (null == descriptor) { - ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(javaClass); - PersistentEntity entity = getPersistentEntity(javaClass, context, mapping); + final MappingIntrospector introspector = MappingIntrospector.getInstance(javaClass); + final PersistentEntity entity = getPersistentEntity(javaClass, context, mapping); - String collection = javaClass.getSimpleName().toLowerCase(); + final String collection = javaClass.getSimpleName().toLowerCase(); for (Annotation anno : javaClass.getAnnotations()) { if (anno instanceof Persistent) { @@ -145,34 +157,41 @@ public class MongoMappingConfigurationStrategy implements MappingConfigurationSt } } - EvaluationContext elContext = createElContext(); + EvaluationContext elContext = new StandardEvaluationContext(); elContext.setVariable("class", javaClass); - PersistentProperty id = extractIdProperty(entity, context, elContext); + PropertyDescriptor idPropDesc = MappingIntrospector.getInstance(entity.getJavaClass()).getIdPropertyDescriptor(); + PersistentProperty id = mappingFactory.createIdentity(entity, context, idPropDesc); - List properties = new LinkedList(); - for (PropertyDescriptor propertyDescriptor : fetcher.getPropertyDescriptors()) { - if (null == id || !propertyDescriptor.getName().equals(id.getName())) { - PersistentProperty p = createPersistentProperty(entity, context, propertyDescriptor, mapping); - if (null != p) { - properties.add(p); - for (Annotation anno : fetcher.getDeclaredField(p.getName()).getDeclaredAnnotations()) { - if (anno instanceof Indexed) { - Indexed idx = (Indexed) anno; - String idxColl = collection; - if (!"".equals(idx.collection())) { - idxColl = idx.collection(); + final List properties = new LinkedList(); + introspector.doWithProperties(new MappingIntrospector.PropertyHandler() { + public void doWithProperty(PropertyDescriptor descriptor, Field field, Expression spelExpr) { + PersistentProperty p = null; + try { + p = createPersistentProperty(entity, context, descriptor, mapping); + if (null != p) { + properties.add(p); + for (Annotation anno : field.getDeclaredAnnotations()) { + if (anno instanceof Indexed) { + Indexed idx = (Indexed) anno; + String idxColl = collection; + if (!"".equals(idx.collection())) { + idxColl = idx.collection(); + } + String name = p.getName(); + if (!"".equals(idx.name())) { + name = idx.name(); + } + ensureIndex(idxColl, name, null, idx.direction(), idx.unique(), idx.dropDups(), idx.sparse()); } - String name = p.getName(); - if (!"".equals(idx.name())) { - name = idx.name(); - } - ensureIndex(idxColl, name, null, idx.direction(), idx.unique(), idx.dropDups(), idx.sparse()); } } + } catch (MappingException e) { + throw new IllegalMappingException(e.getMessage(), e); } } - } + }); + descriptor = new PersistenceDescriptor(entity, id, properties); descriptors.put(javaClass, descriptor); } @@ -191,9 +210,9 @@ public class MongoMappingConfigurationStrategy implements MappingConfigurationSt protected PersistentProperty createPersistentProperty(PersistentEntity entity, MappingContext mappingContext, PropertyDescriptor descriptor, - ClassMapping mapping) { - ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(entity.getJavaClass()); - Field f = fetcher.getDeclaredField(descriptor.getName()); + ClassMapping mapping) throws MappingException { + MappingIntrospector introspector = MappingIntrospector.getInstance(entity.getJavaClass()); + Field f = introspector.getField(descriptor.getName()); if (null != f) { // Handle associations and persistent types @@ -211,10 +230,9 @@ public class MongoMappingConfigurationStrategy implements MappingConfigurationSt protected PersistentProperty extractChildType(PersistentEntity entity, MappingContext mappingContext, PropertyDescriptor descriptor, - ClassMapping mapping) { - - ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(entity.getJavaClass()); - Field f = fetcher.getDeclaredField(descriptor.getName()); + ClassMapping mapping) throws MappingException { + MappingIntrospector introspector = MappingIntrospector.getInstance(entity.getJavaClass()); + Field f = introspector.getField(descriptor.getName()); Class childClass = null; Association assoc = null; @@ -293,64 +311,6 @@ public class MongoMappingConfigurationStrategy implements MappingConfigurationSt } } - protected PersistentProperty extractIdProperty(PersistentEntity entity, - MappingContext mappingContext, - EvaluationContext elContext) { - ClassPropertyFetcher fetcher = ClassPropertyFetcher.forClass(entity.getJavaClass()); - - // Let field annotation override that on the class - IdentifiedBy idBy = extractIdentifiedBy(fetcher.getJavaClass(), null); - String id = extractId(fetcher.getJavaClass(), null); - Assert.notNull(id); - if (id.indexOf("#") > -1) { - id = expressionParser.parseExpression(id).getValue(elContext, String.class); - } - - PropertyDescriptor idPropDesc = null; - switch (idBy) { - case DEFAULT: - idPropDesc = findIdByAnnotation(fetcher); - if (null == idPropDesc) { - idPropDesc = fetcher.getPropertyDescriptor(id); - } - break; - case ANNOTATION: - idPropDesc = findIdByAnnotation(fetcher); - break; - case PROPERTY: - idPropDesc = fetcher.getPropertyDescriptor(id); - break; - case VALUE: - try { - idPropDesc = new ValuePropertyDescriptor("id", entity.getJavaClass(), id); - } catch (IntrospectionException e) { - throw new IllegalStateException(e.getMessage(), e); - } - break; - } - //Assert.notNull(idPropDesc, String.format("No ID property could be found on the entity %s", entity)); - - return (null != idPropDesc ? mappingFactory.createIdentity(entity, mappingContext, idPropDesc) : null); - } - - protected PropertyDescriptor findIdByAnnotation(ClassPropertyFetcher fetcher) { - for (PropertyDescriptor descriptor : fetcher.getPropertyDescriptors()) { - Field f = fetcher.getDeclaredField(descriptor.getName()); - if (null != f && null != f.getAnnotation(Id.class)) { - return descriptor; - } - } - return null; - } - - protected EvaluationContext createElContext() { - StandardEvaluationContext elContext = new StandardEvaluationContext(System.getProperties()); - for (String prop : System.getProperties().stringPropertyNames()) { - elContext.setVariable(prop, System.getProperty(prop)); - } - return elContext; - } - protected void ensureIndex(String collection, final String name, final String def, diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Reference.java similarity index 70% rename from spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Reference.java index 4c30070a1..776b53826 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Aliases.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/annotation/Reference.java @@ -16,11 +16,18 @@ package org.springframework.data.mapping.annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + /** * @author Jon Brisbin */ -public @interface Aliases { +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Reference { - String[] value(); + Class targetClass() default Object.class; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java index 4a51134df..7affe72f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AbstractPersistentEntity.java @@ -138,9 +138,9 @@ public abstract class AbstractPersistentEntity implements PersistentEntity try { return getJavaClass().newInstance(); } catch (InstantiationException e) { - throw new EntityInstantiationException("Unable to create entity of type [" + getJavaClass() + "]: " + e.getMessage(), e); + throw new MappingInstantiationException("Unable to create entity of type [" + getJavaClass() + "]: " + e.getMessage(), e); } catch (IllegalAccessException e) { - throw new EntityInstantiationException("Unable to create entity of type [" + getJavaClass() + "]: " + e.getMessage(), e); + throw new MappingInstantiationException("Unable to create entity of type [" + getJavaClass() + "]: " + e.getMessage(), e); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java index 035583cb1..e3094bc88 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/IllegalMappingException.java @@ -16,13 +16,17 @@ package org.springframework.data.mapping.model; /** * Thrown when an error occurs reading the mapping between object and datastore - * + * * @author Graeme Rocher * @since 1.0 */ public class IllegalMappingException extends RuntimeException { - public IllegalMappingException(String s) { - super(s); - } + public IllegalMappingException(String s, Throwable throwable) { + super(s, throwable); + } + + public IllegalMappingException(String s) { + super(s); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java index ca6ab7485..c193efadf 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingConfigurationStrategy.java @@ -1,5 +1,7 @@ package org.springframework.data.mapping.model; +import org.springframework.data.document.mongodb.mapping.MappingException; + import java.util.List; import java.util.Set; @@ -55,7 +57,7 @@ public interface MappingConfigurationStrategy { * @param classMapping The ClassMapping instance * @return The default identifier mapping */ - IdentityMapping getDefaultIdentityMapping(ClassMapping classMapping); + IdentityMapping getDefaultIdentityMapping(ClassMapping classMapping) throws MappingException; /** * Returns a set of entities that "own" the given entity. Ownership diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java similarity index 85% rename from spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java index c3b368a19..6b208cc7c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiationException.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java @@ -23,8 +23,8 @@ package org.springframework.data.mapping.model; * Time: 9:07 AM * To change this template use File | Settings | File Templates. */ -public class EntityInstantiationException extends RuntimeException { - public EntityInstantiationException(String s, Throwable throwable) { +public class MappingInstantiationException extends RuntimeException { + public MappingInstantiationException(String s, Throwable throwable) { super(s, throwable); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java index cd53ae0eb..a970b48c4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Account.java @@ -29,6 +29,14 @@ public class Account { private String id; private Float balance; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + public Float getBalance() { return balance; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java index e9a64ec5d..5b8ff662a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/MappingTests.java @@ -16,9 +16,6 @@ package org.springframework.data.document.mongodb.mapping; -import com.mongodb.DBObject; -import com.mongodb.util.JSON; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -27,20 +24,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.convert.MongoConverter; import org.springframework.data.document.mongodb.query.Criteria; -import org.springframework.data.document.mongodb.query.Index; -import org.springframework.data.document.mongodb.query.IndexDefinition; import org.springframework.data.document.mongodb.query.Query; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.endsWith; import static org.junit.Assert.assertThat; /** @@ -60,8 +52,9 @@ public class MappingTests { @Test public void setUp() { - template.dropCollection(template.getDefaultCollectionName()); - mappingContext.addPersistentEntity(Person.class); + template.dropCollection("person"); + template.dropCollection("account"); + //mappingContext.addPersistentEntity(Person.class); } @Test @@ -77,11 +70,13 @@ public class MappingTests { Account acct = new Account(); acct.setBalance(1000.00f); + template.insert("account", acct); + List accounts = new ArrayList(); accounts.add(acct); p.setAccounts(accounts); - template.insert(p); + template.insert("person", p); } @Test @@ -91,5 +86,6 @@ public class MappingTests { List result = template.find(new Query(Criteria.where("ssn").is(123456789)), Person.class); assertThat(result.size(), is(1)); assertThat(result.get(0).getAddress().getCountry(), is("USA")); + assertThat(result.get(0).getAccounts(), notNullValue()); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java index 74900f4f5..74052667f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/document/mongodb/mapping/Person.java @@ -16,15 +16,11 @@ package org.springframework.data.document.mongodb.mapping; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.document.mongodb.MongoTemplate; import org.springframework.data.document.mongodb.index.CompoundIndex; import org.springframework.data.document.mongodb.index.CompoundIndexes; import org.springframework.data.document.mongodb.index.Indexed; import org.springframework.data.mapping.annotation.*; -import java.math.BigInteger; import java.util.List; /** @@ -46,7 +42,7 @@ public class Person { private Integer age; @Transient private Integer accountTotal; - @OneToMany + @Reference private List accounts; private Address address; @@ -58,15 +54,22 @@ public class Person { } @PersistenceConstructor - public Person(Integer ssn, String firstName, String lastName, Integer age, List accounts, Address address) { + public Person(Integer ssn, String firstName, String lastName, Integer age, Address address) { this.ssn = ssn; this.firstName = firstName; this.lastName = lastName; this.age = age; - this.accounts = accounts; this.address = address; } + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + public Integer getSsn() { return ssn; }