Browse Source

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.
pull/158/merge
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
2cfd4781bc
  1. 74
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java
  2. 38
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  3. 88
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java

74
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java

@ -41,6 +41,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; @@ -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 { @@ -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.
*

38
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java

@ -61,6 +61,7 @@ import org.springframework.data.mongodb.MongoDbFactory; @@ -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 { @@ -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 { @@ -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 { @@ -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 {

88
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java

@ -310,6 +310,93 @@ public class DbRefMappingMongoConverterUnitTests { @@ -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 { @@ -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;

Loading…
Cancel
Save