Blog do Urubatan
msgbartop
Desenvolvedor, Arquiteto, Palestrante, Coordenador do RSJUG, Patinador e Blogger
msgbarbottom

27 Sep 06 AspectJ + Hibernate-Validator + JSF = Validação muito fácil de ser implementada!

Vi agora pouco no JavaPlanet uma noticia do JavaFree que fala de um post em outro blog sobre como escrever um validador para o SpringMVC utilizando anotações proprias …
Realmente isto é muito fácil, mas ja existe o Hibernate-Annotations, que tem uma extensão para validações muito boa …
Então por que criar outro mais um monte de anotações para a minha aplicação? quer dizer, as extensões para validação do Hibernate-Annotations também permitem a criação de validações proprias, mas podemos tirar proveito de um monte delas ja prontas …
E alem disto, algumas validações, caso estejamos utilizando o hibernate para persistencia também serão propagadas para o banco de dados …

Bom, pensando nisto, eu resolvi este dilema implementando um aspecto que propaga a validação do Hibernate-Validator até a camada JSF da aplicação …
Eu sei que não é a solução perfeita, pois esta validação deveria ser realizada na fase de validação do ciclo de vida JSF, mas o melhor que eu consegui foi fazer isto logo antes da execução do método de Action ou ActionListener …

Este código que vou mostrar faz parte do Spring-Annotation, mas com pequenas alterações pode ser utilizado de forma independente sem problema nenhum.
A ideia é colocar as anotações do hibernate no código do MBean, e da mesma forma que no Hibernate-Validator se desejarmos que esta validação seja propagada para outras classes devemos anotar o componente desejado com @Valid.

este é um exemplo de um MBean que sera validado.






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<ClientPlan> clientPlans;
13     private ClientePlanDao clientePlanDao;
14     private List<SelectItem> clientes;
15     private List<SelectItem> 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<SelectItem> getClientes() {
28         if (clientes == null) {
29             clientes = new ArrayList<SelectItem>();
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<SelectItem> getPlans() {
38         if (plans == null) {
39             plans = new ArrayList<SelectItem>();
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<ClientPlan> getClientPlans() {
56         return clientPlans;
57     }
58 
59     public void setClientPlans(final List<ClientPlan> 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 }


Este aspecto por fazer parte do código do Spring-Annotation, vai ser executado apenas nas seguintes condições:
O MBean precisa estar anotado com @ManagedBean, esta é a flag para o spring-annotation saber que este é um ManagedBean.
O MBean precisa estar anotado com as regras de validação desejadas …
E o método que precisa das validações precisa estar anotado com @IfInvalid, com um parametro opcional de uma página que sera exibida caso ocorra algum erro de validação, caso o parametro não seja fornecido a página anterior sera exibida novamente …
No exemplo a cima, a penas o método salvar tera validação, pois os outros não necessitam desta feature …

A idéia é mais ou menos esta, mas encontrei alguns problemas implementando este aspecto.
O principal deles foi que normalmente os Beans tem todas as regras de validação anotadas corretamente, mas nem sempre temos todos os campos na tela …
Solução para isto?
Mais configuração é claro …

To brincando, simplesmente só estou considerando na validação os campos que estão na tela, ou seja, os campos para os quais existe um value binding.

Bom, vou parar de enrolação, o código para o aspecto esta a baixo.






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 }


Ele pode ser utilizado de duas formas:
primeiro compilando o seu código com o ajc (Compilador do AspectJ)
ou segundo, que é o que eu tenho feito, utilizando o suporte a AspectJ do eclipse, não é suporte a todas as features do AspectJ, mas mais do que suficiente para o que eu ja precisei até agora :D

para ativar isto, basta colocar o seguinte em um applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 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">
    <aop:aspectj-autoproxy />
</beans>

Espero que esta ideia possa ajudar alguem, e se algum de vocês tiverem ideias para melhorar o código é só me avisar :D

PS.: esqueci de comentar, mas este código, faz com que o controle de excessões funcione integrado com o controle  de excessões do JSF, ou seja, as mensagens de erro vão aparecer nos <h:message /> e/ou <h:messages />

Se você gostou deste post, lembre-se de assinar o RSS feed do blog, para ser notificado de novos posts!

Tags: , , ,

Reader's Comments

  1. |

    Particularmente, nao gosto do AspectJ por ter sintaxe propria e compilador especifico. Sempre que preciso AOP, vou para o JBoss…
    Quanto a propagar a validacao do model para a view, eu gosto disso. Na verdade a proposta no Merlin (http://merlin.dev.java.net) tambem eh assim, mas como nao dependo de um fluxo de paginas nele, a coisa fica mais simples.

    Reply to this comment
  2. |

    :D ninguem é obrigado a gostar do aspectJ, mas como deu pra ver no código acima, não usei a sintaxe propria dele, escrevi o aspecto em java com algumas anotações :D mas o esquema do compilador continua valendo sim, a não ser que você esteja utilizando o Spring Framework, ai neste caso, da pra usar um subset do spring framework, apenas colocando no applicationContext.xml a linha
    (se não estou enganado é isto, se não for é bem parecido :D )

    Mas também não é necessário compilar se quiser utilizar o loadtime weaving, que é o que o jboss faz, ai basta criar um META-INF/aop.xml e passar um parametro na inicialização da JVM que ele faz o weaving durante o load da classe :D

    Reply to this comment
  3. |

    PS.: o Merlim parece muito legal, eu estava procurando algo parecido a pouco tempo atraz :D vou baixar do CVS para dar uma olhada :D

    Reply to this comment
  4. |

    [...] O ContractJ é um conjunto de: anotações + aspectos + um plugin para o eclipse para facilitar o uso disto, mas pode ser utilizado em qualquer ambiente que tenha suporte ao AspectJ. [...]

    Reply to this comment
  5. |

    Mas o projeto do Merlin parece ter morrido…
    Que outra solução posso estar adotando?

    Reply to this comment
  6. |

    Olá, gostaria de saber se vc tem exemplos simples de Programação Orientada a Aspecto, como cadastro e consulta apenas usando Hibernate.. se tiver por favor me encaminhar via email, vanessa.lima@wealthsystems.com.br

    Reply to this comment

Leave a Comment