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 5277fbc0b..f64c7f0f0 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 @@ -47,6 +47,7 @@ import org.springframework.data.mongodb.LazyLoadingException; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoDatabaseUtils; import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery; +import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.lang.Nullable; import org.springframework.objenesis.ObjenesisStd; @@ -83,7 +84,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db */ public DefaultDbRefResolver(MongoDatabaseFactory mongoDbFactory) { - super(new DefaultReferenceLoader(mongoDbFactory)); + super(new MongoDatabaseFactoryReferenceLoader(mongoDbFactory)); Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!"); @@ -117,7 +118,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db */ @Override public Document fetch(DBRef dbRef) { - return getReferenceLoader().fetch(DocumentReferenceQuery.singleReferenceFilter(Filters.eq("_id", dbRef.getId())), + return getReferenceLoader().fetchOne(DocumentReferenceQuery.forSingleDocument(Filters.eq("_id", dbRef.getId())), ReferenceCollection.fromDBRef(dbRef)); } @@ -159,7 +160,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db } List result = mongoCollection // - .find(new Document("_id", new Document("$in", ids))) // + .find(new Document(BasicMongoPersistentProperty.ID_FIELD_NAME, new Document("$in", ids))) // .into(new ArrayList<>()); return ids.stream() // @@ -239,7 +240,7 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db private static Stream documentWithId(Object identifier, Collection documents) { return documents.stream() // - .filter(it -> it.get("_id").equals(identifier)) // + .filter(it -> it.get(BasicMongoPersistentProperty.ID_FIELD_NAME).equals(identifier)) // .limit(1); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java index 0692f719b..7e38b6995 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceResolver.java @@ -15,7 +15,10 @@ */ package org.springframework.data.mongodb.core.convert; -import org.springframework.data.mongodb.core.mapping.DocumentReference; +import static org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate.*; + +import java.util.Collections; + import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.lang.Nullable; @@ -37,21 +40,32 @@ public class DefaultReferenceResolver implements ReferenceResolver { @Nullable @Override - public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader, - LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) { + public Object resolveReference(MongoPersistentProperty property, Object source, + ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) { + + LookupFunction lookupFunction = (filter, ctx) -> { + if (property.isCollectionLike() || property.isMap()) { + return getReferenceLoader().fetchMany(filter, ctx); + + } + + Object target = getReferenceLoader().fetchOne(filter, ctx); + return target == null ? Collections.emptyList() + : Collections.singleton(getReferenceLoader().fetchOne(filter, ctx)); + }; if (isLazyReference(property)) { - return createLazyLoadingProxy(property, source, referenceReader, lookupFunction, resultConversionFunction); + return createLazyLoadingProxy(property, source, referenceLookupDelegate, lookupFunction, entityReader); } - return referenceReader.readReference(property, source, lookupFunction, resultConversionFunction); + return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader); } private Object createLazyLoadingProxy(MongoPersistentProperty property, Object source, - ReferenceReader referenceReader, LookupFunction lookupFunction, - ResultConversionFunction resultConversionFunction) { - return new LazyLoadingProxyGenerator(referenceReader).createLazyLoadingProxy(property, source, lookupFunction, - resultConversionFunction); + ReferenceLookupDelegate referenceLookupDelegate, LookupFunction lookupFunction, + MongoEntityReader entityReader) { + return new LazyLoadingProxyFactory(referenceLookupDelegate).createLazyLoadingProxy(property, source, lookupFunction, + entityReader); } protected boolean isLazyReference(MongoPersistentProperty property) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java index a91a48d92..8e9554b6b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DocumentPointerFactory.java @@ -17,12 +17,14 @@ package org.springframework.data.mongodb.core.convert; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bson.Document; + import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.context.MappingContext; @@ -37,9 +39,9 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; */ class DocumentPointerFactory { - private ConversionService conversionService; - private MappingContext, MongoPersistentProperty> mappingContext; - private Map linkageMap; + private final ConversionService conversionService; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final Map linkageMap; public DocumentPointerFactory(ConversionService conversionService, MappingContext, MongoPersistentProperty> mappingContext) { @@ -60,15 +62,24 @@ class DocumentPointerFactory { } else { MongoPersistentEntity persistentEntity = mappingContext - .getPersistentEntity(property.getAssociationTargetType()); + .getRequiredPersistentEntity(property.getAssociationTargetType()); - if (!property.getDocumentReference().lookup().toLowerCase().replaceAll("\\s", "").replaceAll("'", "") + // TODO: Extract method + if (!property.getDocumentReference().lookup().toLowerCase(Locale.ROOT).replaceAll("\\s", "").replaceAll("'", "") .equals("{_id:?#{#target}}")) { - return () -> linkageMap.computeIfAbsent(property.getDocumentReference().lookup(), key -> { - return new LinkageDocument(key); - }).get(persistentEntity, - BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(property.getOwner(), value)); + MongoPersistentEntity valueEntity = mappingContext.getPersistentEntity(value.getClass()); + PersistentPropertyAccessor propertyAccessor; + if (valueEntity == null) { + propertyAccessor = BeanWrapperPropertyAccessorFactory.INSTANCE.getPropertyAccessor(property.getOwner(), + value); + } else { + propertyAccessor = valueEntity.getPropertyAccessor(value); + + } + + return () -> linkageMap.computeIfAbsent(property.getDocumentReference().lookup(), LinkageDocument::new) + .get(persistentEntity, propertyAccessor); } // just take the id as a reference @@ -78,6 +89,8 @@ class DocumentPointerFactory { static class LinkageDocument { + static final Pattern pattern = Pattern.compile("\\?#\\{#?[\\w\\d]*\\}"); + String lookup; org.bson.Document fetchDocument; Map mapMap; @@ -87,16 +100,18 @@ class DocumentPointerFactory { this.lookup = lookup; String targetLookup = lookup; - Pattern pattern = Pattern.compile("\\?#\\{#?[\\w\\d]*\\}"); Matcher matcher = pattern.matcher(lookup); int index = 0; mapMap = new LinkedHashMap<>(); + + // TODO: Make explicit what's happening here while (matcher.find()) { String expr = matcher.group(); - mapMap.put(Integer.valueOf(index), expr.substring(0, expr.length() - 1).replace("?#{#", "").replace("?#{", "") - .replace("target.", "").replaceAll("'", "")); + String sanitized = expr.substring(0, expr.length() - 1).replace("?#{#", "").replace("?#{", "") + .replace("target.", "").replaceAll("'", ""); + mapMap.put(index, sanitized); targetLookup = targetLookup.replace(expr, index + ""); index++; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java similarity index 81% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java index 570a516d9..8c2156df2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyGenerator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java @@ -15,47 +15,46 @@ */ package org.springframework.data.mongodb.core.convert; +import static org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate.*; import static org.springframework.util.ReflectionUtils.*; import java.io.Serializable; import java.lang.reflect.Method; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.cglib.proxy.Callback; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.Factory; import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.data.mongodb.core.convert.ReferenceResolver.LookupFunction; -import org.springframework.data.mongodb.core.convert.ReferenceResolver.ResultConversionFunction; +import org.springframework.data.mongodb.core.convert.ReferenceResolver.MongoEntityReader; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.lang.Nullable; import org.springframework.objenesis.ObjenesisStd; import org.springframework.util.ReflectionUtils; /** * @author Christoph Strobl */ -class LazyLoadingProxyGenerator { +class LazyLoadingProxyFactory { private final ObjenesisStd objenesis; - private final ReferenceReader referenceReader; + private final ReferenceLookupDelegate lookupDelegate; - public LazyLoadingProxyGenerator(ReferenceReader referenceReader) { + public LazyLoadingProxyFactory(ReferenceLookupDelegate lookupDelegate) { - this.referenceReader = referenceReader; + this.lookupDelegate = lookupDelegate; this.objenesis = new ObjenesisStd(true); } public Object createLazyLoadingProxy(MongoPersistentProperty property, Object source, LookupFunction lookupFunction, - ResultConversionFunction resultConversionFunction) { + MongoEntityReader entityReader) { Class propertyType = property.getType(); - LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, referenceReader, lookupFunction, - resultConversionFunction); + LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, lookupDelegate, lookupFunction, + entityReader); if (!propertyType.isInterface()) { @@ -97,13 +96,13 @@ class LazyLoadingProxyGenerator { public static class LazyLoadingInterceptor implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor, Serializable { - private final ReferenceReader referenceReader; - MongoPersistentProperty property; + private final ReferenceLookupDelegate referenceLookupDelegate; + private final MongoPersistentProperty property; private volatile boolean resolved; - private @org.springframework.lang.Nullable Object result; - private Object source; - private LookupFunction lookupFunction; - private ResultConversionFunction resultConversionFunction; + private @Nullable Object result; + private final Object source; + private final LookupFunction lookupFunction; + private final MongoEntityReader entityReader; private final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD, GET_SOURCE_METHOD; @@ -118,22 +117,23 @@ class LazyLoadingProxyGenerator { } } - public LazyLoadingInterceptor(MongoPersistentProperty property, Object source, ReferenceReader reader, - LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) { + public LazyLoadingInterceptor(MongoPersistentProperty property, Object source, ReferenceLookupDelegate reader, + LookupFunction lookupFunction, MongoEntityReader entityReader) { this.property = property; this.source = source; - this.referenceReader = reader; + this.referenceLookupDelegate = reader; this.lookupFunction = lookupFunction; - this.resultConversionFunction = resultConversionFunction; + this.entityReader = entityReader; } @Nullable @Override - public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable { + public Object invoke(MethodInvocation invocation) throws Throwable { return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null); } + @Nullable @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable { @@ -180,6 +180,7 @@ class LazyLoadingProxyGenerator { return method.invoke(target, args); } + @Nullable private Object ensureResolved() { if (!resolved) { @@ -190,7 +191,7 @@ class LazyLoadingProxyGenerator { return this.result; } - private String proxyToString(Object source) { + private String proxyToString(@Nullable Object source) { StringBuilder description = new StringBuilder(); if (source != null) { @@ -203,7 +204,7 @@ class LazyLoadingProxyGenerator { return description.toString(); } - private boolean proxyEquals(@org.springframework.lang.Nullable Object proxy, Object that) { + private boolean proxyEquals(@Nullable Object proxy, Object that) { if (!(that instanceof LazyLoadingProxy)) { return false; @@ -216,11 +217,11 @@ class LazyLoadingProxyGenerator { return proxyToString(proxy).equals(that.toString()); } - private int proxyHashCode(@org.springframework.lang.Nullable Object proxy) { + private int proxyHashCode(@Nullable Object proxy) { return proxyToString(proxy).hashCode(); } - @org.springframework.lang.Nullable + @Nullable private synchronized Object resolve() { if (resolved) { @@ -238,7 +239,7 @@ class LazyLoadingProxyGenerator { // property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); // } - return referenceReader.readReference(property, source, lookupFunction, resultConversionFunction); + return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader); } catch (RuntimeException ex) { throw ex; @@ -254,4 +255,5 @@ class LazyLoadingProxyGenerator { } } } + } 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 2ad4d7523..8a77b51e2 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 @@ -38,6 +38,7 @@ import org.bson.json.JsonReader; import org.bson.types.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -63,6 +64,7 @@ import org.springframework.data.mapping.model.SpELExpressionEvaluator; import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider; import org.springframework.data.mongodb.CodecRegistryProvider; import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.DocumentPointer; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -114,7 +116,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App protected final QueryMapper idMapper; protected final DbRefResolver dbRefResolver; protected final DefaultDbRefProxyHandler dbRefProxyHandler; - protected final ReferenceReader referenceReader; + protected final ReferenceLookupDelegate referenceLookupDelegate; protected @Nullable ApplicationContext applicationContext; protected MongoTypeMapper typeMapper; @@ -123,7 +125,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App private SpELContext spELContext; private @Nullable EntityCallbacks entityCallbacks; - private DocumentPointerFactory documentPointerFactory; + private final DocumentPointerFactory documentPointerFactory; /** * Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}. @@ -154,7 +156,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return MappingMongoConverter.this.getValueInternal(context, prop, bson, evaluator); }); - this.referenceReader = new ReferenceReader(mappingContext, () -> spELContext); + this.referenceLookupDelegate = new ReferenceLookupDelegate(mappingContext, spELContext); this.documentPointerFactory = new DocumentPointerFactory(conversionService, mappingContext); } @@ -361,16 +363,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App parameterProvider); } - private S read(ConversionContext context, MongoPersistentEntity entity, Document bson) { + private S read(ConversionContext context, MongoPersistentEntity entity, Document bson) { SpELExpressionEvaluator evaluator = new DefaultSpELExpressionEvaluator(bson, spELContext); DocumentAccessor documentAccessor = new DocumentAccessor(bson); - if (bson.get("_id") != null) { - - Object existing = context.getPath().getPathItem(bson.get("_id"), entity.getCollection(), entity.getType()); + if (hasIdentifier(bson)) { + S existing = findContextualEntity(context, entity, bson); if (existing != null) { - return (S) existing; + return existing; } } @@ -391,6 +392,16 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App return instance; } + private boolean hasIdentifier(Document bson) { + return bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME) != null; + } + + @Nullable + private S findContextualEntity(ConversionContext context, MongoPersistentEntity entity, Document bson) { + return context.getPath().getPathItem(bson.get(BasicMongoPersistentProperty.ID_FIELD_NAME), entity.getCollection(), + entity.getType()); + } + private S populateProperties(ConversionContext context, MongoPersistentEntity entity, DocumentAccessor documentAccessor, SpELExpressionEvaluator evaluator, S instance) { @@ -509,7 +520,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App ConversionContext context, SpELExpressionEvaluator evaluator) { MongoPersistentProperty property = association.getInverse(); - final Object value = documentAccessor.get(property); + Object value = documentAccessor.get(property); if (value == null) { return; @@ -521,18 +532,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App if (conversionService.canConvert(DocumentPointer.class, property.getActualType())) { - DocumentPointer pointer = new DocumentPointer() { - @Override - public Object getPointer() { - return value; - } - }; + DocumentPointer pointer = () -> value; // collection like special treatment accessor.setProperty(property, conversionService.convert(pointer, property.getActualType())); } else { accessor.setProperty(property, - dbRefResolver.resolveReference(property, value, referenceReader, context::convert)); + dbRefResolver.resolveReference(property, value, referenceLookupDelegate, context::convert)); } return; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java similarity index 85% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java index 66b698077..2483f5754 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultReferenceLoader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoDatabaseFactoryReferenceLoader.java @@ -29,13 +29,13 @@ import com.mongodb.client.MongoCollection; /** * @author Christoph Strobl */ -public class DefaultReferenceLoader implements ReferenceLoader { +public class MongoDatabaseFactoryReferenceLoader implements ReferenceLoader { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultReferenceLoader.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MongoDatabaseFactoryReferenceLoader.class); private final MongoDatabaseFactory mongoDbFactory; - public DefaultReferenceLoader(MongoDatabaseFactory mongoDbFactory) { + public MongoDatabaseFactoryReferenceLoader(MongoDatabaseFactory mongoDbFactory) { Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!"); @@ -43,7 +43,7 @@ public class DefaultReferenceLoader implements ReferenceLoader { } @Override - public Iterable bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context) { + public Iterable fetchMany(DocumentReferenceQuery filter, ReferenceCollection context) { MongoCollection collection = getCollection(context); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java index 8b6c96943..41d7ab3c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/NoOpDbRefResolver.java @@ -16,13 +16,10 @@ package org.springframework.data.mongodb.core.convert; import java.util.List; -import java.util.function.BiFunction; -import java.util.stream.Stream; import org.bson.Document; -import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery; + import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; -import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import com.mongodb.DBRef; @@ -76,9 +73,8 @@ public enum NoOpDbRefResolver implements DbRefResolver { @Nullable @Override - public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader, - LookupFunction lookupFunction, - ResultConversionFunction resultConversionFunction) { + public Object resolveReference(MongoPersistentProperty property, Object source, + ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) { return null; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java index d5c72afad..7cfd5e315 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLoader.java @@ -31,13 +31,13 @@ import com.mongodb.client.MongoCollection; public interface ReferenceLoader { @Nullable - default Document fetch(DocumentReferenceQuery filter, ReferenceCollection context) { + default Document fetchOne(DocumentReferenceQuery filter, ReferenceCollection context) { - Iterator it = bulkFetch(filter, context).iterator(); + Iterator it = fetchMany(filter, context).iterator(); return it.hasNext() ? it.next() : null; } - Iterable bulkFetch(DocumentReferenceQuery filter, ReferenceCollection context); + Iterable fetchMany(DocumentReferenceQuery filter, ReferenceCollection context); interface DocumentReferenceQuery { @@ -52,16 +52,12 @@ public interface ReferenceLoader { default Iterable apply(MongoCollection collection) { return restoreOrder(collection.find(getFilter()).sort(getSort())); } - + default Iterable restoreOrder(Iterable documents) { return documents; } - static DocumentReferenceQuery referenceFilter(Bson bson) { - return () -> bson; - } - - static DocumentReferenceQuery singleReferenceFilter(Bson bson) { + static DocumentReferenceQuery forSingleDocument(Bson bson) { return new DocumentReferenceQuery() { @@ -78,6 +74,22 @@ public interface ReferenceLoader { } }; } + + static DocumentReferenceQuery forManyDocuments(Bson bson) { + + return new DocumentReferenceQuery() { + + @Override + public Bson getFilter() { + return bson; + } + + @Override + public Iterable apply(MongoCollection collection) { + return collection.find(getFilter()).sort(getSort()); + } + }; + } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceReader.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java similarity index 84% rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceReader.java rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java index fb37367b1..3c441c138 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceReader.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceLookupDelegate.java @@ -29,12 +29,12 @@ import java.util.stream.Collectors; import org.bson.Document; import org.bson.conversions.Bson; + import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.SpELContext; import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery; -import org.springframework.data.mongodb.core.convert.ReferenceResolver.LookupFunction; +import org.springframework.data.mongodb.core.convert.ReferenceResolver.MongoEntityReader; import org.springframework.data.mongodb.core.convert.ReferenceResolver.ReferenceCollection; -import org.springframework.data.mongodb.core.convert.ReferenceResolver.ResultConversionFunction; import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -42,58 +42,59 @@ import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.data.mongodb.util.json.ParameterBindingContext; import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; import org.springframework.data.mongodb.util.json.ValueProvider; -import org.springframework.data.util.Lazy; import org.springframework.data.util.Streamable; import org.springframework.expression.EvaluationContext; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import com.mongodb.DBRef; import com.mongodb.client.MongoCollection; /** + * A common delegate for {@link ReferenceResolver} implementations to resolve a reference to one/many target documents + * that are converted to entities. + * * @author Christoph Strobl + * @author Mark Paluch */ -public class ReferenceReader { +public final class ReferenceLookupDelegate { - private final Lazy, MongoPersistentProperty>> mappingContext; - private final Supplier spelContextSupplier; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final SpELContext spELContext; private final ParameterBindingDocumentCodec codec; - public ReferenceReader(MappingContext, MongoPersistentProperty> mappingContext, - Supplier spelContextSupplier) { + public ReferenceLookupDelegate( + MappingContext, MongoPersistentProperty> mappingContext, + SpELContext spELContext) { - this(() -> mappingContext, spelContextSupplier); - } + Assert.notNull(mappingContext, "MappingContext must not be null"); + Assert.notNull(spELContext, "SpELContext must not be null"); - public ReferenceReader( - Supplier, MongoPersistentProperty>> mappingContextSupplier, - Supplier spelContextSupplier) { - - this.mappingContext = Lazy.of(mappingContextSupplier); - this.spelContextSupplier = spelContextSupplier; + this.mappingContext = mappingContext; + this.spELContext = spELContext; this.codec = new ParameterBindingDocumentCodec(); } + @Nullable Object readReference(MongoPersistentProperty property, Object value, LookupFunction lookupFunction, - ResultConversionFunction resultConversionFunction) { + MongoEntityReader entityReader) { - SpELContext spELContext = spelContextSupplier.get(); DocumentReferenceQuery filter = computeFilter(property, value, spELContext); ReferenceCollection referenceCollection = computeReferenceContext(property, value, spELContext); Iterable result = lookupFunction.apply(filter, referenceCollection); + if (property.isCollectionLike()) { + return entityReader.read(result, property.getTypeInformation()); + } + if (!result.iterator().hasNext()) { return null; } - if (property.isCollectionLike()) { - return resultConversionFunction.apply(result, property.getTypeInformation()); - } - - return resultConversionFunction.apply(result.iterator().next(), property.getTypeInformation()); + return entityReader.read(result.iterator().next(), property.getTypeInformation()); } private ReferenceCollection computeReferenceContext(MongoPersistentProperty property, Object value, @@ -107,6 +108,8 @@ public class ReferenceReader { return ReferenceCollection.fromDBRef((DBRef) value); } + String collection = mappingContext.getRequiredPersistentEntity(property.getAssociationTargetType()).getCollection(); + if (value instanceof Document) { Document ref = (Document) value; @@ -120,12 +123,12 @@ public class ReferenceReader { () -> ref.get("db", String.class)); String targetCollection = parseValueOrGet(documentReference.collection(), bindingContext, () -> ref.get("collection", - mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection())); + collection)); return new ReferenceCollection(targetDatabase, targetCollection); } return new ReferenceCollection(ref.getString("db"), ref.get("collection", - mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection())); + collection)); } if (property.isDocumentReference()) { @@ -135,16 +138,16 @@ public class ReferenceReader { String targetDatabase = parseValueOrGet(documentReference.db(), bindingContext, () -> null); String targetCollection = parseValueOrGet(documentReference.collection(), bindingContext, - () -> mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection()); + () -> collection); return new ReferenceCollection(targetDatabase, targetCollection); } return new ReferenceCollection(null, - mappingContext.get().getPersistentEntity(property.getAssociationTargetType()).getCollection()); + collection); } - @Nullable + @SuppressWarnings("unchecked") private T parseValueOrGet(String value, ParameterBindingContext bindingContext, Supplier defaultValue) { if (!StringUtils.hasText(value)) { @@ -153,7 +156,7 @@ public class ReferenceReader { if (!BsonUtils.isJsonDocument(value) && value.contains("?#{")) { String s = "{ 'target-value' : " + value + "}"; - T evaluated = (T) codec.decode(s, bindingContext).get("target-value "); + T evaluated = (T) codec.decode(s, bindingContext).get("target-value"); return evaluated != null ? evaluated : defaultValue.get(); } @@ -186,6 +189,7 @@ public class ReferenceReader { return ctx; } + @SuppressWarnings("unchecked") DocumentReferenceQuery computeFilter(MongoPersistentProperty property, Object value, SpELContext spELContext) { DocumentReference documentReference = property.getDocumentReference(); @@ -196,7 +200,7 @@ public class ReferenceReader { if (property.isCollectionLike() && value instanceof Collection) { List ors = new ArrayList<>(); - for (Object entry : (Collection) value) { + for (Object entry : (Collection) value) { Document decoded = codec.decode(lookup, bindingContext(property, entry, spELContext)); ors.add(decoded); @@ -209,7 +213,7 @@ public class ReferenceReader { Map filterMap = new LinkedHashMap<>(); - for (Entry entry : ((Map) value).entrySet()) { + for (Entry entry : ((Map) value).entrySet()) { Document decoded = codec.decode(lookup, bindingContext(property, entry.getValue(), spELContext)); filterMap.put(entry.getKey(), decoded); @@ -321,9 +325,9 @@ public class ReferenceReader { int compareAgainstReferenceIndex(List referenceList, Document document1, Document document2) { - for (int i = 0; i < referenceList.size(); i++) { + for (Document document : referenceList) { - Set> entries = referenceList.get(i).entrySet(); + Set> entries = document.entrySet(); if (document1.entrySet().containsAll(entries)) { return -1; } @@ -334,4 +338,10 @@ public class ReferenceReader { return referenceList.size(); } } + + @FunctionalInterface + interface LookupFunction { + + Iterable apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java index f29dc16a7..dae2043b4 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ReferenceResolver.java @@ -15,13 +15,10 @@ */ package org.springframework.data.mongodb.core.convert; -import java.util.Collections; - -import org.bson.Document; -import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import com.mongodb.DBRef; @@ -31,22 +28,8 @@ import com.mongodb.DBRef; public interface ReferenceResolver { @Nullable - Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader, - LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction); - - default Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader, - ResultConversionFunction resultConversionFunction) { - - return resolveReference(property, source, referenceReader, (filter, ctx) -> { - if (property.isCollectionLike() || property.isMap()) { - return getReferenceLoader().bulkFetch(filter, ctx); - - } - - Object target = getReferenceLoader().fetch(filter, ctx); - return target == null ? Collections.emptyList() : Collections.singleton(getReferenceLoader().fetch(filter, ctx)); - }, resultConversionFunction); - } + Object resolveReference(MongoPersistentProperty property, Object source, + ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader); ReferenceLoader getReferenceLoader(); @@ -58,6 +41,8 @@ public interface ReferenceResolver { public ReferenceCollection(@Nullable String database, String collection) { + Assert.hasText(collection, "Collection must not be empty or null"); + this.database = database; this.collection = collection; } @@ -76,13 +61,9 @@ public interface ReferenceResolver { } } - @FunctionalInterface - interface LookupFunction { - Iterable apply(DocumentReferenceQuery referenceQuery, ReferenceCollection referenceCollection); - } @FunctionalInterface - interface ResultConversionFunction { - Object apply(Object source, TypeInformation property); + interface MongoEntityReader { + Object read(Object source, TypeInformation property); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java index b7b71a7fe..53af00fc5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentProperty.java @@ -47,7 +47,7 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope private static final Logger LOG = LoggerFactory.getLogger(BasicMongoPersistentProperty.class); - private static final String ID_FIELD_NAME = "_id"; + public static final String ID_FIELD_NAME = "_id"; private static final String LANGUAGE_FIELD_NAME = "language"; private static final Set> SUPPORTED_ID_TYPES = new HashSet>(); private static final Set SUPPORTED_ID_PROPERTY_NAMES = new HashSet(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java index f5d43c8ef..91afb8c6e 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingTestUtils.java @@ -54,7 +54,8 @@ public class LazyLoadingTestUtils { public static void assertProxy(Object proxy, Consumer verification) { - LazyLoadingProxyGenerator.LazyLoadingInterceptor interceptor = (LazyLoadingProxyGenerator.LazyLoadingInterceptor) (proxy instanceof Advised ? ((Advised) proxy).getAdvisors()[0].getAdvice() + LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor = (LazyLoadingProxyFactory.LazyLoadingInterceptor) (proxy instanceof Advised + ? ((Advised) proxy).getAdvisors()[0].getAdvice() : ((Factory) proxy).getCallback(0)); verification.accept(new LazyLoadingProxyValueRetriever(interceptor)); @@ -67,9 +68,9 @@ public class LazyLoadingTestUtils { public static class LazyLoadingProxyValueRetriever { - LazyLoadingProxyGenerator.LazyLoadingInterceptor interceptor; + LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor; - public LazyLoadingProxyValueRetriever(LazyLoadingProxyGenerator.LazyLoadingInterceptor interceptor) { + public LazyLoadingProxyValueRetriever(LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor) { this.interceptor = interceptor; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 9c157db75..d371b32c1 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -1501,18 +1501,19 @@ public class QueryMapperUnitTests { SimpeEntityWithoutId noIdButLookupQuery; } - + + // TODO @Test void xxx() { - + Sample sample = new Sample(); sample.foo = "sample-id"; Query query = query(where("sample").is(sample)); - + org.bson.Document mappedObject = mapper.getMappedObject(query.getQueryObject(), context.getPersistentEntity(WithDocumentReferences.class)); - + System.out.println("mappedObject.toJson(): " + mappedObject.toJson()); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java index 28d512350..9c898d28c 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/BasicMongoPersistentEntityUnitTests.java @@ -30,11 +30,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AliasFor; -import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.mapping.MappingException; -import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; import org.springframework.data.spel.spi.EvaluationContextExtension; @@ -351,6 +350,9 @@ public class BasicMongoPersistentEntityUnitTests { @Document("#{myProperty}") class MappedWithExtension {} + @Document("${value.from.file}") + class MappedWithValue {} + @Document(collation = "#{myCollation}") class WithCollationFromSpEL {} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java index b70930dae..8a462a937 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/performance/ReactivePerformanceTests.java @@ -18,20 +18,13 @@ package org.springframework.data.mongodb.performance; import static org.springframework.data.mongodb.core.query.Criteria.*; import static org.springframework.data.mongodb.core.query.Query.*; -import org.springframework.data.mongodb.core.convert.ReferenceLoader; -import org.springframework.data.mongodb.core.convert.ReferenceLoader.DocumentReferenceQuery; -import org.springframework.data.mongodb.core.convert.ReferenceReader; -import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.text.DecimalFormat; import java.util.*; -import java.util.function.BiFunction; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.bson.Document; import org.bson.types.ObjectId; @@ -48,12 +41,15 @@ import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DbRefResolverCallback; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.convert.ReferenceLoader; +import org.springframework.data.mongodb.core.convert.ReferenceLookupDelegate; 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.data.mongodb.core.query.Query; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.data.mongodb.repository.support.ReactiveMongoRepositoryFactory; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StopWatch; import org.springframework.util.StringUtils; @@ -106,7 +102,8 @@ public class ReactivePerformanceTests { @Nullable @Override - public Object resolveReference(MongoPersistentProperty property, Object source, ReferenceReader referenceReader, LookupFunction lookupFunction, ResultConversionFunction resultConversionFunction) { + public Object resolveReference(MongoPersistentProperty property, Object source, + ReferenceLookupDelegate referenceLookupDelegate, MongoEntityReader entityReader) { return null; }