Skip to content

Commit

Permalink
feat($Validation): support date time range value validation
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnymillergh committed Jun 6, 2021
1 parent 0951f97 commit 96c8b83
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jmsoftware.maf.common.bean;

/**
* <h1>EnumerationBase</h1>
* <p>
* Change description here.
*
* @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
* @date 6/6/21 5:38 PM
**/
public interface EnumerationBase<T extends Number> {
/**
* Gets value.
*
* @return the value
*/
T getValue();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.util.Objects;

/**
Expand All @@ -44,7 +45,7 @@ public class ExceptionControllerAdvice {
* @return custom exception info
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NoHandlerFoundException.class)
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, NoHandlerFoundException exception) {
requestLog(request);
// ATTENTION: Use only ResponseBodyBean.ofStatus() in handleException() method and
Expand All @@ -55,7 +56,7 @@ public ResponseBodyBean<?> handleException(HttpServletRequest request, NoHandler
}

@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request,
HttpRequestMethodNotSupportedException exception) {
requestLog(request);
Expand All @@ -66,7 +67,7 @@ public ResponseBodyBean<?> handleException(HttpServletRequest request,
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, MethodArgumentNotValidException exception) {
requestLog(request);
log.error("Exception occurred when validation on an argument annotated with fails. Exception message: {}",
Expand All @@ -76,7 +77,7 @@ public ResponseBodyBean<?> handleException(HttpServletRequest request, MethodArg
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request,
MethodArgumentTypeMismatchException exception) {
requestLog(request);
Expand All @@ -86,14 +87,14 @@ public ResponseBodyBean<?> handleException(HttpServletRequest request,
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = HttpMessageNotReadableException.class)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, HttpMessageNotReadableException exception) {
requestLog(request);
log.error("HttpMessageNotReadableException: {}", exception.getMessage());
return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST, removeLineSeparator(exception.getMessage()));
}

@ExceptionHandler(value = BaseException.class)
@ExceptionHandler(BaseException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, HttpServletResponse response,
BaseException exception) {
requestLog(request);
Expand All @@ -105,34 +106,51 @@ public ResponseBodyBean<?> handleException(HttpServletRequest request, HttpServl
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = BindException.class)
@ExceptionHandler(BindException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, BindException exception) {
requestLog(request);
log.error("Exception message: {} ", exception.getMessage());
log.error("BindException message: {} ", exception.getMessage());
String message;
if (exception.hasFieldErrors()) {
val fieldError = exception.getFieldError();
message = String.format("Field [%s] has error: %s", Objects.requireNonNull(fieldError).getField(),
fieldError.getDefaultMessage());
} else {
val globalError = exception.getGlobalError();
message = String.format("Bind error: %s", Objects.requireNonNull(globalError).getDefaultMessage());
}
return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST, message);
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, ConstraintViolationException exception) {
requestLog(request);
log.error("ConstraintViolationException message: {} ", exception.getMessage());
return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST, removeLineSeparator(exception.getMessage()));
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
@ExceptionHandler(IllegalArgumentException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, IllegalArgumentException exception) {
requestLog(request);
log.error("Exception message: {} ", exception.getMessage());
log.error("IllegalArgumentException message: {} ", exception.getMessage());
return ResponseBodyBean.ofStatus(HttpStatus.BAD_REQUEST.value(), removeLineSeparator(exception.getMessage()),
null);
}

@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(value = BadCredentialsException.class)
@ExceptionHandler(BadCredentialsException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, BadCredentialsException exception) {
requestLog(request);
// IMPORTANT: org.springframework.security.authentication.BadCredentialsException only exists in the project
// that depends on org.springframework.boot.spring-boot-starter-security
log.error("Exception message: {} ", exception.getMessage());
log.error("BadCredentialsException message: {} ", exception.getMessage());
return ResponseBodyBean.ofStatus(HttpStatus.FORBIDDEN.value(), removeLineSeparator(exception.getMessage()),
null);
}

@ExceptionHandler(value = InternalAuthenticationServiceException.class)
@ExceptionHandler(InternalAuthenticationServiceException.class)
public ResponseBodyBean<?> handleException(HttpServletRequest request, HttpServletResponse response,
InternalAuthenticationServiceException exception) {
requestLog(request);
Expand All @@ -149,7 +167,7 @@ public ResponseBodyBean<?> handleException(HttpServletRequest request, HttpServl
null);
}

@ExceptionHandler(value = {Throwable.class})
@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseBodyBean<?> handleError(Throwable ex) {
log.error("Internal server exception occurred! Exception message: {} ", ex.getMessage(), ex);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.jmsoftware.maf.springcloudstarter.configuration;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
* <h1>MinioConfiguration</h1>
* <p>
* Change description here.
*
* @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
* @date 4/29/21 2:23 PM
**/
@Data
@Slf4j
@Validated
@Component
@ConfigurationProperties(prefix = MinioClientConfiguration.PREFIX)
public class MinioClientConfiguration {
public static final String PREFIX = "minio.client.configuration";
@NotBlank
private String endpoint;
@NotNull
private Integer port;
@NotBlank
private String accessKey;
@NotBlank
private String secretKey;
private Boolean secure = Boolean.TRUE;
private String bucketName;
private String configDir;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.jmsoftware.maf.springcloudstarter.validation.annotation;

import com.jmsoftware.maf.springcloudstarter.validation.validator.DateTimeRangeValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
* Description: DateTimeRangeConstraints, change description here.
*
* @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 2:08 PM
**/
@Documented
@Constraint(validatedBy = DateTimeRangeValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateTimeRangeConstraints {
String message() default "Invalid date time range";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.jmsoftware.maf.springcloudstarter.validation.annotation;

import java.lang.annotation.*;

/**
* Description: DateTimeRangeGroup, change description here.
*
* @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 2:58 PM
**/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DateTimeRangeGroup {
String groupName() default "defaultDateTimeRangeGroup";

DateTimeRangeType type() default DateTimeRangeType.START_TIME;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.jmsoftware.maf.springcloudstarter.validation.annotation;

/**
* Description: DateTimeRangeType, change description here.
*
* @author 钟俊 (zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 3:07 PM
*/
public enum DateTimeRangeType {
/**
* Start time
*/
START_TIME,
/**
* End time
*/
END_TIME,
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.jmsoftware.maf.springcloudstarter.annotation;
package com.jmsoftware.maf.springcloudstarter.validation.annotation;

import com.jmsoftware.maf.springcloudstarter.validation.EnumValueValidator;
import com.jmsoftware.maf.springcloudstarter.validation.validator.EnumValueValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
Expand All @@ -26,11 +27,11 @@
public @interface ValidEnumValue {
String message() default "Invalid enumeration value";

Class<?> targetEnum() default Class.class;
Class<?>[] groups() default {};

boolean ignoreNull() default false;
Class<? extends Payload>[] payload() default {};

Class[] groups() default {};
Class<?> targetEnum() default Class.class;

Class[] payload() default {};
boolean ignoreNull() default false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.jmsoftware.maf.springcloudstarter.validation.validator;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.jmsoftware.maf.springcloudstarter.validation.annotation.DateTimeRangeConstraints;
import com.jmsoftware.maf.springcloudstarter.validation.annotation.DateTimeRangeGroup;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;

/**
* Description: DateTimeRangeValidator, change description here.
*
* @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 6/3/2021 2:10 PM
**/
@Slf4j
public class DateTimeRangeValidator implements ConstraintValidator<DateTimeRangeConstraints, Object> {
public static final int MAX_GROUP_SIZE = 2;

@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
val fields = value.getClass().getDeclaredFields();
final HashSet<Field> annotatedFieldSet = CollUtil.newHashSet();
for (val field : fields) {
val annotation = field.getAnnotation(DateTimeRangeGroup.class);
if (ObjectUtil.isNotNull(annotation)) {
annotatedFieldSet.add(field);
}
}
if (CollUtil.isEmpty(annotatedFieldSet)) {
log.warn("There is not fields annotated by {} in the class({})", value.getClass().getName(),
DateTimeRangeGroup.class.getSimpleName());
return true;
}
final HashMap<String, LinkedList<Field>> dateTimeRangeGroupMap = CollUtil.newHashMap(fields.length);
for (val field : annotatedFieldSet) {
val annotation = field.getAnnotation(DateTimeRangeGroup.class);
if (!dateTimeRangeGroupMap.containsKey(annotation.groupName())) {
dateTimeRangeGroupMap.put(annotation.groupName(), CollUtil.newLinkedList(field));
} else {
dateTimeRangeGroupMap.get(annotation.groupName()).add(field);
if (dateTimeRangeGroupMap.get(annotation.groupName()).size() > MAX_GROUP_SIZE) {
log.error("The length of DateTimeRangeGroup({}) cannot exceed {}!", annotation.groupName(), MAX_GROUP_SIZE);
return false;
}
}
}
for (val entry : dateTimeRangeGroupMap.entrySet()) {
val groupName = entry.getKey();
val fieldList = entry.getValue();
if (fieldList.size() != MAX_GROUP_SIZE) {
log.error("The length of DateTimeRangeGroup({}) is not correct!", groupName);
return false;
}
val dateTimeRangeGroup = fieldList.get(0).getAnnotation(DateTimeRangeGroup.class);
Date startTime = null, endTime = null;
switch (dateTimeRangeGroup.type()) {
case START_TIME:
startTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(0));
endTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(1));
break;
case END_TIME:
startTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(1));
endTime = (Date) ReflectUtil.getFieldValue(value, fieldList.get(0));
break;
default:
}
if (ObjectUtil.isAllNotEmpty(startTime, endTime) && startTime.after(endTime)) {
return false;
}
}
return true;
}
}
Loading

0 comments on commit 96c8b83

Please sign in to comment.