DATAMONGO-383 - Adapt new EntityInstantiator API of Spring Data Commons.

This commit is contained in:
Oliver Gierke
2012-01-23 11:36:55 +01:00
parent 609097c0dc
commit 4c40ef7e3d
4 changed files with 66 additions and 264 deletions

View File

@@ -19,7 +19,7 @@
<org.springframework.version.30>3.0.7.RELEASE</org.springframework.version.30> <org.springframework.version.30>3.0.7.RELEASE</org.springframework.version.30>
<org.springframework.version.40>4.0.0.RELEASE</org.springframework.version.40> <org.springframework.version.40>4.0.0.RELEASE</org.springframework.version.40>
<org.springframework.version.range>[${org.springframework.version.30}, ${org.springframework.version.40})</org.springframework.version.range> <org.springframework.version.range>[${org.springframework.version.30}, ${org.springframework.version.40})</org.springframework.version.range>
<data.commons.version>1.2.0.RELEASE</data.commons.version> <data.commons.version>1.3.0.BUILD-SNAPSHOT</data.commons.version>
<aspectj.version>1.6.11.RELEASE</aspectj.version> <aspectj.version>1.6.11.RELEASE</aspectj.version>
</properties> </properties>
<profiles> <profiles>

View File

@@ -23,6 +23,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.mongodb.core.convert.MongoConverters.BigIntegerToObjectIdConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.BigIntegerToObjectIdConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.ObjectIdToBigIntegerConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.ObjectIdToBigIntegerConverter;
import org.springframework.data.mongodb.core.convert.MongoConverters.ObjectIdToStringConverter; import org.springframework.data.mongodb.core.convert.MongoConverters.ObjectIdToStringConverter;
@@ -39,6 +40,7 @@ public abstract class AbstractMongoConverter implements MongoConverter, Initiali
protected final GenericConversionService conversionService; protected final GenericConversionService conversionService;
protected CustomConversions conversions = new CustomConversions(); protected CustomConversions conversions = new CustomConversions();
protected EntityInstantiators instantiators = new EntityInstantiators();
/** /**
* Creates a new {@link AbstractMongoConverter} using the given {@link GenericConversionService}. * Creates a new {@link AbstractMongoConverter} using the given {@link GenericConversionService}.
@@ -60,6 +62,15 @@ public abstract class AbstractMongoConverter implements MongoConverter, Initiali
this.conversions = conversions; this.conversions = conversions;
} }
/**
* Registers {@link EntityInstantiators} to customize entity instantiation.
*
* @param instantiators
*/
public void setInstantiators(EntityInstantiators instantiators) {
this.instantiators = instantiators == null ? new EntityInstantiators() : instantiators;
}
/** /**
* Registers additional converters that will be available when using the {@link ConversionService} directly (e.g. for * Registers additional converters that will be available when using the {@link ConversionService} directly (e.g. for
* id conversion). These converters are not custom conversions as they'd introduce unwanted conversions (e.g. * id conversion). These converters are not custom conversions as they'd introduce unwanted conversions (e.g.

View File

@@ -1,168 +0,0 @@
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.convert;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
* Abstraction for a {@link PreferredConstructor} alongside mapping information.
*
* @author Oliver Gierke
*/
class MappedConstructor {
private final Set<MappedConstructor.MappedParameter> parameters;
/**
* Creates a new {@link MappedConstructor} from the given {@link MongoPersistentEntity} and {@link MappingContext}.
*
* @param entity must not be {@literal null}.
* @param context must not be {@literal null}.
*/
public MappedConstructor(MongoPersistentEntity<?> entity,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
Assert.notNull(entity);
Assert.notNull(context);
this.parameters = new HashSet<MappedConstructor.MappedParameter>();
for (Parameter<?> parameter : entity.getPreferredConstructor().getParameters()) {
parameters.add(new MappedParameter(parameter, entity, context));
}
}
/**
* Returns whether the given {@link PersistentProperty} is referenced in a constructor argument of the
* {@link PersistentEntity} backing this {@link MappedConstructor}.
*
* @param property must not be {@literal null}.
* @return
*/
public boolean isConstructorParameter(PersistentProperty<?> property) {
Assert.notNull(property);
for (MappedConstructor.MappedParameter parameter : parameters) {
if (parameter.maps(property)) {
return true;
}
}
return false;
}
/**
* Returns the {@link MappedParameter} for the given {@link Parameter}.
*
* @param parameter must not be {@literal null}.
* @return
*/
public MappedParameter getFor(Parameter<?> parameter) {
for (MappedParameter mappedParameter : parameters) {
if (mappedParameter.parameter.equals(parameter)) {
return mappedParameter;
}
}
throw new IllegalStateException(String.format("Didn't find a MappedParameter for %s!", parameter.toString()));
}
/**
* Abstraction of a {@link Parameter} alongside mapping information.
*
* @author Oliver Gierke
*/
static class MappedParameter {
private final MongoPersistentProperty property;
private final Parameter<?> parameter;
/**
* Creates a new {@link MappedParameter} for the given {@link Parameter}, {@link MongoPersistentProperty} and
* {@link MappingContext}.
*
* @param parameter must not be {@literal null}.
* @param entity must not be {@literal null}.
* @param context must not be {@literal null}.
*/
public MappedParameter(Parameter<?> parameter, MongoPersistentEntity<?> entity,
MappingContext<? extends MongoPersistentEntity<?>, ? extends MongoPersistentProperty> context) {
Assert.notNull(parameter);
Assert.notNull(entity);
Assert.notNull(context);
this.parameter = parameter;
PropertyPath propertyPath = PropertyPath.from(parameter.getName(), entity.getType());
PersistentPropertyPath<? extends MongoPersistentProperty> path = context.getPersistentPropertyPath(propertyPath);
this.property = path == null ? null : path.getLeafProperty();
}
/**
* Returns whether there is a SpEL expression configured for this parameter.
*
* @return
*/
public boolean hasSpELExpression() {
return parameter.getKey() != null;
}
/**
* Returns the field name to be used to lookup the value which in turn shall be converted into the constructor
* parameter.
*
* @return
*/
public String getFieldName() {
return property.getFieldName();
}
/**
* Returns the type of the property backing the {@link Parameter}.
*
* @return
*/
public TypeInformation<?> getPropertyTypeInformation() {
return property.getTypeInformation();
}
/**
* Returns whether the given {@link PersistentProperty} is mapped by the parameter.
*
* @param property
* @return
*/
public boolean maps(PersistentProperty<?> property) {
return this.property.equals(property);
}
}
}

