Implementation detailssection
Introduction
As we reviewed in a previous blog with JSF 2 and RichFaces 4 you already have the Bean Validation support all the way to the browser with our client side validation. So you can effectively work with your server side object without any additional view level code related to validation.
This blog is going to review the last (but not least) validation feature which RichFaces 4 adds to JSF 2 . It's called Object Validation and provides a way to validate all your model Object fields considering even ones which were not used in the current view.
Why do you need that? The very first example which came to my mind as it appears frequently on various forums is dependent fields validation.
Password Validation Example
Let's check the password validation example:
<h:form> <rich:panel header="Change password" style="width:500px"> <h:panelGrid columns="3"> <h:outputText value="Enter new password:" /> <h:inputSecret value="#{userBean.password}" id="pass"/> <rich:message for="pass"/> <h:outputText value="Confirm the new password:" /> <h:inputSecret value="#{userBean.confirm}" id="conf"/> <rich:message for="conf"/> </h:panelGrid> <a4j:commandButton value="Store changes" action="#{userBean.storeNewPassword}" /> </rich:panel> </h:form>
And the simple managed bean source:
@ManagedBean @RequestScoped public class UserBean { @Size(min = 5, max = 15, message="Wrong size for password") private String password; @Size(min = 5, max = 15, message="Wrong size for confirmation") private String confirm; //... public void storeNewPassword(){ FacesContext.getCurrentInstance().addMessage("", new FacesMessage(FacesMessage.SEVERITY_INFO, "Succesfully changed!", "Succesfully changed!")); } //Getters and setters }
So we solved half of the problem:
We are seeing the error messages if the password fields do not satisfy the validation rules given. Nothing is checking if passwords are the same though, just that they are independently valid. So what is the easiest way to validate if the fields are equals? We could perform that in action but that is quite late in the JSF lifecycle for validation. We could use some solutions using f:attribute in order to pass dependent field id to the other input and find the component by that id in custom validate method. There are also some other ways which are pretty easy to find across different knowledge-bases'. But most of them are either invoked in an improper phase (like action at INVOKE_APPLICATION) or has possible maintenance problems(f:attribute solution requires us to be patient with id's changes in view as validation depends on them).
RichFaces 4 Object Validation for cross-field validation
This is where RichFaces 4 Object Validation feature come in play! Here is what we propose to use:
<h:form> <rich:graphValidator value="#{userBean}" id="gv"> <rich:panel header="Change password" style="width:500px"> <rich:messages globalOnly="true"/> <rich:messages for="gv"/> <h:panelGrid columns="3"> <h:outputText value="Enter new password:" /> <h:inputSecret value="#{userBean.password}" id="pass"/> <rich:message for="pass"/> <h:outputText value="Confirm the new password:" /> <h:inputSecret value="#{userBean.confirm}" id="conf"/> <rich:message for="conf"/> </h:panelGrid> <a4j:commandButton value="Store changes" action="#{userBean.storeNewPassword}" /> </rich:panel> </rich:graphValidator> </h:form>
And the Bean(Updated with implements Cloneable):
@ManagedBean @RequestScoped public class UserBean implements Cloneable{ @Size(min = 5, max = 15, message="Wrong size for password") private String password=""; @Size(min = 5, max = 15, message="Wrong size for confirmation") private String confirm=""; @AssertTrue(message = "Different passwords entered!") public boolean isPasswordsEquals() { return password.equals(confirm); } public void storeNewPassword(){ FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Succesfully changed!", "Succesfully changed!")); }
So lets look through the sample details. rich:graphValidator tag should wrap the inputs bound to the object which we are interested in validating. The value attribute should be pointed to that bean name. Then at PROCESS_VALIDATION phase all the object fields will be validated according to constraints added. And in that particular case isPassWordEquals will be validated using AssertTrueValidator. That looks just like we expected!
Different passwords entered(but valid according to size):
Verification successful:
Implementation Details
UPDATED(28.02) section below. Actually 3.3.x used the same way. Was written that it worked at model updates by mistake.Just one question appears. Those who know the JSF life-cycle will ask immediately about how the bean could be validated before the model updates? Does RichFaces propose to use validation after process updates phase? No. We perform a clone() on your bean instance at validation phase and updating new instance with all the new values submitted(that's why we still need to wrap all the inputs related to rich:graphValidator). We then validate the cloned object firing validation messages if needed. Your model objects remains clean, lifecycle gets interrupted properly after Process Validations phase and messages encoded to client during Render Response.
NOTE: if cloneNotSupportedException thrown from the object – validation will be done at model updates. And that is the only exception to our architecture which really can't be workaround-ed.
Try it out!
Now having JSF 2 Bean Validation support standardized, RichFaces 4 Client Side Validation and Object Validation features we believe you will be able completely concentrate on your business tasks and will not spend time for any additional JSF development in the validation phase. Try our new features in RichFaces 4.0.0.M6 and let us know about any problems or questions appeared. That will help us greatly to provide those features stable and well documented prior to RichFaces 4 Final release!