Autowired Repository is Null in Custom Constraint Validator

12,066

Solution 1

Which ValidatorFactory are you using. Or to put it another way, hot to you bootstrap Bean Validation? Per default Bean Validation (and Hibernate Validator as reference implentation) does not inject dependencies into ConstraintValidator instances. At least in Bean Validation 1.0.

To enable dependency injection you have to configure a custom ConstraintValidatorFactory. Spring offers SpringConstraintValidatorFactory which is the default when using LocalValidatorFactoryBean - see http://static.springsource.org/spring/docs/3.0.0.RC3/reference/html/ch05s07.html

Also, are you using JPA? I assume so, in which case auto validation on life cycle events automatically. Instead of trying to remove event listeners you can just use the property javax.persistence.validation.mode and set it to NONE. I am wondering whether the right validator is used on life cycle based validation.

It would all depend on your overall Spring configuration and use.

Solution 2

Do you pass the validator factory to be used by JPA when performing validation:

<bean 
    id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="jpaPropertyMap">
        <map>
            <entry 
                key="javax.persistence.validation.factory"
                value-ref="validator"/>
            <!-- ... -->
        </map>
    </property>
    <!-- ... -->
</bean>

That's required since otherwise the default factory (as specified in validation.xml) would be used by JPA.

Share:
12,066
Rohit Banga
Author by

Rohit Banga

Software Development Engineer, AWS X-Ray, AWS Elastic Beanstalk MS CS from Georgia Tech. At Georgia Tech worked on Vein-to-Vein https://github.com/C4G/V2V https://github.com/jembi/bsis Video: http://www.youtube.com/watch?v=O_zIIXepPHc Personal Website: http://iamrohitbanga.com

Updated on June 19, 2022

Comments

  • Rohit Banga
    Rohit Banga almost 2 years

    I have a Spring based webapp. I am using several repository classes with annotations @Repository, @Transactional in my Controllers. That part works fine.

    I created a Custom Constraint Validator which has to access the repository. Now I am not able to understand why the Repository is null. I tried annotating the validator with @Component annotation. My base package containing all these classes is in the part of the xml. So what else should I do to ensure the repository dependency injection works. This is how my Constraint validator looks like.

    public class DonorTypeExistsConstraintValidator implements
        ConstraintValidator<DonorTypeExists, DonorType> {
    
      @Autowired
      private DonorTypeRepository donorTypeRepository;
    
      @Override
      public void initialize(DonorTypeExists constraint) {
    
      }
    
      public boolean isValid(DonorType target, ConstraintValidatorContext context) {
    
       System.out.println("donorType: " + target);
       if (target == null)
         return true;
    
       try {
        if (donorTypeRepository.isDonorTypeValid(target.getDonorType()))
         return true;
       } catch (Exception e) {
        e.printStackTrace();
       }
       return false;
      }
    
      public void setDonorRepository(DonorTypeRepository donorTypeRepository) {
        this.donorTypeRepository = donorTypeRepository;
      }
    }
    

    Update

    Package scan configuration:

    <context:annotation-config />
    
    <!-- Configures the @Controller programming model -->
    <mvc:annotation-driven />
    
    <context:component-scan base-package="controller" />
    <context:component-scan base-package="repository" />
    <context:component-scan base-package="model" />
    <context:component-scan base-package="viewmodel" />
    

    This class is in the package model.donortype. The repository is in the package repository.

    Update

    Something more puzzling, removing all preinsert listeners (in this case only BeanValidationEventListener) fixes the autowiring issue. This has complicated the matter even more.

    I am not even inserting the BeanValidationEventListener as described in the answer below. This is infact contrary to the suggestion here: JSR-303 dependency injection and Hibernate

      @Override
      public void integrate(Configuration configuration, SessionFactoryImplementor implementor,
                            SessionFactoryServiceRegistry registry) {
          logger.info("Registering event listeners");
          System.out.println("here");
          // you can add duplication strategory for duplicate registrations
    
          final EventListenerRegistry eventRegistry = registry.getService(EventListenerRegistry.class);
          // prepend to register before or append to register after
          // this example will register a persist event listener
    
          eventRegistry.prependListeners(EventType.PERSIST, EntitySaveListener.class);
          eventRegistry.appendListeners(EventType.MERGE, EntitySaveListener.class);
          Iterator<PreInsertEventListener> iter = eventRegistry.getEventListenerGroup(EventType.PRE_INSERT).listeners().iterator();
          while (iter.hasNext()) {
            PreInsertEventListener el = iter.next();
            System.out.println(el.getClass());
            BeanValidationEventListener listener = (BeanValidationEventListener) el;
            System.out.println(listener);
            iter.remove();
          }
      }
    

    Update

    I am using JPA. I have a backing form for the JPA entities. In the controller I use InitBinder to instantiate a custom validator binder.setValidator(new EntityBackingFormValidator(binder.getValidator()));. Here is the code for this: https://github.com/C4G/V2V/blob/87dc266043f6d623c101d947a88fa0b0ad536355/src/controller/CollectedSampleController.java

    This custom validator invokes the validation on the default validator and leaves scope to do some custom validations. https://github.com/C4G/V2V/blob/87dc266043f6d623c101d947a88fa0b0ad536355/src/model/collectedsample/CollectedSampleBackingFormValidator.java

    I am using annotations within the entity to create constraints. Apart from the default constraints, I need to define constraints that check whether a entity at the other side of a many-to-one mapping exists or not. Here is the custom constraint validator. https://github.com/C4G/V2V/blob/87dc266043f6d623c101d947a88fa0b0ad536355/src/model/donor/DonorExistsConstraintValidator.java

    Now the autowired repository in this custom constraint validator is null. I am trying to customize code in my CustomIntegrator which is present here https://github.com/C4G/V2V/blob/87dc266043f6d623c101d947a88fa0b0ad536355/src/interceptor/CustomIntegrator.java

    and the xml configuration file present here https://github.com/C4G/V2V/blob/87dc266043f6d623c101d947a88fa0b0ad536355/war/WEB-INF/v2v-servlet.xml

    Hopefully the actual code will help you to answer my question. Thanks!