カスタム宣言の作成


  • 級入学試験
  • 条件チェック
  • 終了時間は少なくとも開始時間より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有効性検査声明
  • IPv 6に関する部分は知識がなく、確認が必要です...
  • @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;
    	}
    }