Wednesday, 10 July 2013

ZK MVVM Form Binding CRUD with Spring and Hibernate - Part 13

More & More Validation using Hibernate Validator

In the previous article 12, We applied some validation constraints to our UserProfile class. Now we will add more constraints to tighten the user inputs as per some business logic.
 

The following constraints are available in the JSR-303 API

  • AssertFalse: The element must be false
  • AssertTrue: The element must be true
  • DecimalMax: The element must be a number and lower or equal to the specified value
  • DecimalMin: The element must be a number and greater or equal to the specified value
  • Digits: Must be a number within an accepted range
  • Future: Must be a date in the future
  • Max: Must be a number with a value lower or equal to the specified maximum
  • Min: Must be a number with a value greater or equal to the specified minimum
  • NotNull: Must not be null
  • Null: Must be null
  • Past: Must be a past date
  • Pattern: Must match a defined regular expression
  • Size: Must match defined boundaries. Valid for strings, collections, maps and arrays



Step 1:
We will add more constraints as follows

package zkexample.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.annotations.NamedQuery;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;

@Entity
@Table(name = "userprofile")
@NamedQuery(name = "UserProfile.findUserByUserID", query = "SELECT usr FROM UserProfile as usr WHERE usr.userLoginID = ?")
public class UserProfile implements Serializable {

/**
*
*/
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@NotBlank(message = "First Name cannot be empty")
@Size(min = 2, message = "First name is too small")
private String firstName;

@NotBlank(message = "Last Name cannot be empty")
@Length(min = 2, max = 50, message = "LastName should be 2 to 50 Characters size")
private String lastName;

private String middleName;

@NotBlank(message = "User Account Number cannot be empty")
@Pattern(regexp = "^[a-zA-Z]{2}-\\d+$", message = "Invalid Account Number Number. First two letter should be Alphabets and then one hyphen and then any digits. For example AA-333, BB-44")
private String userAccountNumber;

@Pattern(regexp = "[0-9]{3}-[0-9]{2}-[0-9]{4}", message = "Invalid SSN format")
private String SSN;

@NotBlank(message = "Address1 cannot be empty")
private String address1;

private String address2;

@NotBlank(message = "City cannot be empty")
private String city;

@NotBlank(message = "State cannot be empty")
private String State;

@NotBlank(message = "ZipCode cannot be empty")
private String zipCode;

@NotBlank(message = "Email cannot be empty")
@org.hibernate.validator.constraints.Email(message = "Invalid Email Format")
private String email;

@NotBlank(message = "Login Name cannot be empty")
private String userLoginID;

@NotBlank(message = "Password cannot be empty")
private String password;

private Integer system;

@NotBlank(message = "Theme cannot be empty")
private String theme;

@Column(name = "userPhoto")
@Lob
private byte[] userPhoto;

@Temporal(TemporalType.DATE)
@Past(message = "Date of birth should be past date")
private Date DOB;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getMiddleName() {
return middleName;
}

public void setMiddleName(String middleName) {
this.middleName = middleName;
}

public String getUserAccountNumber() {
return userAccountNumber;
}

public void setUserAccountNumber(String userAccountNumber) {
this.userAccountNumber = userAccountNumber;
}

public Date getDOB() {
return DOB;
}

public void setDOB(Date dOB) {
DOB = dOB;
}

public String getSSN() {
return SSN;
}

public void setSSN(String sSN) {
SSN = sSN;
}

public String getAddress1() {
return address1;
}

public void setAddress1(String address1) {
this.address1 = address1;
}

public String getAddress2() {
return address2;
}

public void setAddress2(String address2) {
this.address2 = address2;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getState() {
return State;
}

public void setState(String state) {
State = state;
}

public String getZipCode() {
return zipCode;
}

public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getUserLoginID() {
return userLoginID;
}

public void setUserLoginID(String userLoginID) {
this.userLoginID = userLoginID;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Integer getSystem() {
return system;
}

public void setSystem(Integer system) {
this.system = system;
}

public String getTheme() {
return theme;
}

public void setTheme(String theme) {
this.theme = theme;
}

public byte[] getUserPhoto() {
return userPhoto;
}

public void setUserPhoto(byte[] userPhoto) {
this.userPhoto = userPhoto;
}

}


image


Step 2:
We can also create our own custom constraints to validate our fields. For simple example of creating own custom constraints, please look this
post.

Now we will see how we can do cross field validation. For example, in the standard User registration form, there will be two fields for password; password and confirm password. Before submit, it make sense to validate that both the fields should contains the same value. Now we will implement the same in our project

First Let us create the validator Class. Under the package “utilities”, create a class called “FieldMatchValidator” as follows”


package zkexample.utilities;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.apache.commons.beanutils.BeanUtils;

public class FieldMatchValidator implements
ConstraintValidator<FieldMatch, Object> {

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 = BeanUtils
.getProperty(value, firstFieldName);
final Object secondObj = BeanUtils.getProperty(value,
secondFieldName);
return firstObj == null && secondObj == null || firstObj != null
&& firstObj.equals(secondObj);
} catch (final Exception ignore) {

}
return true;
}

}

