From dc4cd818b42c0511ddc4be4d264290bd4ee805fc Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 26 Sep 2024 08:49:53 +0200 Subject: [PATCH] improved error messages if entity not found --- .../hsadminng/errors/DisplayAs.java | 7 +++- .../debitor/HsOfficeDebitorController.java | 23 ++++------- .../persistence/EntityExistsValidator.java | 41 +++++++++++++++++++ ...OfficeDebitorControllerAcceptanceTest.java | 4 +- 4 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 src/main/java/net/hostsharing/hsadminng/persistence/EntityExistsValidator.java diff --git a/src/main/java/net/hostsharing/hsadminng/errors/DisplayAs.java b/src/main/java/net/hostsharing/hsadminng/errors/DisplayAs.java index 020d006a..8eba1c13 100644 --- a/src/main/java/net/hostsharing/hsadminng/errors/DisplayAs.java +++ b/src/main/java/net/hostsharing/hsadminng/errors/DisplayAs.java @@ -11,13 +11,18 @@ import java.lang.annotation.Target; public @interface DisplayAs { class DisplayName { public static String of(final Class clazz) { - final var displayNameAnnot = clazz.getAnnotation(DisplayAs.class); + final var displayNameAnnot = getDisplayNameAnnotation(clazz); return displayNameAnnot != null ? displayNameAnnot.value() : clazz.getSimpleName(); } public static String of(@NotNull final Object instance) { return of(instance.getClass()); } + + private static DisplayAs getDisplayNameAnnotation(final Class clazz) { + final var annot = clazz.getAnnotation(DisplayAs.class); + return annot != null ? annot : getDisplayNameAnnotation(clazz.getSuperclass()); + } } String value() default ""; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java index a9109c13..0770aa35 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorController.java @@ -8,7 +8,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebito import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository; import net.hostsharing.hsadminng.mapper.StandardMapper; -import net.hostsharing.hsadminng.rbac.object.BaseEntity; +import net.hostsharing.hsadminng.persistence.EntityExistsValidator; import org.apache.commons.lang3.Validate; import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; @@ -23,7 +23,6 @@ import jakarta.validation.ValidationException; import java.util.List; import java.util.UUID; -import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR; @RestController @@ -42,6 +41,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { @Autowired private HsOfficeRelationRealRepository relrealRepo; + @Autowired + private EntityExistsValidator entityValidator; + @PersistenceContext private EntityManager em; @@ -85,9 +87,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { if ( body.getDebitorRel() != null ) { body.getDebitorRel().setType(DEBITOR.name()); final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class); - validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor()); - validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder()); - validateEntityExists("debitorRel.contactUuid", debitorRel.getContact()); + entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor()); + entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder()); + entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact()); entityToSave.setDebitorRel(relrealRepo.save(debitorRel)); } else { final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid()); @@ -160,15 +162,4 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi { final var mapped = mapper.map(saved, HsOfficeDebitorResource.class); return ResponseEntity.ok(mapped); } - - // TODO.impl: extract this to some generally usable class? - private > T validateEntityExists(final String property, final T entitySkeleton) { - final var foundEntity = em.find(entitySkeleton.getClass(), entitySkeleton.getUuid()); - if ( foundEntity == null) { - throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid()); - } - - //noinspection unchecked - return (T) foundEntity; - } } diff --git a/src/main/java/net/hostsharing/hsadminng/persistence/EntityExistsValidator.java b/src/main/java/net/hostsharing/hsadminng/persistence/EntityExistsValidator.java new file mode 100644 index 00000000..df0e8637 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/persistence/EntityExistsValidator.java @@ -0,0 +1,41 @@ +package net.hostsharing.hsadminng.persistence; + +import lombok.experimental.UtilityClass; +import net.hostsharing.hsadminng.errors.DisplayAs; +import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName; +import net.hostsharing.hsadminng.rbac.object.BaseEntity; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import jakarta.persistence.Entity; +import jakarta.validation.ValidationException; + +@Service +public class EntityExistsValidator { + + @Autowired + private EntityManagerWrapper em; + + public > void validateEntityExists(final String property, final T entitySkeleton) { + final var foundEntity = em.find(entityClass(entitySkeleton), entitySkeleton.getUuid()); + if ( foundEntity == null) { + throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid()); + } + } + + private static > Class entityClass(final T entityOrProxy) { + final var entityClass = entityClass(entityOrProxy.getClass()); + if (entityClass == null) { + throw new IllegalArgumentException("@Entity not found in superclass hierarchy of " + entityOrProxy.getClass()); + } + return entityClass; + } + + private static Class entityClass(final Class entityOrProxyClass) { + return entityOrProxyClass.isAnnotationPresent(Entity.class) + ? entityOrProxyClass + : entityOrProxyClass.getSuperclass() == null + ? null + : entityClass(entityOrProxyClass.getSuperclass()); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index 7f3b2281..d0954b6a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -405,7 +405,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu .post("http://localhost/api/hs/office/debitors") .then().log().all().assertThat() .statusCode(400) - .body("message", is("ERROR: [400] Unable to find RealContact by debitorRel.contact.uuid: 00000000-0000-0000-0000-000000000000")); + .body("message", is("ERROR: [400] Unable to find RealContact by debitorRel.contactUuid: 00000000-0000-0000-0000-000000000000")); // @formatter:on } @@ -433,7 +433,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu .post("http://localhost/api/hs/office/debitors") .then().log().all().assertThat() .statusCode(400) - .body("message", is("ERROR: [400] Unable to find RealRelation by debitorRel.uuid: 00000000-0000-0000-0000-000000000000")); + .body("message", is("ERROR: [400] Unable to find RealRelation by debitorRelUuid: 00000000-0000-0000-0000-000000000000")); // @formatter:on } }