diff --git a/spring-data-mongodb-parent/pom.xml b/spring-data-mongodb-parent/pom.xml index d32c57501..68400bb5a 100644 --- a/spring-data-mongodb-parent/pom.xml +++ b/spring-data-mongodb-parent/pom.xml @@ -19,7 +19,7 @@ 3.0.7.RELEASE 4.0.0.RELEASE [${org.springframework.version.30}, ${org.springframework.version.40}) - 1.2.0.RELEASE + 1.3.0.BUILD-SNAPSHOT 1.6.11.RELEASE diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/AbstractMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/AbstractMongoConverter.java index a93d30f0a..43677a238 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/AbstractMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/AbstractMongoConverter.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.ConversionServiceFactory; 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.ObjectIdToBigIntegerConverter; 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 CustomConversions conversions = new CustomConversions(); + protected EntityInstantiators instantiators = new EntityInstantiators(); /** * Creates a new {@link AbstractMongoConverter} using the given {@link GenericConversionService}. @@ -60,6 +62,15 @@ public abstract class AbstractMongoConverter implements MongoConverter, Initiali 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 * id conversion). These converters are not custom conversions as they'd introduce unwanted conversions (e.g. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java deleted file mode 100644 index bc1f01152..000000000 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappedConstructor.java +++ /dev/null @@ -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 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, MongoPersistentProperty> context) { - - Assert.notNull(entity); - Assert.notNull(context); - - this.parameters = new HashSet(); - - 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 MongoPersistentProperty> context) { - - Assert.notNull(parameter); - Assert.notNull(entity); - Assert.notNull(context); - - this.parameter = parameter; - - PropertyPath propertyPath = PropertyPath.from(parameter.getName(), entity.getType()); - PersistentPropertyPath 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); - } - } -} \ No newline at end of file diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 1df55a411..3e118cf0e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -15,7 +15,6 @@ */ package org.springframework.data.mongodb.core.convert; -import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; @@ -30,29 +29,30 @@ import org.apache.commons.logging.LogFactory; 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.ConversionException; import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.TypeMapper; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.MappingContext; 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.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.core.QueryMapper; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; -import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -82,6 +82,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App protected boolean useFieldAccessOnly = true; protected MongoTypeMapper typeMapper; + private SpELContext spELContext; + /** * 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.typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext); 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) */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + 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); } + private ParameterValueProvider getParameterProvider(MongoPersistentEntity entity, + DBObject source, DefaultSpELExpressionEvaluator evaluator) { + + MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(source, evaluator); + PersistentEntityParameterValueProvider parameterProvider = new PersistentEntityParameterValueProvider( + entity, provider); + parameterProvider.setSpELEvaluator(evaluator); + + return parameterProvider; + } + private S read(final MongoPersistentEntity entity, final DBObject dbo) { - final StandardEvaluationContext spelCtx = new StandardEvaluationContext(dbo); - spelCtx.addPropertyAccessor(DBObjectPropertyAccessor.INSTANCE); + final DefaultSpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(dbo, spELContext); - if (applicationContext != null) { - spelCtx.setBeanResolver(new BeanFactoryResolver(applicationContext)); - } + ParameterValueProvider provider = getParameterProvider(entity, dbo, evaluator); + EntityInstantiator instantiator = instantiators.getInstantiatorFor(entity); + S instance = instantiator.createInstance(entity, provider); - final MappedConstructor constructor = new MappedConstructor(entity, mappingContext); - - SpELAwareParameterValueProvider delegate = new SpELAwareParameterValueProvider(spelExpressionParser, spelCtx); - ParameterValueProvider provider = new DelegatingParameterValueProvider(constructor, dbo, delegate); - - final BeanWrapper, S> wrapper = BeanWrapper.create(entity, provider, conversionService); + final BeanWrapper, S> wrapper = BeanWrapper.create(instance, conversionService); // Set properties not already set in the constructor entity.doWithProperties(new PropertyHandler() { public void doWithPersistentProperty(MongoPersistentProperty prop) { - boolean isConstructorProperty = constructor.isConstructorParameter(prop); + boolean isConstructorProperty = entity.isConstructorArgument(prop); boolean hasValueForProperty = dbo.containsField(prop.getFieldName()); if (!hasValueForProperty || isConstructorProperty) { return; } - Object obj = getValueInternal(prop, dbo, spelCtx, prop.getSpelExpression()); + Object obj = getValueInternal(prop, dbo, evaluator); wrapper.setProperty(prop, obj, useFieldAccessOnly); } }); @@ -223,7 +234,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App entity.doWithAssociations(new AssociationHandler() { public void doWithAssociation(Association association) { MongoPersistentProperty inverseProp = association.getInverse(); - Object obj = getValueInternal(inverseProp, dbo, spelCtx, inverseProp.getSpelExpression()); + Object obj = getValueInternal(inverseProp, dbo, evaluator); try { wrapper.setProperty(inverseProp, obj); } catch (IllegalAccessException e) { @@ -621,58 +632,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return new DBRef(db, collection, idMapper.convertId(id)); } - @SuppressWarnings("unchecked") - protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, StandardEvaluationContext ctx, - String spelExpr) { + protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator eval) { - Object o; - if (null != spelExpr) { - 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>) 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; + MongoDbPropertyValueProvider provider = new MongoDbPropertyValueProvider(dbo, spELContext); + return provider.getPropertyValue(prop); } /** @@ -858,46 +821,42 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return dbObject; } - private class DelegatingParameterValueProvider implements ParameterValueProvider { + private class MongoDbPropertyValueProvider implements PropertyValueProvider { private final DBObject source; - private final ParameterValueProvider delegate; - private final MappedConstructor constructor; + private final SpELExpressionEvaluator evaluator; - /** - * {@link ParameterValueProvider} to delegate source object lookup to a {@link SpELAwareParameterValueProvider} in - * case a MappCon - * - * @param constructor must not be {@literal null}. - * @param source must not be {@literal null}. - * @param delegate must not be {@literal null}. - */ - public DelegatingParameterValueProvider(MappedConstructor constructor, DBObject source, - SpELAwareParameterValueProvider delegate) { + public MongoDbPropertyValueProvider(DBObject source, SpELContext factory) { + this(source, new DefaultSpELExpressionEvaluator(source, factory)); + } + + public MongoDbPropertyValueProvider(DBObject source, DefaultSpELExpressionEvaluator evaluator) { - Assert.notNull(constructor); Assert.notNull(source); - Assert.notNull(delegate); + Assert.notNull(evaluator); - this.constructor = constructor; this.source = source; - this.delegate = delegate; + this.evaluator = evaluator; } /* * (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") - public T getParameterValue(Parameter parameter) { + public T getPropertyValue(MongoPersistentProperty property) { - MappedConstructor.MappedParameter mappedParameter = constructor.getFor(parameter); - Object value = mappedParameter.hasSpELExpression() ? delegate.getParameterValue(parameter) : source - .get(mappedParameter.getFieldName()); + String expression = property.getSpelExpression(); + TypeInformation type = property.getTypeInformation(); + 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()); } else if (value instanceof BasicDBList) { return (T) getPotentiallyConvertedSimpleRead(readCollectionOrArray(type, (BasicDBList) value), type.getType());