Skip to content

Commit c269271

Browse files
author
Thomas Darimont
committed
DATAMONGO-884 - Improved handling for Object methods in LazyLoadingInterceptor.
We now handle invocations of equals/hashcode/toString Object methods that are not overridden with custom proxy aware logic. This avoids potentially NPEs and makes it easier to debug code that deals with proxies (due to a proper toString representation of a proxy). Original pull request: #158.
1 parent 1917b6b commit c269271

File tree

3 files changed

+160
-3
lines changed

3 files changed

+160
-3
lines changed

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

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
4242
import org.springframework.util.Assert;
4343
import org.springframework.util.ClassUtils;
44+
import org.springframework.util.ReflectionUtils;
4445
import org.springframework.util.StringUtils;
4546

4647
import com.mongodb.DB;
@@ -231,7 +232,18 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
231232
}
232233

233234
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
234-
return method.invoke(proxy, args);
235+
236+
if (ReflectionUtils.isToStringMethod(method)) {
237+
return proxyToString(proxy);
238+
}
239+
240+
if (ReflectionUtils.isEqualsMethod(method)) {
241+
return proxyEquals(proxy, args[0]);
242+
}
243+
244+
if (ReflectionUtils.isHashCodeMethod(method)) {
245+
return proxyHashCode(proxy);
246+
}
235247
}
236248

237249
Object target = ensureResolved();
@@ -243,6 +255,56 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
243255
return method.invoke(target, args);
244256
}
245257

