- 클래스 레벨 어노테이션
- 종료 시간은 시작 시간보다 최소 xx분 뒤여야 하는 조건 체크
interface FromTo {
@Nullable ZonedDateTime getFrom();
ZonedDateTime getTo();
}
@Documented
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FromToIntervalCheck.List.class)
@Constraint(validatedBy=FromToIntervalCheckValidator.class)
public @interface FromToIntervalCheck {
long minutes() default 10L;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String message() default "{bus.loona.FromToIntervalCheck.message}";
@Documented
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface List { FromToIntervalCheck[] value(); }
}
public class FromToIntervalCheckValidator implements ConstraintValidator<FromToIntervalCheck, FromTo> {
private Long minutes = 60L;
private final MessageSourceAccessor accessor;
@Autowired
public FromToIntervalCheckValidator(final MessageSourceAccessor accessor) {
this.accessor = accessor;
}
@Override
public void initialize(final FromToIntervalCheck annotation) {
this.minutes = annotation.minutes();
}
@Override
public boolean isValid(final BaseReq.FromTo req, final ConstraintValidatorContext context) {
final ZonedDateTime from = req.getFrom();
final ZonedDateTime to = req.getTo();
if (Objects.isNull(from)) {
return true;
}
if (TimeUnit.MINUTES.toSeconds(minutes) <= Duration.between(from, to).getSeconds()) {
return true;
}
final String messageTemplate = context.getDefaultConstraintMessageTemplate();
final String code = messageTemplate.substring(1, messageTemplate.length()-1);
final String message = accessor.getMessage(code, new Object[]{minutes, from, to}, LocaleContextHolder.getLocale());
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
return false;
}
}
- IP 유효성 체크 어노테이션
- IPv6 관련 부분은 지식이 없어 확인이 필요..
@Documented
@Repeatable(IpCheck.List.class)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=IpCheckValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
public @interface IpCheck {
String desc() default "";
boolean nullable() default false;
boolean receiveIpv6() default false;
boolean receiveIpv4Reserved() default false;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String message() default "{bus.loona.IpCheck.message}";
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@interface List { IpCheck[] value(); }
}
final class IpCheckPattern {
private static final String EXPR;
static final Pattern EXPR_IPV4;
static final Pattern EXPR_IPV6;
static final List<Pattern> EXPR_IPV4_RESERVED;
static {
EXPR = "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
EXPR_IPV4 = Pattern.compile("^" + EXPR + "\\." + EXPR + "\\." + EXPR + "\\." + EXPR + "$");
EXPR_IPV6 = Pattern.compile("^(((?=(?>.*?::)(?!.*::)))(::)" +
"?([0-9a-fA-F]{1,4}::?){0,5}|([0-9a-fA-F]{1,4}:){6})" +
"(\\2([0-9a-fA-F]{1,4}(::?|$)){0,2}|((25[0-5]|(2[0-4]|1\\d|[1-9])" +
"?\\d)(\\.|$)){4}|[0-9a-fA-F]{1,4}:[0-9a-fA-F]{1,4})(?<![^:]:|\\.)\\z");
EXPR_IPV4_RESERVED = ImmutableList.<Pattern>builder()
.add(Pattern.compile("^" + "(0|10|127)" + "\\." + EXPR + "\\." + EXPR + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "100" + "\\." + "(6[4-9]|[789][0-9]|1[01][0-9]|12[0-7])" + "\\." + EXPR + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "169" + "\\." + "254" + "\\." + EXPR + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "172" + "\\." + "(1[6-9]|2[0-9]|3[01])" + "\\." + EXPR + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "192" + "\\." + "0" + "\\." + "([02])" + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "192" + "\\." + "88" + "\\." + "99" + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "192" + "\\." + "168" + "\\." + EXPR + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "198" + "\\." + "(18|19)" + "\\." + EXPR + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "198" + "\\." + "51" + "\\." + "100" + "\\." + EXPR + "$"))
.add(Pattern.compile("^" + "203" + "\\." + "0" + "\\." + "113" + "\\." + EXPR + "$"))
.add(Pattern.compile("^(22[4-9]|2[34][0-9]|25[0-5])" + "\\." + EXPR + "\\." + EXPR + "\\." + EXPR + "$")).build();
}
private IpCheckPattern() {
throw new UnsupportedOperationException(Constants.UNSUPPORTED_OPERATION_EXCEPTION_MESSAGE);
}
}
public class IpCheckValidator implements ConstraintValidator<IpCheck, String> {
private boolean nullable = false;
private boolean receiveIpv6 = false;
private boolean receiveIpv4Reserved = false;
@Override
public void initialize(final IpCheck annotation) {
this.nullable = annotation.nullable();
this.receiveIpv6 = annotation.receiveIpv6();
this.receiveIpv4Reserved = annotation.receiveIpv4Reserved();
}
@Override
public boolean isValid(@Nullable final String value, final ConstraintValidatorContext context) {
return Objects.isNull(value) ? nullable : checkIpv6(value, checkIpv4Reserved(value, checkIpv4(value)));
}
private boolean checkIpv4(final String source) {
return IpCheckPattern.EXPR_IPV4.matcher(source).matches();
}
private boolean checkIpv4Reserved(final String source, final boolean checked) {
return receiveIpv4Reserved ? checked : checked && IpCheckPattern.EXPR_IPV4_RESERVED.stream().noneMatch(p -> p.matcher(source).matches());
}
private boolean checkIpv6(final String source, final boolean checked) {
return receiveIpv6 ? checked || IpCheckPattern.EXPR_IPV6.matcher(source).matches() : checked;
}
}