I'm having issues while trying to define custom constraint validation with Spring.
My code is available on my GitHub.
Here the issue. I have an entity with a validation constraint on login:
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#UniqueLogin
private String login;
[...]
}
Here is the annotation definition:
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueLoginValidator.class)
public #interface UniqueLogin {
String message() default "{loginIsNotUnique}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And there the custom validator:
public class UniqueLoginValidator implements ConstraintValidator<UniqueLogin, String> {
#Autowired
private UserRepository userRepository;
public boolean isValid(String login, ConstraintValidatorContext context) {
return userRepository.countByLogin(login) == 0;
}
}
When I call repository.save(new User("Kim"));, I got a NullPointerException on the injected userRepository of UniqueLoginValidator.
I assume that Spring as to inject properly the custom validator, and I have to tell him. But, I don't know how. I already tried some stuff found through Stack Overflow and Google, with no luck.
Any help will be appreciate.
I had similar problem, But I was able to inject Spring Bean in custom validator.
The Spring framework automatically detects all classes which implement the ConstraintValidator interface, instantiate them, and wire all dependencies.
I am using Spring Boot 2.0.
Note UniqueFieldValidator is not annotated with any spring bean annotation
Sample code
public class UniqueFieldValidator implements ConstraintValidator<UniqueField, Person> {
#Autowired
PersionList personRepository;
#Override
public boolean isValid(Person object, ConstraintValidatorContext context) {
log.info("Validating Person for Duplicate {}",object);
return personRepository.isPresent(object);
}
}
#Documented
#Constraint(validatedBy = UniqueFieldValidator.class)
#Target({ ElementType.METHOD,ElementType.ANNOTATION_TYPE,ElementType.PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueField {
String message() default "Duplicate Name";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Usage
#Component
#Validated
public class PersonService {
#Autowired
PersionList personRepository;
public void addPerson(#UniqueField Person person) {
personRepository.add(person);
}
}
After few search and discussion, it seems that injecting beans into a Bean Validation Constraint is not a good practice.
That's why Hibernate context is not aware of the Spring context.
Related
So I have a Rest Controller in Spring Boot and for an endpoint, I need to validate its Request Body.
Controller:
#RestController
#Validated
#RequestMapping("/my_endpoint")
public class WorkflowController {
#PostMapping(value = "/blablabla/", consumes = MediaType.APPLICATION_JSON_VALUE)
public List<Object> createDisconnectRequestRest(#RequestBody List<#CustomValidator #Valid RequestObj> dtos) { // here at the validators is the question
... //nevermind
return null;
}
Request object:
#Data
public class RequestObj{
private String comment;
#NotNull // this #NotNull annotation validator is triggered AFTER the custom validator is done. I want this to be first validated and then the custom validator should take place
private List<Long> ids = new ArrayList<>();
}
#Target({FIELD, TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = CustomValidator.class)
#Documented
public #interface ValidRequest {
String message() default "Invalid request";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
CustomValidator:
public class CustomValidator implements ConstraintValidator<ValidRequest, RequestObj> {
// repositories, constructor
#Override
public boolean isValid(RequestObj request, ConstraintValidatorContext constraintValidatorContext) {
myRepository.findAllById(request.getIds()); // I want the #NotNull annotation validate the object before this custom validator
return true;
}
}
Here is the problem:
The first one to be triggered is the CustomValidator and then the RequestObj is being validated. In other words, validation starts with the #CustomValidator annotation and then the #Valid one. I'd like that the first one to be triggered would be the #Valid annotation (so the #NotNull annotation would validate the object first) and then the #CustomValidator should do its job. For example, if the body field ids is NULL, I'd like that the #CustomValidator not even start as the validation had already failed.
This could be because you have applied #CustomValidator annotation at class Level, and classes are loaded first before properties, and that could be the reason you see this order or execution.
You have a few options:
1- Apply this #CustomValidator at specific property level,
2- Set your CustomValidator as a #Component, and remove the #CustomValidator annotation from the class level. Then in your controller method you do:
#Autowired
private CustomValidator customValidator;
#PostMapping(value = "/blablabla/", consumes = MediaType.APPLICATION_JSON_VALUE)
public List<Object> createDisconnectRequestRest(#RequestBody List<#Valid RequestObj> dtos) { // here at the validators is the question
customValidator.validate(xyz);
}
--- Untested Theory ---
Could you try with changing the order of the annotations applied at the method level and see if it makes a difference:
#PostMapping(value = "/blablabla/", consumes = MediaType.APPLICATION_JSON_VALUE)
public List<Object> createDisconnectRequestRest(#RequestBody List<#Valid #CustomValidator RequestObj> dtos) { // here at the validators is the question
... //nevermind
return null;
}
How to add a user define annotation in spring jpa save method only.
I have created a annotation and wanted to use it on the save method of the repository, but the save method is inherited method from CrudRepository of JPA, not sure how can annotation be applied on only that method and not the other method of that repository.
Tried overriding that method in the repository interface and applied the annotation but it didn't worked
Please refer the code below -
Annotation :
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
}
#Aspect
#Configuration
#Slf4j
#ComponentScan(value = "com.somepackage.service")
public class MyAnnotationInterceptor {
#Value("${val}")
private String val;
#Around("#annotation(com.somepackage.service.application.annotation.MyAnnotation)")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
if("TEST".equalsIgnoreCase(val)){
log.info("Test Event")
}else{
joinPoint.proceed();
}
}
}
Repository :
#Transactional
public interface EmployeeEntityRepository extends CrudRepository<EmployeeEntity, String> {
List<EmployeeEntity> findAllByEmpIdAndStatusNot(String empId, String status);
#Query("SELECT emp.empId FROM EmployeeEntity emp WHERE emp.orderId IN ?1")
List<String> findEmployeeIds(List<String> orderIds);
#Override
#MyAnnotation
<S extends EmployeeEntity> Iterable<S> save(Iterable<S> iterable);
}
Service Class:
class EmployeeService {
#Autowired
EmployeeEntityRepository employeeEntityRepo;
public void saveEmployee(List<EmployeeEntity> employeeData) {
employeeEntityRepo.save(employeeData);
employeeEntityRepo.clearCache(employeeData);
/***
.
.
.
Some other logic calculations
.
.
***/
}
}
Can anyone please help me why I can’t save object having field with custom validator in the spring boots?
Scenario:
First I have to validate the field by custom validator(which is working fine) then save entity into the database(which breaks).
I am using Spring boots framework on IntelliJ IDE. The code is on github. https://github.com/mhussainshah1/customvalidation
I have Customer entity
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ContactInfo //Custom Validator
#NotNull
private String contactInfo;
// standard constructor, getters, setters
}
I have ContactInfoExpression entity
#Entity
public class ContactInfoExpression {
#Id
#Column(name="expression_type")
private String type;
private String pattern;
//standard constructor, getters, setters
}
I have ContactInfoExpressionRepository and CustomerRepository
which extends CrudRepository<T, Id>
I use an H2 in-memory database with the following configuration in the application.properties file. The contactInfoType property can be set to one of the values email, phone or website
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.show-sql=true
contactInfoType=email
#contactInfoType=phone
#contactInfoType=website
Custom Validator
#Component
public class ContactInfoValidator implements ConstraintValidator<ContactInfo, String> {
private static final Logger LOG = LogManager.getLogger(ContactInfoValidator.class);
#Value("${contactInfoType}")
private String expressionType;
private String pattern;
#Autowired
private ContactInfoExpressionRepository contactInfoExpressionRepository;
#Override
public void initialize(ContactInfo contactInfo) {
if (StringUtils.isEmptyOrWhitespace(expressionType)) {
LOG.error("Contact info type missing!");
} else {
pattern = contactInfoExpressionRepository.findById(expressionType)
.map(ContactInfoExpression::getPattern).get();
}
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!StringUtils.isEmptyOrWhitespace(pattern)) {
return Pattern.matches(pattern, value);
}
LOG.error("Contact info pattern missing!");
return false;
}
}
Custom Constraint Annotation
#Constraint(validatedBy = { ContactInfoValidator.class })
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RetentionPolicy.RUNTIME)
public #interface ContactInfo {
String message() default "Invalid value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
I use class DataLoader to load data
#Component
public class DataLoader implements CommandLineRunner {
#Autowired
ContactInfoExpressionRepository contactInfoExpressionRepository;
#Autowired
CustomerRepository customerRepository;
#Override
public void run(String... args) throws Exception {
String pattern = "[a-z0-9!#$%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?";
ContactInfoExpression email = new ContactInfoExpression("email", pattern);
contactInfoExpressionRepository.save(email);
pattern = "^([0-9]( |-)?)?(\\(?[0-9]{3}\\)?|[0-9]{3})( |-)?([0-9]{3}( |-)?[0-9]{4}|[a-zA-Z0-9]{7})$";
ContactInfoExpression phone = new ContactInfoExpression("phone", pattern);
contactInfoExpressionRepository.save(phone);
pattern = "^(http:\\/\\/www\\.|https:\\/\\/www\\.|http:\\/\\/|https:\\/\\/)?[a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$";
ContactInfoExpression website = new ContactInfoExpression("website", pattern);
contactInfoExpressionRepository.save(website);
Customer customer1 = new Customer("mhussainshah79#gmail.com");
customerRepository.save(customer1);// Error: can`t save
}
}
I can`t save the customer object having field with custom validator. I am getting the following error at runtime
java.lang.IllegalStateException: Failed to execute CommandLineRunner
Caused by: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
Caused by: javax.persistence.RollbackException: Error while committing the transaction
Caused by: javax.validation.ValidationException: HV000032: Unable to initialize com.example.customvalidation.ContactInfoValidator.
Caused by: java.lang.NullPointerException: null
at com.example.customvalidation.ContactInfoValidator.initialize(ContactInfoValidator.java:41) ~[classes/:na]
at com.example.customvalidation.ContactInfoValidator.initialize(ContactInfoValidator.java:18) ~[classes/:na]
The solution to the problem is to add the following in the application.properties file.
properties spring.jpa.properties.javax.persistence.validation.mode:none
Reference:
How to avoid double validation in Spring Boot applications, Sanjay Patel, 15 May, 2018
https://www.naturalprogrammer.com/blog/16386/switch-off-jpa-validation-spring-boot
The DTO that I use is annotated with javax.validation annotations
For example
#JsonIgnoreProperties(ignoreUnknown = true)
public class StudentDTO {
#NotEmpty
private String name;
#Positive
private Long studentId;
}
What if I have to validate using ConstraintValidator for StudentDTO
Spring MVC has the ability to automatically validate #Controller
inputs. In previous versions it was up to the developer to manually
invoke validation logic.
But in your case , you are trying to validate a DTO object in which case , springboot might not be automatically binding your validator to your model and call the validator.So, in that case, you will need to manually bind the object to the validator.
or you can manually invoke the validator on a bean like :
#AutoWired
Validator validator;
...
validator.validate(book);
You can define a custom validator in springboot for model classes if you want and use annotations :
#Documented
#Constraint(validatedBy = CustomDataValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomDataConstraint {
String message() default "Invalid data";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and then define a validator class like :
public class CustomDataValidator implements
ConstraintValidator<CustomDataConstraint, String> {
#Override
public void initialize(CustomDataConstraint data) {
}
#Override
public boolean isValid(String field,
ConstraintValidatorContext cxt) {
return field!= null;
}
}
Your validator class must implement the ConstraintValidator interface and must implement the isValid method to define the validation rules, define the validation rules can be anything as you wish.Then, you can simply add the annotation to your field like :
#CustomDataConstraint
private String name;
I am using Hibernate validator like #NotEmpty to see if a specific property in a class is empty or not. The class is as as shown:
#Entity
#Table(name="emergency_messages")
public class EmergencyMessages implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id", nullable=false)
private Integer id;
#NotEmpty(message="Home message cannot be empty")
#Column(name="home_page_message")
private String homePageMessage;
#Range(min=0, max=1, message="Please select one of the display announcement value")
#Column(name="messages_enabled")
private Integer messagesEnabled;
}
So far so good. Whenever the property "homePageMessage" is empty I can see that the correct error message in the form in the browser.
Now the situation has changed. The new requirement is that the property "homePageMessage" can be empty only if the other property "messagesEnabled" is set to 1. If it is set to 0 then there should be no empty check done for "homePageMessage". In simple words the validation of "homePageMessage" should now be dependent on the "messagesEnabled" value.
My question: Is this possible to do with annotations? If not, then I will have to dismantle my hibernate validator mechanism and create my own validation class.
I think you need to write custom annotation to achieve this. Also you can use other hibernate validation constraint with custom annotation, no need to remove anything.
Check this link for details.
Following is the code that I came up with (after suggestions from Ajinkya and Alex):
Customized Annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy=HomePageEmptyMessageValidator.class)
public #interface HomePageEmptyMessage {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Customized Validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class HomePageEmptyMessageValidator implements ConstraintValidator<HomePageEmptyMessage, EmergencyMessages> {
#Override
public void initialize(HomePageEmptyMessage homePageEmptyMessage) {
}
#Override
public boolean isValid(EmergencyMessages emergencyMessages, ConstraintValidatorContext context) {
if (emergencyMessages == null) {
return false;
}
Integer messageEnabled = emergencyMessages.getMessagesEnabled();
if (messageEnabled != null) {
if (messageEnabled == 1) {
String homePageMessage = emergencyMessages.getHomePageMessage();
if (Util.isNullOrEmpty(homePageMessage)) {
return false;
} else {
return true;
}
} else {
return true;
}
}
return false;
}
}
Usage of customized annotation in the code:
#Entity
#Table(name="emergency_messages")
#HomePageEmptyMessage(message="Home page annoucement cannot be empty if the Display Announcement is set to Yes")
public class EmergencyMessages implements Serializable {
private static final long serialVersionUID = -7870767517772161300L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id", nullable=false)
private Integer id;
#Column(name="home_page_message")
private String homePageMessage;
#Range(min=0, max=1, message="Please select one of the display announcement value")
#Column(name="messages_enabled")
private Integer messagesEnabled;
}
I hope it helps someone.
What you need is a ConstraintValidator implementation for your entity, using the #Constraint annotation on it.
This is where you will put conditions on fields that depends on other ones. Constraints using annotations on field are supposed to be used for check that can be made on the field itself, and not depending on another ones (like max size, nullable, etc...).