TSK-1682: Refactor TaskRestExceptionHandler to use a Map of ExceptionClass to HttpStatusCode

This commit is contained in:
Lia Lissmann 2021-09-02 11:46:37 +02:00 committed by LiaLissmann
parent aa725edb83
commit 816befbbdc
3 changed files with 88 additions and 67 deletions

View File

@ -40,16 +40,17 @@ Additionally, an optional set of message variables, containing some technical in
| *404 NOT_FOUND* | CLASSIFICATION_WITH_KEY_NOT_FOUND | classificationKey, domain | *404 NOT_FOUND* | CLASSIFICATION_WITH_KEY_NOT_FOUND | classificationKey, domain
| *404 NOT_FOUND* | TASK_COMMENT_NOT_FOUND | taskCommentId | *404 NOT_FOUND* | TASK_COMMENT_NOT_FOUND | taskCommentId
| *404 NOT_FOUND* | TASK_NOT_FOUND | taskId | *404 NOT_FOUND* | TASK_NOT_FOUND | taskId
| *404 NOT_FOUND* | USER_NOT_FOUND | userId
| *404 NOT_FOUND* | WORKBASKET_WITH_ID_NOT_FOUND | workbasketId | *404 NOT_FOUND* | WORKBASKET_WITH_ID_NOT_FOUND | workbasketId
| *404 NOT_FOUND* | WORKBASKET_WITH_KEY_NOT_FOUND | workbasketKey, domain | *404 NOT_FOUND* | WORKBASKET_WITH_KEY_NOT_FOUND | workbasketKey, domain
| *404 NOT_FOUND* | USER_NOT_FOUND | userId
| *409 CONFLICT* | ATTACHMENT_ALREADY_EXISTS | attachmentId, taskId | *409 CONFLICT* | ATTACHMENT_ALREADY_EXISTS | attachmentId, taskId
| *409 CONFLICT* | CLASSIFICATION_ALREADY_EXISTS | classificationKey, domain | *409 CONFLICT* | CLASSIFICATION_ALREADY_EXISTS | classificationKey, domain
| *409 CONFLICT* | ENTITY_NOT_UP_TO_DATE | entityId | *409 CONFLICT* | ENTITY_NOT_UP_TO_DATE | entityId
| *409 CONFLICT* | TASK_ALREADY_EXISTS | externalTaskId | *409 CONFLICT* | TASK_ALREADY_EXISTS | externalTaskId
| *409 CONFLICT* | USER_ALREADY_EXISTS | userID
| *409 CONFLICT* | WORKBASKET_ACCESS_ITEM_ALREADY_EXISTS | accessId, workbasketId | *409 CONFLICT* | WORKBASKET_ACCESS_ITEM_ALREADY_EXISTS | accessId, workbasketId
| *409 CONFLICT* | WORKBASKET_ALREADY_EXISTS | workbasketKey, domain | *409 CONFLICT* | WORKBASKET_ALREADY_EXISTS | workbasketKey, domain
| *409 CONFLICT* | USER_ALREADY_EXISTS | userID | *409 CONFLICT* | WORKBASKET_MARKED_FOR_DELETION | workbasketId
| *413 PAYLOAD_TOO_LARGE* | PAYLOAD_TOO_LARGE | | *413 PAYLOAD_TOO_LARGE* | PAYLOAD_TOO_LARGE |
| *423 LOCKED* | CLASSIFICATION_IN_USE | classificationKey, domain | *423 LOCKED* | CLASSIFICATION_IN_USE | classificationKey, domain
| *423 LOCKED* | WORKBASKET_IN_USE | workbasketId | *423 LOCKED* | WORKBASKET_IN_USE | workbasketId

View File

