From 84846c67b7b6102dffec19b54e6d5ecf2a6172cb Mon Sep 17 00:00:00 2001 From: deepcloudlabs Date: Tue, 23 Jun 2020 14:34:58 +0300 Subject: [PATCH] add bean validation exception handling for rest controller --- hr-microservice-hexagonal/pom.xml | 5 ++ .../hr/controller/EmployeeController.java | 37 +++++++++++++- .../com/example/hr/dto/EmployeeRequest.java | 14 ++++++ .../com/example/hr/dto/RestErrorMessage.java | 31 ++++++++++++ .../example/hr/handler/RestErrorHandler.java | 49 +++++++++++++++++++ .../com/example/hr/orm/EmployeeEntity.java | 12 +++++ .../java/com/example/validation/Email.java | 22 +++++++++ .../java/com/example/validation/Iban.java | 21 ++++++++ .../com/example/validation/IbanValidator.java | 43 ++++++++++++++++ .../example/validation/StrongPassword.java | 25 ++++++++++ .../com/example/validation/TcKimlikNo.java | 20 ++++++++ .../validation/TcKimlikNoValidator.java | 45 +++++++++++++++++ .../resources/ValidationMessages.properties | 7 +++ .../ValidationMessages_tr.properties | 7 +++ 14 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/hr/dto/RestErrorMessage.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/hr/handler/RestErrorHandler.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/validation/Email.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/validation/Iban.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/validation/IbanValidator.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/validation/StrongPassword.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNo.java create mode 100644 hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNoValidator.java create mode 100644 hr-microservice-hexagonal/src/main/resources/ValidationMessages.properties create mode 100644 hr-microservice-hexagonal/src/main/resources/ValidationMessages_tr.properties diff --git a/hr-microservice-hexagonal/pom.xml b/hr-microservice-hexagonal/pom.xml index bd9c023..91b022f 100644 --- a/hr-microservice-hexagonal/pom.xml +++ b/hr-microservice-hexagonal/pom.xml @@ -44,6 +44,11 @@ springfox-swagger2 2.8.0 + + org.hibernate + hibernate-validator + 6.1.5.Final + io.swagger swagger-annotations diff --git a/hr-microservice-hexagonal/src/main/java/com/example/hr/controller/EmployeeController.java b/hr-microservice-hexagonal/src/main/java/com/example/hr/controller/EmployeeController.java index 60cb442..a37c47f 100644 --- a/hr-microservice-hexagonal/src/main/java/com/example/hr/controller/EmployeeController.java +++ b/hr-microservice-hexagonal/src/main/java/com/example/hr/controller/EmployeeController.java @@ -1,8 +1,15 @@ package com.example.hr.controller; +import java.util.Map; + import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -11,18 +18,46 @@ import com.example.hr.application.EmployeeApplication; import com.example.hr.dto.EmployeeRequest; import com.example.hr.dto.EmployeeResponse; +import com.example.hr.orm.EmployeeEntity; +import com.example.validation.TcKimlikNo; @RestController @RequestScope @RequestMapping("/employees") @CrossOrigin +@Validated public class EmployeeController { @Autowired private EmployeeApplication employeeApplication; @PostMapping - public EmployeeResponse hireEmployee(@RequestBody EmployeeRequest request) { + public EmployeeResponse hireEmployee(@RequestBody @Validated EmployeeRequest request) { employeeApplication.hireEmployee(request.toEmployee()); return new EmployeeResponse("success"); } + + @PutMapping("{identity}") + public void updateEmployee(@PathVariable @TcKimlikNo String identity, + @Validated @RequestBody EmployeeEntity e) { + + } + @PatchMapping("{identity}") + public void patchEmployee(@PathVariable @TcKimlikNo String identity, + Map employee) { + EmployeeEntity employeeEntity= null; + var clazz = EmployeeEntity.class; + employee.forEach((field,value)->{ + try { + clazz.getDeclaredField(field).set(employeeEntity, value); + } catch (Exception e) {} + }); + } + + //@GetMapping + + @DeleteMapping("{identity}") + public EmployeeResponse fireEmployee(@PathVariable @TcKimlikNo String identity) { + employeeApplication.fireEmployee(null); + return null; + } } diff --git a/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/EmployeeRequest.java b/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/EmployeeRequest.java index 574f9fe..4e912bf 100644 --- a/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/EmployeeRequest.java +++ b/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/EmployeeRequest.java @@ -1,17 +1,31 @@ package com.example.hr.dto; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + import com.example.hr.domain.Department; import com.example.hr.domain.Employee; import com.example.hr.domain.MoneyCurrency; +import com.example.validation.Iban; +import com.example.validation.TcKimlikNo; public class EmployeeRequest { + @TcKimlikNo private String identity; + @Size(min=6) private String fullname; + @Min(3_000) private double salary; + @Iban private String iban; private boolean fulltime; + @Max(2002) private int birthYear; + @NotNull private byte[] photo; + @NotNull private Department department; public EmployeeRequest() { diff --git a/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/RestErrorMessage.java b/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/RestErrorMessage.java new file mode 100644 index 0000000..ff82df6 --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/hr/dto/RestErrorMessage.java @@ -0,0 +1,31 @@ +package com.example.hr.dto; + +public class RestErrorMessage { + private int errorId; + private String message; + private String debugId; + + public RestErrorMessage(int errorId, String message, String debugId) { + this.errorId = errorId; + this.message = message; + this.debugId = debugId; + } + + public int getErrorId() { + return errorId; + } + + public String getMessage() { + return message; + } + + public String getDebugId() { + return debugId; + } + + @Override + public String toString() { + return "RestErrorMessage [errorId=" + errorId + ", message=" + message + ", debugId=" + debugId + "]"; + } + +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/hr/handler/RestErrorHandler.java b/hr-microservice-hexagonal/src/main/java/com/example/hr/handler/RestErrorHandler.java new file mode 100644 index 0000000..1a7a81e --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/hr/handler/RestErrorHandler.java @@ -0,0 +1,49 @@ +package com.example.hr.handler; + +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import com.example.hr.dto.RestErrorMessage; + +@RestControllerAdvice +public class RestErrorHandler { + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(code = HttpStatus.BAD_REQUEST) + public RestErrorMessage handleException(Exception e) { + return new RestErrorMessage(100,e.getMessage(),"6b8038e5-3b8f-4230-bfe8-4de2c422469f"); + } + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(code = HttpStatus.BAD_REQUEST) + public RestErrorMessage handleConstraintViolationException(ConstraintViolationException e) { + var violations = e.getConstraintViolations() + .stream() + .map(ConstraintViolation::getMessage) + .collect(Collectors.joining("|")); + return new RestErrorMessage(200,violations,"7c248b97-9aa8-46fd-b5b2-9f8ea537635c"); + } + + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(code = HttpStatus.BAD_REQUEST) + public RestErrorMessage handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + var violations = e.getBindingResult() + .getAllErrors() + .stream() + .map(ObjectError::getDefaultMessage) + .collect(Collectors.joining("|")); + return new RestErrorMessage(200,violations,"7c248b97-9aa8-46fd-b5b2-9f8ea537635c"); + } + + +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/hr/orm/EmployeeEntity.java b/hr-microservice-hexagonal/src/main/java/com/example/hr/orm/EmployeeEntity.java index a30eaa0..78a1474 100644 --- a/hr-microservice-hexagonal/src/main/java/com/example/hr/orm/EmployeeEntity.java +++ b/hr-microservice-hexagonal/src/main/java/com/example/hr/orm/EmployeeEntity.java @@ -7,19 +7,31 @@ import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.Table; +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.Size; import com.example.hr.domain.Department; +import com.example.validation.Iban; +import com.example.validation.TcKimlikNo; @Entity @Table(name = "employees") public class EmployeeEntity { @Id @Column(name = "identity") + @TcKimlikNo private String identity; + @Size(min=5) private String fullname; + @Min(3_000) private double salary; + @Iban private String iban; + //@AssertTrue private boolean fulltime; + @Max(2002) private int birthYear; @Lob @Column(columnDefinition = "longblob") diff --git a/hr-microservice-hexagonal/src/main/java/com/example/validation/Email.java b/hr-microservice-hexagonal/src/main/java/com/example/validation/Email.java new file mode 100644 index 0000000..a63f0a3 --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/validation/Email.java @@ -0,0 +1,22 @@ +package com.example.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; +import javax.validation.constraints.Pattern; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Pattern(regexp = "^\\+?[a-z0-9](([-+.]|[_]+)?[a-z0-9]+)*@([a-z0-9]+(\\.|\\-))+[a-z]{2,6}$", message = "{validation.email}") +@Constraint(validatedBy = {}) +public @interface Email { + String message() default "{validation.email}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/validation/Iban.java b/hr-microservice-hexagonal/src/main/java/com/example/validation/Iban.java new file mode 100644 index 0000000..19377f6 --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/validation/Iban.java @@ -0,0 +1,21 @@ +package com.example.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = IbanValidator.class) +public @interface Iban { + String message() default "{validation.iban}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/validation/IbanValidator.java b/hr-microservice-hexagonal/src/main/java/com/example/validation/IbanValidator.java new file mode 100644 index 0000000..97d39d9 --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/validation/IbanValidator.java @@ -0,0 +1,43 @@ +package com.example.validation; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class IbanValidator implements ConstraintValidator { + private static final long MAX = 999999999; + private static final long MODULUS = 97; + + @Override + public void initialize(Iban arg0) { + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext ctx) { + if (value == null || value.length() < 5) { + return false; + } + try { + int modulusResult = calculateModulus(value); + return (modulusResult == 1); + } catch (Exception ex) { + return false; + } + } + + private int calculateModulus(String code) throws Exception { + String reformattedCode = code.substring(4) + code.substring(0, 4); + long total = 0; + for (int i = 0; i < reformattedCode.length(); i++) { + int charValue = Character.getNumericValue(reformattedCode.charAt(i)); + if (charValue < 0 || charValue > 35) { + throw new Exception("Invalid Character[" + i + "] = '" + charValue + "'"); + } + total = (charValue > 9 ? total * 100 : total * 10) + charValue; + if (total > MAX) { + total = (total % MODULUS); + } + } + return (int) (total % MODULUS); + } + +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/validation/StrongPassword.java b/hr-microservice-hexagonal/src/main/java/com/example/validation/StrongPassword.java new file mode 100644 index 0000000..a8d47ba --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/validation/StrongPassword.java @@ -0,0 +1,25 @@ +package com.example.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Pattern.List({ @Pattern(regexp = "^.*\\d+.*$", message = "{validation.strongPassword2}"), + @Pattern(regexp = "^.*[-_]+.*$", message = "{validation.strongPassword3}") }) +@Size(min = 6) +@Constraint(validatedBy = {}) +public @interface StrongPassword { + String message() default "{validation.strongPassword1}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNo.java b/hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNo.java new file mode 100644 index 0000000..7bd8027 --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNo.java @@ -0,0 +1,20 @@ +package com.example.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = TcKimlikNoValidator.class) +public @interface TcKimlikNo { + String message() default "{validation.identityNo}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNoValidator.java b/hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNoValidator.java new file mode 100644 index 0000000..eb57612 --- /dev/null +++ b/hr-microservice-hexagonal/src/main/java/com/example/validation/TcKimlikNoValidator.java @@ -0,0 +1,45 @@ +package com.example.validation; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public class TcKimlikNoValidator implements ConstraintValidator { + + @Override + public void initialize(TcKimlikNo arg0) { + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null) + return false; + if (!value.matches("^\\d{11}$")) { + return false; + } + int[] digits = new int[11]; + for (int i = 0; i < digits.length; ++i) { + digits[i] = value.charAt(i) - '0'; + } + int x = digits[0]; + int y = digits[1]; + for (int i = 1; i < 5; i++) { + x += digits[2 * i]; + } + for (int i = 2; i <= 4; i++) { + y += digits[2 * i - 1]; + } + int c1 = 7 * x - y; + if (c1 % 10 != digits[9]) { + return false; + } + int c2 = 0; + for (int i = 0; i < 10; ++i) { + c2 += digits[i]; + } + if (c2 % 10 != digits[10]) { + return false; + } + return true; + } + +} diff --git a/hr-microservice-hexagonal/src/main/resources/ValidationMessages.properties b/hr-microservice-hexagonal/src/main/resources/ValidationMessages.properties new file mode 100644 index 0000000..bdf0efb --- /dev/null +++ b/hr-microservice-hexagonal/src/main/resources/ValidationMessages.properties @@ -0,0 +1,7 @@ +validation.email=This is not a valid e-mail! +validation.iban=This is not a valid IBAN! +validation.knickname=Must have at least 5-chars! +validation.strongPassword1=Password must be at least six chars! +validation.strongPassword2=Must contain at least one digit! +validation.strongPassword3=Must contain at least one character from the set -,_,$,.! +validation.identityNo=This is not a valid identity number! \ No newline at end of file diff --git a/hr-microservice-hexagonal/src/main/resources/ValidationMessages_tr.properties b/hr-microservice-hexagonal/src/main/resources/ValidationMessages_tr.properties new file mode 100644 index 0000000..5eb98cf --- /dev/null +++ b/hr-microservice-hexagonal/src/main/resources/ValidationMessages_tr.properties @@ -0,0 +1,7 @@ +validation.email=Ge\u00e7ersiz e-posta adresi! +validation.iban=Ge\u00e7ersiz hesap numaras\u0131! +validation.knickname=Takma ad en az 5 karakterden olu\u015Fmal\u0131d\u0131r! +validation.strongPassword1=Parola en az 6 karakterden olu\u015fmal\u0131d\u0131r! +validation.strongPassword2=Parola en az bir rakam i\u00e7ermelidir! +validation.strongPassword3=Parola bu karakterlerden (-,_) en az bir tane i\u00e7ermelidir! +validation.identityNo=Ge\u00e7ersiz kimlik numaras\u0131! \ No newline at end of file