View File

@@ -15,7 +15,6 @@
*/ */
package org.springframework.data.mongodb.core.convert; package org.springframework.data.mongodb.core.convert;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -30,29 +29,30 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.CollectionFactory; import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.support.ConversionServiceFactory; import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.TypeMapper; import org.springframework.data.convert.TypeMapper;
import org.springframework.data.mapping.Association; import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
import org.springframework.data.mapping.model.MappingException; import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.data.mapping.model.ParameterValueProvider;
import org.springframework.data.mapping.model.SpELAwareParameterValueProvider; import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.QueryMapper; import org.springframework.data.mongodb.core.QueryMapper;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -82,6 +82,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
protected boolean useFieldAccessOnly = true; protected boolean useFieldAccessOnly = true;
protected MongoTypeMapper typeMapper; protected MongoTypeMapper typeMapper;
private SpELContext spELContext;
/** /**
* Creates a new {@link MappingMongoConverter} given the new {@link MongoDbFactory} and {@link MappingContext}. * Creates a new {@link MappingMongoConverter} given the new {@link MongoDbFactory} and {@link MappingContext}.
* *
@@ -101,6 +103,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext); this.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext);
this.idMapper = new QueryMapper(this); this.idMapper = new QueryMapper(this);
this.spELContext = new SpELContext(DBObjectPropertyAccessor.INSTANCE);
} }
/** /**
@@ -140,7 +144,9 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/ */
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
this.spELContext = new SpELContext(this.spELContext, applicationContext);
} }
/* /*
@@ -187,34 +193,39 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return read(persistentEntity, dbo); return read(persistentEntity, dbo);
} }
private ParameterValueProvider<MongoPersistentProperty> getParameterProvider(MongoPersistentEntity<?> entity,
DBObject source, DefaultSpELExpressionEvaluator evaluator) {
MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator);
PersistentEntityParameterValueProvider<MongoPersistentProperty> parameterProvider = new PersistentEntityParameterValueProvider<MongoPersistentProperty>(
entity, provider);
parameterProvider.setSpELEvaluator(evaluator);
return parameterProvider;
}
private <S extends Object> S read(final MongoPersistentEntity<S> entity, final DBObject dbo) { private <S extends Object> S read(final MongoPersistentEntity<S> entity, final DBObject dbo) {
final StandardEvaluationContext spelCtx = new StandardEvaluationContext(dbo); final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(dbo, spELContext);
spelCtx.addPropertyAccessor(DBObjectPropertyAccessor.INSTANCE);
if (applicationContext != null) { ParameterValueProvider<MongoPersistentProperty> provider = getParameterProvider(entity, dbo, evaluator);
spelCtx.setBeanResolver(new BeanFactoryResolver(applicationContext)); EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity);
} S instance = instantiator.createInstance(entity, provider);
final MappedConstructor constructor = new MappedConstructor(entity, mappingContext); final BeanWrapper<MongoPersistentEntity<S>, S> wrapper = BeanWrapper.create(instance, conversionService);
SpELAwareParameterValueProvider delegate = new SpELAwareParameterValueProvider(spelExpressionParser, spelCtx);
ParameterValueProvider provider = new DelegatingParameterValueProvider(constructor, dbo, delegate);
final BeanWrapper<MongoPersistentEntity<S>, S> wrapper = BeanWrapper.create(entity, provider, conversionService);
// Set properties not already set in the constructor // Set properties not already set in the constructor
entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() { entity.doWithProperties(new PropertyHandler<MongoPersistentProperty>() {
public void doWithPersistentProperty(MongoPersistentProperty prop) { public void doWithPersistentProperty(MongoPersistentProperty prop) {
boolean isConstructorProperty = constructor.isConstructorParameter(prop); boolean isConstructorProperty = entity.isConstructorArgument(prop);
boolean hasValueForProperty = dbo.containsField(prop.getFieldName()); boolean hasValueForProperty = dbo.containsField(prop.getFieldName());
if (!hasValueForProperty || isConstructorProperty) { if (!hasValueForProperty || isConstructorProperty) {
return; return;
} }
Object obj = getValueInternal(prop, dbo, spelCtx, prop.getSpelExpression()); Object obj = getValueInternal(prop, dbo, evaluator);
wrapper.setProperty(prop, obj, useFieldAccessOnly); wrapper.setProperty(prop, obj, useFieldAccessOnly);
} }
}); });
@@ -223,7 +234,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
entity.doWithAssociations(new AssociationHandler<MongoPersistentProperty>() { entity.doWithAssociations(new AssociationHandler<MongoPersistentProperty>() {
public void doWithAssociation(Association<MongoPersistentProperty> association) { public void doWithAssociation(Association<MongoPersistentProperty> association) {
MongoPersistentProperty inverseProp = association.getInverse(); MongoPersistentProperty inverseProp = association.getInverse();
Object obj = getValueInternal(inverseProp, dbo, spelCtx, inverseProp.getSpelExpression()); Object obj = getValueInternal(inverseProp, dbo, evaluator);
try { try {
wrapper.setProperty(inverseProp, obj); wrapper.setProperty(inverseProp, obj);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
@@ -621,58 +632,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return new DBRef(db, collection, idMapper.convertId(id)); return new DBRef(db, collection, idMapper.convertId(id));
} }
@SuppressWarnings("unchecked") protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator eval) {
protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx,
String spelExpr) {
Object o; MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(dbo, spELContext);
if (null != spelExpr) { return provider.getPropertyValue(prop);
Expression x = spelExpressionParser.parseExpression(spelExpr);
o = x.getValue(ctx);
} else {
Object sourceValue = dbo.get(prop.getFieldName());
if (sourceValue == null) {
return null;
}
Class<?> propertyType = prop.getType();
if (conversions.hasCustomReadTarget(sourceValue.getClass(), propertyType)) {
return conversionService.convert(sourceValue, propertyType);
}
if (sourceValue instanceof DBRef) {
sourceValue = ((DBRef) sourceValue).fetch();
}
if (sourceValue instanceof DBObject) {
if (prop.isMap()) {
return readMap(prop.getTypeInformation(), (DBObject) sourceValue);
} else if (prop.isArray() && sourceValue instanceof BasicDBObject
&& ((DBObject) sourceValue).keySet().size() == 0) {
// It's empty
return Array.newInstance(prop.getComponentType(), 0);
} else if (prop.isCollectionLike() && sourceValue instanceof BasicDBList) {
return readCollectionOrArray((TypeInformation<? extends Collection<?>>) prop.getTypeInformation(),
(BasicDBList) sourceValue);
}
TypeInformation<?> toType = typeMapper.readType((DBObject) sourceValue);
// It's a complex object, have to read it in
if (toType != null) {
// TODO: why do we remove the type?
// dbo.removeField(CUSTOM_TYPE_KEY);
o = read(toType, (DBObject) sourceValue);
} else {
o = read(mappingContext.getPersistentEntity(prop.getTypeInformation()), (DBObject) sourceValue);
}
} else {
o = sourceValue;
}
}
return o;
} }
/** /**
@@ -858,46 +821,42 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
return dbObject; return dbObject;
} }
private class DelegatingParameterValueProvider implements ParameterValueProvider { private class MongoDbPropertyValueProvider implements PropertyValueProvider<MongoPersistentProperty> {
private final DBObject source; private final DBObject source;
private final ParameterValueProvider delegate; private final SpELExpressionEvaluator evaluator;
private final MappedConstructor constructor;
/** public MongoDbPropertyValueProvider(DBObject source, SpELContext factory) {
* {@link ParameterValueProvider} to delegate source object lookup to a {@link SpELAwareParameterValueProvider} in this(source, new DefaultSpELExpressionEvaluator(source, factory));
* case a MappCon }
*
* @param constructor must not be {@literal null}. public MongoDbPropertyValueProvider(DBObject source, DefaultSpELExpressionEvaluator evaluator) {
* @param source must not be {@literal null}.
* @param delegate must not be {@literal null}.
*/
public DelegatingParameterValueProvider(MappedConstructor constructor, DBObject source,
SpELAwareParameterValueProvider delegate) {
Assert.notNull(constructor);
Assert.notNull(source); Assert.notNull(source);
Assert.notNull(delegate); Assert.notNull(evaluator);
this.constructor = constructor;
this.source = source; this.source = source;
this.delegate = delegate; this.evaluator = evaluator;
} }
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) * @see org.springframework.data.convert.PropertyValueProvider#getPropertyValue(org.springframework.data.mapping.PersistentProperty)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T getParameterValue(Parameter<T> parameter) { public <T> T getPropertyValue(MongoPersistentProperty property) {
MappedConstructor.MappedParameter mappedParameter = constructor.getFor(parameter); String expression = property.getSpelExpression();
Object value = mappedParameter.hasSpELExpression() ? delegate.getParameterValue(parameter) : source TypeInformation<?> type = property.getTypeInformation();
.get(mappedParameter.getFieldName()); Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName());
TypeInformation<?> type = mappedParameter.getPropertyTypeInformation(); if (value == null) {
return null;
}
if (value instanceof DBRef) { if (conversions.hasCustomReadTarget(value.getClass(), type.getType())) {
return (T) conversionService.convert(value, type.getType());
} else if (value instanceof DBRef) {
return (T) read(type, ((DBRef) value).fetch()); return (T) read(type, ((DBRef) value).fetch());
} else if (value instanceof BasicDBList) { } else if (value instanceof BasicDBList) {
return (T) getPotentiallyConvertedSimpleRead(readCollectionOrArray(type, (BasicDBList) value), type.getType()); return (T) getPotentiallyConvertedSimpleRead(readCollectionOrArray(type, (BasicDBList) value), type.getType());