diff --git a/CHANGELOG.md b/CHANGELOG.md index 370e93669..88ef78192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Unreleased +### Added + +- The `Problem` (of error responses) is expanded by a hint that gives clearer information about the actual error + ## [9.1.0](https://github.com/dbmdz/metadata-service/releases/tag/9.1.0) - 2024-04-16 ### Added diff --git a/metasvc-client/pom.xml b/metasvc-client/pom.xml index 5a37dd7bc..b55ac8c06 100644 --- a/metasvc-client/pom.xml +++ b/metasvc-client/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Client diff --git a/metasvc-lobid-client/pom.xml b/metasvc-lobid-client/pom.xml index 825a825c9..499f9e48a 100644 --- a/metasvc-lobid-client/pom.xml +++ b/metasvc-lobid-client/pom.xml @@ -7,7 +7,7 @@ io.github.dbmdz.metadata metasvc - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: lobid.org Client diff --git a/metasvc-model/pom.xml b/metasvc-model/pom.xml index 0caad0dae..cc02558f2 100644 --- a/metasvc-model/pom.xml +++ b/metasvc-model/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Model diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpErrorDecoder.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpErrorDecoder.java index 7571d8c1c..0c4220f6f 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpErrorDecoder.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpErrorDecoder.java @@ -14,6 +14,7 @@ import de.digitalcollections.model.exception.http.server.HttpVersionNotSupportedException; import de.digitalcollections.model.exception.http.server.NotImplementedException; import de.digitalcollections.model.exception.http.server.ServiceUnavailableException; +import de.digitalcollections.model.exception.problem.MetasvcProblem; import de.digitalcollections.model.jackson.DigitalCollectionsObjectMapper; import java.net.MalformedURLException; import java.net.URL; @@ -23,7 +24,6 @@ import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.zalando.problem.Problem; import org.zalando.problem.jackson.ProblemModule; public class HttpErrorDecoder { @@ -36,7 +36,7 @@ public class HttpErrorDecoder { } private static HttpException clientException( - String methodKey, int statusCode, String requestUrl, Problem problem) { + String methodKey, int statusCode, String requestUrl, MetasvcProblem problem) { switch (statusCode) { case 401: return new UnauthorizedException(methodKey, statusCode, requestUrl, problem); @@ -57,7 +57,7 @@ private static HttpException clientException( public static HttpException decode(String methodKey, int statusCode, HttpResponse response) { String requestUrl = null; - Problem problem = null; + MetasvcProblem problem = null; if (response != null) { requestUrl = Optional.ofNullable(response.request()) @@ -77,7 +77,7 @@ public static HttpException decode(String methodKey, int statusCode, HttpRespons final byte[] body = (byte[]) response.body(); if (body != null && body.length > 0) { try { - problem = mapper.readerFor(Problem.class).readValue(body); + problem = mapper.readerFor(MetasvcProblem.class).readValue(body); } catch (Exception e) { LOGGER.error( "Got response=" @@ -99,12 +99,12 @@ public static HttpException decode(String methodKey, int statusCode, HttpRespons } private static HttpException genericHttpException( - String methodKey, int statusCode, String requestUrl, Problem problem) { + String methodKey, int statusCode, String requestUrl, MetasvcProblem problem) { return new HttpException(methodKey, statusCode, requestUrl, problem); } private static HttpServerException serverException( - String methodKey, int statusCode, String requestUrl, Problem problem) { + String methodKey, int statusCode, String requestUrl, MetasvcProblem problem) { switch (statusCode) { case 501: return new NotImplementedException(methodKey, statusCode, requestUrl, problem); diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpException.java index 3674a6645..e723fc362 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/HttpException.java @@ -1,13 +1,13 @@ package de.digitalcollections.model.exception.http; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class HttpException extends RuntimeException { private final String methodKey; private final String request; private final Integer statuscode; - private final Problem problem; + private final MetasvcProblem problem; public HttpException(String methodKey, Exception ex) { super(String.format("Got exception for backend call %s.", methodKey), ex); @@ -25,7 +25,7 @@ public HttpException(String methodKey, int statuscode) { this.problem = null; } - public HttpException(String methodKey, int statuscode, String request, Problem problem) { + public HttpException(String methodKey, int statuscode, String request, MetasvcProblem problem) { super(String.format("Got %d for backend call %s.%n⤷ %s", statuscode, methodKey, request)); this.methodKey = methodKey; this.request = request; @@ -45,7 +45,7 @@ public Integer getStatusCode() { return statuscode; } - public Problem getProblem() { + public MetasvcProblem getProblem() { return problem; } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ForbiddenException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ForbiddenException.java index 021733561..1c3aa2714 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ForbiddenException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ForbiddenException.java @@ -1,10 +1,10 @@ package de.digitalcollections.model.exception.http.client; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class ForbiddenException extends HttpClientException { - public ForbiddenException(String methodKey, int status, String request, Problem problem) { + public ForbiddenException(String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/HttpClientException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/HttpClientException.java index 9a2c0ba57..598397513 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/HttpClientException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/HttpClientException.java @@ -1,11 +1,11 @@ package de.digitalcollections.model.exception.http.client; import de.digitalcollections.model.exception.http.HttpException; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class HttpClientException extends HttpException { - public HttpClientException(String methodKey, int status, String request, Problem problem) { + public HttpClientException(String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ImATeapotException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ImATeapotException.java index 84a34868d..a1477159a 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ImATeapotException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ImATeapotException.java @@ -1,6 +1,6 @@ package de.digitalcollections.model.exception.http.client; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; /** * HttpStatusCode 418 denoting the api is wrongfully using a teapot for making coffee as specified @@ -9,7 +9,7 @@ */ public class ImATeapotException extends HttpClientException { - public ImATeapotException(String methodKey, int status, String request, Problem problem) { + public ImATeapotException(String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ResourceNotFoundException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ResourceNotFoundException.java index 27e1d0954..0260028e8 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ResourceNotFoundException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/ResourceNotFoundException.java @@ -1,10 +1,11 @@ package de.digitalcollections.model.exception.http.client; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class ResourceNotFoundException extends HttpClientException { - public ResourceNotFoundException(String methodKey, int status, String request, Problem problem) { + public ResourceNotFoundException( + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnauthorizedException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnauthorizedException.java index eb2b12e6d..945c02cb1 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnauthorizedException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnauthorizedException.java @@ -1,10 +1,11 @@ package de.digitalcollections.model.exception.http.client; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class UnauthorizedException extends HttpClientException { - public UnauthorizedException(String methodKey, int status, String request, Problem problem) { + public UnauthorizedException( + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnavailableForLegalReasonsException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnavailableForLegalReasonsException.java index 033b76d21..21c2ac36b 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnavailableForLegalReasonsException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnavailableForLegalReasonsException.java @@ -1,11 +1,11 @@ package de.digitalcollections.model.exception.http.client; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class UnavailableForLegalReasonsException extends HttpClientException { public UnavailableForLegalReasonsException( - String methodKey, int status, String request, Problem problem) { + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnprocessableEntityException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnprocessableEntityException.java index 58400ac83..aea0adafd 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnprocessableEntityException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/client/UnprocessableEntityException.java @@ -1,11 +1,11 @@ package de.digitalcollections.model.exception.http.client; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class UnprocessableEntityException extends HttpClientException { public UnprocessableEntityException( - String methodKey, int status, String request, Problem problem) { + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/BadGatewayException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/BadGatewayException.java index 4b437de71..7afa98d40 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/BadGatewayException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/BadGatewayException.java @@ -1,10 +1,10 @@ package de.digitalcollections.model.exception.http.server; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class BadGatewayException extends HttpServerException { - public BadGatewayException(String methodKey, int status, String request, Problem problem) { + public BadGatewayException(String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/GatewayTimeOutException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/GatewayTimeOutException.java index 9796d6713..508362243 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/GatewayTimeOutException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/GatewayTimeOutException.java @@ -1,10 +1,11 @@ package de.digitalcollections.model.exception.http.server; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class GatewayTimeOutException extends HttpServerException { - public GatewayTimeOutException(String methodKey, int status, String request, Problem problem) { + public GatewayTimeOutException( + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpServerException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpServerException.java index 850499a3c..77b685b7a 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpServerException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpServerException.java @@ -1,11 +1,11 @@ package de.digitalcollections.model.exception.http.server; import de.digitalcollections.model.exception.http.HttpException; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class HttpServerException extends HttpException { - public HttpServerException(String methodKey, int status, String request, Problem problem) { + public HttpServerException(String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpVersionNotSupportedException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpVersionNotSupportedException.java index 183aef0fd..047840667 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpVersionNotSupportedException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/HttpVersionNotSupportedException.java @@ -1,11 +1,11 @@ package de.digitalcollections.model.exception.http.server; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class HttpVersionNotSupportedException extends HttpServerException { public HttpVersionNotSupportedException( - String methodKey, int status, String request, Problem problem) { + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/NotImplementedException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/NotImplementedException.java index bbad81ecc..7ff7cc484 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/NotImplementedException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/NotImplementedException.java @@ -1,10 +1,11 @@ package de.digitalcollections.model.exception.http.server; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class NotImplementedException extends HttpServerException { - public NotImplementedException(String methodKey, int status, String request, Problem problem) { + public NotImplementedException( + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/ServiceUnavailableException.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/ServiceUnavailableException.java index 35575e0ef..5cf5678e6 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/ServiceUnavailableException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/http/server/ServiceUnavailableException.java @@ -1,11 +1,11 @@ package de.digitalcollections.model.exception.http.server; -import org.zalando.problem.Problem; +import de.digitalcollections.model.exception.problem.MetasvcProblem; public class ServiceUnavailableException extends HttpServerException { public ServiceUnavailableException( - String methodKey, int status, String request, Problem problem) { + String methodKey, int status, String request, MetasvcProblem problem) { super(methodKey, status, request, problem); } } diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/MetasvcProblem.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/MetasvcProblem.java new file mode 100644 index 000000000..03345443a --- /dev/null +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/MetasvcProblem.java @@ -0,0 +1,68 @@ +package de.digitalcollections.model.exception.problem; + +import de.digitalcollections.model.validation.ValidationError; +import java.net.URI; +import java.util.Date; +import java.util.List; +import lombok.Builder; +import lombok.Singular; +import org.zalando.problem.AbstractThrowableProblem; +import org.zalando.problem.StatusType; + +public final class MetasvcProblem extends AbstractThrowableProblem { + + private List errors; + private Date timestamp; + private ProblemHint hint; + + public MetasvcProblem() { + super(); + } + + public MetasvcProblem( + URI type, String title, StatusType status, String detail, URI instance, Date timestamp) { + super(type, title, status, detail, instance); + this.timestamp = timestamp; + } + + @Builder(setterPrefix = "with") + public MetasvcProblem( + URI type, + String title, + StatusType status, + String detail, + URI instance, + Date timestamp, + @Singular List errors, + ProblemHint hint) { + super(type, title, status, detail, instance); + this.timestamp = timestamp; + this.errors = errors; + this.hint = hint; + } + + public MetasvcProblem( + URI type, + String title, + StatusType status, + String detail, + URI instance, + Date timestamp, + ProblemHint hint) { + super(type, title, status, detail, instance); + this.timestamp = timestamp; + this.hint = hint; + } + + public List getErrors() { + return errors; + } + + public Date getTimestamp() { + return timestamp; + } + + public ProblemHint getHint() { + return hint; + } +} diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/ProblemHint.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/ProblemHint.java new file mode 100644 index 000000000..e0caac6eb --- /dev/null +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/ProblemHint.java @@ -0,0 +1,22 @@ +package de.digitalcollections.model.exception.problem; + +public enum ProblemHint { + NONE("No hint available."), + RETRY_RECOMMENDED("The error is caused by a concurrent operation. It might succeed if retried."), + REFERENCED_OBJECT_NOT_EXISTS( + "An included object is not stored yet. Please save it separately before trying this operation again."), + UNIQUE_VIOLATION("One or more of these identifiers exist already."), + MANDATORY_CHECK_FAILED( + "A data integrity check failed. Please check for missing or incorrect properties."), + PROPERTY_MUST_NOT_BE_NULL("Ensure that all necessary/mandatory properties are set."); + + private String description; + + ProblemHint(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/ProblemHinting.java b/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/ProblemHinting.java new file mode 100644 index 000000000..b83ae46a2 --- /dev/null +++ b/metasvc-model/src/main/java/de/digitalcollections/model/exception/problem/ProblemHinting.java @@ -0,0 +1,6 @@ +package de.digitalcollections.model.exception.problem; + +public interface ProblemHinting { + + ProblemHint getHint(); +} diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/jackson/DigitalCollectionsModelModule.java b/metasvc-model/src/main/java/de/digitalcollections/model/jackson/DigitalCollectionsModelModule.java index 5ec0b4759..e0681670b 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/jackson/DigitalCollectionsModelModule.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/jackson/DigitalCollectionsModelModule.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.util.Converter; import com.fasterxml.jackson.databind.util.StdConverter; import de.digitalcollections.model.UniqueObject; +import de.digitalcollections.model.exception.problem.MetasvcProblem; import de.digitalcollections.model.file.MimeType; import de.digitalcollections.model.geo.CoordinateLocation; import de.digitalcollections.model.identifiable.Identifiable; @@ -55,6 +56,7 @@ import de.digitalcollections.model.identifiable.semantic.Subject; import de.digitalcollections.model.identifiable.web.Webpage; import de.digitalcollections.model.jackson.mixin.UniqueObjectMixIn; +import de.digitalcollections.model.jackson.mixin.exception.problem.MetasvcProblemMixIn; import de.digitalcollections.model.jackson.mixin.geo.CoordinateLocationMixIn; import de.digitalcollections.model.jackson.mixin.identifiable.IdentifiableMixIn; import de.digitalcollections.model.jackson.mixin.identifiable.IdentifierMixIn; @@ -309,6 +311,7 @@ public void setupModule(SetupContext context) { context.setMixInAnnotations(LocalizedText.class, LocalizedTextMixIn.class); context.setMixInAnnotations(LocalizedUrlAliases.class, LocalizedUrlAliasesMixIn.class); context.setMixInAnnotations(Mark.class, MarkMixIn.class); + context.setMixInAnnotations(MetasvcProblem.class, MetasvcProblemMixIn.class); context.setMixInAnnotations(Node.class, NodeMixIn.class); context.setMixInAnnotations(Order.class, OrderMixIn.class); context.setMixInAnnotations(OrderedList.class, OrderedListMixIn.class); diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/jackson/mixin/exception/problem/MetasvcProblemMixIn.java b/metasvc-model/src/main/java/de/digitalcollections/model/jackson/mixin/exception/problem/MetasvcProblemMixIn.java new file mode 100644 index 000000000..7def413eb --- /dev/null +++ b/metasvc-model/src/main/java/de/digitalcollections/model/jackson/mixin/exception/problem/MetasvcProblemMixIn.java @@ -0,0 +1,7 @@ +package de.digitalcollections.model.jackson.mixin.exception.problem; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +@JsonTypeInfo(use = Id.NONE) +public interface MetasvcProblemMixIn {} diff --git a/metasvc-model/src/main/java/de/digitalcollections/model/validation/ValidationException.java b/metasvc-model/src/main/java/de/digitalcollections/model/validation/ValidationException.java index 3406c7abe..ebdc2b54b 100644 --- a/metasvc-model/src/main/java/de/digitalcollections/model/validation/ValidationException.java +++ b/metasvc-model/src/main/java/de/digitalcollections/model/validation/ValidationException.java @@ -1,11 +1,14 @@ package de.digitalcollections.model.validation; +import de.digitalcollections.model.exception.problem.ProblemHint; +import de.digitalcollections.model.exception.problem.ProblemHinting; import java.util.ArrayList; import java.util.List; -public class ValidationException extends Exception { +public class ValidationException extends Exception implements ProblemHinting { private List errors = new ArrayList<>(1); + private ProblemHint hint = ProblemHint.NONE; public ValidationException(String msg, Exception e) { super(msg, e); @@ -15,6 +18,11 @@ public ValidationException(String msg) { super(msg); } + public ValidationException(String msg, ProblemHint hint) { + this(msg); + this.hint = hint; + } + public void addError(ValidationError validationError) { errors.add(validationError); } @@ -22,4 +30,9 @@ public void addError(ValidationError validationError) { public List getErrors() { return errors; } + + @Override + public ProblemHint getHint() { + return hint; + } } diff --git a/metasvc-server/backend-api/pom.xml b/metasvc-server/backend-api/pom.xml index 18ebf0a68..0d1337037 100644 --- a/metasvc-server/backend-api/pom.xml +++ b/metasvc-server/backend-api/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Backend API) diff --git a/metasvc-server/backend-api/src/main/java/io/github/dbmdz/metadata/server/backend/api/repository/exceptions/RepositoryException.java b/metasvc-server/backend-api/src/main/java/io/github/dbmdz/metadata/server/backend/api/repository/exceptions/RepositoryException.java index 1308ec58a..d00bd205a 100644 --- a/metasvc-server/backend-api/src/main/java/io/github/dbmdz/metadata/server/backend/api/repository/exceptions/RepositoryException.java +++ b/metasvc-server/backend-api/src/main/java/io/github/dbmdz/metadata/server/backend/api/repository/exceptions/RepositoryException.java @@ -1,6 +1,11 @@ package io.github.dbmdz.metadata.server.backend.api.repository.exceptions; -public class RepositoryException extends Exception { +import de.digitalcollections.model.exception.problem.ProblemHint; +import de.digitalcollections.model.exception.problem.ProblemHinting; + +public class RepositoryException extends Exception implements ProblemHinting { + + private ProblemHint hint = ProblemHint.NONE; public RepositoryException(String message) { super(message); @@ -10,7 +15,22 @@ public RepositoryException(Throwable cause) { super("An unexpected error occured!", cause); } + public RepositoryException(Throwable cause, ProblemHint hint) { + this(cause); + this.hint = hint; + } + public RepositoryException(String message, Throwable cause) { super(message, cause); } + + public RepositoryException(String message, Throwable cause, ProblemHint hint) { + this(message, cause); + this.hint = hint; + } + + @Override + public ProblemHint getHint() { + return hint; + } } diff --git a/metasvc-server/backend-file/pom.xml b/metasvc-server/backend-file/pom.xml index 0eac8bcb2..c7814bb27 100644 --- a/metasvc-server/backend-file/pom.xml +++ b/metasvc-server/backend-file/pom.xml @@ -5,7 +5,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Backend IMPL File) diff --git a/metasvc-server/backend-inmemory/pom.xml b/metasvc-server/backend-inmemory/pom.xml index b43048cca..ee4cf6920 100644 --- a/metasvc-server/backend-inmemory/pom.xml +++ b/metasvc-server/backend-inmemory/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Backend IMPL InMemory) diff --git a/metasvc-server/backend-jdbi/pom.xml b/metasvc-server/backend-jdbi/pom.xml index 18822f750..99b30f208 100644 --- a/metasvc-server/backend-jdbi/pom.xml +++ b/metasvc-server/backend-jdbi/pom.xml @@ -8,7 +8,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Backend IMPL JDBI PostgreSql) diff --git a/metasvc-server/backend-jdbi/src/main/java/io/github/dbmdz/metadata/server/backend/impl/jdbi/UniqueObjectRepositoryImpl.java b/metasvc-server/backend-jdbi/src/main/java/io/github/dbmdz/metadata/server/backend/impl/jdbi/UniqueObjectRepositoryImpl.java index ccd1649d4..88fb4afeb 100644 --- a/metasvc-server/backend-jdbi/src/main/java/io/github/dbmdz/metadata/server/backend/impl/jdbi/UniqueObjectRepositoryImpl.java +++ b/metasvc-server/backend-jdbi/src/main/java/io/github/dbmdz/metadata/server/backend/impl/jdbi/UniqueObjectRepositoryImpl.java @@ -1,6 +1,7 @@ package io.github.dbmdz.metadata.server.backend.impl.jdbi; import de.digitalcollections.model.UniqueObject; +import de.digitalcollections.model.exception.problem.ProblemHint; import de.digitalcollections.model.list.filtering.FilterCriterion; import de.digitalcollections.model.list.filtering.Filtering; import de.digitalcollections.model.list.paging.PageRequest; @@ -113,14 +114,37 @@ protected boolean isConstraintViolationException( * foreign_key_violation: 23503 * unique_violation: 23505 * check_violation: 23514 + * not_null_violation: 23502 */ - return List.of("23503", "23505", "23514").contains(sqlexc.getSQLState()); + return List.of("23503", "23505", "23514", "23502").contains(sqlexc.getSQLState()); } return throwable.getCause() != null ? isConstraintViolationException(throwable.getCause(), useMessage) : false; } + protected ProblemHint getHint(Throwable throwable) { + if (throwable == null) return ProblemHint.NONE; + if (throwable instanceof SQLException sqlexc) { + /* + * Codes see method `isConstraintViolationException` + * serialization_failure: 40001 + */ + return switch (sqlexc.getSQLState()) { + case "23503" -> ProblemHint.REFERENCED_OBJECT_NOT_EXISTS; + case "23505" -> ProblemHint.UNIQUE_VIOLATION; + case "23514" -> ProblemHint.MANDATORY_CHECK_FAILED; + case "23502" -> ProblemHint.PROPERTY_MUST_NOT_BE_NULL; + case "40001" -> ProblemHint.RETRY_RECOMMENDED; + default -> ProblemHint.NONE; + }; + } else if (throwable.getCause() != null) { + return getHint(throwable.getCause()); + } else { + return ProblemHint.NONE; + } + } + private void execInsertUpdate( final String sql, U uniqueObject, final Map bindings, boolean withCallback) throws RepositoryException, ValidationException { @@ -150,13 +174,14 @@ private void execInsertUpdate( } catch (StatementException e) { AtomicReference constraintMessage = new AtomicReference<>(); if (isConstraintViolationException(e, constraintMessage::set)) { - throw new ValidationException(constraintMessage.get()); + throw new ValidationException(constraintMessage.get(), getHint(e)); } + String detailMessage = e.getCause() != null ? e.getCause().getMessage() : e.getMessage(); throw new RepositoryException( - String.format("The SQL statement is defective: %s", detailMessage), e); + String.format("SQL exception: %s", detailMessage), e, getHint(e)); } catch (JdbiException e) { - throw new RepositoryException(e); + throw new RepositoryException(e, getHint(e)); } } diff --git a/metasvc-server/backend-lobid/pom.xml b/metasvc-server/backend-lobid/pom.xml index b02ca47f6..ac27eca29 100644 --- a/metasvc-server/backend-lobid/pom.xml +++ b/metasvc-server/backend-lobid/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Backend IMPL External System lobid.org) diff --git a/metasvc-server/business/pom.xml b/metasvc-server/business/pom.xml index 8729765d7..4fc6d81ad 100644 --- a/metasvc-server/business/pom.xml +++ b/metasvc-server/business/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Business) diff --git a/metasvc-server/pom.xml b/metasvc-server/pom.xml index 8c1673715..e86b6092e 100644 --- a/metasvc-server/pom.xml +++ b/metasvc-server/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server diff --git a/metasvc-server/webapp/pom.xml b/metasvc-server/webapp/pom.xml index c14bb42a3..b9af9143c 100644 --- a/metasvc-server/webapp/pom.xml +++ b/metasvc-server/webapp/pom.xml @@ -6,7 +6,7 @@ io.github.dbmdz.metadata metasvc-server - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT Metadata-Service: Repository Server (Webapp) diff --git a/metasvc-server/webapp/src/main/java/io/github/dbmdz/metadata/server/controller/advice/ExceptionAdvice.java b/metasvc-server/webapp/src/main/java/io/github/dbmdz/metadata/server/controller/advice/ExceptionAdvice.java index ff862f49f..d5db90874 100644 --- a/metasvc-server/webapp/src/main/java/io/github/dbmdz/metadata/server/controller/advice/ExceptionAdvice.java +++ b/metasvc-server/webapp/src/main/java/io/github/dbmdz/metadata/server/controller/advice/ExceptionAdvice.java @@ -1,5 +1,8 @@ package io.github.dbmdz.metadata.server.controller.advice; +import de.digitalcollections.model.exception.problem.MetasvcProblem; +import de.digitalcollections.model.exception.problem.ProblemHint; +import de.digitalcollections.model.exception.problem.ProblemHinting; import de.digitalcollections.model.validation.ValidationException; import io.github.dbmdz.metadata.server.business.api.service.exceptions.ConflictException; import io.github.dbmdz.metadata.server.business.api.service.exceptions.ResourceNotFoundException; @@ -33,12 +36,6 @@ private static URI getRequestUri(ServletWebRequest servletRequest) { .toUri(); } - @ExceptionHandler(UsernameNotFoundException.class) - public ResponseEntity handleNotFound( - UsernameNotFoundException e, ServletWebRequest request) { - return create(Status.NOT_FOUND, e, request); - } - private static Status statusFromExceptionClass(Throwable exc) { if (exc instanceof ResourceNotFoundException) { return Status.NOT_FOUND; @@ -53,11 +50,28 @@ private static Status statusFromExceptionClass(Throwable exc) { } } + private static ProblemHint hintFromException(Throwable exc) { + if (exc == null) return ProblemHint.NONE; + if (exc instanceof ProblemHinting hintedExc) { + return hintedExc.getHint(); + } else if (exc.getCause() != null) { + return hintFromException(exc.getCause()); + } else { + return ProblemHint.NONE; + } + } + + @ExceptionHandler(UsernameNotFoundException.class) + public ResponseEntity handleNotFound( + UsernameNotFoundException e, ServletWebRequest request) { + return create(Status.NOT_FOUND, e, request); + } + @ExceptionHandler(ValidationException.class) public ResponseEntity handleValidationException( ValidationException exception, ServletWebRequest request) { ThrowableProblem problem = - Problem.builder() + MetasvcProblem.builder() .withType( UriComponentsBuilder.fromPath("/errors/") .path(exception.getClass().getSimpleName()) @@ -67,8 +81,9 @@ public ResponseEntity handleValidationException( .withStatus(statusFromExceptionClass(exception)) .withInstance(getRequestUri(request)) .withDetail(exception.getMessage()) - .with("errors", exception.getErrors()) - .with("timestamp", new Date()) + .withErrors(exception.getErrors()) + .withTimestamp(new Date()) + .withHint(exception.getHint()) .build(); return create(problem, request); } @@ -80,7 +95,7 @@ public ResponseEntity handleAllOther(Exception exception, ServletWebReq cause = cause.getCause(); } ThrowableProblem problem = - Problem.builder() + MetasvcProblem.builder() .withType( UriComponentsBuilder.fromPath("/errors/") .path(cause.getClass().getSimpleName()) @@ -90,6 +105,8 @@ public ResponseEntity handleAllOther(Exception exception, ServletWebReq .withStatus(statusFromExceptionClass(cause)) .withDetail(cause.getMessage()) .withInstance(getRequestUri(request)) + .withHint(hintFromException(exception)) + .withTimestamp(new Date()) .build(); if (problem.getStatus() == Status.INTERNAL_SERVER_ERROR) LOGGER.error("Exception stack trace", exception); diff --git a/pom.xml b/pom.xml index 38a75d8b3..fb512c419 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ io.github.dbmdz.metadata metasvc - 9.1.1-SNAPSHOT + 9.2.0-SNAPSHOT pom Metadata-Service