diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/export/phases/training/TrainingPhaseExportDTO.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/export/phases/training/TrainingPhaseExportDTO.java index 8d53385c8ef8c7c88e4dd4b1eb31b66018f6a415..a19e7a94cd21e947efc33647309e7043c5b4e6da 100644 --- a/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/export/phases/training/TrainingPhaseExportDTO.java +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/dto/export/phases/training/TrainingPhaseExportDTO.java @@ -107,7 +107,6 @@ public class TrainingPhaseExportDTO extends AbstractPhaseExportDTO { ", solution='" + solution + '\'' + ", incorrectFlagLimit=" + incorrectFlagLimit + ", title='" + title + '\'' + - ", maxScore=" + maxScore + ", levelType=" + phaseType + '}'; } diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/BadRequestException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/BadRequestException.java new file mode 100644 index 0000000000000000000000000000000000000000..7e011b8e2e896deb6600bc3f824332a2d9a06dc4 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/BadRequestException.java @@ -0,0 +1,28 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.BAD_REQUEST, + reason = "The server cannot or will not process the request due to an apparent client error (e.g., malformed request syntax, size too large, invalid request message framing, or deceptive request routing).") +public class BadRequestException extends RuntimeException { + + public BadRequestException() { + } + + public BadRequestException(String message) { + super(message); + } + + public BadRequestException(String message, Throwable cause) { + super(message, cause); + } + + public BadRequestException(Throwable cause) { + super(cause); + } + + public BadRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/CustomWebClientException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/CustomWebClientException.java new file mode 100644 index 0000000000000000000000000000000000000000..9c8b5c1f33cce7b028d5f0bbd10ef086aacfffc9 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/CustomWebClientException.java @@ -0,0 +1,89 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import cz.muni.ics.kypo.training.adaptive.exceptions.errors.ApiSubError; +import org.springframework.http.HttpStatus; + +/** + * The type Rest template exception. + */ +public class CustomWebClientException extends RuntimeException { + private HttpStatus statusCode; + private ApiSubError apiSubError; + + /** + * Instantiates a new Rest template exception. + * + * @param message the message + * @param statusCode the status code + */ + public CustomWebClientException(String message, HttpStatus statusCode) { + super(message); + this.statusCode = statusCode; + } + + /** + * Instantiates a new Rest template exception. + * + * @param message the message + * @param ex the ex + * @param statusCode the status code + */ + public CustomWebClientException(String message, Throwable ex, HttpStatus statusCode) { + super(message, ex); + this.statusCode = statusCode; + } + + /** + * Instantiates a new Rest template exception. + * + * @param apiSubError detailed information about error. + */ + public CustomWebClientException(ApiSubError apiSubError) { + super(); + this.apiSubError = apiSubError; + this.statusCode = apiSubError.getStatus(); + } + + /** + * Instantiates a new Rest template exception. + * + * @param message the message + * @param apiSubError detailed information about error + */ + public CustomWebClientException(String message, ApiSubError apiSubError) { + super(message); + this.apiSubError = apiSubError; + this.statusCode = apiSubError.getStatus(); + } + + /** + * Instantiates a new Rest template exception. + * + * @param message the message + * @param ex the ex + * @param apiSubError detailed information about error + */ + public CustomWebClientException(String message, Throwable ex, ApiSubError apiSubError) { + super(message, ex); + this.apiSubError = apiSubError; + this.statusCode = apiSubError.getStatus(); + } + + /** + * Gets detailed information about error. + * + * @return detailed information about error + */ + public ApiSubError getApiSubError() { + return apiSubError; + } + + /** + * Gets status code. + * + * @return the status code + */ + public HttpStatus getStatusCode() { + return statusCode; + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityConflictException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityConflictException.java new file mode 100644 index 0000000000000000000000000000000000000000..23405682904524bd6ff0977ff1dfb0bc8d465ac4 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityConflictException.java @@ -0,0 +1,38 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.CONFLICT, reason = "The request could not be completed due to a conflict with the current state of the target resource.") +public class EntityConflictException extends ExceptionWithEntity { + + public EntityConflictException() { + super(); + } + + public EntityConflictException(EntityErrorDetail entityErrorDetail) { + super(entityErrorDetail); + } + + public EntityConflictException(EntityErrorDetail entityErrorDetail, Throwable cause) { + super(entityErrorDetail, cause); + } + + public EntityConflictException(Throwable cause) { + super(cause); + } + + protected String createDefaultReason(EntityErrorDetail entityErrorDetail) { + StringBuilder reason = new StringBuilder("Conflict with the current state of the target entity ") + .append(entityErrorDetail.getEntity()); + if (entityErrorDetail.getIdentifier() != null && entityErrorDetail.getIdentifierValue() != null) { + reason.append(" (") + .append(entityErrorDetail.getIdentifier()) + .append(": ") + .append(entityErrorDetail.getIdentifierValue()) + .append(")"); + } + reason.append("."); + return reason.toString(); + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityErrorDetail.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityErrorDetail.java new file mode 100644 index 0000000000000000000000000000000000000000..023ddba5ed482ac0e70389210f4844bad13ec136 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityErrorDetail.java @@ -0,0 +1,110 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModelProperty; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EntityErrorDetail { + @ApiModelProperty(value = "Class of the entity.", example = "IDMGroup") + private String entity; + @ApiModelProperty(value = "Identifier of the entity.", example = "id") + private String identifier; + @ApiModelProperty(value = "Value of the identifier.", example = "1") + private Object identifierValue; + @ApiModelProperty(value = "Detailed message of the exception", example = "Group with same name already exists.") + private String reason; + + public EntityErrorDetail() { + } + + public EntityErrorDetail(@NotBlank String reason) { + this.reason = reason; + } + + public EntityErrorDetail(@NotNull Class<?> entityClass, + @NotBlank String reason) { + this(reason); + this.entity = entityClass.getSimpleName(); + } + + public EntityErrorDetail(@NotNull Class<?> entityClass, + @NotBlank String identifier, + @NotNull Class<?> identifierClass, + @NotNull Object identifierValue, + @NotBlank String reason) { + this(entityClass, reason); + this.identifier = identifier; + this.identifierValue = identifierClass.cast(identifierValue); + } + + public EntityErrorDetail(@NotNull Class<?> entityClass, + @NotBlank String identifier, + @NotNull Class<?> identifierClass, + @NotNull Object identifierValue) { + this.entity = entityClass.getSimpleName(); + this.identifier = identifier; + this.identifierValue = identifierClass.cast(identifierValue); + } + + public String getEntity() { + return entity; + } + + public void setEntity(@NotBlank String entity) { + this.entity = entity; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(@NotBlank String identifier) { + this.identifier = identifier; + } + + public Object getIdentifierValue() { + return identifierValue; + } + + public void setIdentifierValue(@NotNull Object identifierValue) { + this.identifierValue = identifierValue; + } + + public String getReason() { + return reason; + } + + public void setReason(@NotBlank String reason) { + this.reason = reason; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EntityErrorDetail entity = (EntityErrorDetail) o; + return Objects.equals(getEntity(), entity.getEntity()) && + Objects.equals(getIdentifier(), entity.getIdentifier()) && + Objects.equals(getIdentifierValue(), entity.getIdentifierValue()) && + Objects.equals(getReason(), entity.getReason()); + } + + @Override + public int hashCode() { + return Objects.hash(getEntity(), getIdentifier(), getIdentifierValue(), getReason()); + } + + @Override + public String toString() { + return "EntityErrorDetail{" + + "entity='" + entity + '\'' + + ", identifier='" + identifier + '\'' + + ", identifierValue=" + identifierValue + + ", reason='" + reason + '\'' + + '}'; + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityNotFoundException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..c30c77f11f5c9b61521caee3d42625525b76a7dc --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/EntityNotFoundException.java @@ -0,0 +1,37 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "The requested entity could not be found") +public class EntityNotFoundException extends ExceptionWithEntity { + public EntityNotFoundException() { + super(); + } + + public EntityNotFoundException(EntityErrorDetail entityErrorDetail) { + super(entityErrorDetail); + } + + public EntityNotFoundException(EntityErrorDetail entityErrorDetail, Throwable cause) { + super(entityErrorDetail, cause); + } + + public EntityNotFoundException(Throwable cause) { + super(cause); + } + + protected String createDefaultReason(EntityErrorDetail entityErrorDetail) { + StringBuilder reason = new StringBuilder("Entity ") + .append(entityErrorDetail.getEntity()); + if (entityErrorDetail.getIdentifier() != null && entityErrorDetail.getIdentifierValue() != null) { + reason.append(" (") + .append(entityErrorDetail.getIdentifier()) + .append(": ") + .append(entityErrorDetail.getIdentifierValue()) + .append(")"); + } + reason.append(" not found."); + return reason.toString(); + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ExceptionWithEntity.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ExceptionWithEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..ba05f5c4634182ee020dd05cc63d169d28d4fd69 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ExceptionWithEntity.java @@ -0,0 +1,40 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +public abstract class ExceptionWithEntity extends RuntimeException { + private EntityErrorDetail entityErrorDetail; + + protected ExceptionWithEntity() { + super(); + } + + protected ExceptionWithEntity(EntityErrorDetail entityErrorDetail) { + this.entityErrorDetail = entityErrorDetail; + if (entityErrorDetail.getReason() == null) { + this.entityErrorDetail.setReason(createDefaultReason(this.entityErrorDetail)); + } + } + + protected ExceptionWithEntity(EntityErrorDetail entityErrorDetail, Throwable cause) { + super(cause); + this.entityErrorDetail = entityErrorDetail; + if (entityErrorDetail.getReason() == null) { + this.entityErrorDetail.setReason(createDefaultReason(this.entityErrorDetail)); + } + } + + protected ExceptionWithEntity(Throwable cause) { + super(cause); + } + + public EntityErrorDetail getEntityErrorDetail() { + return entityErrorDetail; + } + + /** + * Method to get default reason of error based on other attributes when no reason is provided. + * @param entityErrorDetail + * @return default detailed reason of error. + */ + protected abstract String createDefaultReason(EntityErrorDetail entityErrorDetail); + +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ForbiddenException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ForbiddenException.java new file mode 100644 index 0000000000000000000000000000000000000000..44ed5c1a9a37812c539acb3105434ace59572894 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ForbiddenException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Request is formed correctly, but the server doesn't want to carry it out.") +public class ForbiddenException extends RuntimeException { + + public ForbiddenException() { + } + + public ForbiddenException(String message) { + super(message); + } + + public ForbiddenException(String message, Throwable ex) { + super(message, ex); + } + + public ForbiddenException(Throwable ex) { + super(ex); + } + +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/InternalServerErrorException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/InternalServerErrorException.java new file mode 100644 index 0000000000000000000000000000000000000000..92455272ab081212c8dadefa8e6253885d088908 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/InternalServerErrorException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, + reason = "A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.") +public class InternalServerErrorException extends RuntimeException { + + public InternalServerErrorException() { + } + + public InternalServerErrorException(String message) { + super(message); + } + + public InternalServerErrorException(String message, Throwable ex) { + super(message, ex); + } + + public InternalServerErrorException(Throwable e) { + super(e); + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/MicroserviceApiException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/MicroserviceApiException.java new file mode 100644 index 0000000000000000000000000000000000000000..75b11b18339129097707abb34f4ecdde2ee0877b --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/MicroserviceApiException.java @@ -0,0 +1,46 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import cz.muni.ics.kypo.training.adaptive.exceptions.errors.ApiSubError; +import org.springframework.web.bind.annotation.ResponseStatus; + +import javax.validation.ConstraintViolationException; + +@ResponseStatus(reason = "Error when calling external service API") +public class MicroserviceApiException extends RuntimeException { + private ApiSubError apiSubError; + + public MicroserviceApiException() { + super(); + } + + public MicroserviceApiException(ApiSubError apiSubError) { + super(); + this.apiSubError = apiSubError; + + } + + public MicroserviceApiException(String message) { + super(message); + } + + public MicroserviceApiException(String message, ConstraintViolationException exception) { + super(message + " Constraint violations: " + exception.getConstraintViolations().toString()); + + } + + public MicroserviceApiException(String message, ApiSubError apiSubError) { + super(message); + this.apiSubError = apiSubError; + } + + + public MicroserviceApiException(ApiSubError apiSubError, Throwable cause) { + super(cause); + this.apiSubError = apiSubError; + + } + + public ApiSubError getApiSubError() { + return apiSubError; + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ResourceNotFoundException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ResourceNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..b65c9ad60182be09148e7fbdbe4e6dfbd2b86efd --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ResourceNotFoundException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "The requested resource was not found") +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException() { + } + + public ResourceNotFoundException(String message) { + super(message); + } + + public ResourceNotFoundException(String message, Throwable ex) { + super(message, ex); + } + + public ResourceNotFoundException(Throwable ex) { + super(ex); + } + +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ResourceNotModifiedException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ResourceNotModifiedException.java new file mode 100644 index 0000000000000000000000000000000000000000..e3fc5429d22e1ced36e351c31a89204fd80d2b24 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/ResourceNotModifiedException.java @@ -0,0 +1,24 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.NOT_MODIFIED, reason = "The requested resource was not modified") +public class ResourceNotModifiedException extends RuntimeException { + + public ResourceNotModifiedException() { + } + + public ResourceNotModifiedException(String message) { + super(message); + } + + public ResourceNotModifiedException(String message, Throwable ex) { + super(message, ex); + } + + public ResourceNotModifiedException(Throwable ex) { + super(ex); + } + +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/TooManyRequestsException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/TooManyRequestsException.java new file mode 100644 index 0000000000000000000000000000000000000000..cce0720310bd63c08bfa1558c2eaa0954a29af0f --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/TooManyRequestsException.java @@ -0,0 +1,38 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.TOO_MANY_REQUESTS, reason = "The user has sent too many requests in a given amount of time (\"rate limiting\").") +public class TooManyRequestsException extends ExceptionWithEntity { + + public TooManyRequestsException() { + super(); + } + + public TooManyRequestsException(EntityErrorDetail entityErrorDetail) { + super(entityErrorDetail); + } + + public TooManyRequestsException(EntityErrorDetail entityErrorDetail, Throwable cause) { + super(entityErrorDetail, cause); + } + + public TooManyRequestsException(Throwable cause) { + super(cause); + } + + protected String createDefaultReason(EntityErrorDetail entityErrorDetail) { + StringBuilder reason = new StringBuilder("User has sent too many requests to obtain entity ") + .append(entityErrorDetail.getEntity()); + if (entityErrorDetail.getIdentifier() != null && entityErrorDetail.getIdentifierValue() != null) { + reason.append(" (") + .append(entityErrorDetail.getIdentifier()) + .append(": ") + .append(entityErrorDetail.getIdentifierValue()) + .append(")"); + } + reason.append("."); + return reason.toString(); + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/UnprocessableEntityException.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/UnprocessableEntityException.java new file mode 100644 index 0000000000000000000000000000000000000000..a68947d846fe81f36adbf09d8b8a9d3c564d9626 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/UnprocessableEntityException.java @@ -0,0 +1,39 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY, reason = "The requested data cannot be processed.") +public class UnprocessableEntityException extends ExceptionWithEntity { + + public UnprocessableEntityException() { + super(); + } + + public UnprocessableEntityException(EntityErrorDetail entityErrorDetail) { + super(entityErrorDetail); + } + + public UnprocessableEntityException(EntityErrorDetail entityErrorDetail, Throwable cause) { + super(entityErrorDetail, cause); + } + + public UnprocessableEntityException(Throwable cause) { + super(cause); + } + + protected String createDefaultReason(EntityErrorDetail entityErrorDetail) { + StringBuilder reason = new StringBuilder("Unable to be process entity ") + .append(entityErrorDetail.getEntity()); + if (entityErrorDetail.getIdentifier() != null && entityErrorDetail.getIdentifierValue() != null) { + reason.append(" (") + .append(entityErrorDetail.getIdentifier()) + .append(": ") + .append(entityErrorDetail.getIdentifierValue()) + .append(")"); + } + reason.append(" not found."); + return reason.toString(); + } + +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/ApiSubError.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/ApiSubError.java new file mode 100644 index 0000000000000000000000000000000000000000..2d3b5321ac530354586542845817ebc348eab4ee --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/ApiSubError.java @@ -0,0 +1,24 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions.errors; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.springframework.http.HttpStatus; + +@ApiModel(value = "ApiSubError", subTypes = {JavaApiError.class, PythonApiError.class}, + description = "Superclass for classes JavaApiError and PythonApiError") +@JsonSubTypes({ + @JsonSubTypes.Type(value = JavaApiError.class, name = "JavaApiError"), + @JsonSubTypes.Type(value = PythonApiError.class, name = "PythonApiError")}) +public abstract class ApiSubError { + @ApiModelProperty(value = "The HTTP response status code", example = "404 Not found (different for each type of exception).") + private HttpStatus status; + + public HttpStatus getStatus() { + return status; + } + + public void setStatus(HttpStatus status) { + this.status = status; + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/JavaApiError.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/JavaApiError.java new file mode 100644 index 0000000000000000000000000000000000000000..22e42035b7a193d1901908c2f3b0256ad2919f79 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/JavaApiError.java @@ -0,0 +1,147 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions.errors; + +import com.fasterxml.jackson.annotation.JsonProperty; +import cz.muni.ics.kypo.training.adaptive.exceptions.EntityErrorDetail; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import org.springframework.http.HttpStatus; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@ApiModel(value = "JavaApiError", description = "A detailed error from another Java mircorservice.", parent = ApiSubError.class) +public class JavaApiError extends ApiSubError { + @ApiModelProperty(value = "The time when the exception occurred", example = "1574062900 (different for each type of exception)") + private long timestamp; + @ApiModelProperty(value = "The specific description of the ApiError.", example = "The IDMGroup could not be found in database (different for each type of exception).") + private String message; + @ApiModelProperty(value = "The list of main reasons of the ApiError.", example = "[The requested resource was not found (different for each type of exception).]") + private List<String> errors; + @ApiModelProperty(value = "The requested URI path which caused error.", example = "/kypo2-rest-user-and-group/api/v1/groups/1000 (different for each type of exception).") + private String path; + @ApiModelProperty(value = "Entity detail related to the error.") + @JsonProperty("entity_error_detail") + private EntityErrorDetail entityErrorDetail; + + private JavaApiError() { + } + + public static JavaApiError of(HttpStatus httpStatus, String message, List<String> errors, String path) { + JavaApiError apiError = new JavaApiError(); + apiError.setTimestamp(System.currentTimeMillis()); + apiError.setStatus(httpStatus); + apiError.setMessage(message); + apiError.setErrors(errors); + apiError.setPath(path); + return apiError; + } + + public static JavaApiError of(HttpStatus httpStatus, String message, String error, String path) { + JavaApiError apiError = new JavaApiError(); + apiError.setTimestamp(System.currentTimeMillis()); + apiError.setStatus(httpStatus); + apiError.setMessage(message); + apiError.setError(error); + apiError.setPath(path); + return apiError; + } + + public static JavaApiError of(HttpStatus httpStatus, String message, List<String> errors) { + JavaApiError apiError = new JavaApiError(); + apiError.setTimestamp(System.currentTimeMillis()); + apiError.setStatus(httpStatus); + apiError.setMessage(message); + apiError.setErrors(errors); + apiError.setPath(""); + return apiError; + } + + public static JavaApiError of(HttpStatus httpStatus, String message, String error) { + JavaApiError apiError = new JavaApiError(); + apiError.setTimestamp(System.currentTimeMillis()); + apiError.setStatus(httpStatus); + apiError.setMessage(message); + apiError.setError(error); + apiError.setPath(""); + return apiError; + } + + public EntityErrorDetail getEntityErrorDetail() { + return entityErrorDetail; + } + + public void setEntityErrorDetail(EntityErrorDetail entityErrorDetail) { + this.entityErrorDetail = entityErrorDetail; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + public List<String> getErrors() { + return errors; + } + + public void setErrors(final List<String> errors) { + this.errors = errors; + } + + public void setError(final String error) { + errors = Arrays.asList(error); + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String toString() { + return "ApiError{" + + "timestamp=" + timestamp + + ", status=" + getStatus() + + ", message='" + message + '\'' + + ", errors=" + errors + + ", path='" + path + '\'' + + ", entityErrorDetail=" + entityErrorDetail + + '}'; + } + + @Override + public int hashCode() { + return Objects.hash(timestamp, getStatus(), message, errors, path); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof JavaApiError)) + return false; + JavaApiError other = (JavaApiError) obj; + return Objects.equals(errors, other.getErrors()) && + Objects.equals(message, other.getMessage()) && + Objects.equals(path, other.getPath()) && + Objects.equals(getStatus(), other.getStatus()) && + Objects.equals(timestamp, other.getTimestamp()); + } + +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/PythonApiError.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/PythonApiError.java new file mode 100644 index 0000000000000000000000000000000000000000..bc61e89789ef78370cd8428c618fc87f1afb3c15 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/errors/PythonApiError.java @@ -0,0 +1,62 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions.errors; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +import java.util.Map; +import java.util.Objects; + +@ApiModel(value = "PythonApiError", description = "A detailed error from another Python mircorservice.", parent = ApiSubError.class) +public class PythonApiError extends ApiSubError { + + @ApiModelProperty(value = "Detail message of the error.", example = "Sandbox could not be found.") + private String detail; + @ApiModelProperty(value = "Parameters to specify details of the error.", example = "name: sandbox" ) + private Map<String, String> parameters; + + public PythonApiError() { + } + + public PythonApiError(String detail) { + this.detail = detail; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public Map<String, String> getParameters() { + return parameters; + } + + public void setParameters(Map<String, String> parameters) { + this.parameters = parameters; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PythonApiError)) return false; + PythonApiError that = (PythonApiError) o; + return Objects.equals(getDetail(), that.getDetail()) && + Objects.equals(getParameters(), that.getParameters()); + } + + @Override + public int hashCode() { + return Objects.hash(getDetail(), getParameters()); + } + + @Override + public String toString() { + return "PythonApiError{" + + "detail='" + detail + '\'' + + ", status=" + getStatus() + + ", parameters=" + parameters + + '}'; + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/responsehandlers/JavaApiResponseErrorHandler.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/responsehandlers/JavaApiResponseErrorHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..317e68f21dec639e237703787287a894f142f67d --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/responsehandlers/JavaApiResponseErrorHandler.java @@ -0,0 +1,45 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions.responsehandlers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import cz.muni.ics.kypo.training.adaptive.exceptions.CustomWebClientException; +import cz.muni.ics.kypo.training.adaptive.exceptions.errors.JavaApiError; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.ResponseErrorHandler; + +import java.io.IOException; +import java.nio.charset.Charset; + +import static org.springframework.http.HttpStatus.Series.CLIENT_ERROR; +import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; + +public class JavaApiResponseErrorHandler implements ResponseErrorHandler { + + private ObjectMapper mapper; + + /** + * Instantiates a new Python api response error handler. + * + * @param mapper the mapper + */ + public JavaApiResponseErrorHandler(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + return ( + response.getStatusCode().series() == CLIENT_ERROR + || response.getStatusCode().series() == SERVER_ERROR); + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + String responseBody = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()); + if(responseBody.isBlank()) { + throw new CustomWebClientException("Error from external microservice. No specific message provided.", response.getStatusCode()); + } + JavaApiError javaApiError = mapper.readValue(responseBody, JavaApiError.class); + throw new CustomWebClientException(javaApiError); + } +} diff --git a/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/responsehandlers/PythonApiResponseErrorHandler.java b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/responsehandlers/PythonApiResponseErrorHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..a6b97d58db5096faeb17f7a1509d6add51c3b390 --- /dev/null +++ b/src/main/java/cz/muni/ics/kypo/training/adaptive/exceptions/responsehandlers/PythonApiResponseErrorHandler.java @@ -0,0 +1,49 @@ +package cz.muni.ics.kypo.training.adaptive.exceptions.responsehandlers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import cz.muni.ics.kypo.training.adaptive.exceptions.CustomWebClientException; +import cz.muni.ics.kypo.training.adaptive.exceptions.errors.PythonApiError; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.StreamUtils; +import org.springframework.web.client.ResponseErrorHandler; + +import java.io.IOException; +import java.nio.charset.Charset; + +import static org.springframework.http.HttpStatus.Series.CLIENT_ERROR; +import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; + +/** + * Handler used for errors returned from Python api. + */ +public class PythonApiResponseErrorHandler implements ResponseErrorHandler { + + private ObjectMapper mapper; + + /** + * Instantiates a new Python api response error handler. + * + * @param mapper the mapper + */ + public PythonApiResponseErrorHandler(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public boolean hasError(ClientHttpResponse response) throws IOException { + return ( + response.getStatusCode().series() == CLIENT_ERROR + || response.getStatusCode().series() == SERVER_ERROR); + } + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + String responseBody = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()); + if(responseBody.isBlank()) { + throw new CustomWebClientException("Error from external microservice. No specific message provided.", response.getStatusCode()); + } + PythonApiError pythonApiError = mapper.readValue(response.getBody(), PythonApiError.class); + pythonApiError.setStatus(response.getStatusCode()); + throw new CustomWebClientException(pythonApiError); + } +}