0

In my custom annotation, I am taking domains I am going to allow for mail

@Documented
@Constraint(validatedBy = ValidEmail.CheckIfValidMail.class)
//@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
//@Repeatable(List.class)
@Retention(RUNTIME)
@Component
public @interface ValidEmail {

    String message() default "Invalid String !!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        ValidEmail[] value();
    }

    @Component
    public class CheckIfValidMail implements ConstraintValidator<ValidEmail, String> {

        @Autowired
        UserServiceImplOld userServiceImpl;

//      @Value("${app.mail.allowedDomains:gmail}")
        private String[] allowedDomainsForMail;

        @Value("${app.mail.allowedDomains:gmail}")
        public void setAllowedDomainsForMail(String[] allowedDomainsForMail) {
            this.allowedDomainsForMail = allowedDomainsForMail;
        }

//      private String[] allowedDomainsForMail = new String[] { "gmail", "rediffmail", "yahoo" };

        protected String message;

        @Override
        public void initialize(ValidEmail validEmail) {
            this.message = validEmail.message() + "  Allowed Domain(s): " + Arrays.toString(allowedDomainsForMail);
        }

        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (value == null)
                return true;
            String allowedDomainRegEx = String.join("|", allowedDomainsForMail);
            String mailRegex = "^(?i)[A-Za-z0-9+_.-]+@(?:" + allowedDomainRegEx + ")\\.(?:.*)$";
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
            return value.matches(emailRegex);
        }

    }
}

If I am calling this from rest API, each time the value is there, But If I am calling validation explicitly from code I can see in debug allowedMailDomains value is null

But, if I am not taking from properties file and hardcoding, it's working both the time like this

Dto object

@Data //lombok
public class UserDto{
    @ValidEmail(message = "Wrong emailId format !!")
    private String emailId;

    @NotNull
    private Boolean isLoginAllowed;

}

In implementation

UserDto addUserDto = new UserDto();
addUserDto.setEmailId("satish");
addUserDto.setisLoginAllowed(false);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
System.out.println("Validating :-\n" + validator.validate(addUserDto));

So, While validating it goes to ValidMail interface, and there allowedMailDomains value is null

Satish Patro
  • 3,645
  • 2
  • 27
  • 53

3 Answers3

0

Try to register LocalValidatorFactoryBean and use it instead of Validation.buildDefaultValidatorFactory(). LocalValidatorFactoryBean should allow you to get dependency injection benefits inside your validator.

UPD: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints

By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This lets your custom ConstraintValidators benefit from dependency injection like any other Spring bean.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class ValidatorConfiguration {
    @Bean
    public ValidatorFactory localValidatorFactoryBean() {
        return new LocalValidatorFactoryBean();
    }
}

Then in your implementation class autowire this bean:

@Autowired
private ValidatorFactory validatorFactory;

And use it instead of DefaultValidatorFactory:

Validator validator = validatorFactory.getValidator();
validator.validate(addUserDto);
-1

To inject the property allowedMailDomains, your class CheckIfValidMail must be a bean. So if you add @Component annotation (for example) to your class, the problem will be resolved.

zouari
  • 967
  • 1
  • 6
  • 13
  • @Component to interface annotation ? from where I am calling it's already having Component and Service, but giving in ValidMail interface, I am still getting null – Satish Patro Nov 16 '19 at 13:14
  • I gave it to the class inside interface with @Component annoation, still getting null – Satish Patro Nov 16 '19 at 13:19
  • The class (implementation) should be a bean. In fact, you need to inject its property. How can Spring Container inject the value if it is not a bean ? – zouari Nov 16 '19 at 13:24
  • CheckIfValidMail is an inner class ? I do not understand how it is inside an interface ? Can you explain please. – zouari Nov 16 '19 at 13:27
  • @zouari edit your answers so I can remove my downvote, you are right, I will explain it in my answer. – engma Nov 16 '19 at 13:53
  • @engma how should I edit it ? In fact, I am a new member and I did not understand your note and the reason of your down vote ! – zouari Nov 16 '19 at 14:39
-1

So here is the issue:

  • When running the code in the REST API, you are running in a Spring container which initializes the CheckIfValidMail as a Component, which then get's all the values and everything from the Spring container that it needs.

  • When you intialize it programatically, it isn't initialized as a Component, and there is no Container as well to get the @Value from, I would even doubt the Properties are loaded at all in the second case (I cannot see the entire code so I don't know for sure).

Without full context here is a solution right the top of my head:

in the validator do this:

private String[] allowedMailDomains;

@Value("${poss.mail.allowedDomains:gmail}")
public void setAllowedMailDomains(String[] allowedMailDomains) {
    this.allowedMailDomains = allowedMailDomains;
}

And then set that value in your programmatic validation, that should then work.

Otherwise, just initialize it in a container and that should fix it.

engma
  • 1,849
  • 2
  • 26
  • 55