spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.27k stars 37.98k forks source link

JSR-303 validation doesn't work with nested properties in case of direct field access [SPR-10623] #15251

Closed spring-projects-issues closed 9 years ago

spring-projects-issues commented 11 years ago

Vladimir Kralik opened SPR-10623 and commented

When I use org.springframework.validation.Validator, the IllegalStateException is thrown.

The javax.validation.Validator works correctly.

Look at attached JUnit-test ( also as an maven project in attachement ).

package com.asseco.ce.valid;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

public class A {   
    @NotNull
    @Valid
    private B b;

    public B getB() {
        return b;
    }
    public void setB(B b) {
        this.b = b;
    }
}
package com.asseco.ce.valid;
import org.hibernate.validator.constraints.NotBlank;

public class B {
    @NotBlank
    private String value;

    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
  <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
 </beans>
package com.asseco.ce.valid;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.validation.DirectFieldBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

import com.asseco.ce.valid.A;
import com.asseco.ce.valid.B;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "ValidationIntegrationTest.xml" } )
public class ValidationIntegrationTest {

    @Autowired private Validator springValidator;
    @Autowired private javax.validation.Validator jsr303Validator;

    private A a;

    @Before
    public void before() {
        a = new A();
        a.setB(new B());
    }
    @Test
    public void jsr303() {
        assertEquals(1, jsr303Validator.validate(a).size());
    }   
    @Test
    public void spring() {
        Errors errors = new DirectFieldBindingResult(a, "A");
        springValidator.validate(a, errors);
        assertEquals(1, errors.getAllErrors().size());
    }
}
java.lang.IllegalStateException: JSR-303 validated property 'b.value' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.processConstraintViolations(SpringValidatorAdapter.java:152)
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:89)
    at com.asseco.ce.valid.ValidationIntegrationTest.spring(ValidationIntegrationTest.java:42)
    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 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:88)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'b.value' of bean class [com.asseco.ce.valid.A]: Field 'b.value' does not exist
    at org.springframework.beans.DirectFieldAccessor.getPropertyValue(DirectFieldAccessor.java:106)
    at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99)
    at org.springframework.validation.AbstractBindingResult.getRawFieldValue(AbstractBindingResult.java:275)
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.processConstraintViolations(SpringValidatorAdapter.java:137)
    ... 31 more

Affects: 3.2.3

Attachments:

Issue Links:

Referenced from: commits https://github.com/spring-projects/spring-framework/commit/bc714888c4f0b970792e29f1e3e7bb8682d60cb4

1 votes, 6 watchers

spring-projects-issues commented 10 years ago

Juergen Hoeller commented

This seems to refer to a general limitation with Spring's direct field access: It doesn't come with support for nested properties. We rather 'insist' on accessor methods for such a scenario; we intend to revisit this for 4.1.

For the time being, since you have accessor methods anyway, have you tried regular bean property binding? That should work fine in your scenario.

Juergen

spring-projects-issues commented 10 years ago

Benjamin M commented

+1 !!!

I am waiting for this since decades!

(Mainly because I just want to use public fields within my DTOs)

spring-projects-issues commented 10 years ago

Maciej Walkowiak commented

I submitted a pull request that fixes this issue by adding nested fields traversing feature for DirectFieldAccessor: https://github.com/spring-projects/spring-framework/pull/543

spring-projects-issues commented 10 years ago

Stéphane Nicoll commented

Work on #14339 fixed this issue. Your test case runs fine with the latest 4.1.0.BUILD-SNAPSHOT

spring-projects-issues commented 9 years ago

chris marx commented

For those of us still on 3.2+, how exactly are we supposed to configure the LocalValidatorFactoryBean such that it does not use direct field access?