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