From 690ebe5c99b542f4c16cb230a47cf3c21908f875 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 28 Feb 2017 11:53:37 +0100 Subject: [PATCH] DATACASS-402 - Extend exception translation for exceptions that map currently to CassandraUncategorizedException. We now provide a more fine-grained exception translation for exceptions that previously mapped to CassandraUncategorizedException. Existing translation to more specific exception does not change. The following translation rules are introduced by this change: * OverloadedException and BootstrappingException map to TransientDataAccessResourceException * NoHostAvailableException, BusyPoolException, ConnectionException, BusyConnectionException map to CassandraConnectionFailureException * QueryConsistencyException, FunctionExecutionException map to DataAccessResourceFailureException --- .../cassandra/support/CassandraAccessor.java | 18 +- .../support/CassandraExceptionTranslator.java | 214 ++++++----- .../support/CassandraAccessorUnitTests.java | 3 +- ...CassandraExceptionTranslatorUnitTests.java | 346 +++++++++++++++--- 4 files changed, 435 insertions(+), 146 deletions(-) diff --git a/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java b/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java index 58602a941..1a2be2731 100644 --- a/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java +++ b/spring-cql/src/main/java/org/springframework/cassandra/support/CassandraAccessor.java @@ -32,6 +32,7 @@ import org.springframework.cassandra.core.RowMapperResultSetExtractor; import org.springframework.cassandra.core.SingleColumnRowMapper; import org.springframework.cassandra.core.session.DefaultSessionFactory; import org.springframework.cassandra.core.session.SessionFactory; +import org.springframework.cassandra.core.support.CQLExceptionTranslator; import org.springframework.dao.DataAccessException; import org.springframework.util.Assert; @@ -67,7 +68,7 @@ public class CassandraAccessor implements InitializingBean { /** Logger available to subclasses */ protected final Logger logger = LoggerFactory.getLogger(getClass()); - protected CassandraExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); + protected CQLExceptionTranslator exceptionTranslator = new CassandraExceptionTranslator(); /** * If this variable is set to a non-negative value, it will be used for setting the {@code fetchSize} property on @@ -121,10 +122,11 @@ public class CassandraAccessor implements InitializingBean { * Exception Hierarchy. * * @param exceptionTranslator exception translator to set; must not be {@literal null}. - * @see org.springframework.cassandra.support.CassandraExceptionTranslator + * @see CQLExceptionTranslator */ - public void setExceptionTranslator(CassandraExceptionTranslator exceptionTranslator) { - Assert.notNull(exceptionTranslator, "CassandraExceptionTranslator must not be null"); + public void setExceptionTranslator(CQLExceptionTranslator exceptionTranslator) { + + Assert.notNull(exceptionTranslator, "CQLExceptionTranslator must not be null"); this.exceptionTranslator = exceptionTranslator; } @@ -133,11 +135,11 @@ public class CassandraAccessor implements InitializingBean { * Exception Hierarchy. * * @return the Cassandra exception translator. - * @see org.springframework.cassandra.support.CassandraExceptionTranslator + * @see CQLExceptionTranslator */ - public CassandraExceptionTranslator getExceptionTranslator() { - Assert.state(this.exceptionTranslator != null, - "CassandraExceptionTranslator was not properly initialized"); + public CQLExceptionTranslator getExceptionTranslator() { + + Assert.state(this.exceptionTranslator != null, "CQLExceptionTranslator was not properly initialized"); return this.exceptionTranslator; } 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 f09a5b70d..1c7e86707 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,130 +15,168 @@ */ package org.springframework.cassandra.support; +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.core.support.CQLExceptionTranslator; -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 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.StringUtils; +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. - * + * 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 CQLExceptionTranslator { - @Override - public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + private static final Set CONNECTION_FAILURE_TYPES = new HashSet<>( + Arrays.asList("NoHostAvailableException", "ConnectionException", "OperationTimedOutException", + "TransportException", "BusyConnectionException", "BusyPoolException")); - if (ex instanceof DataAccessException) { - return (DataAccessException) ex; + 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 (!(ex instanceof DriverException)) { + if (!(exception instanceof DriverException)) { return null; } - return translate(null, null, (DriverException) ex); + return translate(null, null, (DriverException) exception); } + /* (non-Javadoc) + * @see org.springframework.cassandra.core.support.CQLExceptionTranslator#translate(java.lang.String, java.lang.String, com.datastax.driver.core.exceptions.DriverException) + */ @Override - public DataAccessException translate(String task, String cql, DriverException ex) { + public DataAccessException translate(String task, String cql, DriverException exception) { - String message = buildMessage(task, cql, ex); + String message = buildMessage(task, cql, exception); // Remember: subclasses must come before superclasses, otherwise the // superclass would match before the subclass! - if (ex instanceof AuthenticationException) { - return new CassandraAuthenticationException(((AuthenticationException) ex).getHost(), message, ex); + if (exception instanceof AuthenticationException) { + return new CassandraAuthenticationException(((AuthenticationException) exception).getHost(), message, exception); } - if (ex instanceof DriverInternalError) { - return new CassandraInternalException(message, ex); - } - if (ex instanceof InvalidTypeException) { - return new CassandraTypeMismatchException(message, ex); - } - if (ex instanceof NoHostAvailableException) { - return new CassandraConnectionFailureException(((NoHostAvailableException) ex).getErrors(), message, ex); - } - if (ex instanceof ReadTimeoutException) { - return new CassandraReadTimeoutException(((ReadTimeoutException) ex).wasDataRetrieved(), message, ex); - } - if (ex instanceof WriteTimeoutException) { - WriteType writeType = ((WriteTimeoutException) ex).getWriteType(); - return new CassandraWriteTimeoutException(writeType == null ? null : writeType.name(), message, ex); - } - if (ex instanceof TruncateException) { - return new CassandraTruncateException(message, ex); - } - if (ex instanceof UnavailableException) { - UnavailableException ux = (UnavailableException) ex; - return new CassandraInsufficientReplicasAvailableException(ux.getRequiredReplicas(), ux.getAliveReplicas(), - message, ex); - } - if (ex instanceof AlreadyExistsException) { - AlreadyExistsException aex = (AlreadyExistsException) ex; - return aex.wasTableCreation() ? new CassandraTableExistsException(aex.getTable(), message, ex) - : new CassandraKeyspaceExistsException(aex.getKeyspace(), message, ex); + if (exception instanceof DriverInternalError) { + return new CassandraInternalException(message, exception); } - if (ex instanceof InvalidConfigurationInQueryException) { - return new CassandraInvalidConfigurationInQueryException(message, ex); + + if (exception instanceof InvalidTypeException) { + return new CassandraTypeMismatchException(message, exception); } - if (ex instanceof InvalidQueryException) { - return new CassandraInvalidQueryException(message, ex); + + if (exception instanceof ReadTimeoutException) { + return new CassandraReadTimeoutException(((ReadTimeoutException) exception).wasDataRetrieved(), message, + exception); } - if (ex instanceof SyntaxError) { - return new CassandraQuerySyntaxException(message, ex); + + if (exception instanceof WriteTimeoutException) { + + WriteType writeType = ((WriteTimeoutException) exception).getWriteType(); + return new CassandraWriteTimeoutException(writeType == null ? null : writeType.name(), message, exception); } - if (ex instanceof UnauthorizedException) { - return new CassandraUnauthorizedException(message, ex); + + if (exception instanceof TruncateException) { + return new CassandraTruncateException(message, exception); } - if (ex instanceof TraceRetrievalException) { - return new CassandraTraceRetrievalException(message, ex); + + if (exception instanceof UnavailableException) { + + UnavailableException ux = (UnavailableException) exception; + return new CassandraInsufficientReplicasAvailableException(ux.getRequiredReplicas(), ux.getAliveReplicas(), + message, exception); + } + + if (exception instanceof OverloadedException || exception instanceof BootstrappingException) { + return new TransientDataAccessResourceException(message, exception); + } + + if (exception instanceof AlreadyExistsException) { + + AlreadyExistsException aex = (AlreadyExistsException) exception; + + return aex.wasTableCreation() ? new CassandraTableExistsException(aex.getTable(), message, exception) + : new CassandraKeyspaceExistsException(aex.getKeyspace(), message, exception); + } + + if (exception instanceof InvalidConfigurationInQueryException) { + return new CassandraInvalidConfigurationInQueryException(message, exception); + } + + if (exception instanceof InvalidQueryException) { + return new CassandraInvalidQueryException(message, exception); + } + + if (exception instanceof SyntaxError) { + return new CassandraQuerySyntaxException(message, exception); + } + + if (exception instanceof UnauthorizedException) { + return new CassandraUnauthorizedException(message, exception); + } + + if (exception instanceof TraceRetrievalException) { + return new CassandraTraceRetrievalException(message, exception); + } + + + + if (exception instanceof NoHostAvailableException) { + return new CassandraConnectionFailureException(((NoHostAvailableException) exception).getErrors(),message, + 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, message, exception); + } + + if (RESOURCE_FAILURE_TYPES.contains(exceptionType)) { + return new DataAccessResourceFailureException(message, exception); } // unknown or unhandled exception - return new CassandraUncategorizedException(message, ex); + return new CassandraUncategorizedException(message, exception); } /** @@ -146,7 +184,7 @@ public class CassandraExceptionTranslator implements CQLExceptionTranslator { *

* To be called by translator subclasses when creating an instance of a generic * {@link org.springframework.dao.DataAccessException} class. - * + * * @param task readable text describing the task being attempted * @param cql the CQL statement that caused the problem (may be {@code null}) * @param ex the offending {@code DriverException} diff --git a/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraAccessorUnitTests.java b/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraAccessorUnitTests.java index 8c02e7f5f..60d86b313 100755 --- a/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraAccessorUnitTests.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraAccessorUnitTests.java @@ -32,6 +32,7 @@ import com.datastax.driver.core.Session; * {@link CassandraAccessor} class. * * @author John Blum + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class CassandraAccessorUnitTests { @@ -74,7 +75,7 @@ public class CassandraAccessorUnitTests { cassandraAccessor.setExceptionTranslator(null); fail("Missing IllegalArgumentException"); } catch (IllegalArgumentException e) { - assertThat(e).hasMessageContaining("CassandraExceptionTranslator must not be null"); + assertThat(e).hasMessageContaining("must not be null"); } } diff --git a/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorUnitTests.java b/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorUnitTests.java index 7ad1c8b2c..164823ec6 100755 --- a/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorUnitTests.java +++ b/spring-cql/src/test/java/org/springframework/cassandra/support/CassandraExceptionTranslatorUnitTests.java @@ -16,18 +16,26 @@ 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} @@ -37,63 +45,303 @@ import com.datastax.driver.core.exceptions.InvalidQueryException; */ public class CassandraExceptionTranslatorUnitTests { - 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, 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); } @Test // DATACASS-335 public void shouldTranslateWithCqlMessage() { InvalidQueryException cx = new InvalidConfigurationInQueryException(null, "err"); - DataAccessException dax = tx.translate("Query", "SELECT * FROM person", cx); + DataAccessException dax = sut.translate("Query", "SELECT * FROM person", cx); assertThat(dax).hasRootCauseInstanceOf(InvalidQueryException.class).hasMessage( "Query; CQL [SELECT * FROM person]; err; nested exception is com.datastax.driver.core.exceptions.InvalidConfigurationInQueryException: err"); } + + @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); + } }