Skip to main content

AspectJ + Hibernate-Validator + JSF = Very easy to use validation for your web apps!

Posted by urubatan on September 27, 2006 at 2:05 PM PDT

Brasilian portuguese version here.



I have found today at JavaPlanet a news from JavaFree talking about another blogs`s post about how to write a Spring Validator to use custom annotations to make validation happens within springMVC ...

It is really easy and usefull, but there is already Hibernate-Validator ...

So why do I have to create another hand full of annotations? Hibernate-Validatos has already support for me to write my own validations, but using it we can use all the out-of-the-box ready validations provided ...

Besides, some of the provided validations, in case you are using Hibernate for persistence, are propagated to the database too (like max length, not null, ...).



Thinking about that, I have wrote an AspectJ aspect to enable the use of Hibernate-Validator as the validation engine for JSF applications ...

I know it is not the best solution, because this validation should be fired at the Validation Phase, and not as an aspect in the execte application phase, but the best I could do, at least for now, is call the validation right before the action method is called ...



This code bellow is part of the Spring-Annotation project, but with some minor modifications it can be used without the rest of the project, if you want it ...

The main idea is to enable the use of hibernate validator annotations in the MBean code, and if you want a property that references another bean to be validated, just anotate it with @Valid



This is an example MBean to be validated using this annotations (the other annotations are from the Spring-Annotation project).

01 

02 @Bean(name = "clientPlanM")

03 @RoleNeeded("super")

04 @ManagedBean

05 public class ClientPlanManager {

06     @Valid

07     @Value("#{clientPlan}")

08     @Out

09     @DataModelSelection

10     private ClientPlan clientPlan;

11     @DataModel(factory = "#{clientPlanM.list}")

12     private List clientPlans;

13     private ClientePlanDao clientePlanDao;

14     private List clientes;

15     private List plans;

16     private ClienteDao clienteDao;

17     private PlanDao planDao;

18 

19     public void setClienteDao(final ClienteDao clienteDao) {

20         this.clienteDao = clienteDao;

21     }

22 

23     public void setPlanDao(final PlanDao planDao) {

24         this.planDao = planDao;

25     }

26 

27     public List getClientes() {

28         if (clientes == null) {

29             clientes = new ArrayList();

30             for (final MailingCliente m : clienteDao.listAll()) {

31                 clientes.add(new SelectItem(m, m.getNome()));

32             }

33         }

34         return clientes;

35     }

36 

37     public List getPlans() {

38         if (plans == null) {

39             plans = new ArrayList();

40             for (final Plan m : planDao.listAll()) {

41                 plans.add(new SelectItem(m, m.getName()));

42             }

43         }

44         return plans;

45     }

46 

47     public ClientPlan getClientPlan() {

48         return clientPlan;

49     }

50 

51     public void setClientPlan(final ClientPlan clientPlan) {

52         this.clientPlan = clientPlan;

53     }

54 

55     public List getClientPlans() {

56         return clientPlans;

57     }

58 

59     public void setClientPlans(final List clientPlans) {

60         this.clientPlans = clientPlans;

61     }

62 

63     public void setClientePlanDao(final ClientePlanDao clientePlanDao) {

64         this.clientePlanDao = clientePlanDao;

65     }

66 

67     public String create() {

68         clientPlan = new ClientPlan();

69         return "def:form";

70     }

71 

72     @IfInvalid

73     public String save() {

74         if (clientPlan.getId() == null) {

75             clientePlanDao.save(clientPlan);

76         else {

77             clientePlanDao.update(clientPlan);

78         }

79         return list();

80     }

81 

82     public String delete() {

83         clientePlanDao.delete(clientPlan);

84         return list();

85     }

86 

87     public String list() {

88         clientPlans = clientePlanDao.listAll();

89         return "def:list";

90     }

91 }




the aspect bellow being part of Spring-Annotation, will be fired only in the following conditions:

the MBean must be annotated with @ManagedBean, this is the flag for spring-annotation to know that this class is a ManagedBean.

the MBean must be annotated with the wanted validation annotations ...

and the action method must be annotated with @IfInvalid, with an optional parameter telling wish page to show after a validation failure, when this parameter is not present the last page is shown again ...

In the above example, just the method "salvar" needs validation, in that way, only this method has the @Invalid annotation  ...



this is the main idea, but I have found some problems implementing it.

the first one was that not all properties of one bean are allways in the screen, and the only solution I have found to it was:

lots of configuration of course.



....



of course not, it was to validate only the properties that have a value binding in the current submited page.



Well, lets stop talking and start coding:markus@jave.de = -->



01 package net.java.dev.springannotation.aop;

02 

03 import java.lang.reflect.Method;

04 import java.util.Iterator;

05 import javax.faces.application.FacesMessage;

06 import javax.faces.component.UIComponent;

07 import javax.faces.component.UIInput;

08 import javax.faces.context.FacesContext;

09 import javax.faces.el.ValueBinding;

10 import net.java.dev.springannotation.annotation.Bean;

11 import net.java.dev.springannotation.annotation.IfInvalid;

12 import org.aspectj.lang.ProceedingJoinPoint;

13 import org.aspectj.lang.Signature;

14 import org.aspectj.lang.annotation.Around;

15 import org.aspectj.lang.annotation.Aspect;

16 import org.aspectj.lang.annotation.Pointcut;

17 import org.aspectj.lang.reflect.MethodSignature;

18 import org.hibernate.validator.ClassValidator;

19 import org.hibernate.validator.InvalidValue;

20 

21 @Aspect

22 @Bean

23 public class ValidationInterceptor {

24     @Pointcut("@target(net.java.dev.springannotation.annotation.ManagedBean)")

25     public void inAManagedBean(){}

26 

27     @Pointcut("execution(@net.java.dev.springannotation.annotation.IfInvalid public String *.*())")

28     public void needValidation(){}

29     

30     @SuppressWarnings("unchecked")

31     @Around("inAManagedBean() && needValidation()")

32     public Object actionNeedingValidation(final ProceedingJoinPoint thisJoinPointthrows Throwable {

33         final ClassValidator cv = new ClassValidator(thisJoinPoint.getTarget().getClass());

34         final InvalidValue[] invalidmessages = cv.getInvalidValues(thisJoinPoint.getTarget());

35         int unresolvedCount = 0;

36         if ((invalidmessages != null&& (invalidmessages.length > 0)) {

37             final Signature sig = thisJoinPoint.getSignature();

38             IfInvalid ann = null;

39             final String beanName = findName(thisJoinPoint.getTarget());

40             final FacesContext fc = FacesContext.getCurrentInstance();

41             if (sig instanceof MethodSignature) {

42                 final Method method = ((MethodSignaturesig).getMethod();

43                 ann = method.getAnnotation(IfInvalid.class);

44             }

45             for (final InvalidValue iv : invalidmessages) {

46                 final String message = iv.getMessage();

47                 final String clientId = findClientId(fc, fc.getViewRoot(), beanName, iv.getPropertyName());

48                 if (clientId != null) {

49                     fc.addMessage(clientId, new FacesMessage(message));

50                 else

51                     unresolvedCount++;

52             }

53             if (unresolvedCount == || unresolvedCount < invalidmessages.length)

54                 return (ann == null|| "".equals(ann.outcome()) null : ann.outcome();

55             else

56                 return thisJoinPoint.proceed();

57         else {

58             return thisJoinPoint.proceed();

59         }

60     }

61 

62     private String findClientId(final FacesContext ctx, final UIComponent viewRoot, final String beanName, final String propertyPath) {

63         final Iterator i = viewRoot.getFacetsAndChildren();

64         while (i.hasNext()) {

65             final UIComponent c = (UIComponenti.next();

66             if (instanceof UIInput) {

67                 final UIInput inp = (UIInputc;

68                 final ValueBinding vb = inp.getValueBinding("value");

69                 if (vb != null) {

70                     if (("#{" + beanName + "." + propertyPath + "}").equals(vb.getExpressionString())) {

71                         return inp.getClientId(ctx);

72                     }

73                 }

74             else {

75                 final String val = findClientId(ctx, c, beanName, propertyPath);

76                 if (val != null) {

77                     return val;

78                 }

79             }

80         }

81         return null;

82     }

83 

84     private String findName(final Object target) {

85         final Bean b = target.getClass().getAnnotation(Bean.class);

86         if (b != null) {

87             return b.name();

88         }

89         /*

90          * final Name n = target.getClass().getAnnotation(Name.class); if (n != null) { return n.value(); }

91          */

92         return null;

93     }

94 }




This aspect can be used in two ways:

first you can use the ajc command to compile your code (This is the AspectJ compiler)

or seccond, whish is what I really use, you can use the AspectJ support of Spring Framework, it does not supports all the AspectJ features, but it is more than I need to be happy :D



to enable this just edit your applicationContext.xml and put the following code:


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:aop="http://www.springframework.org/schema/aop"

    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"

    default-autowire="byName">

   

&t;/beans>





I hoppe this code to be usefull for some one else, any ideas to make it bether just let me know :D



PS.: just orgot to comment, this code shows the Hibernate Validator messages in the JSF tags and/or




Related Topics >>