Skip to content

Commit 6255541

Browse files
committed
DATAMONGO-1415 - Translate write concern timeouts to TransientDataAccessResourceException.
We now translate timeouts (caused by replication or write concern timeout) into TransientDataAccessResourceException.
1 parent f4a5482 commit 6255541

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2010-2015 the original author or authors.
2+
* Copyright 2010-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.springframework.dao.InvalidDataAccessApiUsageException;
2727
import org.springframework.dao.InvalidDataAccessResourceUsageException;
2828
import org.springframework.dao.PermissionDeniedDataAccessException;
29+
import org.springframework.dao.TransientDataAccessResourceException;
2930
import org.springframework.dao.support.PersistenceExceptionTranslator;
3031
import org.springframework.data.mongodb.BulkOperationException;
3132
import org.springframework.data.mongodb.UncategorizedMongoDbException;
@@ -34,6 +35,7 @@
3435

3536
import com.mongodb.BulkWriteException;
3637
import com.mongodb.MongoException;
38+
import com.mongodb.WriteConcernException;
3739

3840
/**
3941
* Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
@@ -43,6 +45,7 @@
4345
* @author Oliver Gierke
4446
* @author Michal Vich
4547
* @author Christoph Strobl
48+
* @author Mark Paluch
4649
*/
4750
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
4851

@@ -65,6 +68,12 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
6568
*/
6669
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
6770

71+
// Check for a timeout exception
72+
73+
if (ex instanceof WriteConcernException && ReflectiveWriteResultInvoker.wasTimeout((WriteConcernException) ex)) {
74+
return new TransientDataAccessResourceException(ex.getMessage(), ex);
75+
}
76+
6877
// Check for well-known MongoException subclasses.
6978

7079
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015 the original author or authors.
2+
* Copyright 2015-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,8 +19,10 @@
1919
import static org.springframework.util.ReflectionUtils.*;
2020

2121
import java.lang.reflect.Method;
22+
import java.util.Map;
2223

2324
import com.mongodb.MongoException;
25+
import com.mongodb.WriteConcernException;
2426
import com.mongodb.WriteResult;
2527

