Using hibernate validation, we can decouple the validation rules and at the same time make it reusable for future projects. For example we want to validated if the username given already exist on the database and usually we just put our validation logics on the session bean or action (in struts). At the start of the project this looks ok but as the project progress and some new specification changes, new functionalities are added and some parts of your code gets refactored things get a bit messier.

Instead of simply dumping all the validation logic on the session bean we can create a custom hibernate validator which later can be used by annotating the getter of the entity property. Below is my code to validate if the username if it already exist on my database.

Username.java - The validator annotation definition

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ValidatorClass(UsernameValidator.class)
public @interface Username {
String message() default "Username already exists";
}


UserValidator.java - The validator class implementation

public class UsernameValidator implements Validator, Serializable{

private static final long serialVersionUID = -1458203631809206211L;

public void initialize(Username parameters) {
}

public boolean isValid(Object value) {
UserHome userHome = (UserHome) Component.getInstance("userHome");
User user = userHome.findUser((String)value);

// return true if no user with the given value was found
if(user == null) {
return true;
}

// return true if the user retrieved is equals to the current instance in home
if(user.getId() == userHome.getInstance().getId()) {
return true;
}

// return false if the username exist on our database
return false;
}

}
User.java - and finally, the entity class

public class User extends BaseEntity {

private static final long serialVersionUID = 6148649482941380604L;
private String username;

@Length(min=3, max=32, message="Username should be 3-32 characters")
@NotNull
@Username
public String getUsername() {
return username;
}
}
The validation code above simply tries to retrieve the user from database. If it returned null it means that no user with the given user name exist to it returns true (meaning the validation is successful). If a user with the given username is found, it checks if the user instance from userHome component is the same so that the validation will still be successful if we try to save it without modifying anything. Returning false will cause the validation process to return error message which can be later display by using in our xhtml.


References:

hibernate docs

hibernate validator source codes


6 comments:

Good example, that really helped me out. Any idea how to integrate this with the ValidatorMessages.properties resource bundle?

The Hibernate Validator documentation has some horrible, buggy, nonworking code that hints at how to do it, but I haven't figured it out yet.

http://www.hibernate.org/hib_docs/validator/reference/en/html/validator-defineconstraints.html#validator-defineconstraints-own

Let me know if you want to show off your smarts some more ;)

April 25, 2008 at 12:47 PM  

Hi I just tried to implement this. When I try to persist the entity with the annotation I keep getting an java.lang.reflect.InvocationTargetException This only happens when I query the db in the validator. Do you know why?

May 20, 2008 at 1:07 AM  

I got the same problem as Jakob

org.hibernate.AssertionFailure: null id in mdh.eldo.domain.model.identitymanagement.UserRequest entry (don't flush the Session after an exception occurs)
at org.hibernate.event.def.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:78)
at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:187)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:143)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1141)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
at org.hibernate.ejb.QueryImpl.getSingleResult(QueryImpl.java:88)
at mdh.eldo.domain.validator.ValidUsernameValidatorSeam.validate(ValidUsernameValidatorSeam.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:31)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56)
at org.jboss.seam.transaction.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:28)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.bpm.BusinessProcessInterceptor.aroundInvoke(BusinessProcessInterceptor.java:51)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.transaction.TransactionInterceptor$1.work(TransactionInterceptor.java:95)
at org.jboss.seam.util.Work.workInTransaction(Work.java:47)
at org.jboss.seam.transaction.TransactionInterceptor.aroundInvoke(TransactionInterceptor.java:89)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.core.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:44)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107)
at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:185)
at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:103)
at mdh.eldo.domain.validator.ValidUsernameValidatorSeam_$$_javassist_1.validate(ValidUsernameValidatorSeam_$$_javassist_1.java)
at mdh.eldo.domain.validator.ValidUsernameValidator.isValid(ValidUsernameValidator.java:21)
at org.hibernate.validator.ClassValidator.getInvalidValues(ClassValidator.java:386)
at org.hibernate.validator.ClassValidator.getInvalidValues(ClassValidator.java:352)
at org.hibernate.validator.event.ValidateEventListener.validateSubElements(ValidateEventListener.java:162)
at org.hibernate.validator.event.ValidateEventListener.validate(ValidateEventListener.java:138)
at org.hibernate.validator.event.ValidateEventListener.onPreInsert(ValidateEventListener.java:172)
at org.hibernate.action.EntityIdentityInsertAction.preInsert(EntityIdentityInsertAction.java:142)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:65)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
at org.jboss.seam.persistence.EntityManagerProxy.persist(EntityManagerProxy.java:137)
at org.hibernate.search.jpa.impl.FullTextEntityManagerImpl.persist(FullTextEntityManagerImpl.java:93)
at org.jboss.seam.persistence.EntityManagerProxy.persist(EntityManagerProxy.java:137)
at mdh.eldo.action.identitymanagemnt.RequestUserAction.requestNewUser(RequestUserAction.java:40)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.jboss.seam.util.Reflections.invoke(Reflections.java:22)
at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:31)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:56)
at org.jboss.seam.transaction.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:28)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.core.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:77)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.bpm.BusinessProcessInterceptor.aroundInvoke(BusinessProcessInterceptor.java:51)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.transaction.TransactionInterceptor$1.work(TransactionInterceptor.java:95)
at org.jboss.seam.util.Work.workInTransaction(Work.java:47)
at org.jboss.seam.transaction.TransactionInterceptor.aroundInvoke(TransactionInterceptor.java:89)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.core.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:44)
at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:68)
at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:107)
at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:185)
at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:103)
at mdh.eldo.action.identitymanagemnt.RequestUserAction_$$_javassist_2.requestNewUser(RequestUserAction_$$_javassist_2.java)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:329)
at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:342)
at org.jboss.el.parser.AstPropertySuffix.invoke(AstPropertySuffix.java:58)
at org.jboss.el.parser.AstValue.invoke(AstValue.java:96)
at org.jboss.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:276)
at org.richfaces.ui.application.StateMethodExpressionWrapper.invoke(StateMethodExpressionWrapper.java:59)
at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:68)
at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:88)
... 42 more

February 21, 2009 at 9:11 PM  

I found the issue. The default flush mode is AUTO and it must be set to Commit so that the flush occurs after the validator runs the query.

So before the em.persist(object)

do this:


em.setFlushMode(javax.persistence.FlushModeType.COMMIT);

February 22, 2009 at 7:16 PM  

explain please in more details:

if(user.getId() == userHome.getInstance().getId()) {
return true;
}

April 28, 2009 at 9:34 AM  

Hi @All!

First of all thank you for the very good example! Could you be so kind and post the folowing method, because dealing with Seam and hibernate is new for me!

.findUser((String)value)


Thanks in advance

Sebastian

June 1, 2009 at 2:31 AM  

Newer Post Older Post Home

Blogger Template by Blogcrowds