Browse Source

DATAMONGO-1451 - Translate write concern timeouts to TransientDataAccessResourceException.

We now translate timeouts (caused by replication or write concern timeout) into TransientDataAccessResourceException.
pull/370/head
Mark Paluch 10 years ago
parent
commit
f966252ee4
  1. 11
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoExceptionTranslator.java
  2. 34
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReflectiveWriteResultInvoker.java
  3. 62
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoExceptionTranslatorUnitTests.java

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2010-2015 the original author or authors.
* Copyright 2010-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,6 +26,7 @@ import org.springframework.dao.DuplicateKeyException; @@ -26,6 +26,7 @@ import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.PermissionDeniedDataAccessException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
@ -34,6 +35,7 @@ import org.springframework.util.ClassUtils; @@ -34,6 +35,7 @@ import org.springframework.util.ClassUtils;
import com.mongodb.BulkWriteException;
import com.mongodb.MongoException;
import com.mongodb.WriteConcernException;
/**
* Simple {@link PersistenceExceptionTranslator} for Mongo. Convert the given runtime exception to an appropriate
@ -43,6 +45,7 @@ import com.mongodb.MongoException; @@ -43,6 +45,7 @@ import com.mongodb.MongoException;
* @author Oliver Gierke
* @author Michal Vich
* @author Christoph Strobl
* @author Mark Paluch
*/
public class MongoExceptionTranslator implements PersistenceExceptionTranslator {
@ -65,6 +68,12 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator @@ -65,6 +68,12 @@ public class MongoExceptionTranslator implements PersistenceExceptionTranslator
*/
public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
// Check for a timeout exception
if (ex instanceof WriteConcernException && ReflectiveWriteResultInvoker.wasTimeout((WriteConcernException) ex)) {
return new TransientDataAccessResourceException(ex.getMessage(), ex);
}
// Check for well-known MongoException subclasses.
String exception = ClassUtils.getShortName(ClassUtils.getUserClass(ex.getClass()));

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,8 +19,10 @@ import static org.springframework.data.mongodb.util.MongoClientVersion.*; @@ -19,8 +19,10 @@ import static org.springframework.data.mongodb.util.MongoClientVersion.*;
import static org.springframework.util.ReflectionUtils.*;
import java.lang.reflect.Method;
import java.util.Map;
import com.mongodb.MongoException;
import com.mongodb.WriteConcernException;
import com.mongodb.WriteResult;
/**
@ -29,12 +31,15 @@ import com.mongodb.WriteResult; @@ -29,12 +31,15 @@ import com.mongodb.WriteResult;
*
* @author Christoph Strobl
* @author Oliver Gierke
* @author Mark Paluch
* @since 1.7
*/
final class ReflectiveWriteResultInvoker {
private static final Method GET_ERROR_METHOD;
private static final Method WAS_ACKNOWLEDGED_METHOD;
private static final Method GET_RESPONSE;
private static final Method GET_COMMAND_RESULT;
private ReflectiveWriteResultInvoker() {}
@ -42,6 +47,8 @@ final class ReflectiveWriteResultInvoker { @@ -42,6 +47,8 @@ final class ReflectiveWriteResultInvoker {
GET_ERROR_METHOD = findMethod(WriteResult.class, "getError");
WAS_ACKNOWLEDGED_METHOD = findMethod(WriteResult.class, "wasAcknowledged");
GET_RESPONSE = findMethod(WriteConcernException.class, "getResponse");
GET_COMMAND_RESULT = findMethod(WriteConcernException.class, "getCommandResult");
}
/**
@ -64,4 +71,29 @@ final class ReflectiveWriteResultInvoker { @@ -64,4 +71,29 @@ final class ReflectiveWriteResultInvoker {
public static boolean wasAcknowledged(WriteResult writeResult) {
return isMongo3Driver() ? ((Boolean) invokeMethod(WAS_ACKNOWLEDGED_METHOD, writeResult)).booleanValue() : true;
}
/**
* @param writeConcernException
* @return return {@literal true} if the {@link WriteConcernException} indicates a write concern timeout as reason
* @since 1.10
*/
@SuppressWarnings("unchecked")
public static boolean wasTimeout(WriteConcernException writeConcernException) {
Map<Object, Object> response;
if (isMongo3Driver()) {
response = (Map<Object, Object>) invokeMethod(GET_RESPONSE, writeConcernException);
} else {
response = (Map<Object, Object>) invokeMethod(GET_COMMAND_RESULT, writeConcernException);
}
if (response != null && response.containsKey("wtimeout")) {
Object wtimeout = response.get("wtimeout");
if (wtimeout != null && wtimeout.toString().contains("true")) {
return true;
}
}
return false;
}
}

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

@ -17,10 +17,15 @@ package org.springframework.data.mongodb.core; @@ -17,10 +17,15 @@ package org.springframework.data.mongodb.core;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.UnknownHostException;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
@ -33,20 +38,25 @@ import org.springframework.dao.DataAccessResourceFailureException; @@ -33,20 +38,25 @@ import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.dao.TransientDataAccessResourceException;
import org.springframework.data.mongodb.UncategorizedMongoDbException;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.MongoCursorNotFoundException;
import com.mongodb.MongoException;
import com.mongodb.MongoInternalException;
import com.mongodb.MongoSocketException;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcernException;
/**
* Unit tests for {@link MongoExceptionTranslator}.
*
*
* @author Michal Vich
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class MongoExceptionTranslatorUnitTests {
@ -133,6 +143,56 @@ public class MongoExceptionTranslatorUnitTests { @@ -133,6 +143,56 @@ public class MongoExceptionTranslatorUnitTests {
expectExceptionWithCauseMessage(translatedException, InvalidDataAccessResourceUsageException.class);
}
@Test // DATAMONGO-1451
@SuppressWarnings("unchecked")
public void translateTimeoutToTransientDataAccessResourceExceptionWith2xDriver() throws Exception {
assumeThat(MongoClientVersion.isMongo3Driver(), is(false));
Constructor<?> constructor = Class.forName("com.mongodb.CommandResult").getDeclaredConstructor(ServerAddress.class);
constructor.setAccessible(true);
Map<String, Object> commandResult = (Map<String, Object>) constructor.newInstance(new ServerAddress("localhost"));
commandResult.put("wtimeout", true);
commandResult.put("ok", 1);
commandResult.put("n", 0);
commandResult.put("err", "waiting for replication timed out");
commandResult.put("code", 64);
DataAccessException translatedException = translator.translateExceptionIfPossible(
(RuntimeException) ReflectionTestUtils.invokeMethod(commandResult, "getException"));
expectExceptionWithCauseMessage(translatedException, TransientDataAccessResourceException.class);
}
@Test // DATAMONGO-1451
public void translateTimeoutToTransientDataAccessResourceExceptionWith3xDriver() throws Exception {
assumeThat(MongoClientVersion.isMongo3Driver(), is(true));
Class<?> bsonDocumentClass = Class.forName("org.bson.BsonDocument");
Method getWriteResult = Class.forName("com.mongodb.connection.ProtocolHelper").getDeclaredMethod("getWriteResult",
bsonDocumentClass, ServerAddress.class);
String response = "{ \"serverUsed\" : \"10.10.17.35:27017\" , \"ok\" : 1 , \"n\" : 0 , \"wtimeout\" : true , \"err\" : \"waiting for replication timed out\" , \"code\" : 64}";
Object bsonDocument = bsonDocumentClass.getDeclaredMethod("parse", String.class).invoke(null, response);
try {
getWriteResult.setAccessible(true);
getWriteResult.invoke(null, bsonDocument, new ServerAddress("localhost"));
fail("Missing Exception");
} catch (InvocationTargetException e) {
assertThat(e.getTargetException(), is(instanceOf(WriteConcernException.class)));
DataAccessException translatedException = translator
.translateExceptionIfPossible((RuntimeException) e.getTargetException());
expectExceptionWithCauseMessage(translatedException, TransientDataAccessResourceException.class);
}
}
@Test
public void translateUnsupportedException() {

Loading…
Cancel
Save