Bean Validation ist schon längst fester Bestandteil von JSF Projekten und sorgt dafür das Validierungs-Regeln unabhängig von JSF implementiert werden können. Eine Anforderung kommt dabei immer wieder auf: Feldübergreifende Validierung. Seit JSF 2.3 ist das nun möglich, hier ein kurzer Blick darauf und was mit “groups” möglich ist
Fast schon ein alter Hut, die Deklaration von Pflichtfeldern und Mindestlängen für String-Eingaben:
public class DemoModel { @NotNull @Size(min = 4) private String firstname; @NotNull @Size(min = 4) private String lastname; private boolean reciveNewsletter;
JSF wird diese Regeln automatisch in der Validation-Phase heranziehen, diese prüfen und bei Fehlern FacesMessage an den Benutzer weiter geben. Diese Validierung findet ganz automatisch statt und stellt den Default-Fall war.
Ein spannendes Features von Bean Validation sind die so genannten “groups”. Hier können Regeln definiert werden die nur in bestimmten Fällen validiert werden sollen (z.B. auf Basis eines Status oder wenn zugehörige Daten erfasst wurden). Solche Gruppen sind im Kern nur ein Marker-Interface welches dem entsprechenden Attribut der Bean Validation Regel übergeben wird:
public interface NewsletterReciver { } public class DemoModel { ... @Min(value = 18, groups = NewsletterReciver.class) @NotNull(groups = NewsletterReciver.class) private Integer age;
= die Validierung dieser Regeln erfolgt nur bei aktivierter Gruppe “NewsletterReciver”. Gruppen können dann bei der programmatischen Prüfung mittels javax.validation.Validator angegeben werden oder aber auch in die JSF-View integriert werden:
<h:panelGroup class="input"> <h:outputLabel value="Newsletter empfangen:" for="reciveNewsletter" /> <h:selectBooleanCheckbox id="reciveNewsletter" value="#{demoController.model.reciveNewsletter}"> <f:ajax event="change" render="age" /> </h:selectBooleanCheckbox> </h:panelGroup> <h:panelGroup class="input"> <h:outputLabel value="Alter:" for="age" /> <h:inputText id="age" value="#{demoController.model.age}"> <f:validateBean validationGroups="de.gedoplan.blog.jsf.validation.groups.NewsletterReciver" disabled="#{!demoController.model.reciveNewsletter}"/> </h:inputText> </h:panelGroup>
(in unserem Beispiel müssen alle Newsletter-Empfänger mindestens 18 Jahre alt sein)
In JSF 2.3 wurden die Möglichkeiten noch erweitert und ein neues Tag-Hinzugefügt welches es uns erlaubt eine Feld-übergreifende Validierung durch zu führen. Dazu sind folgende Schritte nötig.
1. Aktivierung WholeBeanValidation
<context-param> <param-name>javax.faces.validator.ENABLE_VALIDATE_WHOLE_BEAN</param-name> <param-value>true</param-value> </context-param>
web.xml
2. Eigenen Bean Validation Validator implementieren
@Constraint(validatedBy = ValidAddressValidator.class) @Target(value = {ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @interface ValidAddress { String message() default "Adresse unvollständig"; Class[] groups() default {}; Class[] payload() default {}; } @ApplicationScoped public class ValidAddressValidator implements ConstraintValidator { public boolean isValid(AddressModel value, ConstraintValidatorContext context) { Object[] attributes = new Object[]{value.getNumber(), value.getCity(), value.getStreet()}; return Stream.of(attributes).allMatch(Objects::isNull) || Stream.of(attributes).noneMatch(Objects::isNull) ; } }
(unser Beispiel validiert ein Adress-Objekt, es sollen entweder alle Felder gefüllt sein oder gar keins)
3. JSF View erweitern
<h:form> ... <f:validateWholeBean value="#{demoController.model.address}" /> </h:form>
Der entscheidenden Vorteil gegenüber einer “händischen” Prüfung innerhalb der Submit Methode (s. DemoController@GitHub ) besteht darin das die fehlerhaften Werte bei der Verwendung dieser JSF-Variante noch nicht ins Model übertragen wurden. Stattdessen erstellt JSF eine Kopie des zu prüfenden Objektes und überträgt die Werte erst dann wenn die Prüfung erfolgreich war.