Skip to content

Commit 2cfd478

Browse files
Thomas Darimontodrotbohm
authored andcommitted
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: spring-projects#158.
1 parent 031ab0c commit 2cfd478

File tree

3 files changed

+196
-4
lines changed

3 files changed

+196
-4
lines changed

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

Lines changed: 72 additions & 2 deletions
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;
@@ -230,12 +231,81 @@ public Object intercept(Object obj, Method method, Object[] args, MethodProxy pr
230231
return this.dbref;
231232
}
232233

233-
Object target = isObjectMethod(method) && Object.class.equals(method.getDeclaringClass()) ? obj
234-
: ensureResolved();
234+
if (isObjectMethod(method) && Object.class.equals(method.getDeclaringClass())) {
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+
}
247+
}
248+
249+
Object target = ensureResolved();
250+
251+
if (target == null) {
252+
return null;
253+
}
235254

236255
return method.invoke(target, args);
237256
}
238257

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+
description.append("$").append(LazyLoadingProxy.class.getSimpleName());
275+
276+
return description.toString();
277+
}
278+
279+
/**
280+
* Returns the hashcode for the given {@code proxy}.
281+
*
282+
* @param proxy
283+
* @return
284+
*/
285+
private int proxyHashCode(Object proxy) {
286+
return proxyToString(proxy).hashCode();
287+
}
288+
289+
/**
290+
* Performs an equality check for the given {@code proxy}.
291+
*
292+
* @param proxy
293+
* @param that
294+
* @return
295+
*/
296+
private boolean proxyEquals(Object proxy, Object that) {
297+
298+
if (!(that instanceof LazyLoadingProxy)) {
299+
return false;
300+
}
301+
302+
if (that == proxy) {
303+
return true;
304+
}
305+
306+
return proxyToString(proxy).equals(that.toString());
307+
}
308+
239309
/**
240310
* Will trigger the resolution if the proxy is not resolved already or return a previously resolved result.
241311
*

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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,93 @@ 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+
373+
/**
374+
* @see DATAMONGO-884
375+
*/
376+
@Test
377+
public void hashcodeObjectMethodOnLazyLoadingDbrefShouldNotInitializeProxy() {
378+
379+
String id = "42";
380+
String value = "bubu";
381+
MappingMongoConverter converterSpy = spy(converter);
382+
doReturn(new BasicDBObject("_id", id).append("value", value)).when(converterSpy).readRef((DBRef) any());
383+
384+
BasicDBObject dbo = new BasicDBObject();
385+
WithObjectMethodOverrideLazyDbRefs lazyDbRefs = new WithObjectMethodOverrideLazyDbRefs();
386+
lazyDbRefs.dbRefToPlainObject = new LazyDbRefTarget(id, value);
387+
lazyDbRefs.dbRefToToStringObjectMethodOverride = new ToStringObjectMethodOverrideLazyDbRefTarget(id, value);
388+
converterSpy.write(lazyDbRefs, dbo);
389+
390+
WithObjectMethodOverrideLazyDbRefs result = converterSpy.read(WithObjectMethodOverrideLazyDbRefs.class, dbo);
391+
392+
assertThat(result.dbRefToPlainObject, is(notNullValue()));
393+
assertProxyIsResolved(result.dbRefToPlainObject, false);
394+
395+
assertThat(result.dbRefToPlainObject.hashCode(), is(311365444));
396+
397+
assertProxyIsResolved(result.dbRefToPlainObject, false);
398+
}
399+
313400
/**
314401
* @see DATAMONGO-884
315402
*/
@@ -513,6 +600,7 @@ public boolean equals(Object obj) {
513600

514601
static class WithObjectMethodOverrideLazyDbRefs {
515602

603+
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) LazyDbRefTarget dbRefToPlainObject;
516604
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) ToStringObjectMethodOverrideLazyDbRefTarget dbRefToToStringObjectMethodOverride;
517605
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride2;
518606
@org.springframework.data.mongodb.core.mapping.DBRef(lazy = true) EqualsAndHashCodeObjectMethodOverrideLazyDbRefTarget dbRefEqualsAndHashcodeObjectMethodOverride1;

0 commit comments

Comments
 (0)