From 3650d6ddbee91825ee7a74659674928ada096f1a Mon Sep 17 00:00:00 2001 From: Leah Tabush Date: Mon, 22 Feb 2021 22:50:09 -0800 Subject: [PATCH] mvp --- .../schools/config/SwaggerWebMVC.java | 35 ++ .../exceptions/CustomErrorDetails.java | 45 ++ .../exceptions/ResourceNotFoundException.java | 8 + .../handlers/RestExceptionHandler.java | 572 ++++++++++++++++++ .../schools/models/ErrorDetail.java | 66 ++ .../schools/models/ValidationError.java | 30 + .../schools/services/HelperFunctions.java | 10 + .../schools/services/HelperFunctionsImpl.java | 50 ++ .../src/main/resources/application.properties | 5 + 9 files changed, 821 insertions(+) create mode 100644 schools/src/main/java/com/lambdaschool/schools/config/SwaggerWebMVC.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/exceptions/CustomErrorDetails.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/exceptions/ResourceNotFoundException.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/handlers/RestExceptionHandler.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/models/ErrorDetail.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/models/ValidationError.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/services/HelperFunctions.java create mode 100644 schools/src/main/java/com/lambdaschool/schools/services/HelperFunctionsImpl.java diff --git a/schools/src/main/java/com/lambdaschool/schools/config/SwaggerWebMVC.java b/schools/src/main/java/com/lambdaschool/schools/config/SwaggerWebMVC.java new file mode 100644 index 00000000..4550117f --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/config/SwaggerWebMVC.java @@ -0,0 +1,35 @@ +package com.lambdaschool.schools.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * The application turns off any automatic web page generate done by Spring. This is done to improve exception handling. + * However, we do need some web page generate done for Swagger, so we do that here. + */ +@Configuration +public class SwaggerWebMVC + implements WebMvcConfigurer +{ + /** + * Adds the Swagger web pages to Spring. + * This still gives the following warning + *

+ * No mapping for GET / + * No mapping for GET /csrf + *

+ * All works though + * + * @param registry the place that holds the web pages for Spring + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } +} \ No newline at end of file diff --git a/schools/src/main/java/com/lambdaschool/schools/exceptions/CustomErrorDetails.java b/schools/src/main/java/com/lambdaschool/schools/exceptions/CustomErrorDetails.java new file mode 100644 index 00000000..2af782f6 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/exceptions/CustomErrorDetails.java @@ -0,0 +1,45 @@ +package com.lambdaschool.schools.exceptions; + +import com.lambdaschool.schools.services.HelperFunctions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.WebRequest; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +@Component +public class CustomErrorDetails extends DefaultErrorAttributes +{ + @Autowired + HelperFunctions helperFunctions; + + @Override + public Map getErrorAttributes( + WebRequest webRequest, + boolean includeStackTrace) + { + // title + // status + // detail + // timestamp + // developermessage + // + // errors + + Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); + Map errorDetails = new LinkedHashMap<>(); + + errorDetails.put("title", errorAttributes.get("error")); + errorDetails.put("status", errorAttributes.get("status")); + errorDetails.put("detail", errorAttributes.get("message")); + errorDetails.put("timestamp", errorAttributes.get("timestamp")); + errorDetails.put("developerMessage", "path: " + errorAttributes.get("path")); + + errorDetails.put("errors", helperFunctions.getConstraintViolation(this.getError(webRequest))); + + return errorDetails; + } +} \ No newline at end of file diff --git a/schools/src/main/java/com/lambdaschool/schools/exceptions/ResourceNotFoundException.java b/schools/src/main/java/com/lambdaschool/schools/exceptions/ResourceNotFoundException.java new file mode 100644 index 00000000..2d1a3967 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/exceptions/ResourceNotFoundException.java @@ -0,0 +1,8 @@ +package com.lambdaschool.schools.exceptions; + +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException(String message) { + super("Error from a lambda school app: " + message); + } +} diff --git a/schools/src/main/java/com/lambdaschool/schools/handlers/RestExceptionHandler.java b/schools/src/main/java/com/lambdaschool/schools/handlers/RestExceptionHandler.java new file mode 100644 index 00000000..fcbdd159 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/handlers/RestExceptionHandler.java @@ -0,0 +1,572 @@ +package com.lambdaschool.schools.handlers; + + +import com.lambdaschool.schools.exceptions.ResourceNotFoundException; +import com.lambdaschool.schools.models.ErrorDetail; +import com.lambdaschool.schools.services.HelperFunctions; +import org.springframework.beans.ConversionNotSupportedException; +import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.context.request.async.AsyncRequestTimeoutException; +import org.springframework.web.multipart.support.MissingServletRequestPartException; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.Arrays; +import java.util.Date; + +/** + * This is the driving class when an exception occurs. All exceptions are handled here. + * This class is shared across all controllers due to the annotation RestControllerAdvice; + * this class gives advice to all controllers on how to handle exceptions. + * Due to the annotation Order(Ordered.HIGHEST_PRECEDENCE), this class takes precedence over all other controller advisors. + */ +@Order(Ordered.HIGHEST_PRECEDENCE) +@RestControllerAdvice +public class RestExceptionHandler + extends ResponseEntityExceptionHandler +{ + /** + * Connects this class with the Helper Functions + */ + @Autowired + private HelperFunctions helperFunctions; + + /** + * The constructor for the RestExceptionHandler. Currently we do not do anything special. We just call the parent constructor. + */ + public RestExceptionHandler() + { + super(); + } + + /** + * Our custom handling of ResourceNotFoundExceptions. This gets thrown manually by our application. + * + * @param rnfe All the information about the exception that is thrown. + * @return The error details for displaying to the client plus the status Not Found. + */ + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException rnfe) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(HttpStatus.NOT_FOUND.value()); + errorDetail.setTitle("Resource Not Found"); + errorDetail.setDetail(rnfe.getMessage()); + errorDetail.setDeveloperMessage(rnfe.getClass() + .getName()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(rnfe)); + + return new ResponseEntity<>(errorDetail, + null, + HttpStatus.NOT_FOUND); + } + + + + /** + * All other exceptions not handled elsewhere are handled by this method. + * + * @param ex The actual exception used to get error messages + * @param body The body of this request. Not used in this method. + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. Not used in this method. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleExceptionInternal( + Exception ex, + Object body, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Rest Internal Exception"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /********************* + * The rest of the methods are not required and so are provided for reference only. + * They allow you to better customized exception messages + ********************/ + + /** + * Reports when a correct endpoint is accessed but with an unsupported Http Method. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Incorrect method: " + ex.getMethod()); + errorDetail.setDetail("Path: " + request.getDescription(false) + " | Supported Methods are: " + Arrays.toString(ex.getSupportedMethods())); + errorDetail.setDeveloperMessage("HTTP Method Not Valid for Endpoint (check for valid URI and proper HTTP Method)"); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Reports when a correct endpoint is accessed but with an unsupported content, media, type. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleHttpMediaTypeNotSupported( + HttpMediaTypeNotSupportedException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Incorrect content type: " + ex.getContentType()); + errorDetail.setDetail("Path: " + request.getDescription(false) + " | Supported Content / Media Types are: " + ex.getSupportedMediaTypes()); + errorDetail.setDeveloperMessage("Content / Media Type Not Valid for Endpoint (check for valid URI and proper content / media type)"); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Reports when a correct endpoint is accessed but with an unacceptable content, media, type. + * An unacceptable content, media type is one that server does not support at all, regardless of endpoints. + * Normally the error is unsupported type, so this rarely happens. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleHttpMediaTypeNotAcceptable( + HttpMediaTypeNotAcceptableException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Unacceptable content type: " + ex.getMessage()); + errorDetail.setDetail("Path: " + request.getDescription(false) + " | Supported Content / Media Types are: " + ex.getSupportedMediaTypes()); + errorDetail.setDeveloperMessage("Content / Media Type Not Valid for Endpoint (check for valid URI and proper content / media type)"); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * If a path variable is missing, however, in this application this normally becomes an unhandled endpoint + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleMissingPathVariable( + MissingPathVariableException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle(ex.getVariableName() + " Missing Path Variable"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * A parameter is missing. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Parameter Missing for " + "Path: " + request.getDescription(false)); + errorDetail.setDetail("Parameter Missing: " + ex.getParameterName() + " Type: " + ex.getParameterType()); + errorDetail.setDeveloperMessage(ex.getMessage() + " " + ex.getClass()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Servlet Request Binding Exception. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleServletRequestBindingException( + ServletRequestBindingException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Path: " + request.getDescription(false) + " Request Binding Exception"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Conversion Not Supported. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleConversionNotSupported( + ConversionNotSupportedException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Path: " + request.getDescription(false) + " Conversion Not Support"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName() + " " + ex.getMostSpecificCause()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Type Mismatch. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleTypeMismatch( + TypeMismatchException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Path: " + request.getDescription(false) + " Type Mismatch"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName() + " " + ex.getMostSpecificCause()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Message Not Readable. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Path: " + request.getDescription(false) + " Message Not Readable"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName() + " " + ex.getMostSpecificCause()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Message Not Writable. + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleHttpMessageNotWritable( + HttpMessageNotWritableException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Path: " + request.getDescription(false) + " Message Not Writable"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName() + " " + ex.getMostSpecificCause()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * A when an argument fails the @Valid check + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Method Argument Not Valid"); + errorDetail.setDetail(request.getDescription(false) + " | parameter: " + ex.getParameter()); + errorDetail.setDeveloperMessage(ex.getBindingResult() + .toString()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Missing Servlet Request + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleMissingServletRequestPart( + MissingServletRequestPartException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle(request.getDescription(false) + " Missing Servlet Request"); + errorDetail.setDetail("Request Part Name: " + ex.getRequestPartName() + " | " + ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * Bind Exception + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleBindException( + BindException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Bind Exception"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass() + .getName() + " " + ex.getBindingResult()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + + /** + * Client is trying to access an endpoint that does not exist. Requires additions to the application.properties file. + * server.error.whitelabel.enabled=false + * spring.mvc.throw-exception-if-no-handler-found=true + * spring.resources.add-mappings=false + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param request The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleNoHandlerFoundException( + NoHandlerFoundException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest request) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Rest Endpoint Not Valid"); + errorDetail.setDetail(request.getDescription(false)); + errorDetail.setDeveloperMessage("Rest Handler Not Found (check for valid URI)"); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } + + /** + * An async request has timed out + * + * @param ex The actual exception used to get error messages + * @param headers Headers that are involved in this request. Not used in this method. + * @param status The Http Status generated by the exception. Forwarded to the client. + * @param webRequest The request that was made by the client. + * @return The error details to display to the client plus the status that from the exception. + */ + @Override + protected ResponseEntity handleAsyncRequestTimeoutException( + AsyncRequestTimeoutException ex, + HttpHeaders headers, + HttpStatus status, + WebRequest webRequest) + { + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(status.value()); + errorDetail.setTitle("Async Request Timeout Error"); + errorDetail.setDetail("path: " + webRequest.getDescription(false)); + errorDetail.setDeveloperMessage(ex.getMessage()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + return new ResponseEntity<>(errorDetail, + null, + status); + } +} diff --git a/schools/src/main/java/com/lambdaschool/schools/models/ErrorDetail.java b/schools/src/main/java/com/lambdaschool/schools/models/ErrorDetail.java new file mode 100644 index 00000000..3356ce3a --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/ErrorDetail.java @@ -0,0 +1,66 @@ +package com.lambdaschool.schools.models; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class ErrorDetail { + private String title; + private int status; + private String detail; + private Date timestamp; + private String developerMessage; + + private List errors = new ArrayList<>(); + + public ErrorDetail() { + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getDeveloperMessage() { + return developerMessage; + } + + public void setDeveloperMessage(String developerMessage) { + this.developerMessage = developerMessage; + } + + public List getErrors() { + return errors; + } + + public void setErrors(List errors) { + this.errors = errors; + } +} diff --git a/schools/src/main/java/com/lambdaschool/schools/models/ValidationError.java b/schools/src/main/java/com/lambdaschool/schools/models/ValidationError.java new file mode 100644 index 00000000..df6d13df --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/ValidationError.java @@ -0,0 +1,30 @@ +package com.lambdaschool.schools.models; + +public class ValidationError { + private String code; + private String message; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return "ValidationError{" + + "code='" + code + '\'' + + ", message='" + message + '\'' + + '}'; + } +} diff --git a/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctions.java b/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctions.java new file mode 100644 index 00000000..d5d536e0 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctions.java @@ -0,0 +1,10 @@ +package com.lambdaschool.schools.services; + +import com.lambdaschool.schools.models.ValidationError; + +import java.util.List; + +public interface HelperFunctions { + + List getConstraintViolation(Throwable cause); +} diff --git a/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctionsImpl.java b/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctionsImpl.java new file mode 100644 index 00000000..0c40440e --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctionsImpl.java @@ -0,0 +1,50 @@ +package com.lambdaschool.schools.services; + +import com.lambdaschool.schools.models.ValidationError; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.stereotype.Service; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; + +import java.util.ArrayList; +import java.util.List; + +@Service(value = "helperFunctions") +public class HelperFunctionsImpl implements HelperFunctions +{ + @Override + public List getConstraintViolation(Throwable cause) + { + while ((cause != null) && !(cause instanceof org.hibernate.exception.ConstraintViolationException || cause instanceof MethodArgumentNotValidException)) + { + cause = cause.getCause(); + } + + List listVE = new ArrayList<>(); + + if (cause != null) + { + if (cause instanceof org.hibernate.exception.ConstraintViolationException) + { + org.hibernate.exception.ConstraintViolationException ex = (ConstraintViolationException) cause; + ValidationError newVe = new ValidationError(); + newVe.setCode(ex.getMessage()); + newVe.setMessage(ex.getConstraintName()); + listVE.add(newVe); + } else + { + MethodArgumentNotValidException ex = (MethodArgumentNotValidException) cause; + List fieldErrors = ex.getBindingResult().getFieldErrors(); + for (FieldError err : fieldErrors) + { + ValidationError newVe = new ValidationError(); + newVe.setCode(err.getField()); + newVe.setMessage(err.getDefaultMessage()); + listVE.add(newVe); + } + } + } + + return listVE; + } +} \ No newline at end of file diff --git a/schools/src/main/resources/application.properties b/schools/src/main/resources/application.properties index 9758fe0c..7dc5e0c9 100644 --- a/schools/src/main/resources/application.properties +++ b/schools/src/main/resources/application.properties @@ -23,3 +23,8 @@ spring.datasource.initialization-mode=always # spring.jpa.hibernate.ddl-auto=update # since we have our data in SeedData, do not also load it from data.sql # spring.datasource.initialization-mode=never + +#Turn off Spring Boot Exception Handling +server.error.whitelabel.enabled=false +spring.mvc.throw-exception-if-no-handler-found=true +spring.resources.add-mappings=false \ No newline at end of file