diff --git a/schools/javadocs/com/lambdaschool/zoos/services/ZooServiceImpl.html b/schools/javadocs/com/lambdaschool/zoos/services/ZooServiceImpl.html index 10bef294..845a2d71 100644 --- a/schools/javadocs/com/lambdaschool/zoos/services/ZooServiceImpl.html +++ b/schools/javadocs/com/lambdaschool/zoos/services/ZooServiceImpl.html @@ -376,7 +376,7 @@

update

delete

@Transactional
 public void delete​(long id)
-            throws javax.persistence.EntityNotFoundException
+ throws javax.persistence.ResourceNotFoundException
Description copied from interface: ZooService
Deletes the course record, it student course combinations, and its telephone items from the database based off of the provided primary key
@@ -385,7 +385,7 @@

delete

Parameters:
id - id The primary key (long) of the course you seek.
Throws:
-
javax.persistence.EntityNotFoundException
+
javax.persistence.ResourceNotFoundException
@@ -438,7 +438,7 @@

saveZooAnimalCombo

  • findZooByLikeName

    public java.util.ArrayList<Zoo> findZooByLikeName​(java.lang.String name)
    -                                           throws javax.persistence.EntityNotFoundException
    + throws javax.persistence.ResourceNotFoundException
    Description copied from interface: ZooService
    A list of all zoos whose name contains the given substring A Stretch Goal
    @@ -450,7 +450,7 @@

    findZooByLikeName

    Returns:
    List of zoos whose name contains the given substring
    Throws:
    -
    javax.persistence.EntityNotFoundException
    +
    javax.persistence.ResourceNotFoundException
  • diff --git a/schools/pom.xml b/schools/pom.xml index e68d108f..3c26cf35 100644 --- a/schools/pom.xml +++ b/schools/pom.xml @@ -43,6 +43,24 @@ spring-boot-starter-test test + + io.springfox + springfox-swagger2 + 2.9.2 + + + + + io.springfox + springfox-swagger-ui + 2.9.2 + + + + io.springfox + springfox-bean-validators + 2.9.2 + diff --git a/schools/src/main/java/com/lambdaschool/schools/SchoolsApplication.java b/schools/src/main/java/com/lambdaschool/schools/SchoolsApplication.java index ae94d2dc..6a8435f6 100644 --- a/schools/src/main/java/com/lambdaschool/schools/SchoolsApplication.java +++ b/schools/src/main/java/com/lambdaschool/schools/SchoolsApplication.java @@ -1,8 +1,17 @@ package com.lambdaschool.schools; +import com.lambdaschool.schools.models.slip; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.util.Collections; /** * Main class to start the application. @@ -19,6 +28,28 @@ public class SchoolsApplication */ public static void main(String[] args) { + + RestTemplate restTemplate = new RestTemplate(); + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + + converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL)); + + restTemplate.getMessageConverters().add(converter); + + String requestUrl = "https://api.adviceslip.com/advice"; + + ParameterizedTypeReference responseType = new ParameterizedTypeReference<>() { + }; + + ResponseEntity responseEntity = restTemplate.exchange(requestUrl, HttpMethod.GET,null,responseType); + + + slip ourSlip = responseEntity.getBody(); + System.out.println(ourSlip); + + + SpringApplication.run(SchoolsApplication.class, args); } diff --git a/schools/src/main/java/com/lambdaschool/schools/config/Swagger2Config.java b/schools/src/main/java/com/lambdaschool/schools/config/Swagger2Config.java new file mode 100644 index 00000000..1c18a6e4 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/config/Swagger2Config.java @@ -0,0 +1,63 @@ +package com.lambdaschool.schools.config; + +import org.springframework.context.annotation.Bean; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; + +public class Swagger2Config { + + /*Configures what to document using Swagger + * @Return A docket whcih is the primary interface for Swagger Configuration*/ + + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors + .basePackage("com.lambdaschool.schools")) + .paths(PathSelectors.regex("/.*")) + .build() + .apiInfo(apiEndPointsInfo()); + + + /** + * Configures some information related to the Application for Swagger + * + * @return ApiInfo a Swagger object containing identification information for this application + */ + } + private ApiInfo apiEndPointsInfo() + { + return new ApiInfoBuilder().title("School Model Example") + .description("School Model Example") + .contact(new Contact("George Hatzigeorgio", + "http://www.lambdaschool.com", + "georgehatzigeorgio@gmail.com")) + .license("MIT") + .licenseUrl("https://github.com/LambdaSchool/java-school/blob/master/LICENSE") + .version("1.0.0") + .build(); + } + } + + + + + + + + + + + + + + + + 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..4a78ebf5 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/exceptions/CustomErrorDetails.java @@ -0,0 +1,34 @@ +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 + private 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("timestamp",errorAttributes.get("timestamp")); + errorDetails.put("developerMessage","path:" + errorAttributes.get("path")); + + errorDetails.put("errors", helperFunctions.getValidationErrors(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..2d048d6f --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/exceptions/ResourceNotFoundException.java @@ -0,0 +1,5 @@ +package com.lambdaschool.schools.exceptions; + +public class ResourceNotFoundException extends RuntimeException{ + public ResourceNotFoundException(String message){super("Error from a Lambda School Application"+ message);} +} diff --git a/schools/src/main/java/com/lambdaschool/schools/models/Course.java b/schools/src/main/java/com/lambdaschool/schools/models/Course.java index ccaeb75f..9720976d 100644 --- a/schools/src/main/java/com/lambdaschool/schools/models/Course.java +++ b/schools/src/main/java/com/lambdaschool/schools/models/Course.java @@ -1,6 +1,7 @@ package com.lambdaschool.schools.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.hibernate.validator.constraints.Length; import javax.persistence.*; import java.util.HashSet; @@ -25,7 +26,11 @@ public class Course /** * Name (String) of this Course. Cannot be null and must be unique */ - @Column(nullable = true, + @Length.List({ + @Length(min = 2, message = "The field must be at least 2 characters"), + @Length(max = 30, message = "The field must be less than 30 characters") + }) + @Column(nullable = false, unique = true) private String coursename; 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..887ed7df --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/ErrorDetail.java @@ -0,0 +1,71 @@ +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; + } +} \ No newline at end of file diff --git a/schools/src/main/java/com/lambdaschool/schools/models/Instructor.java b/schools/src/main/java/com/lambdaschool/schools/models/Instructor.java index 19da1bd9..1caffe62 100644 --- a/schools/src/main/java/com/lambdaschool/schools/models/Instructor.java +++ b/schools/src/main/java/com/lambdaschool/schools/models/Instructor.java @@ -1,6 +1,7 @@ package com.lambdaschool.schools.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.hibernate.validator.constraints.Length; import javax.persistence.*; import java.util.ArrayList; @@ -24,6 +25,10 @@ public class Instructor /** * The Instructor's name (String) */ + @Length.List({ + @Length(min = 2, message = "The field must be at least 2 characters"), + @Length(max = 30, message = "The field must be less than 30 characters") + }) @Column(nullable = false) private String name; diff --git a/schools/src/main/java/com/lambdaschool/schools/models/Student.java b/schools/src/main/java/com/lambdaschool/schools/models/Student.java index 6dc4bfab..5b64ccc4 100644 --- a/schools/src/main/java/com/lambdaschool/schools/models/Student.java +++ b/schools/src/main/java/com/lambdaschool/schools/models/Student.java @@ -1,6 +1,7 @@ package com.lambdaschool.schools.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.hibernate.validator.constraints.Length; import javax.persistence.*; import java.util.HashSet; @@ -24,6 +25,10 @@ public class Student /** * The name student (String) */ + @Length.List({ + @Length(min = 2, message = "The field must be at least 2 characters"), + @Length(max = 30, message = "The field must be less than 30 characters") + }) @Column(nullable = false, unique = true) private String name; 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..f378a326 --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/ValidationError.java @@ -0,0 +1,32 @@ +package com.lambdaschool.schools.models; + +public class ValidationError { + private String fieldname; + + private String message; + + public String getFieldname() { + return fieldname; + } + + public ValidationError() { + } + + public ValidationError(String fieldname, String message) { + this.fieldname = fieldname; + this.message = message; + } + + public void setFieldname(String fieldname) { + this.fieldname = fieldname; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} + diff --git a/schools/src/main/java/com/lambdaschool/schools/models/handlers/RestExceptionHandler.java b/schools/src/main/java/com/lambdaschool/schools/models/handlers/RestExceptionHandler.java new file mode 100644 index 00000000..95dcf27b --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/handlers/RestExceptionHandler.java @@ -0,0 +1,52 @@ +package com.lambdaschool.schools.models.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.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.util.Date; + +@RestControllerAdvice +@Order(Ordered.HIGHEST_PRECEDENCE) +public class RestExceptionHandler extends ResponseEntityExceptionHandler { + + @Autowired + private HelperFunctions helperFunctions; + + @ExceptionHandler(ResourceNotFoundException.class) + + public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException rnfe){ + ErrorDetail errorDetail = new ErrorDetail(); + errorDetail.setTimestamp(new Date()); + errorDetail.setTitle("Resource Not Found"); + errorDetail.setStatus(HttpStatus.NOT_FOUND.value()); + errorDetail.setDetail(rnfe.getMessage()); + errorDetail.setDeveloperMessage(rnfe.getClass().getName()); + errorDetail.setErrors(helperFunctions.getValidationErrors(rnfe)); + return new ResponseEntity<>(errorDetail, null, 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.setTitle("Rest Internal Exception"); + errorDetail.setStatus(status.value()); + errorDetail.setDetail(ex.getMessage()); + errorDetail.setDeveloperMessage(ex.getClass().getName()); + errorDetail.setErrors(helperFunctions.getValidationErrors(ex)); + + return new ResponseEntity<>(errorDetail, null,status); + } +} diff --git a/schools/src/main/java/com/lambdaschool/schools/models/slip.java b/schools/src/main/java/com/lambdaschool/schools/models/slip.java new file mode 100644 index 00000000..6645f2be --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/models/slip.java @@ -0,0 +1,32 @@ +package com.lambdaschool.schools.models; + +import com.sun.istack.NotNull; + +public class slip { + private long id; + @NotNull + private String advice; + + + public slip() { + } + + + + + public String getAdvice() { + return advice; + } + + public void setAdvice(String advice) { + this.advice = advice; + } + + @Override + public String toString() { + return "slip{" + + "id=" + id + + ", advice='" + advice + '\'' + + '}'; + } +} 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..bde609de 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; @@ -10,8 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - -import javax.persistence.EntityNotFoundException; import java.util.ArrayList; import java.util.List; @@ -58,7 +57,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 +65,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 +78,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 +86,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 +96,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..647b471c --- /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 getValidationErrors(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..8c919b4b --- /dev/null +++ b/schools/src/main/java/com/lambdaschool/schools/services/HelperFunctionsImpl.java @@ -0,0 +1,53 @@ +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 getValidationErrors(Throwable cause) { + List validationErrorList = new ArrayList<>(); + // Find any data violations that might be associated with the error and report them + // data validations get wrapped in other exceptions as we work through the Spring + // exception chain. Hence we have to search the entire Spring Exception Stack + // to see if we have any violation constraints. + + //JSON <-> Jackson <-> Java Objects <-> JPA/Hibernate <-> SQL + while (cause != null && !(cause instanceof ConstraintViolationException || cause instanceof MethodArgumentNotValidException)) + + { + cause = cause.getCause(); + + } + if (cause != null) { + if (cause instanceof ConstraintViolationException) { + ConstraintViolationException ex = (ConstraintViolationException) cause; + ValidationError newVE = new ValidationError(); + newVE.setMessage(ex.getConstraintName()); + newVE.setFieldname(ex.getMessage()); + validationErrorList.add(newVE); + } else { + MethodArgumentNotValidException ex = (MethodArgumentNotValidException) cause; + + List fieldErrors = ex.getBindingResult().getFieldErrors(); + + for (FieldError fe : fieldErrors) { + ValidationError newVE = new ValidationError(); + newVE.setMessage(fe.getDefaultMessage()); + newVE.setFieldname(fe.getField()); + + validationErrorList.add(newVE); + } + } + } + return validationErrorList; + } + +} \ No newline at end of file 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..830387d8 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; @@ -8,7 +9,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.persistence.EntityNotFoundException; + import java.util.ArrayList; import java.util.List; @@ -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..7e4d6fd3 100644 --- a/schools/src/main/resources/application.properties +++ b/schools/src/main/resources/application.properties @@ -23,3 +23,9 @@ 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 + +// turns off Spring boots automatic exception handling