JPA validáció (2. rész - custom validator API)

Custom validatorok

Mint minden jó keretrendszerben, természetesen itt is van lehetőségünk kiterjeszteni a funkcionalitást, vagyis írhatunk saját validációs annotációkat.

Ez már nem annyira kézenfekvő, és nem árt ha egy kicsit tisztában vagyunk az annotációk működésével, lelki világával. Egy @FiledMatch példán keresztül próbáljuk meg most ezt szemléltetni, amivel is garantálni tudjuk majd, hogy a megadott jelszó, illetve annak ellenőrző mezejében megadott jelszó megegyeznek.

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchImpl.class)
@Documented
public @interface FieldMatch {

String message() default "{constraints.fieldmatch}";
Class[] groups() default {};
Class[] payload() default {};

String first();
String second();

@Target({TYPE, ANNOTATION_TYPE})JPA
@Retention(RUNTIME)
@Documented
@interface List {
FieldMatch[] value();
}
}


Feltételezve, hogy mostanra mindent tudunk az annotációkról, vegyük sorra a még mindig szokatlan részeket. A @Constraint annotáció szintén a validációs keretrendszerrel érkezik, és definiálja az annotációnk implementációs osztályát, vagyis ami a tényleges ellenőrzést megvalósítja. A message(), groups() és payload() metódusok keretrendszeri előírások, melyek a hibaüzenetet, validációs csoportot, és metainformációkat hordozó objektumokat kezelik.

A first() és second() metódusok már a saját elképzelésünk származékai, egész pontosan a két mező nevét fogják hordozni, melyek egyezőségét meg szeretnénk követelni.
A FieldMatch[] value() metódus kicsit trükkös, ezzel ugyanis egyfajta önhivatkozást viszünk a rendszerbe, lehetővé téve, hogy egy annotáció struktúrával több mezőpárosra vonatkozó követelményt is le tudjunk írni, ahogy ezt majd a konkrét példában látni is fogjuk.

public class FieldMatchImpl implements ConstraintValidator {

private String firstFieldName;
private String secondFieldName;

@Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}

@Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
try {
final Object firstObj = value.getClass().getMethod("get" + WordUtils.capitalize(firstFieldName)).invoke(value);
final Object secondObj = value.getClass().getMethod("get" + WordUtils.capitalize(secondFieldName)).invoke(value);
boolean isValid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);

if(!isValid) { //Custom constraint violation
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("password fields have to match").addNode("password").addConstraintViolation();
}
return isValid;
}
catch (final NoSuchMethodException ignore) {//TODO: log error}
catch (final InvocationTargetException ignore) {//TODO: log error}
catch (final IllegalAccessException ignore) {//TODO: log error}

return true;
}
}


Lássuk mi is történik az implementációs osztályban. Miután az inicializálás során megszerezzük a két mező nevét, a törzs metódusban reflection segítségével elvégezzük az előírt ellenőrzést. A példa az érdekesség kedvéért bemutatja a //Custom constaint violation által kommentezett szekcióban, hogy miként írhatjuk felül az annotáció default (interfészben definiált) viselkedését, saját hibaüzenettel például. Persze itt komolyabb, szerteágazóbb funkcionalitást is implementálhatunk, amíg azt az API lehetővé teszi.

Végül pedig lássuk hogyan változik az entitás osztályunk ezek fényében:

@Entity
@Table(name="user")
@FieldMatch.List({
@FieldMatch(first = "password", second = "confirmPassword", message = "passwordFieldsMustMatch")
})
public class User extends CustomValidableEntity implements Serializable {

}

A @FieldMatch.List mutatja a már említett önhivatkozást, ahol a listában további FieldMatch példányokat tart(hat)unk nyilván vesszővel elválasztva. Természetesen az itt felsorolt követelmények egymástól függetlenül lesznek kiértékelve a keretrendszer által, egymás viselkedését, eredményét nem befolyásolják.