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 thisJoinPoint) throws 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 = ((MethodSignature) sig).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 == 0 || 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 = (UIComponent) i.next();
66 if (c instanceof UIInput) {
67 final UIInput inp = (UIInput) c;
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
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
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: Dia a Dia, dia-dia, Java, Trabalho
Particularmente, nao gosto do AspectJ por ter sintaxe propria e compilador especifico. Sempre que preciso AOP, vou para o JBoss…
Reply to this commentQuanto 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.
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
Reply to this commentPS.: o Merlim parece muito legal, eu estava procurando algo parecido a pouco tempo atraz
vou baixar do CVS para dar uma olhada
Reply to this comment[...] 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 commentMas o projeto do Merlin parece ter morrido…
Reply to this commentQue outra solução posso estar adotando?
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