2628
/**
@@ -29,19 +31,24 @@
2931
*
3032
* @author Christoph Strobl
3133
* @author Oliver Gierke
34+
* @author Mark Paluch
3235
* @since 1.7
3336
*/
3437
final class ReflectiveWriteResultInvoker {
3538

3639
private static final Method GET_ERROR_METHOD;
3740
private static final Method WAS_ACKNOWLEDGED_METHOD;
41+
private static final Method GET_RESPONSE;
42+
private static final Method GET_COMMAND_RESULT;
3843

3944
private ReflectiveWriteResultInvoker() {}
4045

4146
static {
4247

4348
GET_ERROR_METHOD = findMethod(WriteResult.class, "getError");
4449
WAS_ACKNOWLEDGED_METHOD = findMethod(WriteResult.class, "wasAcknowledged");
50+
GET_RESPONSE = findMethod(WriteConcernException.class, "getResponse");
51+
GET_COMMAND_RESULT = findMethod(WriteConcernException.class, "getCommandResult");
4552
}
4653

4754
/**
@@ -64,4 +71,29 @@ public static String getError(WriteResult writeResult) {
6471
public static boolean wasAcknowledged(WriteResult writeResult) {
6572
return isMongo3Driver() ? ((Boolean) invokeMethod(WAS_ACKNOWLEDGED_METHOD, writeResult)).booleanValue() : true;
6673
}
74+
75+
/**
76+
* @param writeConcernException
77+
* @return return {@literal true} if the {@link WriteConcernException} indicates a write concern timeout as reason
78+
* @since 1.10
79+
*/
80+
@SuppressWarnings("unchecked")
81+
public static boolean wasTimeout(WriteConcernException writeConcernException) {
82+
83+
Map<Object, Object> response;
84+
if (isMongo3Driver()) {
85+
response = (Map<Object, Object>) invokeMethod(GET_RESPONSE, writeConcernException);
86+
} else {
87+
response = (Map<Object, Object>) invokeMethod(GET_COMMAND_RESULT, writeConcernException);
88+
}
89+
90+
if (response != null && response.containsKey("wtimeout")) {
91+
Object wtimeout = response.get("wtimeout");
92+
if (wtimeout != null && wtimeout.toString().contains("true")) {
93+
return true;
94+
}
95+
}
96+
97+
return false;
98+
}
6799
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717

1818
import static org.hamcrest.CoreMatchers.*;
1919
import static org.junit.Assert.*;
20+
import static org.junit.Assume.*;
2021
import static org.mockito.Mockito.*;
2122

2223
import java.io.IOException;
24+
import java.lang.reflect.Constructor;
25+
import java.lang.reflect.InvocationTargetException;
26+
import java.lang.reflect.Method;
2327
import java.net.UnknownHostException;
28+
import java.util.Map;
2429

2530
import org.junit.Before;
2631
import org.junit.Test;
@@ -33,20 +38,25 @@
3338
import org.springframework.dao.DuplicateKeyException;
3439
import org.springframework.dao.InvalidDataAccessApiUsageException;
3540
import org.springframework.dao.InvalidDataAccessResourceUsageException;
41+
import org.springframework.dao.TransientDataAccessResourceException;
3642
import org.springframework.data.mongodb.UncategorizedMongoDbException;
43+
import org.springframework.data.mongodb.util.MongoClientVersion;
44+
import org.springframework.test.util.ReflectionTestUtils;
3745

3846
import com.mongodb.MongoCursorNotFoundException;
3947
import com.mongodb.MongoException;
4048
import com.mongodb.MongoInternalException;
4149
import com.mongodb.MongoSocketException;
4250
import com.mongodb.ServerAddress;
51+
import com.mongodb.WriteConcernException;
4352

4453
/**
4554
* Unit tests for {@link MongoExceptionTranslator}.
4655
*
4756
* @author Michal Vich
4857
* @author Oliver Gierke
4958
* @author Christoph Strobl
59+
* @author Mark Paluch
5060
*/
5161
@RunWith(MockitoJUnitRunner.class)
5262
public class MongoExceptionTranslatorUnitTests {
@@ -133,6 +143,62 @@ public void translateMongoInternalException() {
133143
expectExceptionWithCauseMessage(translatedException, InvalidDataAccessResourceUsageException.class);
134144
}
135145

146+
/**
147+
* @see DATAMONGO-1451
148+
*/
149+
@Test
150+
@SuppressWarnings("unchecked")
151+
public void translateTimeoutToTransientDataAccessResourceExceptionWith2xDriver() throws Exception {
152+
153+
assumeThat(MongoClientVersion.isMongo3Driver(), is(false));
154+
155+
Constructor<?> constructor = Class.forName("com.mongodb.CommandResult").getDeclaredConstructor(ServerAddress.class);
156+
constructor.setAccessible(true);
157+
158+
Map<String, Object> commandResult = (Map<String, Object>) constructor.newInstance(new ServerAddress("localhost"));
159+
commandResult.put("wtimeout", true);
160+
commandResult.put("ok", 1);
161+
commandResult.put("n", 0);
162+
commandResult.put("err", "waiting for replication timed out");
163+
commandResult.put("code", 64);
164+
165+
DataAccessException translatedException = translator.translateExceptionIfPossible(
166+
(RuntimeException) ReflectionTestUtils.invokeMethod(commandResult, "getException"));
167+
168+
expectExceptionWithCauseMessage(translatedException, TransientDataAccessResourceException.class);
169+
}
170+
171+
/**
172+
* @see DATAMONGO-1451
173+
*/
174+
@Test
175+
public void translateTimeoutToTransientDataAccessResourceExceptionWith3xDriver() throws Exception {
176+
177+
assumeThat(MongoClientVersion.isMongo3Driver(), is(true));
178+
179+
Class<?> bsonDocumentClass = Class.forName("org.bson.BsonDocument");
180+
181+
Method getWriteResult = Class.forName("com.mongodb.connection.ProtocolHelper").getDeclaredMethod("getWriteResult",
182+
bsonDocumentClass, ServerAddress.class);
183+
184+
String response = "{ \"serverUsed\" : \"10.10.17.35:27017\" , \"ok\" : 1 , \"n\" : 0 , \"wtimeout\" : true , \"err\" : \"waiting for replication timed out\" , \"code\" : 64}";
185+
Object bsonDocument = bsonDocumentClass.getDeclaredMethod("parse", String.class).invoke(null, response);
186+
187+
try {
188+
getWriteResult.setAccessible(true);
189+
getWriteResult.invoke(null, bsonDocument, new ServerAddress("localhost"));
190+
fail("Missing Exception");
191+
} catch (InvocationTargetException e) {
192+
193+
assertThat(e.getTargetException(), is(instanceOf(WriteConcernException.class)));
194+
195+
DataAccessException translatedException = translator
196+
.translateExceptionIfPossible((RuntimeException) e.getTargetException());
197+
expectExceptionWithCauseMessage(translatedException, TransientDataAccessResourceException.class);
198+
}
199+
200+
}
201+
136202
@Test
137203
public void translateUnsupportedException() {
138204

0 commit comments

Comments
 (0)