diff --git a/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java b/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java index 3872a4c41..b546b83ac 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraExceptionTranslator.java @@ -1,12 +1,12 @@ /* - * Copyright 2013-2014 the original author or authors. - * + * Copyright 2013-2017 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. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,117 +15,155 @@ */ package org.springframework.cassandra.support; -import org.springframework.cassandra.support.exception.CassandraAuthenticationException; -import org.springframework.cassandra.support.exception.CassandraConnectionFailureException; -import org.springframework.cassandra.support.exception.CassandraInsufficientReplicasAvailableException; -import org.springframework.cassandra.support.exception.CassandraInternalException; -import org.springframework.cassandra.support.exception.CassandraInvalidConfigurationInQueryException; -import org.springframework.cassandra.support.exception.CassandraInvalidQueryException; -import org.springframework.cassandra.support.exception.CassandraKeyspaceExistsException; -import org.springframework.cassandra.support.exception.CassandraQuerySyntaxException; -import org.springframework.cassandra.support.exception.CassandraReadTimeoutException; -import org.springframework.cassandra.support.exception.CassandraTableExistsException; -import org.springframework.cassandra.support.exception.CassandraTraceRetrievalException; -import org.springframework.cassandra.support.exception.CassandraTruncateException; -import org.springframework.cassandra.support.exception.CassandraTypeMismatchException; -import org.springframework.cassandra.support.exception.CassandraUnauthorizedException; -import org.springframework.cassandra.support.exception.CassandraUncategorizedException; -import org.springframework.cassandra.support.exception.CassandraWriteTimeoutException; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.cassandra.support.exception.*; import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.TransientDataAccessResourceException; import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.util.ClassUtils; import com.datastax.driver.core.WriteType; -import com.datastax.driver.core.exceptions.AlreadyExistsException; -import com.datastax.driver.core.exceptions.AuthenticationException; -import com.datastax.driver.core.exceptions.DriverException; -import com.datastax.driver.core.exceptions.DriverInternalError; -import com.datastax.driver.core.exceptions.InvalidConfigurationInQueryException; -import com.datastax.driver.core.exceptions.InvalidQueryException; -import com.datastax.driver.core.exceptions.InvalidTypeException; -import com.datastax.driver.core.exceptions.NoHostAvailableException; -import com.datastax.driver.core.exceptions.ReadTimeoutException; -import com.datastax.driver.core.exceptions.SyntaxError; -import com.datastax.driver.core.exceptions.TraceRetrievalException; -import com.datastax.driver.core.exceptions.TruncateException; -import com.datastax.driver.core.exceptions.UnauthorizedException; -import com.datastax.driver.core.exceptions.UnavailableException; -import com.datastax.driver.core.exceptions.WriteTimeoutException; +import com.datastax.driver.core.exceptions.*; /** - * Simple {@link PersistenceExceptionTranslator} for Cassandra. Convert the given runtime exception to an appropriate - * exception from the {@code org.springframework.dao} hierarchy. Return {@literal null} if no translation is - * appropriate: any other exception may have resulted from user code, and should not be translated. - * + * Simple {@link PersistenceExceptionTranslator} for Cassandra. + *

+ * Convert the given runtime exception to an appropriate exception from the {@code org.springframework.dao} hierarchy. + * Preserves exception if it's already a {@link DataAccessException} and ignores non {@link DriverException}s returning + * {@literal null}. Falls back to {@link CassandraUncategorizedException} in case there's no mapping to a more detailed + * exception. + * * @author Alex Shvid * @author Matthew T. Adams + * @author Mark Paluch */ - +@SuppressWarnings("unchecked") public class CassandraExceptionTranslator implements PersistenceExceptionTranslator { - @Override - public DataAccessException translateExceptionIfPossible(RuntimeException x) { + private static final Set CONNECTION_FAILURE_TYPES = new HashSet( + Arrays.asList("NoHostAvailableException", "ConnectionException", "OperationTimedOutException", + "TransportException", "BusyConnectionException", "BusyPoolException")); - if (x instanceof DataAccessException) { - return (DataAccessException) x; + private static final Set RESOURCE_FAILURE_TYPES = new HashSet( + Arrays.asList("ReadFailureException", "WriteFailureException", "FunctionExecutionException")); + + /* (non-Javadoc) + * @see org.springframework.dao.support.PersistenceExceptionTranslator#translateExceptionIfPossible(java.lang.RuntimeException) + */ + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException exception) { + + if (exception instanceof DataAccessException) { + return (DataAccessException) exception; } - if (!(x instanceof DriverException)) { + if (!(exception instanceof DriverException)) { return null; } // Remember: subclasses must come before superclasses, otherwise the // superclass would match before the subclass! - if (x instanceof AuthenticationException) { - return new CassandraAuthenticationException(((AuthenticationException) x).getHost(), x.getMessage(), x); + if (exception instanceof AuthenticationException) { + return new CassandraAuthenticationException(((AuthenticationException) exception).getHost(), + exception.getMessage(), exception); } - if (x instanceof DriverInternalError) { - return new CassandraInternalException(x.getMessage(), x); - } - if (x instanceof InvalidTypeException) { - return new CassandraTypeMismatchException(x.getMessage(), x); - } - if (x instanceof NoHostAvailableException) { - return new CassandraConnectionFailureException(((NoHostAvailableException) x).getErrors(), x.getMessage(), x); - } - if (x instanceof ReadTimeoutException) { - return new CassandraReadTimeoutException(((ReadTimeoutException) x).wasDataRetrieved(), x.getMessage(), x); - } - if (x instanceof WriteTimeoutException) { - WriteType writeType = ((WriteTimeoutException) x).getWriteType(); - return new CassandraWriteTimeoutException(writeType == null ? null : writeType.name(), x.getMessage(), x); - } - if (x instanceof TruncateException) { - return new CassandraTruncateException(x.getMessage(), x); - } - if (x instanceof UnavailableException) { - UnavailableException ux = (UnavailableException) x; - return new CassandraInsufficientReplicasAvailableException(ux.getRequiredReplicas(), ux.getAliveReplicas(), - x.getMessage(), x); - } - if (x instanceof AlreadyExistsException) { - AlreadyExistsException aex = (AlreadyExistsException) x; - return aex.wasTableCreation() ? new CassandraTableExistsException(aex.getTable(), x.getMessage(), x) - : new CassandraKeyspaceExistsException(aex.getKeyspace(), x.getMessage(), x); + if (exception instanceof DriverInternalError) { + return new CassandraInternalException(exception.getMessage(), exception); } - if (x instanceof InvalidConfigurationInQueryException) { - return new CassandraInvalidConfigurationInQueryException(x.getMessage(), x); + + if (exception instanceof InvalidTypeException) { + return new CassandraTypeMismatchException(exception.getMessage(), exception); } - if (x instanceof InvalidQueryException) { - return new CassandraInvalidQueryException(x.getMessage(), x); + + if (exception instanceof ReadTimeoutException) { + return new CassandraReadTimeoutException(((ReadTimeoutException) exception).wasDataRetrieved(), + exception.getMessage(), exception); } - if (x instanceof SyntaxError) { - return new CassandraQuerySyntaxException(x.getMessage(), x); + + if (exception instanceof WriteTimeoutException) { + + WriteType writeType = ((WriteTimeoutException) exception).getWriteType(); + return new CassandraWriteTimeoutException(writeType == null ? null : writeType.name(), exception.getMessage(), + exception); } - if (x instanceof UnauthorizedException) { - return new CassandraUnauthorizedException(x.getMessage(), x); + + if (exception instanceof TruncateException) { + return new CassandraTruncateException(exception.getMessage(), exception); } - if (x instanceof TraceRetrievalException) { - return new CassandraTraceRetrievalException(x.getMessage(), x); + + if (exception instanceof UnavailableException) { + + UnavailableException ux = (UnavailableException) exception; + return new CassandraInsufficientReplicasAvailableException(ux.getRequiredReplicas(), ux.getAliveReplicas(), + exception.getMessage(), exception); + } + + if (exception instanceof OverloadedException || exception instanceof BootstrappingException) { + return new TransientDataAccessResourceException(exception.getMessage(), exception); + } + + if (exception instanceof AlreadyExistsException) { + + AlreadyExistsException aex = (AlreadyExistsException) exception; + + return aex.wasTableCreation() + ? new CassandraTableExistsException(aex.getTable(), exception.getMessage(), exception) + : new CassandraKeyspaceExistsException(aex.getKeyspace(), exception.getMessage(), exception); + } + + if (exception instanceof InvalidConfigurationInQueryException) { + return new CassandraInvalidConfigurationInQueryException(exception.getMessage(), exception); + } + + if (exception instanceof InvalidQueryException) { + return new CassandraInvalidQueryException(exception.getMessage(), exception); + } + + if (exception instanceof SyntaxError) { + return new CassandraQuerySyntaxException(exception.getMessage(), exception); + } + + if (exception instanceof UnauthorizedException) { + return new CassandraUnauthorizedException(exception.getMessage(), exception); + } + + if (exception instanceof TraceRetrievalException) { + return new CassandraTraceRetrievalException(exception.getMessage(), exception); + } + + if (exception instanceof NoHostAvailableException) { + return new CassandraConnectionFailureException(((NoHostAvailableException) exception).getErrors(), + exception.getMessage(), exception); + } + + String exceptionType = ClassUtils.getShortName(ClassUtils.getUserClass(exception.getClass())); + + if (CONNECTION_FAILURE_TYPES.contains(exceptionType)) { + + Map errorMap = Collections.emptyMap(); + + if (exception instanceof CoordinatorException) { + CoordinatorException cx = (CoordinatorException) exception; + errorMap = Collections. singletonMap(cx.getAddress(), exception); + } + + return new CassandraConnectionFailureException(errorMap, exception.getMessage(), exception); + } + + if (RESOURCE_FAILURE_TYPES.contains(exceptionType)) { + return new DataAccessResourceFailureException(exception.getMessage(), exception); } // unknown or unhandled exception - return new CassandraUncategorizedException(x.getMessage(), x); + return new CassandraUncategorizedException(exception.getMessage(), exception); } } diff --git a/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorTest.java b/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorTest.java index 6478c1af9..b5403a214 100755 --- a/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorTest.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorTest.java @@ -16,73 +16,322 @@ package org.springframework.cassandra.support; import static org.assertj.core.api.Assertions.*; +import static org.junit.Assume.*; + +import java.lang.reflect.Constructor; +import java.net.InetSocketAddress; +import java.util.Collections; import org.junit.Test; -import org.springframework.cassandra.support.exception.CassandraInvalidConfigurationInQueryException; -import org.springframework.cassandra.support.exception.CassandraInvalidQueryException; -import org.springframework.cassandra.support.exception.CassandraKeyspaceExistsException; -import org.springframework.cassandra.support.exception.CassandraSchemaElementExistsException; -import org.springframework.cassandra.support.exception.CassandraTableExistsException; +import org.springframework.cassandra.support.exception.*; +import org.springframework.cassandra.support.exception.CassandraSchemaElementExistsException.ElementType; import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.TransientDataAccessResourceException; +import org.springframework.util.ClassUtils; -import com.datastax.driver.core.exceptions.AlreadyExistsException; -import com.datastax.driver.core.exceptions.InvalidConfigurationInQueryException; -import com.datastax.driver.core.exceptions.InvalidQueryException; +import com.datastax.driver.core.ConsistencyLevel; +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.ProtocolVersion; +import com.datastax.driver.core.WriteType; +import com.datastax.driver.core.exceptions.*; +import com.google.common.reflect.TypeToken; /** * Unit tests for {@link CassandraExceptionTranslator} * * @author Matthew T. Adams + * @author Mark Paluch */ public class CassandraExceptionTranslatorTest { - CassandraExceptionTranslator tx = new CassandraExceptionTranslator(); + InetSocketAddress socketAddress = new InetSocketAddress("localhost", 42); + CassandraExceptionTranslator sut = new CassandraExceptionTranslator(); - @Test - public void testTableExistsException() { - String keyspace = ""; - String table = "tbl"; - AlreadyExistsException cx = new AlreadyExistsException(keyspace, table); - DataAccessException dax = tx.translateExceptionIfPossible(cx); - assertThat(dax).isNotNull(); - assertThat(dax instanceof CassandraTableExistsException).isTrue(); + @Test // DATACASS-402 + public void shouldTranslateAuthenticationException() { - CassandraTableExistsException x = (CassandraTableExistsException) dax; - assertThat(x.getTableName()).isEqualTo(table); - assertThat(x.getElementName()).isEqualTo(x.getTableName()); - assertThat(x.getElementType()).isEqualTo(CassandraSchemaElementExistsException.ElementType.TABLE); - assertThat(x.getCause()).isEqualTo(cx); + DataAccessException result = sut + .translateExceptionIfPossible(new AuthenticationException(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraAuthenticationException.class) + .hasMessageStartingWith("Authentication error on host").hasCauseInstanceOf(AuthenticationException.class); } - @Test - public void testKeyspaceExistsException() { - String keyspace = "ks"; - String table = ""; - AlreadyExistsException cx = new AlreadyExistsException(keyspace, table); - DataAccessException dax = tx.translateExceptionIfPossible(cx); - assertThat(dax).isNotNull(); - assertThat(dax instanceof CassandraKeyspaceExistsException).isTrue(); + @Test // DATACASS-402 + public void shouldTranslateCassandraInternalException() { - CassandraKeyspaceExistsException x = (CassandraKeyspaceExistsException) dax; - assertThat(x.getKeyspaceName()).isEqualTo(keyspace); - assertThat(x.getElementName()).isEqualTo(x.getKeyspaceName()); - assertThat(x.getElementType()).isEqualTo(CassandraSchemaElementExistsException.ElementType.KEYSPACE); - assertThat(x.getCause()).isEqualTo(cx); + DataAccessException result = sut.translateExceptionIfPossible(new DriverInternalError("message")); + + assertThat(result).isInstanceOf(CassandraInternalException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(DriverInternalError.class); } - @Test - public void testInvalidConfigurationInQueryException() { - String msg = "msg"; - InvalidQueryException cx = new InvalidConfigurationInQueryException(null, msg); - DataAccessException dax = tx.translateExceptionIfPossible(cx); - assertThat(dax).isNotNull(); - assertThat(dax instanceof CassandraInvalidConfigurationInQueryException).isTrue(); - assertThat(dax.getCause()).isEqualTo(cx); + @Test // DATACASS-402 + public void shouldTranslateTraceRetrievalException() { - cx = new InvalidQueryException(msg); - dax = tx.translateExceptionIfPossible(cx); - assertThat(dax).isNotNull(); - assertThat(dax instanceof CassandraInvalidQueryException).isTrue(); - assertThat(dax.getCause()).isEqualTo(cx); + DataAccessException result = sut.translateExceptionIfPossible(new TraceRetrievalException("message")); + + assertThat(result).isInstanceOf(CassandraTraceRetrievalException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(TraceRetrievalException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateNoHostAvailableException() { + + DataAccessException result = sut.translateExceptionIfPossible( + new NoHostAvailableException(Collections.singletonMap(socketAddress, (Throwable) new IllegalStateException()))); + + assertThat(result).isInstanceOf(CassandraConnectionFailureException.class) + .hasMessageStartingWith("All host(s) tried").hasCauseInstanceOf(NoHostAvailableException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateInvalidQueryException() { + + DataAccessException result = sut.translateExceptionIfPossible(new InvalidQueryException(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraInvalidQueryException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(InvalidQueryException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateInvalidConfigurationInQueryException() { + + DataAccessException result = sut + .translateExceptionIfPossible(new InvalidConfigurationInQueryException(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraInvalidConfigurationInQueryException.class) + .hasMessageStartingWith("message").hasCauseInstanceOf(InvalidConfigurationInQueryException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateUnauthorizedException() { + + DataAccessException result = sut.translateExceptionIfPossible(new UnauthorizedException(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraUnauthorizedException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(UnauthorizedException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateSyntaxError() { + + DataAccessException result = sut.translateExceptionIfPossible(new SyntaxError(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraQuerySyntaxException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(SyntaxError.class); + } + + @Test // DATACASS-402 + public void shouldTranslateKeyspaceExistsException() { + + AlreadyExistsException cx = new AlreadyExistsException("keyspace", ""); + DataAccessException result = sut.translateExceptionIfPossible(cx); + + assertThat(result).isInstanceOf(CassandraKeyspaceExistsException.class) + .hasMessageStartingWith("Keyspace keyspace already exists").hasCauseInstanceOf(AlreadyExistsException.class); + + CassandraSchemaElementExistsException exception = (CassandraSchemaElementExistsException) result; + + assertThat(exception.getElementName()).isEqualTo("keyspace"); + assertThat(exception.getElementType()).isEqualTo(ElementType.KEYSPACE); + } + + @Test // DATACASS-402 + public void shouldTranslateTableExistsException() { + + AlreadyExistsException cx = new AlreadyExistsException("keyspace", "table"); + DataAccessException result = sut.translateExceptionIfPossible(cx); + + assertThat(result).isInstanceOf(CassandraTableExistsException.class) + .hasMessageStartingWith("Table keyspace.table already exists").hasCauseInstanceOf(AlreadyExistsException.class); + + CassandraSchemaElementExistsException exception = (CassandraSchemaElementExistsException) result; + + assertThat(exception.getElementName()).isEqualTo("table"); + assertThat(exception.getElementType()).isEqualTo(ElementType.TABLE); + } + + @Test // DATACASS-402 + public void shouldTranslateInvalidTypeException() { + + DataAccessException result = sut.translateExceptionIfPossible(new InvalidTypeException("message")); + + assertThat(result).isInstanceOf(CassandraTypeMismatchException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(InvalidTypeException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateUnavailableException() { + + DataAccessException result = sut.translateExceptionIfPossible(new UnavailableException(ConsistencyLevel.ALL, 5, 1)); + + assertThat(result).isInstanceOf(CassandraInsufficientReplicasAvailableException.class) + .hasMessageStartingWith("Not enough replicas available").hasCauseInstanceOf(UnavailableException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateBootstrappingException() { + + DataAccessException result = sut.translateExceptionIfPossible(new BootstrappingException(socketAddress, "message")); + + assertThat(result).isInstanceOf(TransientDataAccessResourceException.class).hasMessageStartingWith("Queried host") + .hasCauseInstanceOf(BootstrappingException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateOverloadedException() { + + DataAccessException result = sut.translateExceptionIfPossible(new OverloadedException(socketAddress, "message")); + + assertThat(result).isInstanceOf(TransientDataAccessResourceException.class).hasMessageStartingWith("Queried host") + .hasCauseInstanceOf(OverloadedException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateTruncateException() { + + DataAccessException result = sut.translateExceptionIfPossible(new TruncateException(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraTruncateException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(TruncateException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateWriteFailureException() { + + DataAccessException result = sut + .translateExceptionIfPossible(new WriteFailureException(ConsistencyLevel.ALL, WriteType.BATCH, 1, 5, 1)); + + assertThat(result).isInstanceOf(DataAccessResourceFailureException.class) + .hasMessageStartingWith("Cassandra failure during").hasCauseInstanceOf(WriteFailureException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateReadFailureException() { + + DataAccessException result = sut + .translateExceptionIfPossible(new ReadFailureException(ConsistencyLevel.ALL, 1, 5, 1, true)); + + assertThat(result).isInstanceOf(DataAccessResourceFailureException.class) + .hasMessageStartingWith("Cassandra failure during").hasCauseInstanceOf(ReadFailureException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateWriteTimeoutException() { + + DataAccessException result = sut + .translateExceptionIfPossible(new WriteTimeoutException(ConsistencyLevel.ALL, WriteType.BATCH, 1, 5)); + + assertThat(result).isInstanceOf(CassandraWriteTimeoutException.class) + .hasMessageStartingWith("Cassandra timeout during").hasCauseInstanceOf(WriteTimeoutException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateReadTimeoutException() { + + DataAccessException result = sut + .translateExceptionIfPossible(new ReadTimeoutException(ConsistencyLevel.ALL, 1, 5, true)); + + assertThat(result).isInstanceOf(CassandraReadTimeoutException.class) + .hasMessageStartingWith("Cassandra timeout during").hasCauseInstanceOf(ReadTimeoutException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateFunctionExecutionException() { + + DataAccessException result = sut + .translateExceptionIfPossible(new FunctionExecutionException(socketAddress, "message")); + + assertThat(result).isInstanceOf(DataAccessResourceFailureException.class).hasMessageStartingWith("message") + .hasCauseInstanceOf(FunctionExecutionException.class); + } + + @Test // DATACASS-402 + @SuppressWarnings("unchecked") + public void shouldTranslateBusyPoolException() throws Exception { + + assumeTrue( + ClassUtils.isPresent("com.datastax.driver.core.exceptions.BusyPoolException", getClass().getClassLoader())); + + DriverException exception = createInstance("com.datastax.driver.core.exceptions.BusyPoolException", + new Class[] { InetSocketAddress.class, Integer.TYPE }, socketAddress, 5); + + DataAccessException result = sut.translateExceptionIfPossible(exception); + + assertThat(result).isInstanceOf(CassandraConnectionFailureException.class).hasMessageContaining("Pool is busy") + .hasCauseInstanceOf(exception.getClass()); + } + + @Test // DATACASS-402 + public void shouldTranslateConnectionException() { + + DataAccessException result = sut.translateExceptionIfPossible(new ConnectionException(socketAddress, "message")); + + assertThat(result).isInstanceOf(CassandraConnectionFailureException.class).hasMessageContaining("] message") + .hasCauseInstanceOf(ConnectionException.class); + } + + @Test // DATACASS-402 + public void shouldTranslateBusyConnectionException() { + + DataAccessException result = sut.translateExceptionIfPossible(new BusyConnectionException(socketAddress)); + + assertThat(result).isInstanceOf(CassandraConnectionFailureException.class) + .hasMessageContaining("Connection has run out of stream").hasCauseInstanceOf(BusyConnectionException.class); + } + + @Test // DATACASS-402 + @SuppressWarnings("unchecked") + public void shouldTranslateFrameTooLongException() throws Exception { + + assumeTrue( + ClassUtils.isPresent("com.datastax.driver.core.exceptions.FrameTooLongException", getClass().getClassLoader())); + + DriverException exception = createInstance("com.datastax.driver.core.exceptions.FrameTooLongException", + new Class[] { Integer.TYPE }, 5); + + DataAccessException result = sut.translateExceptionIfPossible(exception); + + assertThat(result).isInstanceOf(CassandraUncategorizedException.class).hasCauseInstanceOf(exception.getClass()); + } + + @Test // DATACASS-402 + public void shouldTranslateToUncategorized() { + + assertThat(sut.translateExceptionIfPossible( + new CodecNotFoundException("message", DataType.ascii(), TypeToken.of(Class.class)))) + .isInstanceOf(CassandraUncategorizedException.class); + + assertThat(sut.translateExceptionIfPossible( + new UnsupportedProtocolVersionException(socketAddress, ProtocolVersion.NEWEST_SUPPORTED, ProtocolVersion.V1))) + .isInstanceOf(CassandraUncategorizedException.class); + + assertThat(sut.translateExceptionIfPossible(new UnpreparedException(socketAddress, "message"))) + .isInstanceOf(CassandraUncategorizedException.class); + + assertThat(sut.translateExceptionIfPossible(new PagingStateException("message"))) + .isInstanceOf(CassandraUncategorizedException.class); + + assertThat(sut.translateExceptionIfPossible(new UnresolvedUserTypeException("keyspace", "message"))) + .isInstanceOf(CassandraUncategorizedException.class); + + assertThat( + sut.translateExceptionIfPossible(new UnsupportedFeatureException(ProtocolVersion.NEWEST_SUPPORTED, "message"))) + .isInstanceOf(CassandraUncategorizedException.class); + + assertThat(sut.translateExceptionIfPossible(new UnresolvedUserTypeException("keyspace", "message"))) + .isInstanceOf(CassandraUncategorizedException.class); + } + + @SuppressWarnings("unchecked") + public T createInstance(String className, Class argTypes[], Object... args) + throws ReflectiveOperationException { + + Class exceptionClass = (Class) ClassUtils.forName(className, getClass().getClassLoader()); + Constructor constructor = exceptionClass.getDeclaredConstructor(argTypes); + + return constructor.newInstance(args); } }