From 04ff23788cb40f1ced29673181510cfa5cd8d4f7 Mon Sep 17 00:00:00 2001 From: Arne Burmeister Date: Tue, 5 Sep 2023 10:42:55 +0200 Subject: [PATCH 1/6] #281 use data access utils for exception handling --- .../core/template/ArangoTemplate.java | 39 +++++++++---------- .../template/DefaultCollectionOperations.java | 31 +++++++-------- .../core/template/DefaultUserOperation.java | 25 ++++++------ 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java index 1314e94aa..93d78e1bc 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -64,6 +64,7 @@ import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.DataAccessUtils; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.expression.Expression; @@ -150,10 +151,6 @@ private ArangoDatabase db() { }); } - private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { - return exceptionTranslator.translateExceptionIfPossible(exception); - } - private ArangoCollection _collection(final String name) { return _collection(name, null, null); } @@ -299,7 +296,7 @@ public ArangoDBVersion getVersion() throws DataAccessException { } return version; } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -353,7 +350,7 @@ public MultiDocumentEntity delete(final Iterable entityClass, final try { result = _collection(entityClass, id).deleteDocument(determineDocumentKeyFromId(id), options, entityClass); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } potentiallyEmitEvent(new AfterDeleteEvent<>(id, entityClass)); @@ -398,7 +395,7 @@ public MultiDocumentEntity update(final Iterable MultiDocumentEntity replace(final Iterable< try { result = _collection(entityClass).replaceDocuments(toJsonNodeCollection(values), options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } updateDBFields(values, result); @@ -470,7 +467,7 @@ public DocumentEntity replace(final Object id, final Object value, final Documen result = _collection(value.getClass(), id).replaceDocument(determineDocumentKeyFromId(id), toJsonNode(value), options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } updateDBFields(value, result); @@ -491,7 +488,7 @@ public Optional find(final Object id, final Class entityClass, final D JsonNode.class, options); return Optional.ofNullable(fromJsonNode(entityClass, doc)); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -516,7 +513,7 @@ public Iterable find(final Iterable ids, final Class entityClass) final MultiDocumentEntity docs = _collection(entityClass).getDocuments(keys, JsonNode.class); return docs.getDocuments().stream().map(doc -> fromJsonNode(entityClass, doc)).collect(Collectors.toList()); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -530,7 +527,7 @@ public MultiDocumentEntity insert(final Iterable void repsert(final T value) throws DataAccessException { ); result = it.hasNext() ? it.next() : null; } catch (final ArangoDBException e) { - throw exceptionTranslator.translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } updateDBFieldsFromObject(value, result); @@ -635,7 +632,7 @@ public void repsert(final Iterable values, final Class entit entityClass ).asListRemaining(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } updateDBFieldsFromObjects(values, result); @@ -715,7 +712,7 @@ public boolean exists(final Object id, final Class entityClass) throws DataAc try { return _collection(entityClass).documentExists(determineDocumentKeyFromId(id)); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -725,7 +722,7 @@ public void dropDatabase() throws DataAccessException { try { db.drop(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } databaseCache.remove(db.name()); collectionCache.keySet().stream().filter(key -> key.getDb().equals(db.name())) @@ -762,7 +759,7 @@ public Iterable getUsers() throws DataAccessException { try { return arango.getUsers(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } diff --git a/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java b/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java index a5cd3bf86..8f7c9ad4c 100644 --- a/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java +++ b/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java @@ -24,6 +24,7 @@ import java.util.Map; import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.DataAccessUtils; import org.springframework.dao.support.PersistenceExceptionTranslator; import com.arangodb.ArangoCollection; @@ -55,10 +56,6 @@ protected DefaultCollectionOperations(final ArangoCollection collection, this.exceptionTranslator = exceptionTranslator; } - private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { - return exceptionTranslator.translateExceptionIfPossible(exception); - } - @Override public String name() { return collection.name(); @@ -70,7 +67,7 @@ public void drop() throws DataAccessException { try { collection.drop(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -79,7 +76,7 @@ public void truncate() throws DataAccessException { try { collection.truncate(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -89,7 +86,7 @@ public long count() throws DataAccessException { final Long count = collection.count().getCount(); return count != null ? count : -1L; } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -98,7 +95,7 @@ public CollectionPropertiesEntity getProperties() throws DataAccessException { try { return collection.getProperties(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -107,7 +104,7 @@ public Collection getIndexes() throws DataAccessException { try { return collection.getIndexes(); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -117,7 +114,7 @@ public IndexEntity ensurePersistentIndex(final Iterable fields, final Pe try { return collection.ensurePersistentIndex(fields, options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -127,7 +124,7 @@ public IndexEntity ensureGeoIndex(final Iterable fields, final GeoIndexO try { return collection.ensureGeoIndex(fields, options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -138,7 +135,7 @@ public IndexEntity ensureFulltextIndex(final Iterable fields, final Full try { return collection.ensureFulltextIndex(fields, options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -147,7 +144,7 @@ public IndexEntity ensureTtlIndex(Iterable fields, TtlIndexOptions optio try { return collection.ensureTtlIndex(fields, options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -156,7 +153,7 @@ public void dropIndex(final String id) throws DataAccessException { try { collection.deleteIndex(id); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -165,7 +162,7 @@ public void grantAccess(final String username, final Permissions permissions) { try { collection.grantAccess(username, permissions); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -174,7 +171,7 @@ public void resetAccess(final String username) { try { collection.resetAccess(username); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -183,7 +180,7 @@ public Permissions getPermissions(final String username) throws DataAccessExcept try { return collection.getPermissions(username); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } diff --git a/src/main/java/com/arangodb/springframework/core/template/DefaultUserOperation.java b/src/main/java/com/arangodb/springframework/core/template/DefaultUserOperation.java index 28903ec3f..03ccb1b97 100644 --- a/src/main/java/com/arangodb/springframework/core/template/DefaultUserOperation.java +++ b/src/main/java/com/arangodb/springframework/core/template/DefaultUserOperation.java @@ -21,6 +21,7 @@ package com.arangodb.springframework.core.template; import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.DataAccessUtils; import org.springframework.dao.support.PersistenceExceptionTranslator; import com.arangodb.ArangoDBException; @@ -57,16 +58,12 @@ protected DefaultUserOperation(final ArangoDatabase db, final String username, this.collectionCallback = collectionCallback; } - private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { - return exceptionTranslator.translateExceptionIfPossible(exception); - } - @Override public UserEntity get() throws DataAccessException { try { return db.arango().getUser(username); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -75,7 +72,7 @@ public UserEntity create(final String passwd, final UserCreateOptions options) t try { return db.arango().createUser(username, passwd); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -84,7 +81,7 @@ public UserEntity update(final UserUpdateOptions options) throws DataAccessExcep try { return db.arango().updateUser(username, options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -93,7 +90,7 @@ public UserEntity replace(final UserUpdateOptions options) throws DataAccessExce try { return db.arango().replaceUser(username, options); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -102,7 +99,7 @@ public void delete() throws DataAccessException { try { db.arango().deleteUser(username); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -111,7 +108,7 @@ public void grantDefaultDatabaseAccess(final Permissions permissions) throws Dat try { db.arango().grantDefaultDatabaseAccess(username, permissions); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -120,7 +117,7 @@ public void grantDatabaseAccess(final Permissions permissions) throws DataAccess try { db.grantAccess(username, permissions); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -129,7 +126,7 @@ public void resetDatabaseAccess() throws DataAccessException { try { db.resetAccess(username); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -138,7 +135,7 @@ public void grantDefaultCollectionAccess(final Permissions permissions) throws D try { db.grantDefaultCollectionAccess(username, permissions); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } @@ -167,7 +164,7 @@ public Permissions getDatabasePermissions() throws DataAccessException { try { return db.getPermissions(username); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } From 3d662200d562b5ad5ff1962c8e7ca746ff904630 Mon Sep 17 00:00:00 2001 From: Arne Burmeister Date: Tue, 5 Sep 2023 10:48:17 +0200 Subject: [PATCH 2/6] #281 translate exceptions for query --- .../arangodb/springframework/core/template/ArangoTemplate.java | 2 +- .../springframework/repository/query/ArangoAqlQueryTest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java index 93d78e1bc..6834909b4 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -324,7 +324,7 @@ public ArangoCursor query(final String query, final Map b ArangoCursor cursor = db().query(query, JsonNode.class, bindVars == null ? null : prepareBindVars(bindVars), options); return new ArangoExtCursor<>(cursor, entityClass, converter, eventPublisher); } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); + throw DataAccessUtils.translateIfNecessary(e, exceptionTranslator); } } diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index 6518b7858..ee222b0d9 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -1,7 +1,6 @@ package com.arangodb.springframework.repository.query; import com.arangodb.ArangoCursor; -import com.arangodb.ArangoDBException; import com.arangodb.entity.BaseDocument; import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.repository.AbstractArangoRepositoryTest; From ce9fec83da45df168dd7f4bd4922a11116be6475 Mon Sep 17 00:00:00 2001 From: Arne Burmeister Date: Tue, 5 Sep 2023 14:22:01 +0200 Subject: [PATCH 3/6] #281 more specific exception translation --- .../core/util/ArangoExceptionTranslator.java | 83 +++++++++++-------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java index 67247abe8..5a74f6f7c 100644 --- a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java +++ b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java @@ -20,57 +20,53 @@ package com.arangodb.springframework.core.util; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.DataAccessResourceFailureException; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.dao.PermissionDeniedDataAccessException; +import org.springframework.dao.*; import org.springframework.dao.support.PersistenceExceptionTranslator; import com.arangodb.ArangoDBException; import com.arangodb.springframework.ArangoUncategorizedException; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +import static java.util.Map.entry; + /** + * Translate any {@link ArangoDBException} to the appropriate {@link DataAccessException} using response code and error number. + * + * @see General ArangoDB storage errors * @author Mark Vollmary * @author Christian Lechner - * + * @author Arne Burmeister */ public class ArangoExceptionTranslator implements PersistenceExceptionTranslator { @Override public DataAccessException translateExceptionIfPossible(final RuntimeException ex) { DataAccessException dae = null; - if (ex instanceof DataAccessException) { - dae = DataAccessException.class.cast(ex); - } else if (ex instanceof ArangoDBException) { - final ArangoDBException exception = ArangoDBException.class.cast(ex); + if (ex instanceof DataAccessException exception) { + dae = exception; + } else if (ex instanceof ArangoDBException exception) { final Integer responseCode = exception.getResponseCode(); if (responseCode != null) { - switch (responseCode) { - case ArangoErrors.ERROR_HTTP_UNAUTHORIZED: - case ArangoErrors.ERROR_HTTP_FORBIDDEN: - dae = new PermissionDeniedDataAccessException(exception.getMessage(), exception); - break; - case ArangoErrors.ERROR_HTTP_BAD_PARAMETER: - case ArangoErrors.ERROR_HTTP_METHOD_NOT_ALLOWED: - dae = new InvalidDataAccessApiUsageException(exception.getMessage(), exception); - break; - case ArangoErrors.ERROR_HTTP_NOT_FOUND: - dae = new InvalidDataAccessResourceUsageException(exception.getMessage(), exception); - break; - case ArangoErrors.ERROR_HTTP_CONFLICT: - dae = new DataIntegrityViolationException(exception.getMessage(), exception); - break; - case ArangoErrors.ERROR_HTTP_PRECONDITION_FAILED: - case ArangoErrors.ERROR_HTTP_SERVICE_UNAVAILABLE: - dae = new DataAccessResourceFailureException(exception.getMessage(), exception); - break; - case ArangoErrors.ERROR_HTTP_SERVER_ERROR: - default: - dae = new ArangoUncategorizedException(exception.getMessage(), exception); - break; - } + BiFunction constructor = switch (responseCode) { + case ArangoErrors.ERROR_HTTP_UNAUTHORIZED, ArangoErrors.ERROR_HTTP_FORBIDDEN -> PermissionDeniedDataAccessException::new; + case ArangoErrors.ERROR_HTTP_BAD_PARAMETER, ArangoErrors.ERROR_HTTP_METHOD_NOT_ALLOWED -> InvalidDataAccessApiUsageException::new; + case ArangoErrors.ERROR_HTTP_NOT_FOUND -> mostSpecific(exception, Map.ofEntries( + entry(hasErrorNumber(1202), DataRetrievalFailureException::new) + ), InvalidDataAccessResourceUsageException::new); + case ArangoErrors.ERROR_HTTP_CONFLICT -> mostSpecific(exception, Map.ofEntries( + entry(hasErrorNumber(1200).and(errorMessageContains("write-write conflict")), TransientDataAccessResourceException::new), + entry(hasErrorNumber(1200).and(errorMessageContains("_rev")), OptimisticLockingFailureException::new), + entry(hasErrorNumber(1210).and(errorMessageContains("_key")), DuplicateKeyException::new) + ), + DataIntegrityViolationException::new); + case ArangoErrors.ERROR_HTTP_PRECONDITION_FAILED -> OptimisticLockingFailureException::new; + case ArangoErrors.ERROR_HTTP_SERVICE_UNAVAILABLE -> DataAccessResourceFailureException::new; + default -> ArangoUncategorizedException::new; + }; + return constructor.apply(exception.getMessage(), exception); } } if (dae == null) { @@ -79,4 +75,21 @@ public DataAccessException translateExceptionIfPossible(final RuntimeException e return dae; } + private static BiFunction mostSpecific(ArangoDBException exception, + Map, BiFunction> specific, + BiFunction fallback) { + return specific.entrySet().stream() + .filter(entry -> entry.getKey().test(exception)) + .map(Map.Entry::getValue) + .findAny() + .orElse(fallback); + } + + private static Predicate hasErrorNumber(int expected) { + return exception -> exception.getErrorNum() != null && exception.getErrorNum() == expected; + } + + private static Predicate errorMessageContains(String expected) { + return exception -> exception.getErrorMessage() != null && exception.getErrorMessage().contains(expected); + } } From 6bc55da92be90447147ae51ed11a453b3cd782c8 Mon Sep 17 00:00:00 2001 From: Arne Burmeister Date: Tue, 5 Sep 2023 18:16:44 +0200 Subject: [PATCH 4/6] #281 test query exception translation --- .../core/template/ArangoTemplateTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java index 47a069a07..2271fd64a 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java @@ -25,6 +25,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Arrays; import java.util.HashMap; @@ -36,6 +37,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.Test; +import org.springframework.dao.DataRetrievalFailureException; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.domain.Persistable; @@ -115,6 +117,13 @@ public void getDocument() { assertThat(customer.getAddress().getZipCode(), is("22162–1010")); } + + @Test + public void deleteUnknownDocument() { + assertThrows(DataRetrievalFailureException.class, + () -> template.delete("9999", Customer.class)); + } + @Test public void getDocuments() { final Customer c1 = new Customer("John", "Doe", 30); @@ -263,6 +272,12 @@ public void queryWithoutBindParams() { assertThat(customers.get(0).getAge(), is(30)); } + @Test + public void updateQueryForUnknown() { + assertThrows(DataRetrievalFailureException.class, + () -> template.query("UPDATE '9999' WITH { age: 99 } IN `test-customer` RETURN NEW", Customer.class)); + } + @SuppressWarnings("rawtypes") @Test public void queryMap() { From 9853b2c7ecbf7ae0f00b417ef113a1153952fcf2 Mon Sep 17 00:00:00 2001 From: Arne Burmeister Date: Wed, 6 Sep 2023 09:24:37 +0200 Subject: [PATCH 5/6] #281 declare error constants, translator returns null for unsupported exceptions --- .../core/util/ArangoErrors.java | 36 +++++++++---- .../core/util/ArangoExceptionTranslator.java | 54 +++++++++---------- 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java b/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java index fb105d9a7..f3fd3077d 100644 --- a/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java +++ b/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java @@ -21,55 +21,71 @@ package com.arangodb.springframework.core.util; /** + * @see General ArangoDB storage errors * @author Mark Vollmary * @author Christian Lechner - * + * @author Arne Burmeister */ public class ArangoErrors { /** - * bad parameter. Will be raised when the HTTP request does not fulfill the requirements. + * Bad parameter, will be raised when the HTTP request does not fulfill the requirements. */ public static final int ERROR_HTTP_BAD_PARAMETER = 400; /** - * unauthorized. Will be raised when authorization is required but the user is not authorized. + * Unauthorized, will be raised when authorization is required but the user is not authorized. */ public static final int ERROR_HTTP_UNAUTHORIZED = 401; /** - * forbidden. Will be raised when the operation is forbidden. + * Forbidden, will be raised when the operation is forbidden. */ public static final int ERROR_HTTP_FORBIDDEN = 403; /** - * not found. Will be raised when an URI is unknown. + * Not found, will be raised when an URI is unknown. */ public static final int ERROR_HTTP_NOT_FOUND = 404; /** - * method not supported. Will be raised when an unsupported HTTP method is used for an operation. + * Method not supported, will be raised when an unsupported HTTP method is used for an operation. */ public static final int ERROR_HTTP_METHOD_NOT_ALLOWED = 405; /** - * conflict. Will be raised when a conflict is encountered. + * Conflict, will be raised when a conflict is encountered. */ public static final int ERROR_HTTP_CONFLICT = 409; /** - * precondition failed. Will be raised when a precondition for an HTTP request is not met. + * Precondition failed, will be raised when a precondition for an HTTP request is not met. */ public static final int ERROR_HTTP_PRECONDITION_FAILED = 412; /** - * internal server error. Will be raised when an internal server is encountered. + * Internal server error, will be raised when an internal server is encountered. */ public static final int ERROR_HTTP_SERVER_ERROR = 500; /** - * service unavailable. Will be raised when a service is temporarily unavailable. + * Service unavailable, will be raised when a service is temporarily unavailable. */ public static final int ERROR_HTTP_SERVICE_UNAVAILABLE = 503; + /** + * Conflict, will be raised when updating or deleting a document and a conflict has been detected. + */ + public static final int ERROR_ARANGO_CONFLICT = 1200; + + /** + * Document not found, will be raised when a document with a given identifier is unknown. + */ + public static final int ERROR_ARANGO_DOCUMENT_NOT_FOUND = 1202; + + /** + * Unique constraint violated, will be raised when there is a unique constraint violation. + */ + public static final int ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED = 1210; + } diff --git a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java index 5a74f6f7c..a94b72b49 100644 --- a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java +++ b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java @@ -25,17 +25,18 @@ import com.arangodb.ArangoDBException; import com.arangodb.springframework.ArangoUncategorizedException; +import org.springframework.lang.Nullable; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Predicate; +import static com.arangodb.springframework.core.util.ArangoErrors.*; import static java.util.Map.entry; /** * Translate any {@link ArangoDBException} to the appropriate {@link DataAccessException} using response code and error number. * - * @see General ArangoDB storage errors * @author Mark Vollmary * @author Christian Lechner * @author Arne Burmeister @@ -43,36 +44,35 @@ public class ArangoExceptionTranslator implements PersistenceExceptionTranslator { @Override + @Nullable public DataAccessException translateExceptionIfPossible(final RuntimeException ex) { - DataAccessException dae = null; if (ex instanceof DataAccessException exception) { - dae = exception; - } else if (ex instanceof ArangoDBException exception) { + return exception; + } + if (ex instanceof ArangoDBException exception) { final Integer responseCode = exception.getResponseCode(); - if (responseCode != null) { - BiFunction constructor = switch (responseCode) { - case ArangoErrors.ERROR_HTTP_UNAUTHORIZED, ArangoErrors.ERROR_HTTP_FORBIDDEN -> PermissionDeniedDataAccessException::new; - case ArangoErrors.ERROR_HTTP_BAD_PARAMETER, ArangoErrors.ERROR_HTTP_METHOD_NOT_ALLOWED -> InvalidDataAccessApiUsageException::new; - case ArangoErrors.ERROR_HTTP_NOT_FOUND -> mostSpecific(exception, Map.ofEntries( - entry(hasErrorNumber(1202), DataRetrievalFailureException::new) - ), InvalidDataAccessResourceUsageException::new); - case ArangoErrors.ERROR_HTTP_CONFLICT -> mostSpecific(exception, Map.ofEntries( - entry(hasErrorNumber(1200).and(errorMessageContains("write-write conflict")), TransientDataAccessResourceException::new), - entry(hasErrorNumber(1200).and(errorMessageContains("_rev")), OptimisticLockingFailureException::new), - entry(hasErrorNumber(1210).and(errorMessageContains("_key")), DuplicateKeyException::new) - ), - DataIntegrityViolationException::new); - case ArangoErrors.ERROR_HTTP_PRECONDITION_FAILED -> OptimisticLockingFailureException::new; - case ArangoErrors.ERROR_HTTP_SERVICE_UNAVAILABLE -> DataAccessResourceFailureException::new; - default -> ArangoUncategorizedException::new; - }; - return constructor.apply(exception.getMessage(), exception); + if (responseCode == null) { + return new ArangoUncategorizedException(exception.getMessage(), exception); } + BiFunction constructor = switch (responseCode) { + case ERROR_HTTP_UNAUTHORIZED, ERROR_HTTP_FORBIDDEN -> PermissionDeniedDataAccessException::new; + case ERROR_HTTP_BAD_PARAMETER, ERROR_HTTP_METHOD_NOT_ALLOWED -> InvalidDataAccessApiUsageException::new; + case ERROR_HTTP_NOT_FOUND -> mostSpecific(exception, Map.ofEntries( + entry(hasErrorNumber(ERROR_ARANGO_DOCUMENT_NOT_FOUND), DataRetrievalFailureException::new) + ), InvalidDataAccessResourceUsageException::new); + case ERROR_HTTP_CONFLICT -> mostSpecific(exception, Map.ofEntries( + entry(hasErrorNumber(ERROR_ARANGO_CONFLICT).and(errorMessageContains("write-write")), TransientDataAccessResourceException::new), + entry(hasErrorNumber(ERROR_ARANGO_CONFLICT).and(errorMessageContains("_rev")), OptimisticLockingFailureException::new), + entry(hasErrorNumber(ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED).and(errorMessageContains("_key")), DuplicateKeyException::new) + ), + DataIntegrityViolationException::new); + case ERROR_HTTP_PRECONDITION_FAILED -> OptimisticLockingFailureException::new; + case ERROR_HTTP_SERVICE_UNAVAILABLE -> DataAccessResourceFailureException::new; + default -> ArangoUncategorizedException::new; + }; + return constructor.apply(exception.getMessage(), exception); } - if (dae == null) { - throw ex; - } - return dae; + return null; } private static BiFunction mostSpecific(ArangoDBException exception, @@ -81,7 +81,7 @@ private static BiFunction mostSp return specific.entrySet().stream() .filter(entry -> entry.getKey().test(exception)) .map(Map.Entry::getValue) - .findAny() + .findFirst() .orElse(fallback); } From e2d057d264ed58e60b7e1ae48b97097c4a5ee4e7 Mon Sep 17 00:00:00 2001 From: Michele Rastelli Date: Wed, 6 Sep 2023 14:39:42 +0200 Subject: [PATCH 6/6] Update src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java --- .../springframework/core/util/ArangoExceptionTranslator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java index a94b72b49..055c23e96 100644 --- a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java +++ b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java @@ -66,7 +66,10 @@ public DataAccessException translateExceptionIfPossible(final RuntimeException e entry(hasErrorNumber(ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED).and(errorMessageContains("_key")), DuplicateKeyException::new) ), DataIntegrityViolationException::new); - case ERROR_HTTP_PRECONDITION_FAILED -> OptimisticLockingFailureException::new; + case ERROR_HTTP_PRECONDITION_FAILED -> mostSpecific(exception, Map.ofEntries( + entry(hasErrorNumber(ERROR_ARANGO_CONFLICT).and(errorMessageContains("_rev")), OptimisticLockingFailureException::new) + ), + DataAccessResourceFailureException::new); case ERROR_HTTP_SERVICE_UNAVAILABLE -> DataAccessResourceFailureException::new; default -> ArangoUncategorizedException::new; };