From 12a3badada364dc3e6cee0d5ab94a49c77ecc484 Mon Sep 17 00:00:00 2001 From: PaulBredl Date: Tue, 16 Apr 2024 11:41:59 +0200 Subject: [PATCH 1/2] add more custom exceptions and repository utility --- .../ExceptionToGraphQlErrorConverter.java | 15 +++--- .../ExceptionWithGraphQlErrorType.java | 11 +++++ .../exception/MeitrexNotFoundException.java | 23 +++++++++ .../ResourceAlreadyExistsException.java | 25 ++++++++++ .../common/persistence/MeitrexRepository.java | 48 ++++++++++++++++--- 5 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionWithGraphQlErrorType.java create mode 100644 src/main/java/de/unistuttgart/iste/meitrex/common/exception/MeitrexNotFoundException.java create mode 100644 src/main/java/de/unistuttgart/iste/meitrex/common/exception/ResourceAlreadyExistsException.java diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionToGraphQlErrorConverter.java b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionToGraphQlErrorConverter.java index 2f5d1c1..13c3541 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionToGraphQlErrorConverter.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionToGraphQlErrorConverter.java @@ -39,14 +39,17 @@ public static GraphQLError resolveToSingleError(@NonNull final Throwable ex, @No } private static ErrorType getErrorType(final Throwable ex) { - // HINT we cannot use switch expressions here because JDK 17 does not support them yet - if (ex instanceof EntityNotFoundException) { - return ErrorType.DataFetchingException; - } else if (ex.getClass().getSimpleName().contains("ValidationException")) { + // special case for validation exceptions since we don't want to add the full graphql dependency + // just for the exception type + if (ex.getClass().getSimpleName().contains("ValidationException")) { return ErrorType.ValidationError; } - // HINT add more error types here - return ErrorType.ExecutionAborted; + + return switch (ex) { + case EntityNotFoundException ignored -> ErrorType.DataFetchingException; + case ExceptionWithGraphQlErrorType graphQLError -> graphQLError.getErrorType(); + default -> ErrorType.ExecutionAborted; + }; } /** diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionWithGraphQlErrorType.java b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionWithGraphQlErrorType.java new file mode 100644 index 0000000..34e864a --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ExceptionWithGraphQlErrorType.java @@ -0,0 +1,11 @@ +package de.unistuttgart.iste.meitrex.common.exception; + +import graphql.ErrorType; + +/** + * Interface for exceptions that have a {@link ErrorType}. + */ +public interface ExceptionWithGraphQlErrorType { + + ErrorType getErrorType(); +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/exception/MeitrexNotFoundException.java b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/MeitrexNotFoundException.java new file mode 100644 index 0000000..6d193b0 --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/MeitrexNotFoundException.java @@ -0,0 +1,23 @@ +package de.unistuttgart.iste.meitrex.common.exception; + +import graphql.ErrorType; +import jakarta.persistence.EntityNotFoundException; + +/** + * Exception that is thrown when a resource is not found. + */ +public class MeitrexNotFoundException extends EntityNotFoundException implements ExceptionWithGraphQlErrorType { + public MeitrexNotFoundException(String entityName, Object id) { + this("Resource " + entityName + " with id " + id + " not found"); + } + + public MeitrexNotFoundException(String reason) { + super(reason); + } + + + @Override + public ErrorType getErrorType() { + return ErrorType.DataFetchingException; + } +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ResourceAlreadyExistsException.java b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ResourceAlreadyExistsException.java new file mode 100644 index 0000000..d3c78c3 --- /dev/null +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/exception/ResourceAlreadyExistsException.java @@ -0,0 +1,25 @@ +package de.unistuttgart.iste.meitrex.common.exception; + +import graphql.ErrorType; + +/** + * Exception to be thrown when a resource already exists, but it is tried to be created again. + */ +public class ResourceAlreadyExistsException extends RuntimeException implements ExceptionWithGraphQlErrorType { + public ResourceAlreadyExistsException(String reason) { + super(reason); + } + + public ResourceAlreadyExistsException(String resourceName, Object id) { + this(resourceName + " with id " + id + " already exists."); + } + + public ResourceAlreadyExistsException(Class resourceClass, Object id) { + this(resourceClass.getSimpleName() + " with id " + id + " already exists."); + } + + @Override + public ErrorType getErrorType() { + return ErrorType.ValidationError; + } +} diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java b/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java index 7843dfa..772476f 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java @@ -1,7 +1,8 @@ package de.unistuttgart.iste.meitrex.common.persistence; +import de.unistuttgart.iste.meitrex.common.exception.MeitrexNotFoundException; +import de.unistuttgart.iste.meitrex.common.exception.ResourceAlreadyExistsException; import de.unistuttgart.iste.meitrex.common.util.MeitrexCollectionUtils; -import jakarta.persistence.EntityNotFoundException; import org.springframework.data.jpa.repository.JpaRepository; import java.util.ArrayList; @@ -32,7 +33,7 @@ default List findAllByIdPreservingOrder(final List ids) { /** * Gets all entities with the given ids and preserves the order of the ids. - * If an entity is not found, an EntityNotFoundException is thrown. + * If an entity is not found, an MeitrexNotFoundException is thrown. * * @param ids The ids of the entities to get. * @return The entities with the given ids in order of the given ids. @@ -50,12 +51,47 @@ default List getAllByIdPreservingOrder(final List ids) { } if (!missingIds.isEmpty()) { - throw new EntityNotFoundException("Entities(s) with id(s) " - + missingIds.stream().map(ID::toString) - .collect(Collectors.joining(", ")) - + " not found"); + String missingIdsString = missingIds.stream().map(ID::toString).collect(Collectors.joining(", ")); + + throw new MeitrexNotFoundException("Entities(s) with id(s) " + missingIdsString + " not found"); } return allById; } + + /** + * Finds an entity by its id or throws a ResourceNotFoundException if the entity is not found. + * This method is similar to {@link JpaRepository#getReferenceById(Object)}, + * but throws a custom MeitrexNotFoundException instead of an EntityNotFoundException. + * + * @param id The id of the entity to find. + * @return The entity with the given id. + * @throws MeitrexNotFoundException If the entity is not found. + */ + default T findByIdOrThrow(final ID id) { + return findById(id).orElseThrow(() -> new MeitrexNotFoundException(getEntityName(), id)); + } + + /** + * Checks if an entity with the given id exists and throws an exception if it does. + * + * @param id The id of the entity to check. + * @throws ResourceAlreadyExistsException If the entity exists. + */ + default void requireNotExists(final ID id) { + if (existsById(id)) { + throw new ResourceAlreadyExistsException(getEntityName(), id); + } + } + + /** + * Gets the name of the entity for error messages. + * Sub-classes should override this method to provide a name. + * By default, the name is derived from the repository class name. + * + * @return The name of the entity. + */ + default String getEntityName() { + return getClass().getSimpleName().replace("Repository", ""); + } } From af3223615bc2faa1ed502bc2db4dadf9ce847b80 Mon Sep 17 00:00:00 2001 From: PaulBredl Date: Tue, 16 Apr 2024 11:50:08 +0200 Subject: [PATCH 2/2] add another util method --- .../meitrex/common/persistence/MeitrexRepository.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java b/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java index 772476f..9285515 100644 --- a/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java +++ b/src/main/java/de/unistuttgart/iste/meitrex/common/persistence/MeitrexRepository.java @@ -72,6 +72,16 @@ default T findByIdOrThrow(final ID id) { return findById(id).orElseThrow(() -> new MeitrexNotFoundException(getEntityName(), id)); } + /** + * Same as {@link #findByIdOrThrow(Object)}, but depending on the context, the naming + * of this method might be more appropriate. + * + * @see #findByIdOrThrow(Object) + */ + default T requireExists(final ID id) { + return findByIdOrThrow(id); + } + /** * Checks if an entity with the given id exists and throws an exception if it does. *