Skip to content

Commit f966252

Browse files
committed
DATAMONGO-1451 - Translate write concern timeouts to TransientDataAccessResourceException.
We now translate timeouts (caused by replication or write concern timeout) into TransientDataAccessResourceException.
1 parent 5d62e01 commit f966252

File tree

3 files changed

+104
-3
lines changed

3 files changed

+104
-3
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: 61 additions & 1 deletion
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}.
46-
*
55+
*
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,56 @@ public void translateMongoInternalException() {
133143
expectExceptionWithCauseMessage(translatedException, InvalidDataAccessResourceUsageException.class);
134144
}
135145

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

0 commit comments

Comments
 (0)