Skip to content

Commit 528de58

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 e6ea34a commit 528de58

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

+36-27
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public CloseableIterator<T> doInCollection(DBCollection collection) throws Mongo
341341
ReadDbObjectCallback<T> readCallback = new ReadDbObjectCallback<T>(mongoConverter, entityType, collection
342342
.getName());
343343

344-
return new CloseableIterableCusorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
344+
return new CloseableIterableCursorAdapter<T>(cursorPreparer.prepare(cursor), exceptionTranslator, readCallback);
345345
}
346346
});
347347
}
@@ -445,7 +445,7 @@ public <T> T execute(DbCallback<T> action) {
445445
DB db = this.getDb();
446446
return action.doInDB(db);
447447
} catch (RuntimeException e) {
448-
throw potentiallyConvertRuntimeException(e);
448+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
449449
}
450450
}
451451

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

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

1551+
Scanner scanner = null;
1552+
15511553
try {
1552-
return new Scanner(functionResource.getInputStream()).useDelimiter("\\A").next();
1554+
scanner = new Scanner(functionResource.getInputStream());
1555+
return scanner.useDelimiter("\\A").next();
15531556
} catch (IOException e) {
15541557
throw new InvalidDataAccessApiUsageException(String.format("Cannot read map-reduce file %s!", function), e);
1558+
} finally {
1559+
if (scanner != null) {
1560+
scanner.close();
1561+
}
15551562
}
15561563
}
15571564

@@ -1814,7 +1821,7 @@ private DBCollection getAndPrepareCollection(DB db, String collectionName) {
18141821
prepareCollection(collection);
18151822
return collection;
18161823
} catch (RuntimeException e) {
1817-
throw potentiallyConvertRuntimeException(e);
1824+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
18181825
}
18191826
}
18201827

@@ -1840,7 +1847,7 @@ private <T> T executeFindOneInternal(CollectionCallback<DBObject> collectionCall
18401847
collectionName)));
18411848
return result;
18421849
} catch (RuntimeException e) {
1843-
throw potentiallyConvertRuntimeException(e);
1850+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
18441851
}
18451852
}
18461853

@@ -1893,7 +1900,7 @@ private <T> List<T> executeFindMultiInternal(CollectionCallback<DBCursor> collec
18931900
}
18941901
}
18951902
} catch (RuntimeException e) {
1896-
throw potentiallyConvertRuntimeException(e);
1903+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
18971904
}
18981905
}
18991906

@@ -1923,7 +1930,7 @@ private void executeQueryInternal(CollectionCallback<DBCursor> collectionCallbac
19231930
}
19241931

19251932
} catch (RuntimeException e) {
1926-
throw potentiallyConvertRuntimeException(e);
1933+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
19271934
}
19281935
}
19291936

@@ -2002,18 +2009,6 @@ protected void handleAnyWriteResultErrors(WriteResult writeResult, DBObject quer
20022009
}
20032010
}
20042011

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

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

20572066
/**
@@ -2298,7 +2307,7 @@ public DBCursor prepare(DBCursor cursor) {
22982307
}
22992308

23002309
} catch (RuntimeException e) {
2301-
throw potentiallyConvertRuntimeException(e);
2310+
throw potentiallyConvertRuntimeException(e, exceptionTranslator);
23022311
}
23032312

23042313
return cursorToUse;
@@ -2345,20 +2354,20 @@ public GeoResult<T> doWith(DBObject object) {
23452354
* @since 1.7
23462355
* @author Thomas Darimont
23472356
*/
2348-
static class CloseableIterableCusorAdapter<T> implements CloseableIterator<T> {
2357+
static class CloseableIterableCursorAdapter<T> implements CloseableIterator<T> {
23492358

23502359
private volatile Cursor cursor;
23512360
private PersistenceExceptionTranslator exceptionTranslator;
23522361
private DbObjectCallback<T> objectReadCallback;
23532362

23542363
/**
2355-
* Creates a new {@link CloseableIterableCusorAdapter} backed by the given {@link Cursor}.
2364+
* Creates a new {@link CloseableIterableCursorAdapter} backed by the given {@link Cursor}.
23562365
*
23572366
* @param cursor
23582367
* @param exceptionTranslator
23592368
* @param objectReadCallback
23602369
*/
2361-
public CloseableIterableCusorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator,
2370+
public CloseableIterableCursorAdapter(Cursor cursor, PersistenceExceptionTranslator exceptionTranslator,
23622371
DbObjectCallback<T> objectReadCallback) {
23632372

23642373
this.cursor = cursor;
@@ -2376,7 +2385,7 @@ public boolean hasNext() {
23762385
try {
23772386
return cursor.hasNext();
23782387
} catch (RuntimeException ex) {
2379-
throw exceptionTranslator.translateExceptionIfPossible(ex);
2388+
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
23802389
}
23812390
}
23822391

@@ -2392,7 +2401,7 @@ public T next() {
23922401
T converted = objectReadCallback.doWith(item);
23932402
return converted;
23942403
} catch (RuntimeException ex) {
2395-
throw exceptionTranslator.translateExceptionIfPossible(ex);
2404+
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
23962405
}
23972406
}
23982407

@@ -2403,7 +2412,7 @@ public void close() {
24032412
try {
24042413
c.close();
24052414
} catch (RuntimeException ex) {
2406-
throw exceptionTranslator.translateExceptionIfPossible(ex);
2415+
throw potentiallyConvertRuntimeException(ex, exceptionTranslator);
24072416
} finally {
24082417
cursor = null;
24092418
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)