Bean Validation 2 – Custom Constraint
Arkadaşlar merhaba. Bean validation api’sini incelemeye devam ediyoruz. Bir önceki makalemizde konuya giriş yapmış standart anotasyonları incelemiştik. Bu yazımızda, standart anotasyonların yeterli gelmediği durumlar da nasıl kendimize özgü kısıtlar yazabiliriz ona bakacağız.
Önceki örneğimizden devam edelim. Var olan kısıtlardan birinde bir değişikliğe gidildiğini ve bizden şöyle bir kısıt eklememiz istendiğini düşünelim.
“publish alanı üzerindeki true olma validasyonunu kaldırmamız ve eğer subject A ise, publish özelliği true , diğerlerinde ise false olmalı koşulu eklememiz istensin.”
subject alanına göre publish alanını geçerlilik kontrolüne sokmamız isteniyor. Bu api ile gelen anotasyonlar ile yapabileceğimiz bir kontrol değil. Bunun için custom bir constraint yazacağız. Custom Constraint bir anotasyon ve kısıt mantığını içeren sınıftan oluşacak. Tabi bu sınıf ConstraintValidator interface’ini implemente edecek. Anotasyonu yazarak başlayalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ValidateUsingAnotherStringField { /*mandatory*/ String message() default ""; Class<? extends Payload>[] payload() default {}; Class<?>[] groups() default {}; /*User defined*/ String fieldName(); String dependFieldName(); String expectedDependFieldValue(); String expectedFieldValue(); } |
İki ayrı sınıf alanına ihtiyacımız olduğundan, anotasyonumuz sınıf seviyesinde kullanılacak. Message, groups ve payload alanları zorunlu alanlardır. Groups ve payload ‘un ne olduğunu bir sonraki makalede inceleyeceğiz inşallah. Diğer alanlar bizim ihtiyacımız olan alanlardır. FieldName ve expectedFieldValue subject alanını gösterecek, diğer iki alan ise publish alanını için kullanılacak. Şimdi validator sınıfımızı oluşturalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class ValidaterOfValidateUsingAnotherStringField implements ConstraintValidator<ValidateUsingAnotherStringField, Object> { private String fieldName; private String expectedFieldValue; private String dependFieldName; private String expectedDependFieldValue; @Override public void initialize(ValidateUsingAnotherStringField constraintAnnotation) { this.fieldName = constraintAnnotation.fieldName(); this.expectedFieldValue = constraintAnnotation.expectedFieldValue(); this.dependFieldName = constraintAnnotation.dependFieldName(); this.expectedDependFieldValue = constraintAnnotation.expectedDependFieldValue(); } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { try { //subject alanı (fieldName) degeri expectedFieldValue ise (kosulumuzda A olacak bu deger) if(BeanUtils.getProperty(value, fieldName).equalsIgnoreCase(expectedFieldValue)) { // publish(dependValue) degeri expectedDependFieldValue (kosulumuzda true) değerine eşit olmalı. Eşit değilse validasyon hatası olur. if(!BeanUtils.getProperty(value, dependFieldName).equalsIgnoreCase(expectedDependFieldValue)) { return false; } } else { if(BeanUtils.getProperty(value, dependFieldName).equalsIgnoreCase(expectedDependFieldValue)) { return false; } } return true; } catch (Exception e) { // TODO: handle exception } return false; } } |
ConstraintValidator interface’ine iki parametre geçtik. İlki bu sınıfın hangi anotasyonu kabul edeceğini gösteriyor ve biz bunu initialize metotunda kullanıyoruz. Bu sayede anotasyon üzerindeki değerleri alıp sınıf değişkenlerimize set ediyoruz.
İkinci parametre ise, valide edilecek olan değişkeni gösteriyor. Biz burada sınıfın kendisini bekliyoruz ve o yüzden Object olarak kullandık.
isValid metotu ile validasyon kodumuzu yazıyoruz.
Son bir iş olarak, yazdığımız anotasyon içerisine aşağıdaki satırı ekliyoruz ve böylece bu anotasyonun hangi sınıf tarafından valide edileceğini söylemiş oluyoruz.
1 2 3 4 5 |
@Constraint(validatedBy=ValidaterOfValidateUsingAnotherStringField.class) |
Article sınıfımız şu şekilde değişti.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@ValidateUsingAnotherStringField(fieldName="subject", expectedFieldValue="A", dependFieldName="publish",expectedDependFieldValue="true",message="Gecersiz publish degeri") public class Article { @NotBlank(message = "author alanı boş olamaz!") private String author; @NotNull(message = "Subject alanı boş olamaz") @Pattern(regexp="A|B|C") private String subject; @NotEmpty(message = "category listesi bos olamaz") private List<@NotNull(message = "category elemanları null olamaz") String> category; @NotBlank(message = "Title alanı boş olamaz") @Size(max = 30, message = "Title en çok 30 karakter olabilir") private String title; @FutureOrPresent(message = "lastSaveDate geçmişte olamaz") private LocalDate lastSaveDate; private Boolean publish; @NotBlank(message = "Text alanı null olamaz") private String text; |
publish alanımızın eski anotasyonunu kaldırdım. Ayrıca text alanının size anotasyonunu kaldırdım. Article sınıfının üzerine yazdığımız custom constrainti ekledik.
Starter sınıfımız da şu şekilde olacak.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class Starter { public static void main(String[] args) { Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); Set<ConstraintViolation<Article>> violations = validator.validate(initializeInstance()); violations.forEach(violation -> { System.out.println(violation.getMessage()); }); } static Article initializeInstance() { Article article = new Article(); article.setAuthor("Author"); article.setCategory(Arrays.asList("A","B")); article.setLastSaveDate(LocalDate.now()); article.setPublish(false); article.setSubject("A"); article.setText("Text"); article.setTitle("Title"); return article; } } |
Çalıştırdığımızda aşağıdaki çıktıyı almış olmamız lazım.
1 2 3 4 5 |
Publish alanı true olmalı |
Umarım faydalı olmuştur. Gayret bizden tevfik Allah’tan.
Kaynak Kod : https://github.com/volkanozdemir/validation-api-custom-constraint.git