Spring Boot custom constraint validation component - java

config class
#ComponentScan(basePackages = {"validator"})
class AppConfiguration { ... }
annotation class
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueLoginValidator.class)
public #interface UniqueLogin {
String message() default "{com.dolszewski.blog.UniqueLogin.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
validator class
#Component
class UniqueLoginValidator implements ConstraintValidator<UniqueLogin, String> {
private UserRepository userRepository;
public UniqueLoginValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void initialize(UniqueLogin constraint) {
}
public boolean isValid(String login, ConstraintValidatorContext context) {
return login != null && !userRepository.findByLogin(login).isPresent();
}
}
I have a class with property #UniqueLogin String login, I also use other annotations like #Size and #Max, the last 2 works, but my custom annotation does not work.
Can you please help to understand why spring do not call custom validator?

It worked for me to create inside src/main/resources/META-INF/services a file named javax.validation.ConstraintValidator with a list new line separated of all qualified name of custom constraint validators you created.
This way, Spring will automatically register the custom validator.
This file will be automatically checked from Spring and included into built artifact.
Be careful of annotation configuration after applying this solution. You should annotate with #Constraint(validatedBy = { }) to prevent double validator initialization.

Related

Injected services become null in Custom Constraint Validators

I have tried to create a custom annotation with a constraint validator in the spring boot framework and I'm getting an error of injected services being null when executing the custom validator.
And, I have checked with the previous answers related to this. But, any answer didn't work for me now.
Some of them are as follows,
javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator
Autowired gives Null value in Custom Constraint validator
http://dolszewski.com/spring/custom-validation-annotation-in-spring/
My custom annotation and constraint validator as follows,
Custom Annotation
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Constraint(validatedBy = UniqueFoodNameValidator.class)
public #interface UniqueFoodName {
String message() default "{UniqueFoodName}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Custom Constraint Validator
public class UniqueFoodNameValidator implements ConstraintValidator<UniqueFoodName, String> {
private FoodService foodService;
#Autowired
public UniqueFoodNameValidator(FoodService foodService) {
this.foodService = foodService;
}
#Override
public void initialize(UniqueFoodName constraintAnnotation) {
}
#Override
public boolean isValid(String foodName, ConstraintValidatorContext context) {
return Objects.isNull(foodName) || foodService.get(foodName).isEmpty();
}
}
So, I would really appreciate If someone cloud help me to figure out the issue and to resolve this.
Note that: FoodService is an interface and it's methods implemented in the FoodServiceImpl class.
A bit late answer, but hope it will be useful for someone.
I suppose the problem is in your Hibernate configuration. The thing is that you validate the field twice. It happens because both Spring and Hibernate run validation automatically.
If you debug isValid method, you will see the flow runs into the method twice first time your FoodService will be injected, but not the second time.
To fix it you can disable Hibernate auto validation. For example:
private Properties hibernateSettings() {
Properties settings = new Properties();
// some settings
settings.put("javax.persistence.validation.mode", "none");
return settings;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setHibernateProperties(hibernateSettings());
sessionFactoryBean.setPackagesToScan("com.springmvc.models");
return sessionFactoryBean;
}

javax.validation.ConstraintValidator created twice (spring boot)

I'm using Spring Boot 2.2.4 and Hibernate 5.4.10. I cannot understand why the validator objects are created twice: the first time with Spring Boot context and the second time without it. I tried javax.persistance.validation.mode=none - nothing changed. Main problem is that objects created with Spring context are ignored.
Annotation:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {CustomForBaseEntity.class, CustomForDto.class})
public #interface Custom {
String message() default "{Your license plate has the wrong format. "
+ "Please don't use special symbols.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Base class:
abstract class CustomValidator<T> implements ConstraintValidator<LicensePlateValidation, T> {
private Pattern dependency;
#Autowired
public void setSpringDependency(#Value("${path}") final String dependency) {
this.dependency = Pattern.compile(dependency);
}
}
Both derived classes looks same, so there is only one:
#Component
public class CustomForBaseEntity extends LicensePlateValidator<Vehicle> {
#Override
public boolean isValid(Vehicle vehicle, ConstraintValidatorContext constraintValidatorContext) {
String licensePlate = vehicle.getLicensePlate();
return checkLicensePlate(licensePlate, constraintValidatorContext);
}
}
As mentioned in Spring Boot documentation, I'm created bean of LocalValidatorFactoryBean:
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}