@ -6,6 +6,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -31,15 +32,20 @@ import pro.taskana.classification.api.exceptions.ClassificationAlreadyExistExcep
import pro.taskana.classification.api.exceptions.ClassificationInUseException; import pro.taskana.classification.api.exceptions.ClassificationInUseException;
import pro.taskana.classification.api.exceptions.ClassificationNotFoundException; import pro.taskana.classification.api.exceptions.ClassificationNotFoundException;
import pro.taskana.classification.api.exceptions.MalformedServiceLevelException; import pro.taskana.classification.api.exceptions.MalformedServiceLevelException;
import pro.taskana.common.api.exceptions.AutocommitFailedException;
import pro.taskana.common.api.exceptions.ConcurrencyException; import pro.taskana.common.api.exceptions.ConcurrencyException;
import pro.taskana.common.api.exceptions.ConnectionNotSetException;
import pro.taskana.common.api.exceptions.DomainNotFoundException; import pro.taskana.common.api.exceptions.DomainNotFoundException;
import pro.taskana.common.api.exceptions.ErrorCode; import pro.taskana.common.api.exceptions.ErrorCode;
import pro.taskana.common.api.exceptions.InvalidArgumentException; import pro.taskana.common.api.exceptions.InvalidArgumentException;
import pro.taskana.common.api.exceptions.MismatchedRoleException; import pro.taskana.common.api.exceptions.MismatchedRoleException;
import pro.taskana.common.api.exceptions.SystemException;
import pro.taskana.common.api.exceptions.TaskanaException; import pro.taskana.common.api.exceptions.TaskanaException;
import pro.taskana.common.api.exceptions.TaskanaRuntimeException; import pro.taskana.common.api.exceptions.TaskanaRuntimeException;
import pro.taskana.common.api.exceptions.UnsupportedDatabaseException;
import pro.taskana.common.api.exceptions.WrongCustomHolidayFormatException; import pro.taskana.common.api.exceptions.WrongCustomHolidayFormatException;
import pro.taskana.common.internal.util.MapCreator; import pro.taskana.common.internal.util.MapCreator;
import pro.taskana.common.internal.util.Pair;
import pro.taskana.common.rest.models.ExceptionRepresentationModel; import pro.taskana.common.rest.models.ExceptionRepresentationModel;
import pro.taskana.spi.history.api.exceptions.TaskanaHistoryEventNotFoundException; import pro.taskana.spi.history.api.exceptions.TaskanaHistoryEventNotFoundException;
import pro.taskana.task.api.exceptions.AttachmentPersistenceException; import pro.taskana.task.api.exceptions.AttachmentPersistenceException;
@ -53,7 +59,6 @@ import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.user.api.exceptions.UserAlreadyExistException; import pro.taskana.user.api.exceptions.UserAlreadyExistException;
import pro.taskana.user.api.exceptions.UserNotFoundException; import pro.taskana.user.api.exceptions.UserNotFoundException;
import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException; import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
import pro.taskana.workbasket.api.exceptions.NotAuthorizedToQueryWorkbasketException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketInUseException; import pro.taskana.workbasket.api.exceptions.WorkbasketInUseException;
@ -67,52 +72,62 @@ public class TaskanaRestExceptionHandler extends ResponseEntityExceptionHandler
public static final String ERROR_KEY_QUERY_MALFORMED = "QUERY_PARAMETER_MALFORMED"; public static final String ERROR_KEY_QUERY_MALFORMED = "QUERY_PARAMETER_MALFORMED";
public static final String ERROR_KEY_PAYLOAD = "PAYLOAD_TOO_LARGE"; public static final String ERROR_KEY_PAYLOAD = "PAYLOAD_TOO_LARGE";
public static final String ERROR_KEY_UNKNOWN_ERROR = "UNKNOWN_ERROR";
@ExceptionHandler({ private static final Map<String, HttpStatus> HTTP_STATUS_BY_ERRORCODE_KEY =
MismatchedRoleException.class, Stream.of(
MismatchedWorkbasketPermissionException.class, Pair.of(MalformedServiceLevelException.ERROR_KEY, HttpStatus.BAD_REQUEST),
MismatchedTaskCommentCreatorException.class, Pair.of(WrongCustomHolidayFormatException.ERROR_KEY, HttpStatus.BAD_REQUEST),
NotAuthorizedToQueryWorkbasketException.class Pair.of(DomainNotFoundException.ERROR_KEY, HttpStatus.BAD_REQUEST),
}) Pair.of(InvalidArgumentException.ERROR_KEY, HttpStatus.BAD_REQUEST),
protected ResponseEntity<Object> handleForbiddenExceptions(TaskanaException ex, WebRequest req) { Pair.of(ERROR_KEY_QUERY_MALFORMED, HttpStatus.BAD_REQUEST),
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.FORBIDDEN); Pair.of(InvalidCallbackStateException.ERROR_KEY, HttpStatus.BAD_REQUEST),
} Pair.of(InvalidOwnerException.ERROR_KEY, HttpStatus.BAD_REQUEST),
Pair.of(InvalidTaskStateException.ERROR_KEY, HttpStatus.BAD_REQUEST),
@ExceptionHandler({ //
ClassificationNotFoundException.class, Pair.of(MismatchedRoleException.ERROR_KEY, HttpStatus.FORBIDDEN),
TaskCommentNotFoundException.class, Pair.of(MismatchedTaskCommentCreatorException.ERROR_KEY, HttpStatus.FORBIDDEN),
TaskNotFoundException.class, Pair.of(MismatchedWorkbasketPermissionException.ERROR_KEY_ID, HttpStatus.FORBIDDEN),
TaskanaHistoryEventNotFoundException.class, Pair.of(
WorkbasketNotFoundException.class, MismatchedWorkbasketPermissionException.ERROR_KEY_KEY_DOMAIN,
UserNotFoundException.class HttpStatus.FORBIDDEN),
}) //
protected ResponseEntity<Object> handleNotFound(TaskanaException ex, WebRequest req) { Pair.of(ClassificationNotFoundException.ERROR_KEY_ID, HttpStatus.NOT_FOUND),
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.NOT_FOUND); Pair.of(ClassificationNotFoundException.ERROR_KEY_KEY_DOMAIN, HttpStatus.NOT_FOUND),
} Pair.of(TaskCommentNotFoundException.ERROR_KEY, HttpStatus.NOT_FOUND),
Pair.of(TaskNotFoundException.ERROR_KEY, HttpStatus.NOT_FOUND),
@ExceptionHandler({ Pair.of(UserNotFoundException.ERROR_KEY, HttpStatus.NOT_FOUND),
TaskAlreadyExistException.class, Pair.of(WorkbasketNotFoundException.ERROR_KEY_ID, HttpStatus.NOT_FOUND),
ClassificationAlreadyExistException.class, Pair.of(WorkbasketNotFoundException.ERROR_KEY_KEY_DOMAIN, HttpStatus.NOT_FOUND),
ConcurrencyException.class, Pair.of(TaskanaHistoryEventNotFoundException.ERROR_KEY, HttpStatus.NOT_FOUND),
WorkbasketAlreadyExistException.class, //
WorkbasketAccessItemAlreadyExistException.class, Pair.of(AttachmentPersistenceException.ERROR_KEY, HttpStatus.CONFLICT),
UserAlreadyExistException.class, Pair.of(ClassificationAlreadyExistException.ERROR_KEY, HttpStatus.CONFLICT),
AttachmentPersistenceException.class, Pair.of(ConcurrencyException.ERROR_KEY, HttpStatus.CONFLICT),
WorkbasketMarkedForDeletionException.class Pair.of(TaskAlreadyExistException.ERROR_KEY, HttpStatus.CONFLICT),
}) Pair.of(UserAlreadyExistException.ERROR_KEY, HttpStatus.CONFLICT),
protected ResponseEntity<Object> handleConflictExceptions(TaskanaException ex, WebRequest req) { Pair.of(WorkbasketAccessItemAlreadyExistException.ERROR_KEY, HttpStatus.CONFLICT),
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.CONFLICT); Pair.of(WorkbasketAlreadyExistException.ERROR_KEY, HttpStatus.CONFLICT),
} Pair.of(WorkbasketMarkedForDeletionException.ERROR_KEY, HttpStatus.CONFLICT),
//
@ExceptionHandler({WorkbasketInUseException.class, ClassificationInUseException.class}) Pair.of(ERROR_KEY_PAYLOAD, HttpStatus.PAYLOAD_TOO_LARGE),
protected ResponseEntity<Object> handleWorkbasketInUse(TaskanaException ex, WebRequest req) { //
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.LOCKED); Pair.of(ClassificationInUseException.ERROR_KEY, HttpStatus.LOCKED),
} Pair.of(WorkbasketInUseException.ERROR_KEY, HttpStatus.LOCKED),
//
Pair.of(AutocommitFailedException.ERROR_KEY, HttpStatus.INTERNAL_SERVER_ERROR),
Pair.of(ConnectionNotSetException.ERROR_KEY, HttpStatus.INTERNAL_SERVER_ERROR),
Pair.of(SystemException.ERROR_KEY, HttpStatus.INTERNAL_SERVER_ERROR),
Pair.of(UnsupportedDatabaseException.ERROR_KEY, HttpStatus.INTERNAL_SERVER_ERROR),
Pair.of(ERROR_KEY_UNKNOWN_ERROR, HttpStatus.INTERNAL_SERVER_ERROR))
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
@ExceptionHandler(MaxUploadSizeExceededException.class) @ExceptionHandler(MaxUploadSizeExceededException.class)
protected ResponseEntity<Object> handleMaxUploadSizeExceededException( protected ResponseEntity<Object> handleMaxUploadSizeExceededException(
MaxUploadSizeExceededException ex, WebRequest req) { MaxUploadSizeExceededException ex, WebRequest req) {
return buildResponse(ErrorCode.of(ERROR_KEY_PAYLOAD), ex, req, HttpStatus.PAYLOAD_TOO_LARGE); HttpStatus status =
HTTP_STATUS_BY_ERRORCODE_KEY.getOrDefault(
ERROR_KEY_PAYLOAD, HttpStatus.INTERNAL_SERVER_ERROR);
return buildResponse(ErrorCode.of(ERROR_KEY_PAYLOAD), ex, req, status);
} }
@ExceptionHandler(BeanInstantiationException.class) @ExceptionHandler(BeanInstantiationException.class)
@ -120,46 +135,43 @@ public class TaskanaRestExceptionHandler extends ResponseEntityExceptionHandler
BeanInstantiationException ex, WebRequest req) { BeanInstantiationException ex, WebRequest req) {
if (ex.getCause() instanceof InvalidArgumentException) { if (ex.getCause() instanceof InvalidArgumentException) {
InvalidArgumentException cause = (InvalidArgumentException) ex.getCause(); InvalidArgumentException cause = (InvalidArgumentException) ex.getCause();
return buildResponse(cause.getErrorCode(), ex, req, HttpStatus.BAD_REQUEST); return handleTaskanaException(cause, req);
} }
return buildResponse(null, ex, req, HttpStatus.INTERNAL_SERVER_ERROR); return buildResponse(null, ex, req, HttpStatus.INTERNAL_SERVER_ERROR);
} }
@ExceptionHandler({ // This ExceptionHandler exists to convert IllegalArgumentExceptions to InvalidArgumentExceptions.
InvalidTaskStateException.class, // Once IllegalArgumentExceptions are no longer in use, you can delete this \(*_*)/
InvalidCallbackStateException.class,
InvalidOwnerException.class,
InvalidArgumentException.class,
DomainNotFoundException.class,
MalformedServiceLevelException.class,
WrongCustomHolidayFormatException.class
})
protected ResponseEntity<Object> handleBadRequestExceptions(TaskanaException ex, WebRequest req) {
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(IllegalArgumentException.class) @ExceptionHandler(IllegalArgumentException.class)
protected ResponseEntity<Object> handleIllegalArgumentException( protected ResponseEntity<Object> handleIllegalArgumentException(
IllegalArgumentException ex, WebRequest req) { IllegalArgumentException ex, WebRequest req) {
return buildResponse( HttpStatus status =
ErrorCode.of(InvalidArgumentException.ERROR_KEY), ex, req, HttpStatus.BAD_REQUEST); HTTP_STATUS_BY_ERRORCODE_KEY.getOrDefault(
InvalidArgumentException.ERROR_KEY, HttpStatus.INTERNAL_SERVER_ERROR);
return buildResponse(ErrorCode.of(InvalidArgumentException.ERROR_KEY), ex, req, status);
} }
@ExceptionHandler(TaskanaRuntimeException.class) @ExceptionHandler(TaskanaRuntimeException.class)
protected ResponseEntity<Object> handleUnknownTaskanaRuntimeExceptions( protected ResponseEntity<Object> handleTaskanaRuntimeException(
TaskanaRuntimeException ex, WebRequest req) { TaskanaRuntimeException ex, WebRequest req) {
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.INTERNAL_SERVER_ERROR); HttpStatus status =
HTTP_STATUS_BY_ERRORCODE_KEY.getOrDefault(
ex.getErrorCode().getKey(), HttpStatus.INTERNAL_SERVER_ERROR);
return buildResponse(ex.getErrorCode(), ex, req, status);
} }
@ExceptionHandler(TaskanaException.class) @ExceptionHandler(TaskanaException.class)
protected ResponseEntity<Object> handleUnknownTaskanaExceptions( protected ResponseEntity<Object> handleTaskanaException(TaskanaException ex, WebRequest req) {
TaskanaException ex, WebRequest req) { HttpStatus status =
return buildResponse(ex.getErrorCode(), ex, req, HttpStatus.INTERNAL_SERVER_ERROR); HTTP_STATUS_BY_ERRORCODE_KEY.getOrDefault(
ex.getErrorCode().getKey(), HttpStatus.INTERNAL_SERVER_ERROR);
return buildResponse(ex.getErrorCode(), ex, req, status);
} }
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleGeneralException(Exception ex, WebRequest req) { protected ResponseEntity<Object> handleGeneralException(Exception ex, WebRequest req) {
return buildResponse(ErrorCode.of("UNKNOWN_ERROR"), ex, req, HttpStatus.INTERNAL_SERVER_ERROR); return buildResponse(
ErrorCode.of(ERROR_KEY_UNKNOWN_ERROR), ex, req, HttpStatus.INTERNAL_SERVER_ERROR);
} }
@Override @Override

