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 f64c7f0f0..70fc880cc 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 @@ -15,13 +15,6 @@ */ package org.springframework.data.mongodb.core.convert; -import static org.springframework.util.ReflectionUtils.*; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -29,30 +22,18 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; import org.bson.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.dao.DataAccessException; + import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.dao.support.PersistenceExceptionTranslator; -import org.springframework.data.mongodb.ClientSessionException; -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; import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import com.mongodb.DBRef; @@ -74,8 +55,6 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDbRefResolver.class); private final MongoDatabaseFactory mongoDbFactory; - private final PersistenceExceptionTranslator exceptionTranslator; - private final ObjenesisStd objenesis; /** * Creates a new {@link DefaultDbRefResolver} with the given {@link MongoDatabaseFactory}. @@ -84,13 +63,11 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db */ public DefaultDbRefResolver(MongoDatabaseFactory mongoDbFactory) { - super(new MongoDatabaseFactoryReferenceLoader(mongoDbFactory)); + super(new MongoDatabaseFactoryReferenceLoader(mongoDbFactory), mongoDbFactory.getExceptionTranslator()); Assert.notNull(mongoDbFactory, "MongoDbFactory translator must not be null!"); this.mongoDbFactory = mongoDbFactory; - this.exceptionTranslator = mongoDbFactory.getExceptionTranslator(); - this.objenesis = new ObjenesisStd(true); } /* @@ -180,44 +157,9 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db private Object createLazyLoadingProxy(MongoPersistentProperty property, @Nullable DBRef dbref, DbRefResolverCallback callback, DbRefProxyHandler handler) { - Class propertyType = property.getType(); - LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback); + Object lazyLoadingProxy = getProxyFactory().createLazyLoadingProxy(property, callback, dbref); - if (!propertyType.isInterface()) { - - Factory factory = (Factory) objenesis.newInstance(getEnhancedTypeFor(propertyType)); - factory.setCallbacks(new Callback[] { interceptor }); - - return handler.populateId(property, dbref, factory); - } - - ProxyFactory proxyFactory = new ProxyFactory(); - - for (Class type : propertyType.getInterfaces()) { - proxyFactory.addInterface(type); - } - - proxyFactory.addInterface(LazyLoadingProxy.class); - proxyFactory.addInterface(propertyType); - proxyFactory.addAdvice(interceptor); - - return handler.populateId(property, dbref, proxyFactory.getProxy(LazyLoadingProxy.class.getClassLoader())); - } - - /** - * Returns the CGLib enhanced type for the given source type. - * - * @param type - * @return - */ - private Class getEnhancedTypeFor(Class type) { - - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(type); - enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); - enhancer.setInterfaces(new Class[] { LazyLoadingProxy.class }); - - return enhancer.createClass(); + return handler.populateId(property, dbref, lazyLoadingProxy); } /** @@ -244,249 +186,6 @@ public class DefaultDbRefResolver extends DefaultReferenceResolver implements Db .limit(1); } - /** - * A {@link MethodInterceptor} that is used within a lazy loading proxy. The property resolving is delegated to a - * {@link DbRefResolverCallback}. The resolving process is triggered by a method invocation on the proxy and is - * guaranteed to be performed only once. - * - * @author Thomas Darimont - * @author Oliver Gierke - * @author Christoph Strobl - */ - static class LazyLoadingInterceptor - implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor, Serializable { - - private static final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD; - - private final DbRefResolverCallback callback; - private final MongoPersistentProperty property; - private final PersistenceExceptionTranslator exceptionTranslator; - - private volatile boolean resolved; - private final @Nullable DBRef dbref; - private @Nullable Object result; - - static { - try { - INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("getTarget"); - TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef"); - FINALIZE_METHOD = Object.class.getDeclaredMethod("finalize"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Creates a new {@link LazyLoadingInterceptor} for the given {@link MongoPersistentProperty}, - * {@link PersistenceExceptionTranslator} and {@link DbRefResolverCallback}. - * - * @param property must not be {@literal null}. - * @param dbref can be {@literal null}. - * @param callback must not be {@literal null}. - */ - public LazyLoadingInterceptor(MongoPersistentProperty property, @Nullable DBRef dbref, - PersistenceExceptionTranslator exceptionTranslator, DbRefResolverCallback callback) { - - Assert.notNull(property, "Property must not be null!"); - Assert.notNull(exceptionTranslator, "Exception translator must not be null!"); - Assert.notNull(callback, "Callback must not be null!"); - - this.dbref = dbref; - this.callback = callback; - this.exceptionTranslator = exceptionTranslator; - this.property = property; - } - - /* - * (non-Javadoc) - * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) - */ - @Override - public Object invoke(@Nullable MethodInvocation invocation) throws Throwable { - return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null); - } - - /* - * (non-Javadoc) - * @see org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy) - */ - @Nullable - @Override - public Object intercept(Object obj, Method method, Object[] args, @Nullable MethodProxy proxy) throws Throwable { - - if (INITIALIZE_METHOD.equals(method)) { - return ensureResolved(); - } - - if (TO_DBREF_METHOD.equals(method)) { - return this.dbref; - } - - if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) { - - if (ReflectionUtils.isToStringMethod(method)) { - return proxyToString(proxy); - } - - if (ReflectionUtils.isEqualsMethod(method)) { - return proxyEquals(proxy, args[0]); - } - - if (ReflectionUtils.isHashCodeMethod(method)) { - return proxyHashCode(proxy); - } - - // DATAMONGO-1076 - finalize methods should not trigger proxy initialization - if (FINALIZE_METHOD.equals(method)) { - return null; - } - } - - Object target = ensureResolved(); - - if (target == null) { - return null; - } - - ReflectionUtils.makeAccessible(method); - - return method.invoke(target, args); - } - - /** - * Returns a to string representation for the given {@code proxy}. - * - * @param proxy - * @return - */ - private String proxyToString(@Nullable Object proxy) { - - StringBuilder description = new StringBuilder(); - if (dbref != null) { - description.append(dbref.getCollectionName()); - description.append(":"); - description.append(dbref.getId()); - } else { - description.append(System.identityHashCode(proxy)); - } - description.append("$").append(LazyLoadingProxy.class.getSimpleName()); - - return description.toString(); - } - - /** - * Returns the hashcode for the given {@code proxy}. - * - * @param proxy - * @return - */ - private int proxyHashCode(@Nullable Object proxy) { - return proxyToString(proxy).hashCode(); - } - - /** - * Performs an equality check for the given {@code proxy}. - * - * @param proxy - * @param that - * @return - */ - private boolean proxyEquals(@Nullable Object proxy, Object that) { - - if (!(that instanceof LazyLoadingProxy)) { - return false; - } - - if (that == proxy) { - return true; - } - - return proxyToString(proxy).equals(that.toString()); - } - - /** - * Will trigger the resolution if the proxy is not resolved already or return a previously resolved result. - * - * @return - */ - @Nullable - private Object ensureResolved() { - - if (!resolved) { - this.result = resolve(); - this.resolved = true; - } - - return this.result; - } - - /** - * Callback method for serialization. - * - * @param out - * @throws IOException - */ - private void writeObject(ObjectOutputStream out) throws IOException { - - ensureResolved(); - out.writeObject(this.result); - } - - /** - * Callback method for deserialization. - * - * @param in - * @throws IOException - */ - private void readObject(ObjectInputStream in) throws IOException { - - try { - this.resolved = true; - this.result = in.readObject(); - } catch (ClassNotFoundException e) { - throw new LazyLoadingException("Could not deserialize result", e); - } - } - - /** - * Resolves the proxy into its backing object. - * - * @return - */ - @Nullable - private synchronized Object resolve() { - - if (resolved) { - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Accessing already resolved lazy loading property {}.{}", - property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); - } - return result; - } - - try { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Resolving lazy loading property {}.{}", - property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); - } - - return callback.resolve(property); - - } catch (RuntimeException ex) { - - DataAccessException translatedException = this.exceptionTranslator.translateExceptionIfPossible(ex); - - if (translatedException instanceof ClientSessionException) { - throw new LazyLoadingException("Unable to lazily resolve DBRef! Invalid session state.", ex); - } - - throw new LazyLoadingException("Unable to lazily resolve DBRef!", - translatedException != null ? translatedException : ex); - } - } - } - /** * Customization hook for obtaining the {@link MongoCollection} for a given {@link DBRef}. * 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 a678fd7da..f801b8d99 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 @@ -19,6 +19,7 @@ import static org.springframework.data.mongodb.core.convert.ReferenceLookupDeleg import java.util.Collections; +import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.core.mapping.DBRef; import org.springframework.data.mongodb.core.mapping.DocumentReference; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; @@ -30,10 +31,13 @@ import org.springframework.util.Assert; * proxies} for associations that should be lazily loaded. * * @author Christoph Strobl + * @author Mark Paluch + * @since 3.3 */ public class DefaultReferenceResolver implements ReferenceResolver { private final ReferenceLoader referenceLoader; + private final LazyLoadingProxyFactory proxyFactory; private final LookupFunction collectionLookupFunction = (filter, ctx) -> getReferenceLoader().fetchMany(filter, ctx); private final LookupFunction singleValueLookupFunction = (filter, ctx) -> { @@ -43,13 +47,17 @@ public class DefaultReferenceResolver implements ReferenceResolver { /** * Create a new instance of {@link DefaultReferenceResolver}. - * + * * @param referenceLoader must not be {@literal null}. + * @param exceptionTranslator must not be {@literal null}. */ - public DefaultReferenceResolver(ReferenceLoader referenceLoader) { - + public DefaultReferenceResolver(ReferenceLoader referenceLoader, PersistenceExceptionTranslator exceptionTranslator) { + Assert.notNull(referenceLoader, "ReferenceLoader must not be null!"); + Assert.notNull(exceptionTranslator, "ExceptionTranslator must not be null!"); + this.referenceLoader = referenceLoader; + this.proxyFactory = new LazyLoadingProxyFactory(exceptionTranslator); } @Override @@ -92,9 +100,14 @@ public class DefaultReferenceResolver implements ReferenceResolver { return referenceLoader; } + LazyLoadingProxyFactory getProxyFactory() { + return proxyFactory; + } + private Object createLazyLoadingProxy(MongoPersistentProperty property, Object source, ReferenceLookupDelegate referenceLookupDelegate, LookupFunction lookupFunction, MongoEntityReader entityReader) { - return new LazyLoadingProxyFactory(referenceLookupDelegate).createLazyLoadingProxy(property, source, lookupFunction, - entityReader); + return proxyFactory.createLazyLoadingProxy(property, it -> { + return referenceLookupDelegate.readReference(it, source, lookupFunction, entityReader); + }, source); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxy.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxy.java index 8be711198..a2a2df8c8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxy.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxy.java @@ -15,18 +15,18 @@ */ package org.springframework.data.mongodb.core.convert; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.LazyLoadingInterceptor; import org.springframework.lang.Nullable; import com.mongodb.DBRef; /** - * Allows direct interaction with the underlying {@link LazyLoadingInterceptor}. + * Allows direct interaction with the underlying {@code LazyLoadingInterceptor}. * * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch * @since 1.5 + * @see LazyLoadingProxyFactory */ public interface LazyLoadingProxy { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java index 8c2156df2..f77b96c71 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/LazyLoadingProxyFactory.java @@ -15,46 +15,59 @@ */ 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.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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.MongoEntityReader; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.mongodb.ClientSessionException; +import org.springframework.data.mongodb.LazyLoadingException; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.lang.Nullable; import org.springframework.objenesis.ObjenesisStd; import org.springframework.util.ReflectionUtils; +import com.mongodb.DBRef; + /** + * {@link ProxyFactory} to create a proxy for {@link MongoPersistentProperty#getType()} to resolve a reference lazily. + * * @author Christoph Strobl + * @author Mark Paluch */ class LazyLoadingProxyFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(LazyLoadingProxyFactory.class); + private final ObjenesisStd objenesis; - private final ReferenceLookupDelegate lookupDelegate; - public LazyLoadingProxyFactory(ReferenceLookupDelegate lookupDelegate) { + private final PersistenceExceptionTranslator exceptionTranslator; - this.lookupDelegate = lookupDelegate; + public LazyLoadingProxyFactory(PersistenceExceptionTranslator exceptionTranslator) { + this.exceptionTranslator = exceptionTranslator; this.objenesis = new ObjenesisStd(true); } - public Object createLazyLoadingProxy(MongoPersistentProperty property, Object source, LookupFunction lookupFunction, - MongoEntityReader entityReader) { + public Object createLazyLoadingProxy(MongoPersistentProperty property, DbRefResolverCallback callback, + Object source) { Class propertyType = property.getType(); - LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, source, lookupDelegate, lookupFunction, - entityReader); + LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, callback, source, exceptionTranslator); if (!propertyType.isInterface()) { @@ -96,17 +109,9 @@ class LazyLoadingProxyFactory { public static class LazyLoadingInterceptor implements MethodInterceptor, org.springframework.cglib.proxy.MethodInterceptor, Serializable { - private final ReferenceLookupDelegate referenceLookupDelegate; - private final MongoPersistentProperty property; - private volatile boolean resolved; - private @Nullable Object result; - private final Object source; - private final LookupFunction lookupFunction; - private final MongoEntityReader entityReader; + private static final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD, GET_SOURCE_METHOD; - private final Method INITIALIZE_METHOD, TO_DBREF_METHOD, FINALIZE_METHOD, GET_SOURCE_METHOD; - - { + static { try { INITIALIZE_METHOD = LazyLoadingProxy.class.getMethod("getTarget"); TO_DBREF_METHOD = LazyLoadingProxy.class.getMethod("toDBRef"); @@ -117,14 +122,20 @@ class LazyLoadingProxyFactory { } } - public LazyLoadingInterceptor(MongoPersistentProperty property, Object source, ReferenceLookupDelegate reader, - LookupFunction lookupFunction, MongoEntityReader entityReader) { + private final MongoPersistentProperty property; + private final DbRefResolverCallback callback; + private final Object source; + private final PersistenceExceptionTranslator exceptionTranslator; + private volatile boolean resolved; + private @Nullable Object result; + + public LazyLoadingInterceptor(MongoPersistentProperty property, DbRefResolverCallback callback, Object source, + PersistenceExceptionTranslator exceptionTranslator) { this.property = property; + this.callback = callback; this.source = source; - this.referenceLookupDelegate = reader; - this.lookupFunction = lookupFunction; - this.entityReader = entityReader; + this.exceptionTranslator = exceptionTranslator; } @Nullable @@ -142,7 +153,7 @@ class LazyLoadingProxyFactory { } if (TO_DBREF_METHOD.equals(method)) { - return null; + return source instanceof DBRef ? source : null; } if (GET_SOURCE_METHOD.equals(method)) { @@ -152,7 +163,7 @@ class LazyLoadingProxyFactory { if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) { if (ReflectionUtils.isToStringMethod(method)) { - return proxyToString(proxy); + return proxyToString(source); } if (ReflectionUtils.isEqualsMethod(method)) { @@ -160,7 +171,7 @@ class LazyLoadingProxyFactory { } if (ReflectionUtils.isHashCodeMethod(method)) { - return proxyHashCode(proxy); + return proxyHashCode(); } // DATAMONGO-1076 - finalize methods should not trigger proxy initialization @@ -195,7 +206,13 @@ class LazyLoadingProxyFactory { StringBuilder description = new StringBuilder(); if (source != null) { - description.append(source); + if (source instanceof DBRef) { + description.append(((DBRef) source).getCollectionName()); + description.append(":"); + description.append(((DBRef) source).getId()); + } else { + description.append(source); + } } else { description.append(System.identityHashCode(source)); } @@ -217,8 +234,36 @@ class LazyLoadingProxyFactory { return proxyToString(proxy).equals(that.toString()); } - private int proxyHashCode(@Nullable Object proxy) { - return proxyToString(proxy).hashCode(); + private int proxyHashCode() { + return proxyToString(source).hashCode(); + } + + /** + * Callback method for serialization. + * + * @param out + * @throws IOException + */ + private void writeObject(ObjectOutputStream out) throws IOException { + + ensureResolved(); + out.writeObject(this.result); + } + + /** + * Callback method for deserialization. + * + * @param in + * @throws IOException + */ + private void readObject(ObjectInputStream in) throws IOException { + + try { + this.resolved = true; + this.result = in.readObject(); + } catch (ClassNotFoundException e) { + throw new LazyLoadingException("Could not deserialize result", e); + } } @Nullable @@ -226,32 +271,31 @@ class LazyLoadingProxyFactory { if (resolved) { - // if (LOGGER.isTraceEnabled()) { - // LOGGER.trace("Accessing already resolved lazy loading property {}.{}", - // property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); - // } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Accessing already resolved lazy loading property {}.{}", + property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); + } return result; } try { - // if (LOGGER.isTraceEnabled()) { - // LOGGER.trace("Resolving lazy loading property {}.{}", - // property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); - // } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Resolving lazy loading property {}.{}", + property.getOwner() != null ? property.getOwner().getName() : "unknown", property.getName()); + } - return referenceLookupDelegate.readReference(property, source, lookupFunction, entityReader); + return callback.resolve(property); } catch (RuntimeException ex) { - throw ex; - // DataAccessException translatedException = this.exceptionTranslator.translateExceptionIfPossible(ex); - // - // if (translatedException instanceof ClientSessionException) { - // throw new LazyLoadingException("Unable to lazily resolve DBRef! Invalid session state.", ex); - // } + DataAccessException translatedException = exceptionTranslator.translateExceptionIfPossible(ex); - // throw new LazyLoadingException("Unable to lazily resolve DBRef!", - // translatedException != null ? translatedException : ex); + if (translatedException instanceof ClientSessionException) { + throw new LazyLoadingException("Unable to lazily resolve DBRef! Invalid session state.", ex); + } + + throw new LazyLoadingException("Unable to lazily resolve DBRef!", + translatedException != null ? translatedException : ex); } } } 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 91235b527..c2cf67660 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,6 +15,7 @@ */ package org.springframework.data.mongodb.core.convert; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.util.TypeInformation; @@ -27,12 +28,15 @@ import com.mongodb.DBRef; * The {@link ReferenceResolver} allows to load and convert linked entities. * * @author Christoph Strobl + * @since 3.3 */ +@FunctionalInterface public interface ReferenceResolver { /** - * Resolve the association defined via the given property from a given source value. May deliver a - * {@link LazyLoadingProxy proxy instance} in case of a lazy loading association. + * Resolve the association defined via the given property from a given source value. May return a + * {@link LazyLoadingProxy proxy instance} in case of a lazy loading association. The resolved value is assignable to + * {@link PersistentProperty#getType()}. * * @param property the association defining property. * @param source the association source value. @@ -79,7 +83,7 @@ public interface ReferenceResolver { /** * Get the target collection name. - * + * * @return never {@literal null}. */ public String getCollection() { @@ -98,7 +102,7 @@ public interface ReferenceResolver { } /** - * Domain type conversion callback interface that allows to read + * Domain type conversion callback interface that allows to read the {@code source} object into a mapped object. */ @FunctionalInterface interface MongoEntityReader { @@ -107,7 +111,7 @@ public interface ReferenceResolver { * Read values from the given source into an object defined via the given {@link TypeInformation}. * * @param source never {@literal null}. - * @param typeInformation information abount the desired target type. + * @param typeInformation information about the desired target type. * @return never {@literal null}. */ Object read(Object source, TypeInformation typeInformation); 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 84e7e2c2d..d5285e7d2 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 @@ -70,16 +70,16 @@ import com.mongodb.client.MongoDatabase; * @author Mark Paluch */ @ExtendWith(MockitoExtension.class) -public class DbRefMappingMongoConverterUnitTests { +class DbRefMappingMongoConverterUnitTests { - MappingMongoConverter converter; - MongoMappingContext mappingContext; + private MappingMongoConverter converter; + private MongoMappingContext mappingContext; @Mock MongoDatabaseFactory dbFactory; - DefaultDbRefResolver dbRefResolver; + private DefaultDbRefResolver dbRefResolver; @BeforeEach - public void setUp() { + void setUp() { when(dbFactory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); @@ -89,7 +89,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-347 - public void createsSimpleDBRefCorrectly() { + void createsSimpleDBRefCorrectly() { Person person = new Person(); person.id = "foo"; @@ -100,7 +100,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-657 - public void convertDocumentWithMapDBRef() { + void convertDocumentWithMapDBRef() { Document mapValDocument = new Document(); mapValDocument.put("_id", BigInteger.ONE); @@ -145,7 +145,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-347 - public void createsDBRefWithClientSpecCorrectly() { + void createsDBRefWithClientSpecCorrectly() { PropertyPath path = PropertyPath.from("person", PersonClient.class); MongoPersistentProperty property = mappingContext.getPersistentPropertyPath(path).getLeafProperty(); @@ -159,7 +159,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - public void lazyLoadingProxyForLazyDbRefOnInterface() { + void lazyLoadingProxyForLazyDbRefOnInterface() { String id = "42"; String value = "bubu"; @@ -180,7 +180,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - public void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { + void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { String id = "42"; String value = "bubu"; @@ -201,7 +201,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - public void lazyLoadingProxyForLazyDbRefOnConcreteType() { + void lazyLoadingProxyForLazyDbRefOnConcreteType() { String id = "42"; String value = "bubu"; @@ -222,7 +222,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor() { + void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructor() { String id = "42"; String value = "bubu"; @@ -243,7 +243,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - public void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructorButWithoutDefaultConstructor() { + void lazyLoadingProxyForLazyDbRefOnConcreteTypeWithPersistenceConstructorButWithoutDefaultConstructor() { String id = "42"; String value = "bubu"; @@ -266,7 +266,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - public void lazyLoadingProxyForSerializableLazyDbRefOnConcreteType() { + void lazyLoadingProxyForSerializableLazyDbRefOnConcreteType() { String id = "42"; String value = "bubu"; @@ -288,7 +288,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-884 - public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { + void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { String id = "42"; String value = "bubu"; @@ -309,7 +309,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-884 - public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { + void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; String value = "bubu"; @@ -337,7 +337,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-884 - public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { + void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; String value = "bubu"; @@ -362,7 +362,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-884 - public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { + void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() { String id = "42"; String value = "bubu"; @@ -385,7 +385,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-884 - public void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() { + void lazyLoadingProxyForEqualsAndHashcodeObjectMethodOverridingDbref() { String id = "42"; String value = "bubu"; @@ -414,7 +414,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-987 - public void shouldNotGenerateLazyLoadingProxyForNullValues() { + void shouldNotGenerateLazyLoadingProxyForNullValues() { Document document = new Document(); ClassWithLazyDbRefs lazyDbRefs = new ClassWithLazyDbRefs(); @@ -432,7 +432,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1005 - public void shouldBeAbleToStoreDirectReferencesToSelf() { + void shouldBeAbleToStoreDirectReferencesToSelf() { Document document = new Document(); @@ -448,7 +448,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1005 - public void shouldBeAbleToStoreNestedReferencesToSelf() { + void shouldBeAbleToStoreNestedReferencesToSelf() { Document document = new Document(); @@ -467,7 +467,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1012 - public void shouldEagerlyResolveIdPropertyWithFieldAccess() { + void shouldEagerlyResolveIdPropertyWithFieldAccess() { MongoPersistentEntity entity = mappingContext.getRequiredPersistentEntity(ClassWithLazyDbRefs.class); MongoPersistentProperty property = entity.getRequiredPersistentProperty("dbRefToConcreteType"); @@ -489,7 +489,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1012 - public void shouldNotEagerlyResolveIdPropertyWithPropertyAccess() { + void shouldNotEagerlyResolveIdPropertyWithPropertyAccess() { MongoPersistentEntity entity = mappingContext.getRequiredPersistentEntity(ClassWithLazyDbRefs.class); MongoPersistentProperty property = entity.getRequiredPersistentProperty("dbRefToConcreteTypeWithPropertyAccess"); @@ -507,7 +507,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1076 - public void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoked() throws Exception { + void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoked() throws Exception { MongoPersistentEntity entity = mappingContext .getRequiredPersistentEntity(WithObjectMethodOverrideLazyDbRefs.class); @@ -525,7 +525,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1194 - public void shouldBulkFetchListOfReferences() { + void shouldBulkFetchListOfReferences() { String id1 = "1"; String id2 = "2"; @@ -553,7 +553,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1666 - public void shouldBulkFetchSetOfReferencesForConstructorCreation() { + void shouldBulkFetchSetOfReferencesForConstructorCreation() { String id1 = "1"; String id2 = "2"; @@ -575,7 +575,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1194 - public void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointToDifferentCollections() { + void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointToDifferentCollections() { String id1 = "1"; String id2 = "2"; @@ -603,7 +603,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1194 - public void shouldBulkFetchMapOfReferences() { + void shouldBulkFetchMapOfReferences() { MapDBRefVal val1 = new MapDBRefVal(); val1.id = BigInteger.ONE; @@ -635,7 +635,7 @@ public class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1194 - public void shouldBulkFetchLazyMapOfReferences() { + void shouldBulkFetchLazyMapOfReferences() { MapDBRefVal val1 = new MapDBRefVal(); val1.id = BigInteger.ONE; @@ -722,15 +722,15 @@ public class DbRefMappingMongoConverterUnitTests { @Id String id; String value; - public LazyDbRefTarget() { + LazyDbRefTarget() { this(null); } - public LazyDbRefTarget(String id) { + LazyDbRefTarget(String id) { this(id, null); } - public LazyDbRefTarget(String id, String value) { + LazyDbRefTarget(String id, String value) { this.id = id; this.value = value; } @@ -750,7 +750,7 @@ public class DbRefMappingMongoConverterUnitTests { @Id @AccessType(Type.PROPERTY) String id; - public LazyDbRefTargetPropertyAccess(String id) { + LazyDbRefTargetPropertyAccess(String id) { this.id = id; } @@ -767,7 +767,7 @@ public class DbRefMappingMongoConverterUnitTests { public LazyDbRefTargetWithPeristenceConstructor() {} @PersistenceConstructor - public LazyDbRefTargetWithPeristenceConstructor(String id, String value) { + LazyDbRefTargetWithPeristenceConstructor(String id, String value) { super(id, value); this.persistenceConstructorCalled = true; } @@ -783,7 +783,7 @@ public class DbRefMappingMongoConverterUnitTests { boolean persistenceConstructorCalled; @PersistenceConstructor - public LazyDbRefTargetWithPeristenceConstructorWithoutDefaultConstructor(String id, String value) { + LazyDbRefTargetWithPeristenceConstructorWithoutDefaultConstructor(String id, String value) { super(id, value); this.persistenceConstructorCalled = true; } @@ -797,7 +797,7 @@ public class DbRefMappingMongoConverterUnitTests { public SerializableLazyDbRefTarget() {} - public SerializableLazyDbRefTarget(String id, String value) { + SerializableLazyDbRefTarget(String id, String value) { super(id, value); } @@ -810,7 +810,7 @@ public class DbRefMappingMongoConverterUnitTests { public ToStringObjectMethodOverrideLazyDbRefTarget() {} - public ToStringObjectMethodOverrideLazyDbRefTarget(String id, String value) { + ToStringObjectMethodOverrideLazyDbRefTarget(String id, String value) { super(id, value); } @@ -830,7 +830,7 @@ public class DbRefMappingMongoConverterUnitTests { public EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget() {} - public EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget(String id, String value) { + EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget(String id, String value) { super(id, value); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java index d7a287047..a65214610 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolverUnitTests.java @@ -37,6 +37,7 @@ import org.mockito.quality.Strictness; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.DocumentTestUtils; +import org.springframework.data.mongodb.core.MongoExceptionTranslator; import com.mongodb.DBRef; import com.mongodb.client.FindIterable; @@ -63,6 +64,7 @@ class DefaultDbRefResolverUnitTests { void setUp() { when(factoryMock.getMongoDatabase()).thenReturn(dbMock); + when(factoryMock.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); when(dbMock.getCollection(anyString(), any(Class.class))).thenReturn(collectionMock); when(collectionMock.find(any(Document.class))).thenReturn(cursorMock); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java index 5b758136e..d357ca0f8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/LazyLoadingInterceptorUnitTests.java @@ -26,7 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.LazyLoadingException; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.LazyLoadingInterceptor; +import org.springframework.data.mongodb.core.convert.LazyLoadingProxyFactory.LazyLoadingInterceptor; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import com.mongodb.DBRef; @@ -37,20 +37,20 @@ import com.mongodb.DBRef; * @author Christoph Strobl */ @ExtendWith(MockitoExtension.class) -public class LazyLoadingInterceptorUnitTests { +class LazyLoadingInterceptorUnitTests { @Mock MongoPersistentProperty propertyMock; @Mock DBRef dbrefMock; @Mock DbRefResolverCallback callbackMock; @Test // DATAMONGO-1437 - public void shouldPreserveCauseForNonTranslatableExceptions() throws Throwable { + void shouldPreserveCauseForNonTranslatableExceptions() throws Throwable { NullPointerException npe = new NullPointerException("Some Exception we did not think about."); when(callbackMock.resolve(propertyMock)).thenThrow(npe); assertThatExceptionOfType(LazyLoadingException.class).isThrownBy(() -> { - new LazyLoadingInterceptor(propertyMock, dbrefMock, new NullExceptionTranslator(), callbackMock).intercept(null, + new LazyLoadingInterceptor(propertyMock, callbackMock, dbrefMock, new NullExceptionTranslator()).intercept(null, LazyLoadingProxy.class.getMethod("getTarget"), null, null); }).withCause(npe); } 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 91afb8c6e..15e953930 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 @@ -16,12 +16,12 @@ package org.springframework.data.mongodb.core.convert; import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.convert.LazyLoadingProxyFactory.*; import java.util.function.Consumer; import org.springframework.aop.framework.Advised; import org.springframework.cglib.proxy.Factory; -import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver.LazyLoadingInterceptor; import org.springframework.data.mongodb.core.mapping.Unwrapped; import org.springframework.test.util.ReflectionTestUtils; @@ -54,7 +54,7 @@ public class LazyLoadingTestUtils { public static void assertProxy(Object proxy, Consumer verification) { - LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor = (LazyLoadingProxyFactory.LazyLoadingInterceptor) (proxy instanceof Advised + LazyLoadingInterceptor interceptor = (LazyLoadingInterceptor) (proxy instanceof Advised ? ((Advised) proxy).getAdvisors()[0].getAdvice() : ((Factory) proxy).getCallback(0)); @@ -68,9 +68,9 @@ public class LazyLoadingTestUtils { public static class LazyLoadingProxyValueRetriever { - LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor; + LazyLoadingInterceptor interceptor; - public LazyLoadingProxyValueRetriever(LazyLoadingProxyFactory.LazyLoadingInterceptor interceptor) { + public LazyLoadingProxyValueRetriever(LazyLoadingInterceptor interceptor) { this.interceptor = interceptor; }