Spring Boot JSR-303/349 configuration
Asked Answered
W

3

9

In my Spring Boot 1.5.1 application I'm trying to configure support of JSR-303 / JSR-349 validation.

I have added a following annotations @NotNull @Size(min = 1) to my method:

@Service
@Transactional
public class DecisionDaoImpl extends BaseDao implements DecisionDao {

    @Override
    public Decision create(@NotNull @Size(min = 1) String name, String description, String url, String imageUrl, Decision parentDecision, Tenant tenant, User user) {
        ...
    }

}

I'm trying to invoke this method from my test, but it does not fail on the validation constraints.

This is my test and configs:

@SpringBootTest(classes = { TestConfig.class, Neo4jTestConfig.class })
@RunWith(SpringRunner.class)
@Transactional
public class TenantTest {

    @Test
    public void testCreateDecision() {
        User user1 = userService.createUser("test1", "test1", "[email protected]", null, null);
        Tenant tenant1 = tenantDao.create("Tenant 1", "Tenant 1 description", false, user1);

        // the following line should fail on the validation constraint because name parameter is null but it doesn't
        final Decision rootDecision = decisionDao.create(null, "Root decision 1 description", null, tenant1, user1);

...


@Configuration
@ComponentScan("com.example")
@SpringBootApplication(exclude={Neo4jDataAutoConfiguration.class})
public class TestConfig {
}

What am I doing wrong and how to configure JSR-303 there ?

UPDATED

I have added

public Decision create(@Valid @NotNull @Size(min = 1) String name, String description, Decision parentDecision, Tenant tenant, User author) {

but it still doesn't work

I have added @Validated to my DecisionDaoImpl but it fails now with a following exception:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'decisionDaoImpl': Bean with name 'decisionDaoImpl' has been injected into other beans [criterionGroupDaoImpl,characteristicGroupDaoImpl,tenantDaoImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:585)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    ... 43 common frames omitted

I have also added @Lazy annotation in a places where I'm autowiring my DecisionDao but right now my test fails with a following exception:

javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not alter the parameter constraint configuration, but method public com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDaoImpl.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User) changes the configuration of public abstract com.example.domain.model.entity.decision.Decision com.example.domain.dao.decision.DecisionDao.create(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.Long,java.lang.Long,com.example.domain.model.entity.user.User).
    at org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints.apply(OverridingMethodMustNotAlterParameterConstraints.java:24)
    at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.assertCorrectnessOfConfiguration(ExecutableMetaData.java:456)
Wollis answered 2/2, 2017 at 8:31 Comment(4)
Regarding that last exception with OverridingMethodMustNotAlterParameterConstraints, wondering whether applying the same JSR 303 related annotations to the base interface DecisionDao would solve that. Perhaps you could try and apply them only at the interface level.Merca
I have moved validation constraints to the base interface but then the validation stopped working as previously without any exceptionWollis
And if you apply the same annotations exactly in both the interface and the impl?Merca
Finally I got it working. I have a set of overloaded create methods so I have applied validation constraints to all of them at the base interface. Now, everything is working fine.Wollis
L
2

Move your validation to interface, as follows:

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

public interface DecisionDao {

     Decision create(@Valid @NotNull @Size(min = 1) String name,
            String description, String url, String imageUrl);
}

Annotate your DecisionDaoImpl with @Validated, as follows:

import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Service
@Validated
public class DecisionDaoImpl extends BaseDao implements DecisionDao {

    @Override
    public Decision create(String name,
            String description, String url, String imageUrl) {
        System.out.println(name);
        return new Decision();
    }

}

Modify your test case to verify for javax.validation.ConstraintViolationException using assertj or ExpectedException, as follows:

import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import javax.validation.ConstraintViolationException;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@ContextConfiguration(classes = { TenantTest.Config.class })
@RunWith(SpringRunner.class)
public class TenantTest {

    @Autowired
    private DecisionDao decisionDao;

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Test
    public void testCreateDecisionUsingAssertj() {
        assertThatExceptionOfType(ConstraintViolationException.class)
                .isThrownBy(
                        () -> decisionDao.create(null,
                                "Root decision 1 description", null, null));
    }

    @Test
    public void testCreateDecision() {
       expectedException.expect(ConstraintViolationException.class);
       decisionDao.create(null, "Root decision 1 description", null, null);
    }

    @Configuration
    public static class Config {
        @Bean
        public MethodValidationPostProcessor methodValidationPostProcessor() {
            return new MethodValidationPostProcessor();
        }

        @Bean
        public DecisionDao decisionDao() {
            return new DecisionDaoImpl();
        }
    }
}

Make sure you have hibernate-validator in your classpath along with @StanislavL answer:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

And an optional dependency for org.assertj.core.api.Assertions.assertThatExceptionOfType, as:

<dependency>
     <groupId>org.assertj</groupId>
     <artifactId>assertj-core</artifactId>
     <version>3.3.0</version>
     <scope>test</scope>
</dependency>

For sample example, you can refer arpitaggarwal/jsr-303

Language answered 2/2, 2017 at 9:44 Comment(8)
Yes, I have - hibernate-validator 5.2.4.Final. Not worksWollis
thanks for your answer. I have added @Validated to my DecisionDaoImpl but it fails now with a following exception: Bean with name 'decisionDaoImpl' has been injected into other beans [criterionGroupDaoImpl,characteristicGroupDaoImpl,tenantDaoImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.Wollis
Hope it help you - #28985644Language
Thanks. I have added @Lazy annotation in a places where I'm autowiring my DecisionDao but right now my test fails with another exception. Please see my updated question for more details.Wollis
You have to mention that you are expecting exception while running @Test, as I did in my example @Test - expectedException.expect(ConstraintDeclarationException.class);Language
Yes, but it also fails with the same exception when name parameter is not null or emptyWollis
Thank you very much. Finally I got it working. I have a set of overloaded create methods so I have applied validation constraints to all of them at the base interface. Now, everything is working fine.Wollis
I also moved Validated annotation to the base interface. Is it okay ?Wollis
A
1

You need @Valid annotation

Marks a property, method parameter or method return type for validation cascading. Constraints defined on the object and its properties are be validated when the property, method parameter or method return type is validated.

Asymmetry answered 2/2, 2017 at 8:46 Comment(3)
I have added @Valid annotation to my method parameter but it still doesn't work. I have updated my question with this.Wollis
Could you add , BindingResult result parameter in the end of the method and check result.hasErrors() in the method? Looks like you expect exception but it does not throw exception but provides some errors.Asymmetry
What implementation of BindingResult should be provided during method call ? I'm not invoking the method from Spring MVC for example.Wollis
M
1

The constraint annotations are meant to applied to JavaBeans. See http://beanvalidation.org/1.0/spec/#constraintsdefinitionimplementation-constraintdefinition

You have the constraint annotation @NotNull, @Size, etc. applied within the DAO. You must create a Java Bean, e.g. "Person", that wraps those attributes (name, description, etc.), then pass "Person" as a parameter to the Controller method. If you need to use a DAO instead of a controller, it will need to be instrumented to perform the validation. You may be on your own in that regard with regard to AOP, etc., unless something has changed since this post: http://forum.spring.io/forum/spring-projects/container/82643-annotation-driven-jsr-303-validation-on-service-and-dao-tier

Update: Well, looks like it (method level validation JSR-349) is supported now see http://blog.codeleak.pl/2012/03/how-to-method-level-validation-in.html for an example, similar to Arpit's answer. Updated title of question to reflect this latest JSR.

Merca answered 3/2, 2017 at 13:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.