Skip to content

Commit f1354c4

Browse files
committed
Revise DocumentCallback nullability constraints.
DocumentCallback is now generally non-nullable for both, the input Document and the returned result expecting EntityReader to always return a non-null object. Also, use try-with-resources where applicable. Closes #3648
1 parent ff7588f commit f1354c4

File tree

5 files changed

+97
-84
lines changed

5 files changed

+97
-84
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

+38-68
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.bson.conversions.Bson;
2929
import org.slf4j.Logger;
3030
import org.slf4j.LoggerFactory;
31+
3132
import org.springframework.beans.BeansException;
3233
import org.springframework.context.ApplicationContext;
3334
import org.springframework.context.ApplicationContextAware;
@@ -46,6 +47,7 @@
4647
import org.springframework.data.geo.GeoResult;
4748
import org.springframework.data.geo.GeoResults;
4849
import org.springframework.data.geo.Metric;
50+
import org.springframework.data.mapping.MappingException;
4951
import org.springframework.data.mapping.callback.EntityCallbacks;
5052
import org.springframework.data.mapping.context.MappingContext;
5153
import org.springframework.data.mongodb.MongoDatabaseFactory;
@@ -102,7 +104,6 @@
102104
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
103105
import org.springframework.data.util.CloseableIterator;
104106
import org.springframework.data.util.Optionals;
105-
import org.springframework.jca.cci.core.ConnectionCallback;
106107
import org.springframework.lang.Nullable;
107108
import org.springframework.util.Assert;
108109
import org.springframework.util.ClassUtils;
@@ -972,7 +973,7 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String col
972973
for (Document element : results) {
973974

974975
GeoResult<T> geoResult = callback.doWith(element);
975-
aggregate = aggregate.add(new BigDecimal(geoResult.getDistance().getValue()));
976+
aggregate = aggregate.add(BigDecimal.valueOf(geoResult.getDistance().getValue()));
976977
result.add(geoResult);
977978
}
978979

