From 6f06ccec8e2e1a835311295acc7199956e9f69cf Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 30 Jul 2014 08:26:14 +0200 Subject: [PATCH] DATAMONGO-1012 - Identifier initialization for lazy DBRef proxies with field access. We now initialize the ID property for proxies created for lazily initialized DBRefs. This will allow the lookup of ID properties for types that use field access without initializing the entire proxy. --- .../core/convert/DbRefProxyHandler.java | 28 +++++++ .../mongodb/core/convert/DbRefResolver.java | 3 +- .../convert/DefaultDbRefProxyHandler.java | 79 +++++++++++++++++++ .../core/convert/DefaultDbRefResolver.java | 12 +-- .../convert/DefaultDbRefResolverCallback.java | 61 ++++++++++++++ .../core/convert/MappingMongoConverter.java | 50 ++++++------ .../mongodb/core/convert/ValueResolver.java | 42 ++++++++++ .../DbRefMappingMongoConverterUnitTests.java | 21 +++++ 8 files changed, 267 insertions(+), 29 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefProxyHandler.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefProxyHandler.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverCallback.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ValueResolver.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefProxyHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefProxyHandler.java new file mode 100644 index 000000000..a5612d8ab --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefProxyHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 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 org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +import com.mongodb.DBRef; + +/** + * @author Oliver Gierke + */ +public interface DbRefProxyHandler { + + Object populateId(MongoPersistentProperty property, DBRef source, Object proxy); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefResolver.java index 18ae6fa58..47910299b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DbRefResolver.java @@ -39,7 +39,8 @@ public interface DbRefResolver { * @param callback will never be {@literal null}. * @return */ - Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback); + Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback, + DbRefProxyHandler proxyHandler); /** * Creates a {@link DBRef} instance for the given {@link org.springframework.data.mongodb.core.mapping.DBRef} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefProxyHandler.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefProxyHandler.java new file mode 100644 index 000000000..d3f753160 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefProxyHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright 2014 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 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.SpELContext; +import org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.mongodb.DBRef; + +/** + * @author Oliver Gierke + */ +class DefaultDbRefProxyHandler implements DbRefProxyHandler { + + private final SpELContext spELContext; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final ValueResolver resolver; + + /** + * @param spELContext must not be {@literal null}. + * @param conversionService must not be {@literal null}. + * @param mappingContext must not be {@literal null}. + */ + public DefaultDbRefProxyHandler(SpELContext spELContext, + MappingContext, MongoPersistentProperty> mappingContext, ValueResolver resolver) { + + this.spELContext = spELContext; + this.mappingContext = mappingContext; + this.resolver = resolver; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.DbRefProxyHandler#populateId(com.mongodb.DBRef, java.lang.Object) + */ + @Override + public Object populateId(MongoPersistentProperty property, DBRef source, Object proxy) { + + if (source == null) { + return proxy; + } + + MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(property); + MongoPersistentProperty idProperty = persistentEntity.getIdProperty(); + + if (idProperty.usePropertyAccess()) { + return proxy; + } + + SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(proxy, spELContext); + BeanWrapper proxyWrapper = BeanWrapper.create(proxy, null); + + DBObject object = new BasicDBObject(idProperty.getFieldName(), source.getId()); + ObjectPath objectPath = ObjectPath.ROOT.push(proxy, persistentEntity, null); + proxyWrapper.setProperty(idProperty, resolver.getValueInternal(idProperty, object, evaluator, objectPath)); + + return proxy; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java index 50a13c998..37c6fbbac 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java @@ -77,13 +77,14 @@ public class DefaultDbRefResolver implements DbRefResolver { * @see org.springframework.data.mongodb.core.convert.DbRefResolver#resolveDbRef(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, org.springframework.data.mongodb.core.convert.DbRefResolverCallback) */ @Override - public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback) { + public Object resolveDbRef(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback, + DbRefProxyHandler handler) { Assert.notNull(property, "Property must not be null!"); Assert.notNull(callback, "Callback must not be null!"); if (isLazyDbRef(property)) { - return createLazyLoadingProxy(property, dbref, callback); + return createLazyLoadingProxy(property, dbref, callback, handler); } return callback.resolve(property); @@ -112,7 +113,8 @@ public class DefaultDbRefResolver implements DbRefResolver { * @param callback must not be {@literal null}. * @return */ - private Object createLazyLoadingProxy(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback) { + private Object createLazyLoadingProxy(MongoPersistentProperty property, DBRef dbref, DbRefResolverCallback callback, + DbRefProxyHandler handler) { Class propertyType = property.getType(); LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback); @@ -122,7 +124,7 @@ public class DefaultDbRefResolver implements DbRefResolver { Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType)); factory.setCallbacks(new Callback[] { interceptor }); - return factory; + return handler.populateId(property, dbref, factory); } ProxyFactory proxyFactory = new ProxyFactory(); @@ -135,7 +137,7 @@ public class DefaultDbRefResolver implements DbRefResolver { proxyFactory.addInterface(propertyType); proxyFactory.addAdvice(interceptor); - return proxyFactory.getProxy(); + return handler.populateId(property, dbref, proxyFactory.getProxy()); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverCallback.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverCallback.java new file mode 100644 index 000000000..d4785f190 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverCallback.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 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 org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +import com.mongodb.DBObject; + +/** + * Default implementation of {@link DbRefResolverCallback}. + * + * @author Oliver Gierke + */ +class DefaultDbRefResolverCallback implements DbRefResolverCallback { + + private final DBObject surroundingObject; + private final ObjectPath path; + private final ValueResolver resolver; + private final SpELExpressionEvaluator evaluator; + + /** + * Creates a new {@link DefaultDbRefResolverCallback} using the given {@link DBObject}, {@link ObjectPath}, + * {@link ValueResolver} and {@link SpELExpressionEvaluator}. + * + * @param surroundingObject must not be {@literal null}. + * @param path must not be {@literal null}. + * @param evaluator must not be {@literal null}. + * @param resolver must not be {@literal null}. + */ + public DefaultDbRefResolverCallback(DBObject surroundingObject, ObjectPath path, SpELExpressionEvaluator evaluator, + ValueResolver resolver) { + + this.surroundingObject = surroundingObject; + this.path = path; + this.resolver = resolver; + this.evaluator = evaluator; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.DbRefResolverCallback#resolve(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty) + */ + @Override + public Object resolve(MongoPersistentProperty property) { + return resolver.getValueInternal(property, surroundingObject, evaluator, path); + } +} 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 a2e91ef11..9ef211408 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 @@ -31,7 +31,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.ConversionServiceFactory; +import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.data.convert.CollectionFactory; import org.springframework.data.convert.EntityInstantiator; import org.springframework.data.convert.TypeMapper; @@ -74,7 +74,7 @@ import com.mongodb.DBRef; * @author Thomas Darimont * @author Christoph Strobl */ -public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware { +public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, ValueResolver { protected static final Logger LOGGER = LoggerFactory.getLogger(MappingMongoConverter.class); @@ -82,6 +82,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App protected final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); protected final QueryMapper idMapper; protected final DbRefResolver dbRefResolver; + protected ApplicationContext applicationContext; protected MongoTypeMapper typeMapper; protected String mapKeyDotReplacement = null; @@ -94,11 +95,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * @param mongoDbFactory must not be {@literal null}. * @param mappingContext must not be {@literal null}. */ - @SuppressWarnings("deprecation") public MappingMongoConverter(DbRefResolver dbRefResolver, MappingContext, MongoPersistentProperty> mappingContext) { - super(ConversionServiceFactory.createDefaultConversionService()); + super(new DefaultConversionService()); Assert.notNull(dbRefResolver, "DbRefResolver must not be null!"); Assert.notNull(mappingContext, "MappingContext must not be null!"); @@ -277,7 +277,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App entity.doWithAssociations(new AssociationHandler() { public void doWithAssociation(Association association) { - MongoPersistentProperty property = association.getInverse(); + final MongoPersistentProperty property = association.getInverse(); Object value = dbo.get(property.getName()); if (value == null) { @@ -285,13 +285,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App } DBRef dbref = value instanceof DBRef ? (DBRef) value : null; - wrapper.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, new DbRefResolverCallback() { - @Override - public Object resolve(MongoPersistentProperty property) { - return getValueInternal(property, dbo, evaluator, currentPath); - } - })); + DbRefProxyHandler handler = new DefaultDbRefProxyHandler(spELContext, mappingContext, + MappingMongoConverter.this); + DbRefResolverCallback callback = new DefaultDbRefResolverCallback(dbo, currentPath, evaluator, + MappingMongoConverter.this); + + wrapper.setProperty(property, dbRefResolver.resolveDbRef(property, dbref, callback, handler)); } }); @@ -813,9 +813,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App idMapper.convertId(id)); } - protected Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator, + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.ValueResolver#getValueInternal(org.springframework.data.mongodb.core.mapping.MongoPersistentProperty, com.mongodb.DBObject, org.springframework.data.mapping.model.SpELExpressionEvaluator, java.lang.Object) + */ + @Override + public Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator, ObjectPath path) { - return new MongoDbPropertyValueProvider(dbo, evaluator, path).getPropertyValue(prop); } @@ -1036,11 +1040,11 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App /** * Creates a new {@link MongoDbPropertyValueProvider} for the given source, {@link SpELExpressionEvaluator} and - * parent object. + * {@link ObjectPath}. * * @param source must not be {@literal null}. * @param evaluator must not be {@literal null}. - * @param parent can be {@literal null}. + * @param path can be {@literal null}. */ public MongoDbPropertyValueProvider(DBObject source, SpELExpressionEvaluator evaluator, ObjectPath path) { @@ -1078,7 +1082,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App private class ConverterAwareSpELExpressionParameterValueProvider extends SpELExpressionParameterValueProvider { - private final ObjectPath parent; + private final ObjectPath path; /** * Creates a new {@link ConverterAwareSpELExpressionParameterValueProvider}. @@ -1088,10 +1092,10 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * @param delegate must not be {@literal null}. */ public ConverterAwareSpELExpressionParameterValueProvider(SpELExpressionEvaluator evaluator, - ConversionService conversionService, ParameterValueProvider delegate, ObjectPath parent) { + ConversionService conversionService, ParameterValueProvider delegate, ObjectPath path) { super(evaluator, conversionService, delegate); - this.parent = parent; + this.path = path; } /* @@ -1100,23 +1104,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App */ @Override protected T potentiallyConvertSpelValue(Object object, Parameter parameter) { - return readValue(object, parameter.getType(), parent); + return readValue(object, parameter.getType(), path); } } @SuppressWarnings("unchecked") - private T readValue(Object value, TypeInformation type, ObjectPath parent) { + private T readValue(Object value, TypeInformation type, ObjectPath path) { Class rawType = type.getType(); if (conversions.hasCustomReadTarget(value.getClass(), rawType)) { return (T) conversionService.convert(value, rawType); } else if (value instanceof DBRef) { - return potentiallyReadOrResolveDbRef((DBRef) value, type, parent, rawType); + return potentiallyReadOrResolveDbRef((DBRef) value, type, path, rawType); } else if (value instanceof BasicDBList) { - return (T) readCollectionOrArray(type, (BasicDBList) value, parent); + return (T) readCollectionOrArray(type, (BasicDBList) value, path); } else if (value instanceof DBObject) { - return (T) read(type, (DBObject) value, parent); + return (T) read(type, (DBObject) value, path); } else { return (T) getPotentiallyConvertedSimpleRead(value, rawType); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ValueResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ValueResolver.java new file mode 100644 index 000000000..04b7f667e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ValueResolver.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 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 org.springframework.data.mapping.model.SpELExpressionEvaluator; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; + +import com.mongodb.DBObject; + +/** + * Internal API to trigger the resolution of properties. + * + * @author Oliver Gierke + */ +interface ValueResolver { + + /** + * Resolves the value for the given {@link MongoPersistentProperty} within the given {@link DBObject} using the given + * {@link SpELExpressionEvaluator} and {@link ObjectPath}. + * + * @param prop + * @param dbo + * @param evaluator + * @param parent + * @return + */ + Object getValueInternal(MongoPersistentProperty prop, DBObject dbo, SpELExpressionEvaluator evaluator, + ObjectPath parent); +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java index 01bcc1d17..b865c9688 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java @@ -30,6 +30,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.bson.types.ObjectId; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,10 +39,12 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.mapping.model.BeanWrapper; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoExceptionTranslator; import org.springframework.data.mongodb.core.convert.MappingMongoConverterUnitTests.Person; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.util.SerializationUtils; @@ -491,6 +494,24 @@ public class DbRefMappingMongoConverterUnitTests { assertThat(found.nested.reference, is(found)); } + @Test + public void testname() { + + MongoPersistentEntity entity = mappingContext.getPersistentEntity(ClassWithLazyDbRefs.class); + MongoPersistentProperty property = entity.getPersistentProperty("dbRefToConcreteType"); + + Object dbRef = converter.toDBRef(new LazyDbRefTarget(new ObjectId().toString()), property); + + DBObject object = new BasicDBObject("dbRefToConcreteType", dbRef); + + ClassWithLazyDbRefs result = converter.read(ClassWithLazyDbRefs.class, object); + + BeanWrapper wrapper = BeanWrapper.create(result.dbRefToConcreteType, null); + MongoPersistentProperty idProperty = mappingContext.getPersistentEntity(LazyDbRefTarget.class).getIdProperty(); + + assertThat(wrapper.getProperty(idProperty), is(notNullValue())); + } + private Object transport(Object result) { return SerializationUtils.deserialize(SerializationUtils.serialize(result)); }