Next we need to create our annotation based on the above validator class. Create an interface called “FieldMatch” under the package “utilities” as follows

package zkexample.utilities;


import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;

/**
* * Validation annotation to validate that 2 fields have the same value. * An
* array of fields and their matching confirmation fields can be supplied. * *
* Example, compare 1 pair of fields: * @FieldMatch(first = "password", second =
* "confirmPassword", message = "The password fields must match") * * Example,
* compare more than 1 pair of fields: * @FieldMatch.List({ * @FieldMatch(first =
* "password", second = "confirmPassword", message =
* "The password fields must match"), * @FieldMatch(first = "email", second =
* "confirmEmail", message = "The email fields must match")})
*/
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
public @interface FieldMatch {
String message() default "{constraints.fieldmatch}";

Class[] groups() default {};

Class[] payload() default {};

/** * @return The first field */
String first();

/** * @return The second field */
String second();

/**
* * Defines several @FieldMatch annotations on the same element * * @see
* FieldMatch
*/
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
FieldMatch[] value();
}
}


image

Now we are ready to use our own custom validator in the POJO Class.

Step 3:
Next we will modify our UserProfile.java to use the above validator. Before that, we need to add one more property with @Transient annotation as follows

@Transient
private String confirmPassword;

public String getConfirmPassword()
{
return confirmPassword;
}

public void setConfirmPassword(String confirmPassword)
{
this.confirmPassword = confirmPassword;
}


You can read more about @Transient annotation here.  Now we will add the above custom validation in the top of the class as follows

@Entity
@Table(name = "userprofile")
@NamedQuery(name = "UserProfile.findUserByUserID", query = "SELECT usr FROM UserProfile as usr WHERE usr.userLoginID = ?")
@FieldMatch.List({ @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"), })
public class UserProfile implements Serializable {


Step 4:
Next we need to change our UI to add this new field Confirm Password. Open UserCRUD.zul  and change the bottom part as follows

	<panel width="95%" sclass="sectionPanel">
<panelchildren>

<grid sclass="vgrid">
<columns>
<column width="20%"></column>
<column width="20%"></column>
<column width="20%"></column>
<column width="20%"></column>
</columns>
<rows>
<row>
<vlayout>
<hlayout>
<label value="Login ID"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="loginid" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.userLoginID)" />
</vlayout>
<vlayout>
<hlayout>
<label value="Password"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="password" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.password)" />
</vlayout>
<vlayout>
<hlayout>
<label value="Confirm Password"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="confirmpassword" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.confirmPassword)" />
</vlayout>
<vlayout>
<hlayout>
<label value="Theme"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<combobox model="@load(vm.themes)" hflex="1"
width="30%" mold="rounded" readonly="@load(vm.makeAsReadOnly)"
selectedItem="@bind(fx.theme)" value="@bind(fx.theme)" />
</vlayout>
</row>
</rows>
</grid>
</panelchildren>
</panel>


That’s all. Now you can run and see how all our validation part works.

You can download the source here.
Don’t forget to check online demo here. Use username as wing and password as wing
Resources
http://stackoverflow.com/questions/1972933/cross-field-validation-with-hibernate-validator-jsr-303
http://cloverink.net/cross-field-validation-with-hibernate-validator-jsr-303/
http://emrpms.blogspot.in/2012/06/annotate-non-persistent-properties-with.html
http://emrpms.blogspot.in/2012/08/hibernate-validator-examples.html

2 comments:

  1. My friend, thanks very much for your help and effort.
    But im still having trouble putting it together, could you make a link to download a complet .war?

    Sorry if asking to much.
    See you

    ReplyDelete
  2. Master, thanks for this great job.
    But i have a doubt, imagine im in a controller extends genericfowardcomposet

    Then i want to create 5 thread to make them run at same time, how can i extends or pass this sessionsfactory or beans? Ive tried SpringUtil.getBean("bean") but receive a message only possible in zk enviroment.

    Thanks for your help

    ReplyDelete