DATAMONGO-884 - Improved handling for Object methods in LazyLoadingInterceptor.
We now handle invocations of equals(…)/hashCode()/toString() methods that are not overridden with custom proxy aware logic. This avoids potentially NullPointerExceptions and makes it easier to debug code that deals with proxies (due to a proper toString representation of a proxy). Original pull request: #158.
This commit is contained in:
committed by
Oliver Gierke
parent
031ab0c07b
commit
2cfd4781bc
@@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.mongodb.DB;
|
||||
@@ -230,12 +231,81 @@ public class DefaultDbRefResolver implements DbRefResolver {
|
||||
return this.dbref;
|
||||
}
|
||||
|
||||
Object target = isObjectMethod(method) && Object.class.equals(method.getDeclaringClass()) ? obj
|
||||
: ensureResolved();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Object target = ensureResolved();
|
||||
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return method.invoke(target, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a to string representation for the given {@code proxy}.
|
||||
*
|
||||
* @param proxy
|
||||
* @return
|
||||
*/
|
||||
private String proxyToString(Object proxy) {
|
||||
|
||||
StringBuilder description = new StringBuilder();
|
||||
if (dbref != null) {
|
||||
description.append(dbref.getRef());
|
||||
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(Object proxy) {
|
||||
return proxyToString(proxy).hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an equality check for the given {@code proxy}.
|
||||
*
|
||||
* @param proxy
|
||||
* @param that
|
||||
* @return
|
||||
*/
|
||||
private boolean proxyEquals(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.
|
||||
*
|
||||
|
||||
@@ -61,6 +61,7 @@ import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.convert.CustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.LazyLoadingProxy;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.Index.Duplicates;
|
||||
@@ -2550,6 +2551,35 @@ public class MongoTemplateTests {
|
||||
assertThat(savedMessage.normalContent.text, is(content.text));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-884
|
||||
*/
|
||||
@Test
|
||||
public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyingDbrefWasDeletedInbetween() {
|
||||
|
||||
template.dropCollection(SomeTemplate.class);
|
||||
template.dropCollection(SomeContent.class);
|
||||
|
||||
SomeContent content = new SomeContent();
|
||||
content.id = "C1";
|
||||
content.text = "BUBU";
|
||||
template.save(content);
|
||||
|
||||
SomeTemplate tmpl = new SomeTemplate();
|
||||
tmpl.id = "T1";
|
||||
tmpl.content = content; // @DBRef(lazy=true) tmpl.content
|
||||
|
||||
template.save(tmpl);
|
||||
|
||||
SomeTemplate savedTmpl = template.findById(tmpl.id, SomeTemplate.class);
|
||||
|
||||
template.remove(content);
|
||||
|
||||
assertThat(savedTmpl.getContent().toString(), is("someContent:C1$LazyLoadingProxy"));
|
||||
assertThat(savedTmpl.getContent(), is(instanceOf(LazyLoadingProxy.class)));
|
||||
assertThat(savedTmpl.getContent().getText(), is(nullValue()));
|
||||
}
|
||||
|
||||
static class DocumentWithDBRefCollection {
|
||||
|
||||
@Id public String id;
|
||||
@@ -2730,7 +2760,7 @@ public class MongoTemplateTests {
|
||||
EnumValue value;
|
||||
}
|
||||
|
||||
static class SomeTemplate {
|
||||
public static class SomeTemplate {
|
||||
|
||||
String id;
|
||||
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) SomeContent content;
|
||||
@@ -2740,10 +2770,14 @@ public class MongoTemplateTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class SomeContent {
|
||||
public static class SomeContent {
|
||||
|
||||
String id;
|
||||
String text;
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
static class SomeMessage {
|
||||
|
||||
@@ -310,6 +310,93 @@ public class DbRefMappingMongoConverterUnitTests {
|
||||
assertProxyIsResolved(result.dbRefToToStringObjectMethodOverride, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-884
|
||||
*/
|
||||
@Test
|
||||
public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
|
||||
|
||||
String id = "42";
|
||||
String value = "bubu";
|
||||
MappingMongoConverter converterSpy = spy(converter);
|
||||
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
|
||||
|
||||
BasicDBObject dbo = new BasicDBObject();
|
||||
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
|
||||
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
|
||||
converterSpy.write(lazyDbRefs, dbo);
|
||||
|
||||
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
|
||||
|
||||
assertThat(result.dbRefToPlainObject, is(notNullValue()));
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, false);
|
||||
|
||||
// calling Object#toString does not initialize the proxy.
|
||||
String proxyString = result.dbRefToPlainObject.toString();
|
||||
assertThat(proxyString, is("lazyDbRefTarget" + ":" + id + "$LazyLoadingProxy"));
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, false);
|
||||
|
||||
// calling another method not declared on object triggers proxy initialization.
|
||||
assertThat(result.dbRefToPlainObject.getValue(), is(value));
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-884
|
||||
*/
|
||||
@Test
|
||||
public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
|
||||
|
||||
String id = "42";
|
||||
String value = "bubu";
|
||||
MappingMongoConverter converterSpy = spy(converter);
|
||||
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
|
||||
|
||||
BasicDBObject dbo = new BasicDBObject();
|
||||
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
|
||||
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
|
||||
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
|
||||
converterSpy.write(lazyDbRefs, dbo);
|
||||
|
||||
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
|
||||
|
||||
assertThat(result.dbRefToPlainObject, is(notNullValue()));
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, false);
|
||||
|
||||
assertThat(result.dbRefToPlainObject, is(equalTo(result.dbRefToPlainObject)));
|
||||
assertThat(result.dbRefToPlainObject, is(not(equalTo(null))));
|
||||
assertThat(result.dbRefToPlainObject, is(not(equalTo((Object) lazyDbRefs.dbRefToToStringObjectMethodOverride))));
|
||||
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-884
|
||||
*/
|
||||
@Test
|
||||
public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
|
||||
|
||||
String id = "42";
|
||||
String value = "bubu";
|
||||
MappingMongoConverter converterSpy = spy(converter);
|
||||
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
|
||||
|
||||
BasicDBObject dbo = new BasicDBObject();
|
||||
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
|
||||
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
|
||||
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
|
||||
converterSpy.write(lazyDbRefs, dbo);
|
||||
|
||||
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
|
||||
|
||||
assertThat(result.dbRefToPlainObject, is(notNullValue()));
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, false);
|
||||
|
||||
assertThat(result.dbRefToPlainObject.hashCode(), is(311365444));
|
||||
|
||||
assertProxyIsResolved(result.dbRefToPlainObject, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-884
|
||||
*/
|
||||
@@ -513,6 +600,7 @@ public class DbRefMappingMongoConverterUnitTests {
|
||||
|
||||
static class WithObjectMethodOverrideLazyDbRefs {
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) LazyDbRefTarget dbRefToPlainObject;
|
||||
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) ToStringObjectMethodOverrideLazyDbRefTarget dbRefToToStringObjectMethodOverride;
|
||||
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride2;
|
||||
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride1;
|
||||
|
||||
Reference in New Issue
Block a user