Fixes, handling associations

This commit is contained in:
J. Brisbin
2011-03-08 14:19:20 -06:00
parent 40ef4d7ad9
commit ff8b0a166e
12 changed files with 370 additions and 183 deletions

View File

@@ -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 <jbrisbin@vmware.com>
*/
@@ -103,6 +108,10 @@ public class MappingMongoConverter implements MongoConverter, ApplicationContext
}
public <S extends Object> S read(Class<S> 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<S>();
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> T getParameterValue(String name, Class<T> 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<Object> 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.
*

View File

@@ -19,17 +19,27 @@ package org.springframework.data.document.mongodb.mapping;
/**
* @author Jon Brisbin <jbrisbin@vmware.com>
*/
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;
}
}

View File

@@ -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<T> {
private final Map<String, Method> setters;
private final Map<String, Method> getters;
private Field idField = null;
private PropertyDescriptor idPropertyDescriptor = null;
private List<String> ignoredProperties = new ArrayList<String>() {{
add("class");
}};
private List<Class<?>> validIdTypes = new ArrayList<Class<?>>() {{
add(ObjectId.class);
add(String.class);
add(BigInteger.class);
}};
private SpelExpressionParser parser = new SpelExpressionParser();
private Map<String, Expression> expressions = new HashMap<String, Expression>();
private PreferredConstructor preferredConstructor = null;
@@ -104,7 +112,7 @@ public class MappingIntrospector<T> {
try {
this.beanInfo = Introspector.getBeanInfo(clazz);
} catch (IntrospectionException e) {
throw new MappingException(e, clazz);
throw new MappingException(e.getMessage(), e, clazz);
}
Map<String, PropertyDescriptor> properties = new HashMap<String, PropertyDescriptor>();
Map<String, Field> fields = new HashMap<String, Field>();
@@ -122,24 +130,43 @@ public class MappingIntrospector<T> {
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<T> {
}
}
}
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<T> {
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<T> {
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<T> {
paramClassTypes[i] = targetType;
}
preferredConstructor = new PreferredConstructor((Constructor<T>) constructor, paramNames, paramClassTypes);
if (constructor.isAnnotationPresent(PersistenceConstructor.class)) {
// We're done
break;
}
}
}
}
@@ -224,11 +275,15 @@ public class MappingIntrospector<T> {
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<T> {
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<T> {
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<T> {
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<T> {
return field;
}
public Class<?> getTargetClass() {
return targetClass;
}
}
private class PreferredConstructor {

View File

@@ -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<PersistentProperty> 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<PersistentProperty> properties = new LinkedList<PersistentProperty>();
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<PersistentProperty> properties = new LinkedList<PersistentProperty>();
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,

View File

@@ -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 <jbrisbin@vmware.com>
*/
public @interface Aliases {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Reference {
String[] value();
Class<?> targetClass() default Object.class;
}

View File

@@ -138,9 +138,9 @@ public abstract class AbstractPersistentEntity<T> implements PersistentEntity<T>
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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Account> accounts = new ArrayList<Account>();
accounts.add(acct);
p.setAccounts(accounts);
template.insert(p);
template.insert("person", p);
}
@Test
@@ -91,5 +86,6 @@ public class MappingTests {
List<Person> 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());
}
}

View File

@@ -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<Account> accounts;
private Address address;
@@ -58,15 +54,22 @@ public class Person {
}
@PersistenceConstructor
public Person(Integer ssn, String firstName, String lastName, Integer age, List<Account> 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;
}