how can I load spring bean with hiberate mysql data spring-rest

I am trying to perform validation in spring and for that I need some data to be available before I execute the validation. That data is in my sql inside the table . I am looking for solution which will load my spring bean from the mysql table and that bean I can use to get the data for the validation .
Check out this example.. you just need declare your validator as bean.
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyValidatorImpl.class)
#Documented
public #interface MyValidator {
String message() default "invalid";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
#Component // <---- this will allow you to access spring component
public class MyValidatorImpl implements ConstraintValidator<MyValidator, String> {
#Autowired MyDAO myDAO;
public void initialize(MyValidator constraint) {
}
public boolean isValid(String s, ConstraintValidatorContext context) {
return false;
}
}

Custom validation for RequestParam doesn't work with Spring MVC

I can't make custom validation working with Spring MVC. I implemented own annotation for parameter and custom validator for it (all is given below), but validation never happens. Any ideas would be really appreciated.
Controller
#Validated
#RestController
public class FooController {
#RequestMapping(value = "/somepath",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String get(#CustomParam #RequestParam(String fooParam) {
return "Hello";
}
}
Custom Request Parameter
#Documented
#Constraint(validatedBy = CustomValidator.class)
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomParam {
String message() default "Wrong!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom Validator
#Component
public class CustomValidator implements ConstraintValidator<CustomParam, String> {
#Override
public void initialize(CustomParam param) {}
#Override
public boolean isValid(String givenParam, ConstraintValidatorContext context) {
// some custom validation is here, never enter this method though
}
}
Seems I got what is wrong here. Because I use Spring Boot, it uses hibernate validator by default. To fix this, I followed this answer and just changed my Spring configuration with adding the beans.
#Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}

Using #Autowired component in custom JSR-303 validator

I'm trying to implement a custom validator for my model classes that autowires a custom bean of mine (declared via #Component).
In this, I followed the Spring documentation on that topic. My AuthenticationFacade object is implemented according to this tutorial.
When running my tests, however, the autowired attribute in the Validator object is always null. Why is that?
Here are the relevant parts of my code:
My custom bean, AuthenticationFacadeImpl.java
#Component
public class AuthenticationFacadeImpl implements AuthenticationFacade {
boolean hasAnyRole(Collection<String> roles) {
// checks currently logged in user roles
}
}
My custom constraint, HasAnyRoleConstraint.java
#Constraint(validatedBy = HasAnyRoleConstraintValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public #interface HasAnyRole {
String[] value();
String message() default "{HasAnyRole}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
My custom validator, HasAnyRoleConstraintValidator.java
#Component
public class HasAnyRoleConstraintValidator implements ConstraintValidator<HasAnyRole, Object> {
#Autowired
AuthenticationFacade authenticationFacade;
private String[] roles;
#Override
public void initialize(HasAnyRole hasAnyRole) {
this.roles = hasAnyRole.value();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext constraintValidatorContext) {
return target == null || authenticationFacade.hasAnyRole(Arrays.asList(this.roles));
}
}
The model class, Article.java
#Entity
public class Article {
// ...
#HasAnyRole({"EDITOR", "ADMIN"})
private String title;
// ...
}
The service object, ArticleServiceImpl.java
#Service
public class ArticleServiceImpl implements ArticleService {
#Autowired
private ArticleRepository articleRepository;
#Autowired
private AuthenticationFacade authenticationFacade;
#Autowired
private Validator validator;
#Override
#PreAuthorize("hasAnyRole('ADMIN', 'EDITOR')")
public boolean createArticle(Article article, Errors errors) {
articleRepository.save(article);
return true;
}
The Errors object that gets fed into the createArticle method is intended to come from the Spring controller, which gets fed a model object with the #Valid annotation.
The repository, ArticleRepository.java, uses Spring Data JPA's JpaRepository
public interface ArticleRepository extends JpaRepository<Article, Long> {
}
I solved this for now by ditching Dependency Injection for the Validator class, instead instantiating an instance of AuthenticationFacadeImpl in the constructor.
Would still be interesting, though how to combine the use of #Valid in the Controllers with custom validators + #Autowired attributes in the Model without explicitely calling the Validator in the code...
If your validator is instantiated outside the Spring context, then you can use Spring’s AOP #Configurable magic to register it in context and get autowiring work. All what you need is to annotate HasAnyRoleConstraintValidator with #Configurable and enable compile time, or load time aspects weaving.

Categories