Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a SpringConstraintValidatorFactory variant for use with validation.xml [SPR-13327] #17912

Closed
spring-projects-issues opened this issue Aug 6, 2015 · 14 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Aug 6, 2015

Fábio Carvalho opened SPR-13327 and commented

I have a RESTful application using:

  • RESTEasy 3.0.9 as JAX-RS implementation
  • Spring 4.1.7 as DI framework
  • Hibernate Validator 5.0.1 as Bean Validations 1.1 framework.

This application relies on the integration between RESTEasy and Spring, and it also has some specific custom constraint validators, which are supposed to be Spring beans, and where I would like to inject some other Spring beans. That is the goal I am trying to achieve, and, according to my findings after debugging a lot, that is not working well because of a bug in Spring class org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory.

In order to allow injection to a custom constraint validator, which is also supposed to be a Spring bean, I need to configure the Bean Validations engine to use a custom validator factory, and that can be done by configuration in the validation.xml file.
Under my Bean Validations configuration file validation.xml file I added the Spring constraint validator factory, according to Spring documentation, as seen below.

<constraint-validator-factory>org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory</constraint-validator-factory>

So, if I don't set the Spring constraint validator factory (as seen above), obviously the custom validator is not a Spring bean, so I get a NullPointerException when attempting to refer to a member that were supposed to be injected.

But, if I do set the Spring constraint validator factory, then my server fails to start with the following exception:

Caused by: javax.validation.ValidationException: HV000064: Unable to instantiate constraint factory class: class org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory.
	at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:51) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
	at org.hibernate.validator.internal.util.ReflectionHelper.run(ReflectionHelper.java:671) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
	at org.hibernate.validator.internal.util.ReflectionHelper.newInstance(ReflectionHelper.java:219) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
	at org.hibernate.validator.internal.xml.ValidationBootstrapParameters.setConstraintFactory(ValidationBootstrapParameters.java:189) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
	... 31 common frames omitted
Caused by: java.lang.InstantiationException: org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory
	at java.lang.Class.newInstance(Class.java:359) ~[na:1.7.0_45]
	at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:48) ~[hibernate-validator-5.0.1.Final.jar:5.0.1.Final]
	... 34 common frames omitted

After debugging that, I realized that Hibernate Validator tries to instantiate SpringConstraintValidatorFactory using reflection, and it expects SpringConstraintValidatorFactory to have a public and no argument constructor (see the Hibernate Validator lines of code mentioned in the stack trace), but SpringConstraintValidatorFactory does not have it, and that is why the InstantiationException exception is thrown.

I have also tested with and without LocalValidatorFactoryBean Spring bean, but it did not help at all.

<beans:bean id="validatorFactory" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

About how my custom validator gets triggered

As said in earlier, my application is a JAX-RS application. So, I am not injecting my custom validator anywhere in my code, it gets triggered behind the scenes by the JAX-RS engine via annotations I use in my REST endpoint. For example, I have created a custom validator to evaluate zip codes, and I have a REST endpoint that receives as an URI parameter the actual zip code. So, in this use case, this is how I define the validation constraint (see below). So, as you can see, I never inject the validator, and no, I am not trying to get my validator by calling the factory explicitly. Everything happens behind the scenes, done by the JAX-RS engine (via this jar called resteasy-validator-provider-11).

