Friday, January 7, 2011

Validation in JBoss Seam


Validation is one of the first things you test out when evaluating a web framework and its often one of the most time consuming to wrap your head around. In this post I’ll talk about three types of validation mechanisms present in JBoss’ Seam which will get you on your way towards validating web forms without too much pain:
I’m going to cover three cases:
  1. Validating required fields without using annotations
  2. Validating manually and displaying custom messages
  3. Validating fields using Hibernate annotations in Seam beans
We’ll also display messages using a simple resource bundle (java.util.ResourceBundle) which will allow us to internationalize our forms and messages. Let’s dive into the examples:

Validating required fields without using annotations

This is the most general case, you want to force the user to input something in your text fields and if they don’t, you want to display a message telling them just that.
Here’s a JSF form which asks for a username and password:

01<h:messages/>
02<h:form id="LoginForm">
03    <table>
04    <tr>
05        <td><h:outputText value="#{messages['login.username']}"/></td>
06        <td>
07        <h:inputText required="true" value="#{user.username}" label="#{messages['login.username']}"/>
08        </td>
09    </tr>
10    <tr>
11        <td><h:outputText value="#{messages['login.password']}"/></td>
12        <td>
13        <h:inputSecret required="true" value="#{user.password}" label="#{messages['login.password']}"/>
14        </td>
15    </tr>
16    <tr>
17        <td></td>
18        <td>
19        <h:commandButton action="#{loginAction.login}"
20                 value="#{messages['button.login']}"/>
21        </td>
22    </tr>
23    </table>
24</h:form>
Specifying required=”true” is all you need to validate required fields. The matter of displaying messages is left to the <h:messages/> tag which iterates through the list of messages generated by the validation phase and displays them sequentially.
By default validation messages are displayed in a format that is not very intuitive, in order to change the format you must override a property in your messages.properties file located at the root of your classpath:
javax.faces.component.UIInput.REQUIRED={0} is a required field.
The placeholder is {0} is automatically filled by the label attribute of the <h:inputText> tag. So in the above case if login.username is defined to be “Username” in the resource bundle, the messages that will be printed will be: Username is a required field.
If validation passes, then the login() method on the bean referenced by loginAction will be called (as specified by the <h:commandButton> tag.

Validating manually and displaying custom messages

This is probably the most powerful way of validating user input – programmatically. Let’s face it, the built-in validators can only do so much, at some point you end up writing java code to validate input by checking it against a database or queue etc. Going back to the login example, say we need to check the values supplied by the user against a database and if they match, let them into the system and if they don’t, kick them back to the login page with a message. The login page does not change, let’s get to the LoginAction class:

01
@Name( "loginAction" )
02public class LoginAction {
03  
04    @In
05    private User user;
06  
07    @In(create=true)
08    private FacesMessages facesMessages;
09  
10    public String login() {
11        if (user.getUsername().equals( "Arsenalist" ) && user.getPassword().equals( "Raptors" )) {
12            return "success";
13        } else {
14            facesMessages.addFromResourceBundle("login.failed");
15            return "failure";
16        }
17    }
18}

It’s very obvious what’s happening here. The injected instance of FacesMessages allows you to use its addFromResourceBundle() method to specify a message that will be displayed in the view resulting from returning “failure”. Just for completeness sakes, here’s the faces-config.xml navigation rule:

01
<navigation-rule>
02    <from-view-id>/pages/login.xhtml</from-view-id>
03    <navigation-case>
04        <from-action>#{loginAction.login}</from-action>
05        <from-outcome>success</from-outcome>
06        <to-view-id>/pages/home.xhtml</to-view-id>
07    </navigation-case>
08    <navigation-case>
09        <from-action>#{loginAction.login}</from-action>
10        <from-outcome>failure</from-outcome>
11        <to-view-id>/pages/login.xhtml</to-view-id>
12    </navigation-case>
13</navigation-rule>

Validating fields using Hibernate annotations in Seam beans

The next step is to apply more complex validation without resorting to writing Java code and for that Seam relies on Hibernate’s validation framework. As an aside, this dependency is probably the most annoying thing about Seam at this point but it works well enough to look past.
Let’s say we need to force the value of username to be atleast 5 characters. In order to do this we need to annotate two things:
  1. The injected user variable in LoginAction so Seam knows to validate it
  2. The User objects properties to specify what to validate.Here’s the relevant piece in LoginAction.java:
    1. 1@Name( "loginAction" )
      2public class LoginAction {
      3    @In @Valid
      4    private User user;
      5    public String login() {
      6        . . . .
      7    }
       
      Here’s the relevant piece in User.java:
      01
      @Name( "user" )
      02public class User {
      03  
      04    private String username;
      05    private String password;
      06  
      07    @Length(min=5, message= "#{messages['login.username.length']}" )
      08    public String getUsername() {
      09        return username;
      10    }
      11    . . . .
       
      Notice how we’re using a resource key inside an annotation to print the message. This will look for a resource bundle called messages and inside it for the login.username.length property to retrieve the message. You could alternately just write the message in plain old English inside the annotation but why would you want to when you can internationalize. The last thing we need to do is tell the JSF page that it will need validation (I think this is a somewhat redundant step since the bean is already annotated but nonetheless it is required). You must wrap the fields of the login form around the <s:validateAll> tag like so:
      1
      <h:messages/>
      2<h:form id="LoginForm">
      3    <s:validateAll>
      4            . . . .
      5        <h:outputText value="#{messages['login.username']}"/></td>
      6            . . . .
      7    </s:validateAll>
      8</h:form>
       
      Note: Be sure to specify required=”true” for the fields you want to apply annotation validations to. Otherwise, you’ll see some very weird results such as only non-blank fields getting the annotation validations applied to while the blank fields get considered valid. Here’s a list of annotations that you can use to validate java beans:
      • @Length(min=, max=) Checks whether the string length matches the range
      • @Max(value=) Checks that the value is less than or equal to the max
      • @Min(value=) Checks that the value is greater than or equal to the min
      • @NotNull Checks that the value is not null
      • @Past For a date object, checks that the date is in the past
      • @Future For a date object, checks that the date is in the future
      • @Pattern(regex=”regexp”, flag=) For a string, checks that the string matches this pattern
      • @Range(min=, max=) Checks whether the value is between the min and the max
      • @Size(min=, max=) For collections, checks that the size is between the two
      • @AssertFalse Asserts that the evaluation of the method is false
      • @AssertTrue Asserts that the evaluation of the method is true
      • @Valid For a collection or a map, checks that all the objects they contain are valid
      • @Email Checks whether the string conforms to the email address specification
      That’s all there is to it.

1 comment:

  1. 1xbet korean | Sports betting
    1xbet korean sports betting online for a limited time only, affiliation 1xbet we guarantee you that you can enjoy our games for a long time without any problems. Bet on the 1xbet

    ReplyDelete