diff --git a/pom.xml b/pom.xml index 28e7260e25..f8d2694aab 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAMONGO-884-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index 69a19c150b..a2caf38f0d 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAMONGO-884-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAMONGO-884-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 7302043a15..9a353bf88f 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAMONGO-884-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index 0415f6eb5e..df0124cc1d 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAMONGO-884-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 6e84732769..03a9d4eaf7 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.5.0.BUILD-SNAPSHOT + 1.5.0.DATAMONGO-884-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java index e518dd8abf..90a6caab77 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultDbRefResolver.java +++ b/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.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 Object intercept(Object obj, Method method, Object[] args, MethodProxy pr 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. * diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java index c8ceb0306f..f3cc79f948 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java @@ -61,6 +61,7 @@ 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 void savingAndReassigningLazyLoadingProxies() { 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 @@ static class ObjectWithEnumValue { 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 SomeContent getContent() { } } - static class SomeContent { + public static class SomeContent { String id; String text; + + public String getText() { + return text; + } } static class SomeMessage { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java index fc9914fd01..04c4a66eaf 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java @@ -310,6 +310,93 @@ public void lazyLoadingProxyForToStringObjectMethodOverridingDbref() { 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 boolean equals(Object obj) { 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;