@GET
@Path("/echozip/{zip}")
public String echoZip(@ValidateZip @PathParam("zip") String zip) {

About constraint-validator-factory

Spring document does not refer to the constraint-validator-factory property, or even to the file validation.xml at all, that comes from Bean Validations 1.1 document, which Hibernate Validator complies with. See it here.


Affects: 4.1.7, 4.2 GA

Issue Links:

Referenced from: commits 6d1b8b5

@spring-projects-issues
Copy link
Collaborator Author

Stéphane Nicoll commented

I am confused. If you are using Spring for DI then all you need to do is create a LocalValidatorFactoryBean (as in your last code snippet) and inject a javax.validation.Validator in your code.

It looks like you're trying to get your Validator via ValidatorFactory factory = Validation.buildDefaultValidatorFactory() which bypass Spring completely. If you want your constraint validator to use Spring, you should retrieve the one that was created by Spring.

I haven't found anything in the doc that refers to constraint-validator-factory

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Hello Stéphane.

+About how my custom validator gets triggered+
You have to keep in mind that, as I said in the issue description, my application is a JAX-RS application. So, I am not injecting my custom validator anywhere in my code, it gets triggered behind the scenes by the JAX-RS engine via annotations I use in my REST endpoint. For example, I have created a custom validator to evaluate zip codes, and I have a REST endpoint that receives as an URI parameter the actual zip code. So, in this use case, this is how I define the validation constraint (see below). So, as you can see, I never inject the validator, and no, I am not trying to get my validator by calling the factory explicitly. Everything happens behind the scenes, done by the JAX-RS engine (via this jar called resteasy-validator-provider-11).

@GET
@Path("/echozip/{zip}")
public String echoZip(@ValidateZip @PathParam("zip") String zip) {

+About constraint-validator-factory+
You are right, Spring document does not refer to the constraint-validator-factory property, or even to the file validation.xml at all, that comes from Bean Validations 1.1 document, which Hibernate Validator complies with. See it here.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

SpringConstraintValidatorFactory isn't meant to be used like that: It's rather designed for programmatic use, e.g. in LocalValidatorFactoryBean but also in custom bootstrap code. So let me rephrase this issue: You would like to have a ConstraintValidatorFactory class with a default constructor, meant to be used with validation.xml, delegating to the current Spring application context for autowiring purposes.

Now, we generally don't have a "current" application context concept in the core container, but we do have such a notion for a Spring web application in the form of ContextLoader.getCurrentWebApplicationContext(). We can add a corresponding ConstraintValidatorFactory implementation to the spring-web module for 4.2.1, e.g. named SpringWebConstraintValidatorFactory, meant to be used with JAX-RS / JAX-WS.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Hello Juergen,
Could you please elaborate on the statement below?
"SpringConstraintValidatorFactory isn't meant to be used like that: It's rather designed for programmatic use"
I understand it is nice to have a ConstraintValidatorFactory that allows programmatic usage, but I don't see how it can be acceptable to do not adhere to the standard way to implement Bean Validation 1.1 configuration, which is via the validation.xml file (as stated in the Bean Validation 1.1 spec in here).
I just want to see if you can clarify that, because I would see it as a defect, not a enhancement. Unless you don't consider Spring is in compliance with Bean Validation 1.1.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Well, Bean Validation has a standard API for programmatic configuration as well which allows for applying ConstraintValidatorFactory instances, not classes with default constructors. validation.xml comes with many limitations, this being one of them, and is just one way of bootstrapping a Bean Validation provider. Our existing core SpringConstraintValidatorFactory requires access to a BeanFactory handle, which is why it is designed for programmatic configuration - and also used like that in LocalValidatorFactoryBean (which it essentially is a part of). That constructor argument is a key part of its purpose.

You're asking for a similar autowiring ConstraintValidatorFactory variant that automatically uses a "current" Spring application context, instead of specifiying a BeanFactory on construction. That's fine but please note that Spring only has such a "current context" concept in specific environments, e.g. within a web application. Designing a ConstraintValidatorFactory for such scenarios, picking up that current web application context and therefore not requiring any constructor arguments, is what I intend to do for this JIRA ticket. This will then fit your requirement of being usable with the declarative validation.xml configuration style.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Ok, thanks for the explanation and for working on this.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

The proposed SpringWebConstraintValidatorFactory variant is now available in the org.springframework.web.bind.support package. Feel free to give it an early try against an upcoming 4.2.1.BUILD-SNAPSHOT...

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Thanks, I will test it and let you know. BTW, do you have a target date to release 4.2.1?

@spring-projects-issues
Copy link
Collaborator Author

Sam Brannen commented

To see if Spring has scheduled a date for a fix version, simply click on the green link for the "Fix Version".

For example, for 4.2.1: https://jira.spring.io/browse/SPR/fixforversion/15226

... which shows a currently scheduled release date of September 1st. ;)

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Thanks Sam!

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Hey Juergen, it did not work.
The application context could not be retrieved.
I am attaching to this issue the exception and stack trace, plus my web.xml file.
Let me know if there is anything else you need.

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

Apparently, for some weird and unfortunate reason, I cannot attach files in here, so I am going to paste them as comments below.

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

java.lang.IllegalStateException: No WebApplicationContext registered for current thread - consider overriding SpringWebConstraintValidatorFactory.getWebApplicationContext()
	at org.springframework.web.bind.support.SpringWebConstraintValidatorFactory.getWebApplicationContext(SpringWebConstraintValidatorFactory.java:63) ~[spring-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
	at org.springframework.web.bind.support.SpringWebConstraintValidatorFactory.getInstance(SpringWebConstraintValidatorFactory.java:44) ~[spring-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager.createAndInitializeValidator(ConstraintValidatorManager.java:177) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager.getInitializedValidator(ConstraintValidatorManager.java:122) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getConstraintValidatorNoUnwrapping(ConstraintTree.java:303) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getConstraintValidatorInstanceForAutomaticUnwrapping(ConstraintTree.java:244) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.getInitializedConstraintValidator(ConstraintTree.java:163) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:116) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:87) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:73) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForGroup(ValidatorImpl.java:1398) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersForGroup(ValidatorImpl.java:1198) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParametersInContext(ValidatorImpl.java:1069) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:298) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:252) ~[hibernate-validator-5.2.1.Final.jar:5.2.1.Final]
	at org.jboss.resteasy.plugins.validation.GeneralValidatorImpl.validateAllParameters(GeneralValidatorImpl.java:168) ~[resteasy-validator-provider-11-3.0.9.Final.jar:na]
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:116) ~[resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:296) ~[resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:250) ~[resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:237) ~[resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356) [resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179) [resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220) [resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) [resteasy-jaxrs-3.0.9.Final.jar:na]
	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51) [resteasy-jaxrs-3.0.9.Final.jar:na]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) [servlet-api.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) [tomcat7-websocket.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at com.netflix.hystrix.contrib.requestservlet.HystrixRequestContextServletFilter.doFilter(HystrixRequestContextServletFilter.java:53) [hystrix-request-servlet-1.3.16.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at com.paypal.platform.service.rest.CoreContextFilter.doFilter(CoreContextFilter.java:75) [jaxrs-service-paypal-1.0.9-SNAPSHOT.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at com.paypal.platform.service.filters.SSLApplicationFilter.doFilter(SSLApplicationFilter.java:64) [jaxrs-service-paypal-1.0.9-SNAPSHOT.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at com.paypal.platform.service.rest.logging.OuterTransactionCalFilter.doFilter(OuterTransactionCalFilter.java:81) [jaxrs-service-paypal-1.0.9-SNAPSHOT.jar:na]
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) [spring-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) [spring-web-4.2.1.RELEASE.jar:4.2.1.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at com.ebay.raptor.kernel.filter.RaptorDispatchFilter.doFilter(RaptorDispatchFilter.java:60) [platform-core-2.1.22-SNAPSHOT.jar:na]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.47]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.47]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.47]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.47]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [catalina.jar:7.0.47]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.47]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100) [catalina.jar:7.0.47]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.47]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.47]
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041) [tomcat-coyote.jar:7.0.47]
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603) [tomcat-coyote.jar:7.0.47]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721) [tomcat-coyote.jar:7.0.47]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679) [tomcat-coyote.jar:7.0.47]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_45]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_45]
	at java.lang.Thread.run(Thread.java:744) [na:1.7.0_45]

@spring-projects-issues
Copy link
Collaborator Author

Fábio Carvalho commented

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">  
  <display-name>hello</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
        	/WEB-INF/spring/context.xml
        	/WEB-INF/spring/spring-jms.xml
        </param-value>
    </context-param>
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
    </context-param>

    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <servlet-name>*</servlet-name>      
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
</web-app>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants