Fixes, handling associations
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user