Sobrescrever ValidatorFactoryCreator do VRaptor

Olá amigos do fórum,

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.

Minha idéia era dar um jeito de injetar o meu repositorio dentro da classe da validação (que implementa ConstraintValidator). Como isso nao é possível de forma “nativa” eu dei uma olhada na abordagem do Spring, que usa um ConstraintValidatorFactory customizado para poder criar os validadores a partir do contexto do Spring, dessa forma dá pra injetar objetos do dominio neles :stuck_out_tongue: (mais detalhes aqui http://static.springsource.org/spring/docs/3.0.0.RC3/reference/html/ch05s07.html, e aqui http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/validation/beanvalidation/LocalValidatorFactoryBean.html) .

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?

Obrigado amigos!

tenta usar as anotações de scope do spring pra deixar esse cara como primary=true…

mas pra criar anotação customizada vc não deveria precisar sobrescrever essa classe… só criar a anotação e a implementação deveria ser suficiente.

Lucas, obrigado pela resposta,

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?

Valeu!

Xi…tô usando a anotação @Primary (no Spring 3 essa anotação equivale a primary=true no xml) e o mesmo erro persiste…

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:

Valeu!

não crie uma componentFactory… crie um FactoryBean do spring com @Primary, que deve funcionar.

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.

Valeu lucas, obrigado!

abre uma issue por favor:

Tá criado lá lucas, obrigado novamente!

thx =)

Olá amigos,

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?

Lucas, obrigado pela resposta.

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.

Lucas,

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.

Estou usando o hibernate-validator-cdi.

para a injeção funcionar para a jpa, é possível que vc tenha que registrar algo no persistence.xml…

a JPA também chama a validação antes do persist.

nunca fiz isso, mas talvez essa issue ajude: