6

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!

Community
  • 1
  • 1
Rohit Banga
  • 18,458
  • 31
  • 113
  • 191
  • In which package is the class *DonorTypeExistsConstraintValidator* and also show your package scan configuration. – Japan Trivedi Nov 28 '12 at 07:56
  • 1
    have you included or AutowiredAnnotationBeanPostProcessor in the configuration? – Rebzie Nov 28 '12 at 08:01
  • 1
    http://stackoverflow.com/questions/2712345/jsr-303-dependency-injection-and-hibernate ? – Affe Nov 28 '12 at 08:24
  • @Affe I am not able to understand the response to that question. IIRC I got it working correctly last week without doing that. Butnow it is not working. – Rohit Banga Nov 28 '12 at 17:28
  • Reverting to the old commit is just too much work. A lot of things have changed since then in my model and UI. So looking at the code for obvious things. – Rohit Banga Nov 28 '12 at 17:46
  • More information ... I am using `@InitBinder` with `binder.setValidator(new MyBackingFormValidator(binder.getValidator()));`. Could that be the reason the dependency injection is not working. – Rohit Banga Nov 28 '12 at 17:50
  • @Affe I looked at the github project referenced in detail. It does look promising. But there is a problem - the approach does not work with Hibernate 4. Event Listeners property cannot be set in the new version. I am trying an Integrators based approach as well. stackoverflow.com/a/11672377/161628. But it reports a Duplicate Event Listener registered error. Trying to addDuplicationStrategy but that is throwing another exception. – Rohit Banga Nov 29 '12 at 00:16
  • Sorry, I have not used Hibernate 4 yet at all, don't really have any ideas for you. I just happened to remember having seen that question in the past and thought it might be your answer. – Affe Nov 29 '12 at 07:20

4 Answers4

16

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.

Hardy
  • 18,659
  • 3
  • 49
  • 65
  • Thanks for your response. I have added more information to the question. I am using JPA but I am not sure I understand your point. why I should disable validation? – Rohit Banga Nov 29 '12 at 16:11
  • I am not saying you should disable validation, just disable the automatic life cycle based validation (on entity updates and persists). If I understand your code correctly you are doing validation explicitly using a Validator instance you retrieve from somewhere (I don't know what DataBinder is or does). In this case event based validation seems not required. Looking at your spring beans config I don't see any ValidatorFactory configuration. I think you should use LocalValidatorFactoryBean. – Hardy Nov 30 '12 at 10:45
  • I am trying to understand the documentation given here which sounds similar to your suggestion ... but I am not sure where to put this code http://docs.jboss.org/hibernate/validator/4.0.1/reference/en/html/validator-bootstrapping.html#d0e2303 – Rohit Banga Dec 03 '12 at 07:31
  • The documentation you are referring to is using the programmatic bootstrapping. That would work if you programmatically want to create the ValidatorFactory and Validator instance and then manually validate beans. Let's step here for a second, what do you want to do? Do you want to manually validate (aka calling Validator.#validate()) yourself somewhere in your code or do you want to get automatic validation on JPA life cycle events working? – Hardy Dec 04 '12 at 07:37
  • I disabled the lifecycle events validation by setting javax.persistence.validation.mode to none. It wasn't working before because I was putting the property at a wrong place. I am using Constraint Validator annotations for existence checks so that I can show the error in the UI if the referred entity exists or not. I am not using lifecycle events validation because then I will not know which all fields caused the error. It throws some MySQL exceptions. – Rohit Banga Dec 04 '12 at 17:03
  • In addition to constraint validators I have created Spring validators which wrap the default validator (LocalValidatorFactoryBean if I am correct) to invoke the constraint validators along with some custom validations for which annotations do not exist. Although now I feel I could in principle move all my validations to some Constraint validator. But that's my design for now. Any suggestions? – Rohit Banga Dec 04 '12 at 17:04
  • I would also move validation into JSR 303 Bean Validation constraints. Where no built-in constraints exists I would create custom ones. Using Bean Validation you are using an official validation standard which is easily portable. Maybe you also want one day to now to the validation on the form level, but on entity persistence. – Hardy Dec 04 '12 at 23:01
  • I actually came across a usecase where I need to validate two entities simulataneously. I have a table of Blood Tests. Each Blood Test has a set of valid test results. A valid test result table exists but each blood test in table 1 has a subset of valid results in the second table. To see if the test is valid in the first table and a result is valid in the second table can be checked with bean validation. What about checking if we want to check that the result corresponds to the test for which it was created. – Rohit Banga Dec 05 '12 at 02:28
  • This can be handled in the Spring validator. – Rohit Banga Dec 05 '12 at 02:29
  • To be honest I cannot follow your your example above. Especially confusing is that you talk about tables. Really the thing is call Bean Validation, so we should talk entities/beans and their relationships. Can you validate to entities which do not have a reference to each other. No, unless you can look up (somehow) one of them in your custom ConstraintValidator. But that's sounds reasonable to me. Provided that is the problem you tried to explain here. – Hardy Dec 05 '12 at 13:22
  • And yes, if there is really no way to solve your problem with Bean Validation, but with Spring validator - by all means use the latter. – Hardy Dec 05 '12 at 13:23
  • yeah that was the problem I wanted to describe. yes you are right I should talk about beans rather than tables. – Rohit Banga Dec 05 '12 at 15:54
3

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.

Gunnar
  • 18,095
  • 1
  • 53
  • 73
0

Do you have an @Component annotation on the DonorTypeExistsConstraintValidator?

That has killed me in the past.

noplay
  • 2,391
  • 6
  • 26
  • 33
0

No luck with LocalValidatorFactoryBean. I have done some hack to solve this problem.

@Component
public class ServiceUtils
{
    private static ServiceUtils instance;

    @Autowired
    private ListableBeanFactory factory;

    @PostConstruct
    public void init()
    {
        instance = this;
    }

    public static ListableBeanFactory getFactory()
    {
        return instance.factory;
    }
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Ishara Samantha
  • 393
  • 4
  • 8