커스텀 어노테이션 작성

·2022년 1월 14일
0
  • 클래스 레벨 어노테이션
  • 종료 시간은 시작 시간보다 최소 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]?)";

		// https://www.owasp.org/index.php/OWASP_Validation_Regex_Repository
		EXPR_IPV4 = Pattern.compile("^" + EXPR + "\\." + EXPR + "\\." + EXPR + "\\." + EXPR + "$");

		/*
		fe80:0000:0000:0000:0204:61ff:fe9d:f156 // full form of Ipv6
		fe80:0:0:0:204:61ff:fe9d:f156 // drop leading zeroes
		fe80::204:61ff:fe9d:f156 // collapse multiple zeroes to :: in the Ipv6 address
		fe80:0000:0000:0000:0204:61ff:254.157.241.86 // Ipv4 dotted quad at the end
		fe80:0:0:0:0204:61ff:254.157.241.86 // drop leading zeroes, Ipv4 dotted quad at the end
		fe80::204:61ff:254.157.241.86 // dotted quad at the end, multiple zeroes collapsed
		*/
		// https://community.helpsystems.com/forums/intermapper/miscellaneous-topics/5acc4fcf-fa83-e511-80cf-0050568460e4
		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");

		// https://en.wikipedia.org/wiki/Reserved_IP_addresses#cite_note-1
		EXPR_IPV4_RESERVED = ImmutableList.<Pattern>builder()
				// 0.0.0.0 – 0.255.255.255 / 10.0.0.0 – 10.255.255.255 / 127.0.0.0 – 127.255.255.255
				.add(Pattern.compile("^" + "(0|10|127)" + "\\." + EXPR + "\\." + EXPR + "\\." + EXPR + "$"))
				// 100.64.0.0 – 100.127.255.255
				.add(Pattern.compile("^" + "100" + "\\." + "(6[4-9]|[789][0-9]|1[01][0-9]|12[0-7])" + "\\." + EXPR + "\\." + EXPR + "$"))
				// 169.254.0.0 – 169.254.255.255
				.add(Pattern.compile("^" + "169" + "\\." + "254" + "\\." + EXPR + "\\." + EXPR + "$"))
				// 172.16.0.0 – 172.31.255.255
				.add(Pattern.compile("^" + "172" + "\\." + "(1[6-9]|2[0-9]|3[01])" + "\\." + EXPR + "\\." + EXPR + "$"))
				// 192.0.0.0 – 192.0.0.255 / 192.0.2.0 – 192.0.2.255
				.add(Pattern.compile("^" + "192" + "\\." + "0" + "\\." + "([02])" + "\\." + EXPR + "$"))
				// 192.88.99.0 – 192.88.99.255
				.add(Pattern.compile("^" + "192" + "\\." + "88" + "\\." + "99" + "\\." + EXPR + "$"))
				// 192.168.0.0 – 192.168.255.255
				.add(Pattern.compile("^" + "192" + "\\." + "168" + "\\." + EXPR + "\\." + EXPR + "$"))
				// 198.18.0.0 – 198.19.255.255
				.add(Pattern.compile("^" + "198" + "\\." + "(18|19)" + "\\." + EXPR + "\\." + EXPR + "$"))
				// 198.51.100.0 – 198.51.100.255
				.add(Pattern.compile("^" + "198" + "\\." + "51" + "\\." + "100" + "\\." + EXPR + "$"))
				// 203.0.113.0 – 203.0.113.255
				.add(Pattern.compile("^" + "203" + "\\." + "0" + "\\." + "113" + "\\." + EXPR + "$"))
				// 224.0.0.0 – 239.255.255.255 / 240.0.0.0 – 255.255.255.254 / 255.255.255.255
				.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;
	}
}
profile
Java 일용 노동자.

0개의 댓글