diff --git a/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java b/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java index a53ba715c..3db1d3ba7 100644 --- a/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java +++ b/src/main/java/org/springframework/data/redis/connection/ClusterCommandExecutor.java @@ -137,7 +137,7 @@ public class ClusterCommandExecutor implements DisposableBean { return new NodeResult<>(node, cmd.doInCluster(client)); } catch (RuntimeException ex) { - RuntimeException translatedException = convertToDataAccessExeption(ex); + RuntimeException translatedException = convertToDataAccessException(ex); if (translatedException instanceof ClusterRedirectException) { ClusterRedirectException cre = (ClusterRedirectException) translatedException; return executeCommandOnSingleNode(cmd, @@ -234,13 +234,13 @@ public class ClusterCommandExecutor implements DisposableBean { } } catch (ExecutionException e) { - RuntimeException ex = convertToDataAccessExeption((Exception) e.getCause()); + RuntimeException ex = convertToDataAccessException((Exception) e.getCause()); exceptions.put(entry.getKey().getNode(), ex != null ? ex : e.getCause()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - RuntimeException ex = convertToDataAccessExeption((Exception) e.getCause()); + RuntimeException ex = convertToDataAccessException((Exception) e.getCause()); exceptions.put(entry.getKey().getNode(), ex != null ? ex : e.getCause()); break; } @@ -315,7 +315,7 @@ public class ClusterCommandExecutor implements DisposableBean { return new NodeResult<>(node, cmd.doInCluster(client, key), key); } catch (RuntimeException ex) { - RuntimeException translatedException = convertToDataAccessExeption(ex); + RuntimeException translatedException = convertToDataAccessException(ex); throw translatedException != null ? translatedException : ex; } finally { this.resourceProvider.returnResourceForSpecificNode(node, client); @@ -326,8 +326,7 @@ public class ClusterCommandExecutor implements DisposableBean { return this.topologyProvider.getTopology(); } - @Nullable - private DataAccessException convertToDataAccessExeption(Exception e) { + private DataAccessException convertToDataAccessException(Exception e) { return exceptionTranslationStrategy.translate(e); } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java index af6838f8d..217596b49 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterConnection.java @@ -40,7 +40,8 @@ import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.ClusterStateFailureException; import org.springframework.data.redis.ExceptionTranslationStrategy; -import org.springframework.data.redis.PassThroughExceptionTranslationStrategy; +import org.springframework.data.redis.FallbackExceptionTranslationStrategy; +import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.*; import org.springframework.data.redis.connection.ClusterCommandExecutor.ClusterCommandCallback; import org.springframework.data.redis.connection.ClusterCommandExecutor.MultiKeyClusterCommandCallback; @@ -63,7 +64,7 @@ import org.springframework.util.Assert; */ public class JedisClusterConnection implements DefaultedRedisClusterConnection { - private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy( + private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy( JedisConverters.exceptionConverter()); private final Log log = LogFactory.getLog(getClass()); @@ -653,7 +654,10 @@ public class JedisClusterConnection implements DefaultedRedisClusterConnection { */ protected DataAccessException convertJedisAccessException(Exception ex) { - return EXCEPTION_TRANSLATION.translate(ex); + + DataAccessException translated = EXCEPTION_TRANSLATION.translate(ex); + + return translated != null ? translated : new RedisSystemException(ex.getMessage(), ex); } /* diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java index 0df670886..4a7e51eac 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnection.java @@ -43,6 +43,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.ExceptionTranslationStrategy; import org.springframework.data.redis.FallbackExceptionTranslationStrategy; import org.springframework.data.redis.RedisConnectionFailureException; +import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.*; import org.springframework.data.redis.connection.convert.TransactionResultConverter; import org.springframework.lang.Nullable; @@ -207,7 +208,7 @@ public class JedisConnection extends AbstractRedisConnection { broken = true; } - return exception; + return exception != null ? exception : new RedisSystemException(ex.getMessage(), ex); } /* diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java index dd776a2bd..c712eee1d 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -422,6 +422,7 @@ public class LettuceConnection extends AbstractRedisConnection { /** * 'Native' or 'raw' execution of the given command along-side the given arguments. * + * @see RedisCommands#execute(String, byte[]...) * @param command Command to execute * @param commandOutputTypeHint Type of Output to use, may be (may be {@literal null}). * @param args Possible command arguments (may be {@literal null}) diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionUnitTests.java index e66f2e7a5..6ba9e886d 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisClusterConnectionUnitTests.java @@ -16,6 +16,7 @@ package org.springframework.data.redis.connection.jedis; import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.springframework.data.redis.connection.ClusterTestVariables.*; @@ -47,6 +48,7 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.data.redis.ClusterStateFailureException; +import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.connection.ClusterInfo; import org.springframework.data.redis.connection.RedisClusterCommands.AddSlots; import org.springframework.data.redis.connection.RedisClusterNode; @@ -107,7 +109,7 @@ public class JedisClusterConnectionUnitTests { } @Test // DATAREDIS-315 - public void thowsExceptionWhenClusterCommandExecturorIsNull() { + public void throwsExceptionWhenClusterCommandExecutorIsNull() { expectedException.expect(IllegalArgumentException.class); @@ -352,6 +354,20 @@ public class JedisClusterConnectionUnitTests { new JedisClusterTopologyProvider(clusterMock).getTopology(); } + @Test // DATAREDIS-603 + public void translatesUnknownExceptions() { + + IllegalArgumentException exception = new IllegalArgumentException("Aw, snap!"); + + expectedException.expect(RedisSystemException.class); + expectedException.expectMessage(exception.getMessage()); + expectedException.expectCause(is(exception)); + + doThrow(exception).when(clusterMock).set("foo".getBytes(), "bar".getBytes()); + + connection.set("foo".getBytes(), "bar".getBytes()); + } + static class StubJedisCluster extends JedisCluster { JedisClusterConnectionHandler connectionHandler; diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java index 8905b2570..41f2bb5be 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionUnitTestSuite.java @@ -15,13 +15,19 @@ */ package org.springframework.data.redis.connection.lettuce; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisFuture; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.async.RedisAsyncCommands; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.codec.RedisCodec; +import io.lettuce.core.protocol.RedisCommand; import java.lang.reflect.InvocationTargetException; @@ -29,6 +35,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Suite; +import org.mockito.Mockito; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.redis.connection.AbstractConnectionUnitTestBase; import org.springframework.data.redis.connection.RedisServerCommands.ShutdownOption; @@ -136,6 +143,43 @@ public class LettuceConnectionUnitTestSuite { verify(syncCommandsMock, times(1)).select(1); } + + @Test // DATAREDIS-603 + public void translatesUnknownExceptions() { + + IllegalArgumentException exception = new IllegalArgumentException("Aw, snap!"); + + when(syncCommandsMock.set(any(), any())).thenThrow(exception); + connection = new LettuceConnection(null, 0, clientMock, null, 1); + + try { + connection.set("foo".getBytes(), "bar".getBytes()); + } catch (Exception e) { + + assertThat(e.getMessage(), containsString(exception.getMessage())); + assertThat(e.getCause(), is((Throwable) exception)); + } + } + + @Test // DATAREDIS-603 + public void translatesPipelineUnknownExceptions() throws Exception { + + IllegalArgumentException exception = new IllegalArgumentException("Aw, snap!"); + + RedisCommand future = mock(RedisCommand.class, Mockito.withSettings().extraInterfaces(RedisFuture.class)); + + when(((RedisFuture) future).get()).thenThrow(exception); + when(asyncCommandsMock.set(any(byte[].class), any(byte[].class))).thenReturn((RedisFuture) future); + connection = new LettuceConnection(null, 0, clientMock, null, 1); + connection.openPipeline(); + + try { + connection.set("foo".getBytes(), "bar".getBytes()); + } catch (Exception e) { + assertThat(e.getMessage(), containsString(exception.getMessage())); + assertThat(e.getCause(), is((Throwable) exception)); + } + } } public static class LettucePipelineConnectionUnitTests extends LettuceConnectionUnitTests {