Skip to content

Commit 2026cd3

Browse files
committed
DATAMONGO-1276 - Fixed potential NullPointerExceptions in MongoTemplate.
Triggering data access exception translation could lead to NullPointerException in cases where PersistenceExceptionTranslator returned null because the original exception couldn't be translated and the result was directly used from a throw clause. This is now fixed by consistently the potentiallyConvertRuntimeException(…) method, which was made static to be able to refer to it from nested static classes. Refactored Scanner usage to actually close the Scanner instance to prevent a resource leak.
1 parent 4e8fdb9 commit 2026cd3

File tree

2 files changed

+119
-27
lines changed

2 files changed

+119
-27
lines changed

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

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ public CloseableIterator<T> doInCollection(DBCollection collection) throws Mongo
340340

341341
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType);
342342

343-
return new CloseableIterableCusorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
343+
return new CloseableIterableCursorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
344344
}
345345
});
346346
}
@@ -444,7 +444,7 @@ public <T> T execute(DbCallback<T> action) {
444444
DB db = this.getDb();
445445
return action.doInDB(db);
446446
} catch (RuntimeException e) {
447-
throw potentiallyConvertRuntimeException(e);
447+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
448448
}
449449
}
450450

@@ -460,7 +460,7 @@ public <T> T execute(String collectionName, CollectionCallback<T> callback) {
460460
DBCollection collection = getAndPrepareCollection(getDb(), collectionName);
461461
return callback.doInCollection(collection);
462462
} catch (RuntimeException e) {
463-
throw potentiallyConvertRuntimeException(e);
463+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
464464
}
465465
}
466466

@@ -1546,10 +1546,17 @@ protected String replaceWithResourceIfNecessary(String function) {
15461546
throw new InvalidDataAccessApiUsageException(String.format("Resource %s not found!", function));
15471547
}
15481548

1549+
Scanner scanner = null;
1550+
15491551
try {
1550-
return new Scanner(functionResource.getInputStream()).useDelimiter("\\A").next();
1552+
scanner = new Scanner(functionResource.getInputStream());
1553+
return scanner.useDelimiter("\\A").next();
15511554
} catch (IOException e) {
15521555
throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e);
1556+
} finally {
1557+
if (scanner != null) {
1558+
scanner.close();
1559+
}
15531560
}
15541561
}
15551562

@@ -1812,7 +1819,7 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) {
18121819
prepareCollection(collection);
18131820
return collection;
18141821
} catch (RuntimeException e) {
1815-
throw potentiallyConvertRuntimeException(e);
1822+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
18161823
}
18171824
}
18181825

@@ -1838,7 +1845,7 @@ private <T> T executeFindOneInternal(CollectionCallback<DBObject> collectionCall
18381845
collectionName)));
18391846
return result;
18401847
} catch (RuntimeException e) {
1841-
throw potentiallyConvertRuntimeException(e);
1848+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
18421849
}
18431850
}
18441851

@@ -1891,7 +1898,7 @@ private <T> List<T> executeFindMultiInternal(CollectionCallback<DBCursor> collec
18911898
}
18921899
}
18931900
} catch (RuntimeException e) {
1894-
throw potentiallyConvertRuntimeException(e);
1901+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
18951902
}
18961903
}
18971904

@@ -1921,7 +1928,7 @@ private void executeQueryInternal(CollectionCallback<DBCursor> collectionCallbac
19211928
}
19221929

19231930
} catch (RuntimeException e) {
1924-
throw potentiallyConvertRuntimeException(e);
1931+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
19251932
}
19261933
}
19271934

@@ -2000,18 +2007,6 @@ protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject quer
20002007
}
20012008
}
20022009

2003-
/**
2004-
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
2005-
* exception if the conversation failed. Thus allows safe rethrowing of the return value.
2006-
*
2007-
* @param ex
2008-
* @return
2009-
*/
2010-
private RuntimeException potentiallyConvertRuntimeException(RuntimeException ex) {
2011-
RuntimeException resolved = this.exceptionTranslator.translateExceptionIfPossible(ex);
2012-
return resolved == null ? ex : resolved;
2013-
}
2014-
20152010
/**
20162011
* Inspects the given {@link CommandResult} for erros and potentially throws an
20172012
* {@link InvalidDataAccessApiUsageException} for that error.
@@ -2050,6 +2045,20 @@ private DBObject getMappedSortObject(Query query, Class<?> type) {
20502045
return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type));
20512046
}
20522047

2048+
/**
2049+
* Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original
2050+
* exception if the conversation failed. Thus allows safe re-throwing of the return value.
2051+
*
2052+
* @param ex the exception to translate
2053+
* @param exceptionTranslator the {@link PersistenceExceptionTranslator} to be used for translation
2054+
* @return
2055+
*/
2056+
private static RuntimeException potentiallyConvertRuntimeException(RuntimeException ex,
2057+
PersistenceExceptionTranslator exceptionTranslator) {
2058+
RuntimeException resolved = exceptionTranslator.translateExceptionIfPossible(ex);
2059+
return resolved == null ? ex : resolved;
2060+
}
2061+
20532062
// Callback implementations
20542063

