Search code examples
javaspring-bootspring-data-jpahibernate-validator

How to perform an @Entity query inside a ConstraintValidator


The scenario is that before persisting a Log entity class, its property, String description should be checked if it contains at least a word found in the IllegalWord entity class. Here is the mapping of the two entity classes:

// Log.java
@Entity
public class Log {
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    @Id
    private Long id;

    @NotContainingIllegalWords
    private String description;
}

// IllegalWord.java
@Entity
public class IllegalWord {
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    @Id
    private Long id;
    private String word;
}

Since I will be performing a select * to the IllegalWord entity class, I created a repository class for it:

// IllegalWordRepository.java
@Repository
public interface IllegalWordRepository extends CrudRepository<IllegalWord, Long> {}

And then created the ConstraintValidator validator class that will be used by NotContainingIllegalWords annotation, that in turn, will be use to annotate the String description field of Log entity class:

// NotContainingIllegalWordsValidator.java
public class NotContainingIllegalWordsValidator implements ConstraintValidator<NotContainingIllegalWords, Object> {
    private static final Logger log = LoggerFactory.getLogger(NotContainingIllegalWordsValidator.class);

    @Autowired
    private IllegalWordRepository illegalWordRepository;
    
    public void initialize(NotContainingIllegalWords constraintAnnotation) {}

    public boolean isValid(String value, ConstraintValidatorContext cxt) {
        log.debug("illegalWordRepository is null? " + (illegalWordRepository == null));
        // Returns "illegalWordRepository is null? true"
        // It is not injected even with the @Autowired annotation.

        boolean valid = true;
        
        Collection<IllegalWord> illegalWords = illegalWordRepository.findAll();
        // Encounters a NullPointerException here.
        
        // valid = ...loop through illegalWords collection and match regex (or whatever optimal approach)
        // with @param value to check if it contains the illegal word.
        
        return valid;
    }

I thought it will be as straight-forward like that. But the statement illegalWordRepository.findAll() throws an error because the illegalWordRepository variable is null. Notice that I tried to check if it is null in the preceding statement.

I assumed that I have something wrong coded within the repository class so I attempted to used @Autowired private IllegalWordRepository illegalWordRepository inside a @Service annotated class and suprisingly it is injected there properly (e.i. not null):

// IllegalWordService.java
@Service
public class IllegalWordService {
    private static final Logger log = LoggerFactory.getLogger(IllegalWordService.class);
    
    @Autowired
    private IllegalWordRepository illegalWordRepository;

    public IllegalWord generate(String word) {
        log.debug("illegalWordRepository is null? " + (illegalWordRepository == null));
        // Returns "illegalWordRepository is null? false"
        
        IllegalWord illegalWord = new IllegalWord();
        illegalWord.setWord(word);
        
        illegalWordRepository.save(illegalWord);
        // Didn't encounter a NullPointerException here.

        return illegalWord;
    }
}

Therefore, I guess nothing is wrong with the IllegalWordRepository repository class. It's just that it is not injected in NotContainingIllegalWordsValidator validator class as I intended it to be with the @Autowired annotation (if that is how @Autowired annotation was intended to function even, I am sorry I am new in Spring Framework.).

If there is a proper approach on how to perform a @Entity query inside a ConstraintValidator instance, please tell me.

Related unanswered SO question: Inject Repository inside ConstraintValidator with Spring 4 and message interpolation configuration


Failed Attempt:

I tried to annotate the NotContainingIllegalWordsValidator class with @Configurable annotation, like so:

@Configurable(autowire=Autowire.BY_NAME, preConstruction=true)
public class NotContainingIllegalWordsValidator implements ConstraintValidator<NotContainingIllegalWords, Object> {

but the illegalWordRepository property remains null.


Solution

  • Since Your validator is not initialized by Spring, you can't inject anything into it. You'd have to access the ApplicationContext through a static variable.

    @SpringBootApplication
    public class MyApplication {
    
      private static ApplicationContext applicationContext;
    
      public static void main(final String[] args) {
        applicationContext = SpringApplication.run(MyApplication.class, args);
      }
      public static ApplicationContext getApplicationContext() {
        return applicationContext;
      }
    }
    

    And in your ConstraintValidator:

    public class NotContainingIllegalWordsValidator implements ConstraintValidator<NotContainingIllegalWords, Object> {
      public boolean isValid(String value, ConstraintValidatorContext cxt) {
        ApplicationContext applicationContext = MyApplication.getApplicationContext();
        IllegalWordRepository illegalWordRepository = applicationContext.getBean(IllegalWordRepository.class);
        ...
      }
    }