@@ -2751,25 +2752,24 @@ private MongoCollection<Document> getAndPrepareCollection(MongoDatabase db, Stri
27512752
* Internal method using callbacks to do queries against the datastore that requires reading a single object from a
27522753
* collection of objects. It will take the following steps
27532754
* <ol>
2754-
* <li>Execute the given {@link ConnectionCallback} for a {@link Document}.</li>
2755+
* <li>Execute the given {@link CollectionCallback} for a {@link Document}.</li>
27552756
* <li>Apply the given {@link DocumentCallback} to each of the {@link Document}s to obtain the result.</li>
27562757
* <ol>
27572758
*
27582759
* @param <T>
27592760
* @param collectionCallback the callback to retrieve the {@link Document} with
2760-
* @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
2761+
* @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
27612762
* @param collectionName the collection to be queried
27622763
* @return
27632764
*/
27642765
@Nullable
27652766
private <T> T executeFindOneInternal(CollectionCallback<Document> collectionCallback,
2766-
DocumentCallback<T> objectCallback, String collectionName) {
2767+
DocumentCallback<T> documentCallback, String collectionName) {
27672768

27682769
try {
27692770

2770-
T result = objectCallback
2771-
.doWith(collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName)));
2772-
return result;
2771+
Document document = collectionCallback.doInCollection(getAndPrepareCollection(doGetDatabase(), collectionName));
2772+
return document != null ? documentCallback.doWith(document) : null;
27732773
} catch (RuntimeException e) {
27742774
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
27752775
}
@@ -2779,7 +2779,7 @@ private <T> T executeFindOneInternal(CollectionCallback<Document> collectionCall
27792779
* Internal method using callback to do queries against the datastore that requires reading a collection of objects.
27802780
* It will take the following steps
27812781
* <ol>
2782-
* <li>Execute the given {@link ConnectionCallback} for a {@link FindIterable}.</li>
2782+
* <li>Execute the given {@link CollectionCallback} for a {@link FindIterable}.</li>
27832783
* <li>Prepare that {@link FindIterable} with the given {@link CursorPreparer} (will be skipped if
27842784
* {@link CursorPreparer} is {@literal null}</li>
27852785
* <li>Iterate over the {@link FindIterable} and applies the given {@link DocumentCallback} to each of the
@@ -2789,36 +2789,27 @@ private <T> T executeFindOneInternal(CollectionCallback<Document> collectionCall
27892789
* @param <T>
27902790
* @param collectionCallback the callback to retrieve the {@link FindIterable} with
27912791
* @param preparer the {@link CursorPreparer} to potentially modify the {@link FindIterable} before iterating over it
2792-
* @param objectCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
2792+
* @param documentCallback the {@link DocumentCallback} to transform {@link Document}s into the actual domain type
27932793
* @param collectionName the collection to be queried
27942794
* @return
27952795
*/
27962796
private <T> List<T> executeFindMultiInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
2797-
CursorPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) {
2797+
CursorPreparer preparer, DocumentCallback<T> documentCallback, String collectionName) {
27982798

27992799
try {
28002800

2801-
MongoCursor<Document> cursor = null;
2802-
2803-
try {
2804-
2805-
cursor = preparer
2806-
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
2807-
.iterator();
2801+
try (MongoCursor<Document> cursor = preparer
2802+
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
2803+
.iterator()) {
28082804

28092805
List<T> result = new ArrayList<>();
28102806

28112807
while (cursor.hasNext()) {
28122808
Document object = cursor.next();
2813-
result.add(objectCallback.doWith(object));
2809+
result.add(documentCallback.doWith(object));
28142810
}
28152811

28162812
return result;
2817-
} finally {
2818-
2819-
if (cursor != null) {
2820-
cursor.close();
2821-
}
28222813
}
28232814
} catch (RuntimeException e) {
28242815
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
@@ -2828,24 +2819,13 @@ private <T> List<T> executeFindMultiInternal(CollectionCallback<FindIterable<Doc
28282819
private void executeQueryInternal(CollectionCallback<FindIterable<Document>> collectionCallback,
28292820
CursorPreparer preparer, DocumentCallbackHandler callbackHandler, String collectionName) {
28302821

2831-
try {
2832-
2833-
MongoCursor<Document> cursor = null;
2834-
2835-
try {
2836-
2837-
cursor = preparer
2838-
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
2839-
.iterator();
2822+
try (MongoCursor<Document> cursor = preparer
2823+
.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection)
2824+
.iterator()) {
28402825

28412826
while (cursor.hasNext()) {
28422827
callbackHandler.processDocument(cursor.next());
28432828
}
2844-
} finally {
2845-
if (cursor != null) {
2846-
cursor.close();
2847-
}
2848-
}
28492829
} catch (RuntimeException e) {
28502830
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
28512831
}
@@ -3143,8 +3123,7 @@ public Document doInCollection(MongoCollection<Document> collection) throws Mong
31433123

31443124
interface DocumentCallback<T> {
31453125

3146-
@Nullable
3147-
T doWith(@Nullable Document object);
3126+
T doWith(Document object);
31483127
}
31493128

31503129
/**
@@ -3168,22 +3147,19 @@ private class ReadDocumentCallback<T> implements DocumentCallback<T> {
31683147
this.collectionName = collectionName;
31693148
}
31703149

3171-
@Nullable
3172-
public T doWith(@Nullable Document document) {
3173-
3174-
T source = null;
3150+
public T doWith(Document document) {
31753151

3176-
if (document != null) {
31773152
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
3178-
source = reader.read(type, document);
3179-
}
3153+
T entity = reader.read(type, document);
31803154

3181-
if (source != null) {
3182-
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
3183-
source = maybeCallAfterConvert(source, document, collectionName);
3184-
}
3155+
if (entity == null) {
3156+
throw new MappingException(String.format("EntityReader %s returned null", reader));
3157+
}
31853158

3186-
return source;
3159+
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
3160+
entity = maybeCallAfterConvert(entity, document, collectionName);
3161+
3162+
return entity;
31873163
}
31883164
}
31893165

@@ -3216,8 +3192,7 @@ private class ProjectingReadCallback<S, T> implements DocumentCallback<T> {
32163192
* @see org.springframework.data.mongodb.core.MongoTemplate.DocumentCallback#doWith(org.bson.Document)
32173193
*/
32183194
@SuppressWarnings("unchecked")
3219-
@Nullable
3220-
public T doWith(@Nullable Document document) {
3195+
public T doWith(Document document) {
32213196

32223197
if (document == null) {
32233198
return null;
@@ -3228,15 +3203,16 @@ public T doWith(@Nullable Document document) {
32283203

32293204
maybeEmitEvent(new AfterLoadEvent<>(document, targetType, collectionName));
32303205

3231-
Object source = reader.read(typeToRead, document);
3232-
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
3206+
Object entity = reader.read(typeToRead, document);
32333207

3234-
if (result != null) {
3235-
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
3236-
result = maybeCallAfterConvert(result, document, collectionName);
3208+
if (entity == null) {
3209+
throw new MappingException(String.format("EntityReader %s returned null", reader));
32373210
}
32383211

3239-
return (T) result;
3212+
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
3213+
3214+
maybeEmitEvent(new AfterConvertEvent<>(document, result, collectionName));
3215+
return (T) maybeCallAfterConvert(result, document, collectionName);
32403216
}
32413217
}
32423218

@@ -3373,8 +3349,7 @@ static class GeoNearResultDocumentCallback<T> implements DocumentCallback<GeoRes
33733349
this.metric = metric;
33743350
}
33753351

3376-
@Nullable
3377-
public GeoResult<T> doWith(@Nullable Document object) {
3352+
public GeoResult<T> doWith(Document object) {
33783353

33793354
double distance = Double.NaN;
33803355
if (object.containsKey(distanceField)) {
@@ -3401,10 +3376,6 @@ static class CloseableIterableCursorAdapter<T> implements CloseableIterator<T> {
34013376

34023377
/**
34033378
* Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link MongoCollection}.
3404-
*
3405-
* @param cursor
3406-
* @param exceptionTranslator
3407-
* @param objectReadCallback
34083379
*/
34093380
CloseableIterableCursorAdapter(MongoIterable<Document> cursor, PersistenceExceptionTranslator exceptionTranslator,
34103381
DocumentCallback<T> objectReadCallback) {
@@ -3448,8 +3419,7 @@ public T next() {
34483419

34493420
try {
34503421
Document item = cursor.next();
3451-
T converted = objectReadCallback.doWith(item);
3452-
return converted;
3422+
return objectReadCallback.doWith(item);
34533423
} catch (RuntimeException ex) {
34543424
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
34553425
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

+25-13
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@
2222
import reactor.util.function.Tuple2;
2323
import reactor.util.function.Tuples;
2424

25-
import java.util.*;
25+
import java.util.ArrayList;
26+
import java.util.Arrays;
27+
import java.util.Collection;
28+
import java.util.Collections;
29+
import java.util.HashMap;
30+
import java.util.Iterator;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Optional;
2634
import java.util.concurrent.TimeUnit;
2735
import java.util.function.Consumer;
2836
import java.util.function.Function;
@@ -36,6 +44,7 @@
3644
import org.reactivestreams.Subscriber;
3745
import org.slf4j.Logger;
3846
import org.slf4j.LoggerFactory;
47+
3948
import org.springframework.beans.BeansException;
4049
import org.springframework.context.ApplicationContext;
4150
import org.springframework.context.ApplicationContextAware;
@@ -51,6 +60,7 @@
5160
import org.springframework.data.geo.Distance;
5261
import org.springframework.data.geo.GeoResult;
5362
import org.springframework.data.geo.Metric;
63+
import org.springframework.data.mapping.MappingException;
5464
import org.springframework.data.mapping.PersistentEntity;
5565
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
5666
import org.springframework.data.mapping.context.MappingContext;
@@ -3152,13 +3162,14 @@ public Mono<T> doWith(Document document) {
31523162

31533163
maybeEmitEvent(new AfterLoadEvent<>(document, type, collectionName));
31543164

3155-
T source = reader.read(type, document);
3156-
if (source != null) {
3157-
maybeEmitEvent(new AfterConvertEvent<>(document, source, collectionName));
3158-
return maybeCallAfterConvert(source, document, collectionName);
3165+
T entity = reader.read(type, document);
3166+
3167+
if (entity == null) {
3168+
throw new MappingException(String.format("EntityReader %s returned null", reader));
31593169
}
31603170

3161-
return Mono.empty();
3171+
maybeEmitEvent(new AfterConvertEvent<>(document, entity, collectionName));
3172+
return maybeCallAfterConvert(entity, document, collectionName);
31623173
}
31633174
}
31643175

@@ -3196,16 +3207,17 @@ public Mono<T> doWith(Document document) {
31963207

31973208
maybeEmitEvent(new AfterLoadEvent<>(document, typeToRead, collectionName));
31983209

3199-
Object source = reader.read(typeToRead, document);
3200-
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, source) : source;
3210+
Object entity = reader.read(typeToRead, document);
32013211

3202-
T castEntity = (T) result;
3203-
if (castEntity != null) {
3204-
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
3205-
return maybeCallAfterConvert(castEntity, document, collectionName);
3212+
if (entity == null) {
3213+
throw new MappingException(String.format("EntityReader %s returned null", reader));
32063214
}
32073215

3208-
return Mono.empty();
3216+
Object result = targetType.isInterface() ? projectionFactory.createProjection(targetType, entity) : entity;
3217+
3218+
T castEntity = (T) result;
3219+
maybeEmitEvent(new AfterConvertEvent<>(document, castEntity, collectionName));
3220+
return maybeCallAfterConvert(castEntity, document, collectionName);
32093221
}
32103222
}
32113223

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ public void setEntityCallbacks(EntityCallbacks entityCallbacks) {
270270
* (non-Javadoc)
271271
* @see org.springframework.data.mongodb.core.core.MongoReader#read(java.lang.Class, com.mongodb.Document)
272272
*/
273-
public <S extends Object> S read(Class<S> clazz, final Bson bson) {
273+
public <S extends Object> S read(Class<S> clazz, Bson bson) {
274274
return read(ClassTypeInformation.from(clazz), bson);
275275
}
276276

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.mockito.Mockito;
5151
import org.mockito.junit.jupiter.MockitoSettings;
5252
import org.mockito.quality.Strictness;
53+
5354
import org.springframework.beans.factory.annotation.Value;
5455
import org.springframework.context.ApplicationContext;
5556
import org.springframework.context.ApplicationListener;
@@ -64,8 +65,10 @@
6465
import org.springframework.data.convert.CustomConversions;
6566
import org.springframework.data.domain.Sort;
6667
import org.springframework.data.geo.Point;
68+
import org.springframework.data.mapping.MappingException;
6769
import org.springframework.data.mapping.callback.EntityCallbacks;
6870
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
71+
import org.springframework.data.mapping.context.MappingContext;
6972
import org.springframework.data.mongodb.MongoDatabaseFactory;
7073
import org.springframework.data.mongodb.core.aggregation.*;
7174
import org.springframework.data.mongodb.core.aggregation.ComparisonOperators.Gte;
@@ -394,11 +397,24 @@ void findAllAndRemoveShouldRetrieveMatchingDocumentsPriorToRemoval() {
394397
verify(collection, times(1)).find(Mockito.eq(query.getQueryObject()), any(Class.class));
395398
}
396399

400+
@Test // GH-3648
401+
void shouldThrowExceptionIfEntityReaderReturnsNull() {
402+
403+
when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
404+
when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0)));
405+
MappingMongoConverter converter = mock(MappingMongoConverter.class);
406+
when(converter.getMappingContext()).thenReturn((MappingContext) mappingContext);
407+
template = new MongoTemplate(factory, converter);
408+
409+
assertThatExceptionOfType(MappingException.class).isThrownBy(() -> template.findAll(Person.class))
410+
.withMessageContaining("returned null");
411+
}
412+
397413
@Test // DATAMONGO-566
398414
void findAllAndRemoveShouldRemoveDocumentsReturedByFindQuery() {
399415

400-
Mockito.when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
401-
Mockito.when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0)))
416+
when(cursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
417+
when(cursor.next()).thenReturn(new org.bson.Document("_id", Integer.valueOf(0)))
402418
.thenReturn(new org.bson.Document("_id", Integer.valueOf(1)));
403419

404420
ArgumentCaptor<org.bson.Document> queryCaptor = ArgumentCaptor.forClass(org.bson.Document.class);

0 commit comments

Comments
 (0)