258+
/**
259+
* Returns a to string representation for the given {@code proxy}.
260+
*
261+
* @param proxy
262+
* @return
263+
*/
264+
private String proxyToString(Object proxy) {
265+
266+
StringBuilder description = new StringBuilder();
267+
if (dbref != null) {
268+
description.append(dbref.getRef());
269+
description.append(":");
270+
description.append(dbref.getId());
271+
} else {
272+
description.append(System.identityHashCode(proxy));
273+
}
274+
275+
return description.append("$").append(LazyLoadingProxy.class.getSimpleName()).toString();
276+
}
277+
278+
/**
279+
* Returns the hashcode for the given {@code proxy}.
280+
*
281+
* @param proxy
282+
* @return
283+
*/
284+
public int proxyHashCode(Object proxy) {
285+
return proxyToString(proxy).hashCode();
286+
}
287+
288+
/**
289+
* Performs an equality check for the given {@code proxy}.
290+
*
291+
* @param proxy
292+
* @param that
293+
* @return
294+
*/
295+
public boolean proxyEquals(Object proxy, Object that) {
296+
297+
if (!(that instanceof LazyLoadingProxy)) {
298+
return false;
299+
}
300+
301+
if (that == proxy) {
302+
return true;
303+
}
304+
305+
return proxyToString(proxy).equals(that.toString());
306+
}
307+
246308
/**
247309
* Will trigger the resolution if the proxy is not resolved already or return a previously resolved result.
248310
*

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.springframework.data.mongodb.core.convert.CustomConversions;
6262
import org.springframework.data.mongodb.core.convert.DbRefResolver;
6363
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
64+
import org.springframework.data.mongodb.core.convert.LazyLoadingProxy;
6465
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
6566
import org.springframework.data.mongodb.core.index.Index;
6667
import org.springframework.data.mongodb.core.index.Index.Duplicates;
@@ -2550,6 +2551,35 @@ public void savingAndReassigningLazyLoadingProxies() {
25502551
assertThat(savedMessage.normalContent.text, is(content.text));
25512552
}
25522553

2554+
/**
2555+
* @see DATAMONGO-884
2556+
*/
2557+
@Test
2558+
public void callingNonObjectMethodsOnLazyLoadingProxyShouldReturnNullIfUnderlyingDbrefWasDeletedInbetween() {
2559+
2560+
template.dropCollection(SomeTemplate.class);
2561+
template.dropCollection(SomeContent.class);
2562+
2563+
SomeContent content = new SomeContent();
2564+
content.id = "C1";
2565+
content.text = "BUBU";
2566+
template.save(content);
2567+
2568+
SomeTemplate tmpl = new SomeTemplate();
2569+
tmpl.id = "T1";
2570+
tmpl.content = content; // @DBRef(lazy=true) tmpl.content
2571+
2572+
template.save(tmpl);
2573+
2574+
SomeTemplate savedTmpl = template.findById(tmpl.id, SomeTemplate.class);
2575+
2576+
template.remove(content);
2577+
2578+
assertThat(savedTmpl.getContent().toString(), is("someContent:C1$LazyLoadingProxy"));
2579+
assertThat(savedTmpl.getContent(), is(instanceOf(LazyLoadingProxy.class)));
2580+
assertThat(savedTmpl.getContent().getText(), is(nullValue()));
2581+
}
2582+
25532583
static class DocumentWithDBRefCollection {
25542584

25552585
@Id public String id;
@@ -2730,7 +2760,7 @@ static class ObjectWithEnumValue {
27302760
EnumValue value;
27312761
}
27322762

2733-
static class SomeTemplate {
2763+
public static class SomeTemplate {
27342764

27352765
String id;
27362766
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) SomeContent content;
@@ -2740,10 +2770,14 @@ public SomeContent getContent() {
27402770
}
27412771
}
27422772

2743-
static class SomeContent {
2773+
public static class SomeContent {
27442774

27452775
String id;
27462776
String text;
2777+
2778+
public String getText() {
2779+
return text;
2780+
}
27472781
}
27482782

27492783
static class SomeMessage {

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,66 @@ public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() {
310310
assertProxyIsResolved(result.dbRefToToStringObjectMethodOverride, true);
311311
}
312312

313+
/**
314+
* @see DATAMONGO-884
315+
*/
316+
@Test
317+
public void callingToStringObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
318+
319+
String id = "42";
320+
String value = "bubu";
321+
MappingMongoConverter converterSpy = spy(converter);
322+
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
323+
324+
BasicDBObject dbo = new BasicDBObject();
325+
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
326+
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
327+
converterSpy.write(lazyDbRefs, dbo);
328+
329+
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
330+
331+
assertThat(result.dbRefToPlainObject, is(notNullValue()));
332+
assertProxyIsResolved(result.dbRefToPlainObject, false);
333+
334+
// calling Object#toString does not initialize the proxy.
335+
String proxyString = result.dbRefToPlainObject.toString();
336+
assertThat(proxyString, is("lazyDbRefTarget" + ":" + id + "$LazyLoadingProxy"));
337+
assertProxyIsResolved(result.dbRefToPlainObject, false);
338+
339+
// calling another method not declared on object triggers proxy initialization.
340+
assertThat(result.dbRefToPlainObject.getValue(), is(value));
341+
assertProxyIsResolved(result.dbRefToPlainObject, true);
342+
}
343+
344+
/**
345+
* @see DATAMONGO-884
346+
*/
347+
@Test
348+
public void equalsObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
349+
350+
String id = "42";
351+
String value = "bubu";
352+
MappingMongoConverter converterSpy = spy(converter);
353+
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
354+
355+
BasicDBObject dbo = new BasicDBObject();
356+
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
357+
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
358+
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
359+
converterSpy.write(lazyDbRefs, dbo);
360+
361+
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
362+
363+
assertThat(result.dbRefToPlainObject, is(notNullValue()));
364+
assertProxyIsResolved(result.dbRefToPlainObject, false);
365+
366+
assertThat(result.dbRefToPlainObject, is(equalTo(result.dbRefToPlainObject)));
367+
assertThat(result.dbRefToPlainObject, is(not(equalTo(null))));
368+
assertThat(result.dbRefToPlainObject, is(not(equalTo((Object) lazyDbRefs.dbRefToToStringObjectMethodOverride))));
369+
370+
assertProxyIsResolved(result.dbRefToPlainObject, false);
371+
}
372+
313373
/**
314374
* @see DATAMONGO-884
315375
*/
@@ -513,6 +573,7 @@ public boolean equals(Object obj) {
513573

514574
static class WithObjectMethodOverrideLazyDbRefs {
515575

576+
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) LazyDbRefTarget dbRefToPlainObject;
516577
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) ToStringObjectMethodOverrideLazyDbRefTarget dbRefToToStringObjectMethodOverride;
517578
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride2;
518579
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride1;

0 commit comments

Comments
 (0)