20552064
/**
@@ -2292,7 +2301,7 @@ public DBCursor prepare(DBCursor cursor) {
22922301
}
22932302

22942303
} catch (RuntimeException e) {
2295-
throw potentiallyConvertRuntimeException(e);
2304+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
22962305
}
22972306

22982307
return cursorToUse;
@@ -2339,20 +2348,20 @@ public GeoResult<T> doWith(DBObject object) {
23392348
* @since 1.7
23402349
* @author Thomas Darimont
23412350
*/
2342-
static class CloseableIterableCusorAdapter<T> implements CloseableIterator<T> {
2351+
static class CloseableIterableCursorAdapter<T> implements CloseableIterator<T> {
23432352

23442353
private volatile Cursor cursor;
23452354
private PersistenceExceptionTranslator exceptionTranslator;
23462355
private DbObjectCallback<T> objectReadCallback;
23472356

23482357
/**
2349-
* Creates a new {@link CloseableIterableCusorAdapter} backed by the given {@link Cursor}.
2358+
* Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}.
23502359
*
23512360
* @param cursor
23522361
* @param exceptionTranslator
23532362
* @param objectReadCallback
23542363
*/
2355-
public CloseableIterableCusorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator,
2364+
public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator,
23562365
DbObjectCallback<T> objectReadCallback) {
23572366

23582367
this.cursor = cursor;
@@ -2370,7 +2379,7 @@ public boolean hasNext() {
23702379
try {
23712380
return cursor.hasNext();
23722381
} catch (RuntimeException ex) {
2373-
throw exceptionTranslator.translateExceptionIfPossible(ex);
2382+
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
23742383
}
23752384
}
23762385

@@ -2386,7 +2395,7 @@ public T next() {
23862395
T converted = objectReadCallback.doWith(item);
23872396
return converted;
23882397
} catch (RuntimeException ex) {
2389-
throw exceptionTranslator.translateExceptionIfPossible(ex);
2398+
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
23902399
}
23912400
}
23922401

@@ -2397,7 +2406,7 @@ public void close() {
23972406
try {
23982407
c.close();
23992408
} catch (RuntimeException ex) {
2400-
throw exceptionTranslator.translateExceptionIfPossible(ex);
2409+
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
24012410
} finally {
24022411
cursor = null;
24032412
exceptionTranslator = null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core;
17+
18+
import static org.mockito.Mockito.*;
19+
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
import org.mockito.Mock;
24+
import org.mockito.runners.MockitoJUnitRunner;
25+
import org.springframework.dao.support.PersistenceExceptionTranslator;
26+
import org.springframework.data.mongodb.core.MongoTemplate.CloseableIterableCursorAdapter;
27+
import org.springframework.data.mongodb.core.MongoTemplate.DbObjectCallback;
28+
import org.springframework.data.util.CloseableIterator;
29+
30+
import com.mongodb.Cursor;
31+
32+
/**
33+
* Unit tests for {@link CloseableIterableCursorAdapter}.
34+
*
35+
* @author Oliver Gierke
36+
* @see DATAMONGO-1276
37+
*/
38+
@RunWith(MockitoJUnitRunner.class)
39+
public class CloseableIterableCursorAdapterUnitTests {
40+
41+
@Mock PersistenceExceptionTranslator exceptionTranslator;
42+
@Mock DbObjectCallback<Object> callback;
43+
44+
Cursor cursor;
45+
CloseableIterator<Object> adapter;
46+
47+
@Before
48+
public void setUp() {
49+
50+
this.cursor = doThrow(IllegalArgumentException.class).when(mock(Cursor.class));
51+
this.adapter = new CloseableIterableCursorAdapter<Object>(cursor, exceptionTranslator, callback);
52+
}
53+
54+
/**
55+
* @see DATAMONGO-1276
56+
*/
57+
@Test(expected = IllegalArgumentException.class)
58+
public void propagatesOriginalExceptionFromAdapterDotNext() {
59+
60+
cursor.next();
61+
adapter.next();
62+
}
63+
64+
/**
65+
* @see DATAMONGO-1276
66+
*/
67+
@Test(expected = IllegalArgumentException.class)
68+
public void propagatesOriginalExceptionFromAdapterDotHasNext() {
69+
70+
cursor.hasNext();
71+
adapter.hasNext();
72+
}
73+
74+
/**
75+
* @see DATAMONGO-1276
76+
*/
77+
@Test(expected = IllegalArgumentException.class)
78+
public void propagatesOriginalExceptionFromAdapterDotClose() {
79+
80+
cursor.close();
81+
adapter.close();
82+
}
83+
}

0 commit comments

Comments
 (0)