View File

@ -26,6 +26,8 @@ import pro.taskana.task.api.exceptions.MismatchedTaskCommentCreatorException;
import pro.taskana.task.api.exceptions.TaskAlreadyExistException; import pro.taskana.task.api.exceptions.TaskAlreadyExistException;
import pro.taskana.task.api.exceptions.TaskCommentNotFoundException; import pro.taskana.task.api.exceptions.TaskCommentNotFoundException;
import pro.taskana.task.api.exceptions.TaskNotFoundException; import pro.taskana.task.api.exceptions.TaskNotFoundException;
import pro.taskana.user.api.exceptions.UserAlreadyExistException;
import pro.taskana.user.api.exceptions.UserNotFoundException;
import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException; import pro.taskana.workbasket.api.exceptions.MismatchedWorkbasketPermissionException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAccessItemAlreadyExistException;
import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException; import pro.taskana.workbasket.api.exceptions.WorkbasketAlreadyExistException;
@ -103,4 +105,10 @@ class ExceptionErrorKeyTest {
assertThat(TaskanaRestExceptionHandler.ERROR_KEY_QUERY_MALFORMED) assertThat(TaskanaRestExceptionHandler.ERROR_KEY_QUERY_MALFORMED)
.isEqualTo("QUERY_PARAMETER_MALFORMED"); .isEqualTo("QUERY_PARAMETER_MALFORMED");
} }
@Test
void should_ProvideConsistentErrorKey_For_UserExceptions() {
assertThat(UserNotFoundException.ERROR_KEY).isEqualTo("USER_NOT_FOUND");
assertThat(UserAlreadyExistException.ERROR_KEY).isEqualTo("USER_ALREADY_EXISTS");
}
} }