Skip to content

Commit

Permalink
Remove duplicate LazyLoadingInterceptor code by reusing LazyLoadingPr…
Browse files Browse the repository at this point in the history
…oxyFactory.

Original pull request: #3647.
Closes #3602.
  • Loading branch information
mp911de committed May 21, 2021
1 parent 5ab75eb commit dae0ac3
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 411 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,44 +15,25 @@
*/
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;
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;
Expand All @@ -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}.
Expand All @@ -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);
}

/*
Expand Down Expand Up @@ -180,44 +157,9 @@ public List<Document> bulkFetch(List<DBRef> refs) {
private Object createLazyLoadingProxy(MongoPersistentProperty property, @Nullable DBRef dbref,
DbRefResolverCallback callback, DbRefProxyHandler handler) {

Class<?> propertyType = property.getType();
LazyLoadingInterceptor interceptor = new LazyLoadingInterceptor(property, dbref, exceptionTranslator, callback);

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);
Object lazyLoadingProxy = getProxyFactory().createLazyLoadingProxy(property, callback, dbref);

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

/**
Expand All @@ -244,249 +186,6 @@ private static Stream<Document> documentWithId(Object identifier, Collection<Doc
.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}.
*
Expand Down
Loading

0 comments on commit dae0ac3

Please # to comment.