Preciso criar uma anotação customizada de validação, usando bean validation e tal, onde vou precisar fazer uma consulta no banco para fazer a validação.
Bom pra que a bagaça funcionasse com o Vraptor pensei em sobrescrever o objeto que cria o ValidatorFactory (ValidatorFactoryCreator). Fiz essa classe:
@Component
@ApplicationScoped
public class CustomValidatorFactoryCreator extends ValidatorFactoryCreator {
private static final Logger logger = LoggerFactory.getLogger(ValidatorFactoryCreator.class);
private final ApplicationContext context;
private ValidatorFactory factory;
public CustomValidatorFactoryCreator(ApplicationContext context){
this.context = context;
}
@Override
public void buildFactory() {
final Configuration<?> cfg = Validation.byDefaultProvider().configure();
factory = cfg.traversableResolver(new JSR303TraversableResolver())
.constraintValidatorFactory(new SpringConstraintValidatorFactory(context.getAutowireCapableBeanFactory())) //esse SpringConstraintValidatorFactory vai criar as instancias dos validators a partir do contexto
.buildValidatorFactory();
logger.debug("Initializing JSR303 factory for bean validation");
}
public ValidatorFactory getInstance() {
return factory;
}
}
porém, na inicialização…
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.validation.ValidatorFactory] is defined: expected single matching bean but found 2: [br.com.caelum.vraptor.validator.ValidatorFactoryCreator, br.com.meuprojeto.validator.CustomValidatorFactoryCreator]
Agora os dois beans estão no contexto, o que nao é o que eu queria…será que não é possível sobrescrever o ValidatorFactoryCreator?
Acho que me expressei mal. Pra criar uma anotação de validação customizada realmente nao é preciso sobrescrever essa classe, como voce disse basta criar a anotação e a implementação.
Mas o que eu preciso é injetar um bean do Spring dentro dessa implementação. Esse codigo com o Spring seria um “workaround” pra fazer a classe da validação ser um bean do Spring e assim poderia injetar coisas lá dentro.
Vou testar a anotação que citou, as anotações de escopo do Spring tem o mesmo efeito que usar as do VRaptor?
poxa, tambem tentei sobrescrever o JSR303ValidatorFactory, e o mesmo problema ocorreu…teria que ser em algum desses lugares pra eu conseguir customizar o ConstraintValidatorFactory… mais algum coelho na cartola ai Lucas? :lol:
Oi lucas, tentei fazer isso tambem mas o problema permaneceu, acho que o problema está no spring que se perde depois com as duas factories…bom acabei resolvendo de uma outra forma, incluindo a anotação @Configurable na classe da validação, aí o spring consegue injetar as dependencias anotadas no momento da criação da instancia (usando aspectos). Até encontrar alguma outra solução vou manter assim.
De qualquer forma acho que seria legal voces avaliarem pra fazer isso funcionar com o VRaptor (ou talvez implementar algo diferente), para que a gente possa criar validators com dependencias. A solução até já existe no spring mas nao tá funcionando com o Vraptor, como eu relatei acima.
Estou reativando o tópico por estar enfrentando um problema similar.
Estou usando o VRaptor 3.5.3 + Guice, pelo que vi, esta versão já contempla a funcionalidade identificada a partir desse tópico. No entanto, não estou conseguindo criar uma anotação customizada em que tenho a necessidade de injetar um componente, mais especificamente, um EntityManager.
Peço a ajuda de vocês para tentar solucionar o problema.
Obrigado.
Segue a implementação da minha constraint e a exceção apresentada:
@Component
public class UnicoValidador implements ConstraintValidator<Unico, EntidadeUnicidade<String>> {
private EntityManager entityManager;
private String campo;
public UnicoValidador(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public void initialize(Unico constraintAnnotation) {
this.campo = constraintAnnotation.campo();
}
@Override
public boolean isValid(EntidadeUnicidade<String> entidade, ConstraintValidatorContext context) {
Long id = entidade.getId();
Object campoUnidade = entidade.getCampoUnidade();
String nomeClasse = entidade.getClass().getSimpleName();
Query query = entityManager.createQuery("select 1 from " + nomeClasse + " e where e." + campo + " = :value and id != :id");
query.setParameter("value", campoUnidade);
query.setParameter("id", id);
return query.getResultList().isEmpty();
}
}
br.com.caelum.vraptor.interceptor.ApplicationLogicException: your controller raised an exception
at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:96)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:61)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.ParametersInstantiatorInterceptor.intercept(ParametersInstantiatorInterceptor.java:96)
at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.ExceptionHandlerInterceptor.intercept(ExceptionHandlerInterceptor.java:67)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.FlashInterceptor.intercept(FlashInterceptor.java:83)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.InstantiateInterceptor.intercept(InstantiateInterceptor.java:48)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:56)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.logique.lsvraptorarq.interceptador.NormalizaParametroInterceptador.intercept(NormalizaParametroInterceptador.java:50)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.logique.lsvraptorarq.interceptador.ExcecaoInterceptador.intercept(ExcecaoInterceptador.java:36)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.logique.lsvraptorarq.interceptador.TransacaoInterceptador.intercept(TransacaoInterceptador.java:30)
at br.com.caelum.vraptor.core.LazyInterceptorHandler.execute(LazyInterceptorHandler.java:59)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.interceptor.ResourceLookupInterceptor.intercept(ResourceLookupInterceptor.java:69)
at br.com.caelum.vraptor.core.ToInstantiateInterceptorHandler.execute(ToInstantiateInterceptorHandler.java:54)
at br.com.caelum.vraptor.core.DefaultInterceptorStack.next(DefaultInterceptorStack.java:54)
at br.com.caelum.vraptor.core.EnhancedRequestExecution.execute(EnhancedRequestExecution.java:44)
at br.com.caelum.vraptor.VRaptor$1.insideRequest(VRaptor.java:93)
at br.com.caelum.vraptor.ioc.guice.GuiceProvider.provideForRequest(GuiceProvider.java:82)
at br.com.caelum.vraptor.VRaptor.doFilter(VRaptor.java:99)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: class br.com.logique.lsvraptorarq.anotacao.validador.UnicoValidador.
at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:51)
at org.hibernate.validator.internal.util.ReflectionHelper.newInstance(ReflectionHelper.java:152)
at org.hibernate.validator.internal.engine.ConstraintValidatorFactoryImpl.getInstance(ConstraintValidatorFactoryImpl.java:33)
at org.hibernate.validator.internal.engine.ConstraintTree.createAndInitializeValidator(ConstraintTree.java:363)
at org.hibernate.validator.internal.engine.ConstraintTree.getInitializedValidator(ConstraintTree.java:351)
at org.hibernate.validator.internal.engine.ConstraintTree.validateConstraints(ConstraintTree.java:171)
at org.hibernate.validator.internal.engine.ConstraintTree.validateConstraints(ConstraintTree.java:124)
at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:86)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraint(ValidatorImpl.java:442)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:387)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:351)
at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:303)
at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:133)
at br.com.caelum.vraptor.validator.DefaultBeanValidator.validate(DefaultBeanValidator.java:69)
at br.com.caelum.vraptor.validator.DefaultValidator.validate(DefaultValidator.java:86)
at br.com.logique.lsvraptorarq.controlador.CRUDControlador.salvar(CRUDControlador.java:38)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:61)
... 50 more
Caused by: java.lang.InstantiationException: br.com.logique.lsvraptorarq.anotacao.validador.UnicoValidador
at java.lang.Class.newInstance(Unknown Source)
at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:48)
... 70 more
Essa classe é instanciada pelo Hibernate, e não pelo VRaptor, então a injeção de dependências não vai funcionar. E o erro é pq a classe não possui um construtor sem argumentos.
No VRaptor 4 isso tem uma chance de funcionar por causa do CDI e o ConstraintValidator fazer parte do Bean Validations do java. Você pode migrar pro 4?
Eu já tinha percebido o problema na tentativa de injeção pelo construtor, porém, pensei que desse para contornar criando algum ComponentFactory específico ou sobrescrevendo a Factory do ConstraintValidator.
Em relação a migração, eu já estava pensando em atualizar o projeto para a versão 4 do VRaptor, esse foi o empurrão final kkkkk.
Mais uma vez, obrigado.
Após migrar para a versão 4 do VRaptor continuei tendo o mesmo problema.
Ocorrem duas chamadas ao meu ConstraintValidator durante uma transação: uma durante a execução do método validate do Validator e outra durante a execução do persist do EntityManager. O que é intrigante é que na primeira a injeção ocorre normalmente, já na segunda os valores injetados sempre ficam nulos.
A segunda chamada ocorre devido aos eventos pre-persist e pre-update disparados pelo JPA. Eu consigo desabilitar esses eventos no persistence.xml, porém não acredito que esse seja a melhor solução.