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..4918e072 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/config/SwaggerWebMVC.java @@ -0,0 +1,35 @@ +package com.lambdaschool.usermodel.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..8c52e9d0 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/exceptions/CustomErrorDetails.java @@ -0,0 +1,33 @@ +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.LinkedHashMap; +import java.util.Map; + +@Component +public class CustomErrorDetails extends DefaultErrorAttributes +{ + @Autowired + HelperFunctions helperFunctions; + + @Override + public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { + 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("timestamps", errorAttributes.get("timestamp")); + errorDetails.put("developerMessage", "path: " + errorAttributes.get("path")); + + errorDetails.put("errors", helperFunctions.getConstraintViolation(this.getError(webRequest))); + + return errorDetails; + } +} 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..8f6a671c --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/exceptions/ResourceNotFoundException.java @@ -0,0 +1,9 @@ +package com.lambdaschool.schools.exceptions; + +public class ResourceNotFoundException extends RuntimeException +{ + public ResourceNotFoundException(String message) + { + super(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..5851b470 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/handlers/RestExceptionHandler.java @@ -0,0 +1,74 @@ +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.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.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.Date; + +@Order(Ordered.HIGHEST_PRECEDENCE) +@RestControllerAdvice +public class RestExceptionHandler extends ResponseEntityExceptionHandler +{ + @Autowired + private HelperFunctions helperFunctions; + + public RestExceptionHandler() + { + super(); + } + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourcesNotFoundException(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, HttpStatus.NOT_FOUND); + } + + @Override + protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { + ErrorDetail errorDetail = new ErrorDetail(); + + errorDetail.setTimestamp(new Date()); + errorDetail.setStatus(HttpStatus.NOT_FOUND.value()); + errorDetail.setTitle("REST Internal Exception!"); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass().getName()); + errorDetail.setErrors(helperFunctions.getConstraintViolation(ex)); + + + return new ResponseEntity<>(errorDetail, status); + } + + @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); + } +} 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..d29901c1 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/ErrorDetail.java @@ -0,0 +1,68 @@ +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 = "Found an error with School: " + 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..6bde5adc --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/ValidationError.java @@ -0,0 +1,31 @@ +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/CoursesServiceImpl.java b/schools/src/main/java/com/lambdaschool/schools/services/CoursesServiceImpl.java index 77861321..2b88523d 100644 --- a/schools/src/main/java/com/lambdaschool/schools/services/CoursesServiceImpl.java +++ b/schools/src/main/java/com/lambdaschool/schools/services/CoursesServiceImpl.java @@ -1,5 +1,6 @@ package com.lambdaschool.schools.services; +import com.lambdaschool.schools.exceptions.ResourceNotFoundException; import com.lambdaschool.schools.models.Course; import com.lambdaschool.schools.models.Instructor; import com.lambdaschool.schools.models.StudCourses; @@ -58,7 +59,7 @@ public List findAll() public Course findCourseById(long id) { return courserepos.findById(id) - .orElseThrow(() -> new EntityNotFoundException("Course id " + id + " not found!")); + .orElseThrow(() -> new ResourceNotFoundException("Course id " + id + " not found!")); } @Transactional @@ -66,7 +67,7 @@ public Course findCourseById(long id) public void delete(long id) { courserepos.findById(id) - .orElseThrow(() -> new EntityNotFoundException("Course id " + id + " not found!")); + .orElseThrow(() -> new ResourceNotFoundException("Course id " + id + " not found!")); courserepos.deleteById(id); } @@ -79,7 +80,7 @@ public Course save(Course course) if (course.getCourseid() != 0) { Course oldCourse = courserepos.findById(course.getCourseid()) - .orElseThrow(() -> new EntityNotFoundException("Course id " + course.getCourseid() + " not found!")); + .orElseThrow(() -> new ResourceNotFoundException("Course id " + course.getCourseid() + " not found!")); newCourse.setCourseid(course.getCourseid()); } @@ -87,7 +88,7 @@ public Course save(Course course) newCourse.setCoursename(course.getCoursename()); Instructor newInstructor = instructorrepos.findById(course.getInstructor() .getInstructorid()) - .orElseThrow(() -> new EntityNotFoundException("Instructor id " + course.getInstructor() + .orElseThrow(() -> new ResourceNotFoundException("Instructor id " + course.getInstructor() .getInstructorid() + " not found!")); newCourse.setInstructor(newInstructor); @@ -97,7 +98,7 @@ public Course save(Course course) { Student newStudent = studentrepos.findById(sc.getStudent() .getStudentid()) - .orElseThrow(() -> new EntityNotFoundException("Instructor id " + sc.getStudent() + .orElseThrow(() -> new ResourceNotFoundException("Instructor id " + sc.getStudent() .getStudentid() + " not found!")); newCourse.getStudents() 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..689f0070 --- /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.exceptions.ResourceNotFoundException; +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..6e0aabad --- /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; + } +} diff --git a/schools/src/main/java/com/lambdaschool/schools/services/StudentServiceImpl.java b/schools/src/main/java/com/lambdaschool/schools/services/StudentServiceImpl.java index 1ad495cd..7afa7117 100644 --- a/schools/src/main/java/com/lambdaschool/schools/services/StudentServiceImpl.java +++ b/schools/src/main/java/com/lambdaschool/schools/services/StudentServiceImpl.java @@ -1,5 +1,6 @@ package com.lambdaschool.schools.services; +import com.lambdaschool.schools.exceptions.ResourceNotFoundException; import com.lambdaschool.schools.models.Course; import com.lambdaschool.schools.models.StudCourses; import com.lambdaschool.schools.models.Student; @@ -49,7 +50,7 @@ public List findAll() public Student findStudentById(long id) { return studentrepos.findById(id) - .orElseThrow(() -> new EntityNotFoundException("Student id " + id + " not found!")); + .orElseThrow(() -> new ResourceNotFoundException("Student id " + id + " not found!")); } @Transactional @@ -57,7 +58,7 @@ public Student findStudentById(long id) public void delete(long id) { studentrepos.findById(id) - .orElseThrow(() -> new EntityNotFoundException("Student id " + id + " not found!")); + .orElseThrow(() -> new ResourceNotFoundException("Student id " + id + " not found!")); studentrepos.deleteById(id); } @@ -70,7 +71,7 @@ public Student save(Student student) if (student.getStudentid() != 0) { Student oldStudent = studentrepos.findById(student.getStudentid()) - .orElseThrow(() -> new EntityNotFoundException("Student id " + student.getStudentid() + " not found!")); + .orElseThrow(() -> new ResourceNotFoundException("Student id " + student.getStudentid() + " not found!")); newStudent.setStudentid(student.getStudentid()); } diff --git a/schools/src/main/resources/application.properties b/schools/src/main/resources/application.properties index 9758fe0c..1fb45b88 100644 --- a/schools/src/main/resources/application.properties +++ b/schools/src/main/resources/application.properties @@ -23,3 +23,10 @@ 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 +server.error.whitelabel.enabled=false +# +# +spring.mvc.throw-exception-if-no-handler-found=true +# +# +spring.resources.add-mappings=false