jersey 2 constraintvalidator called twice - validation

Using Jersy 2 on Glassfish 4.
I have a custom ConstraintValidator that is being called to validate a bean parameter.
The ConstraintValidator is correctly injected with Jersey resources using #Context. (Changed #Context to #Inject to solve problem with resources being null when ConstraintValidator is called twice by the system.)
Problem the isValid() method is called twice I can see the logging being printed twice before the update() method is being called.
I added an interceptor to do some debug logging.
First the constraintvalidator.isValid() is called then my Interceptor then constraintvalidator.isValid() is called again and then only my REST resource method.
(This class does not contain any injected resources.)
public class StudyValidator implements ConstraintValidator<StudyCheck, StudyBeanREST> {
private static final Logger log = Logger.getLogger(StudyValidator.class);
#Override
public void initialize(StudyCheck constraintAnnotation) {
}
#Override
public boolean isValid(StudyBeanREST study, ConstraintValidatorContext context) {
log.info("Validating study: " + study);
Integer version = study.getVersion();
if(version == null || version < 0) {
return false;
}
return true;
}
}
The annotation:
#Target({ElementType.PARAMETER,ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {StudyValidator.class})
public #interface StudyCheck {
String message() default "{error.version}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And where I use my annotation:
#PUT
#RolesAllowed({"management"})
public StudyBeanREST update(#StudyCheck StudyBeanREST study) throws RecordNotFoundException, UpdateNotAllowedException {
Study updated = studyEJB.update(study.getJpa());
study.setJpa(updated);
return study;
}

Related

In SpringBoot, how do I create a custom validator for a MultipartFile parameter?

I'm using Spring Boot 2.4. I have the following controller with a method that accepts a MultipartFile object.
#RestController
public class MyController extends AbstractController
...
#Override
public ResponseEntity<ResponseData> add(
...
#Parameter(description = "file detail") #Validated #RequestPart("myFile")
MultipartFile myFile,
...
) {
I would like to validate that this MultipartFile contains the data that I want (e.g. is of a particular mime type). So I have written the below validator ...
#Documented
#Constraint(validatedBy = MultipartFileValidator.class)
#Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MultipartFileConstraint {
String message() default "Incorrect file type.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and its implementation class ...
public class MultipartFileValidator
implements ConstraintValidator<MultipartFileConstraint, MultipartFile> {
#Override
public void initialize(final MultipartFileConstraint constraintAnnotation) {
log.info("\n\n\n\nconstructor called\n\n\n\n");
}
#Override
public boolean isValid(
MultipartFile file, ConstraintValidatorContext constraintValidatorContext) {
log.info("Validating file");
...
}
}
However, when I invoke my endpoint, I don't see that my validator is called (for one, the log statement is never printed nor breakpoints hit). What else do I need to do to register my validator for this MultipartFile param?
As per the Spring Documentation:
Can also be used with method level validation, indicating that a
specific class is supposed to be validated at the method level (acting
as a pointcut for the corresponding validation interceptor), but also
optionally specifying the validation groups for method-level
validation in the annotated class. Applying this annotation at the
method level allows for overriding the validation groups for a
specific method but does not serve as a pointcut; a class-level
annotation is nevertheless necessary to trigger method validation for
a specific bean to begin with. Can also be used as a meta-annotation
on a custom stereotype annotation or a custom group-specific validated
annotation.
So, here we have to keep in mind what are the placement of #Validated and validator annotation.
Code:
Controller class : #Validated added at class level and #ValidFile (Custom validator annotation) in the method
#RestController
#Validated
#Slf4j
public class MyController {
#RequestMapping("/add")
public ResponseEntity<ResponseData> add(#ValidFile #RequestParam("file") MultipartFile file) {
log.info("File Validated");
return ResponseEntity.status(HttpStatus.OK).body(new ResponseData("Valid file received"));
}
}
Validator Annotation
#Documented
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {FileValidator.class})
public #interface ValidFile {
Class<? extends Payload> [] payload() default{};
Class<?>[] groups() default {};
String message() default "Only pdf,xml,jpeg,jpg files are allowed";
}
Validator class
#Slf4j
public class FileValidator implements ConstraintValidator<ValidFile, MultipartFile> {
#Override
public void initialize(ValidFile validFile) {
log.info("File validator initialized!!");
}
#Override
public boolean isValid(MultipartFile multipartFile,
ConstraintValidatorContext constraintValidatorContext) {
log.info("Validating file");
String contentType = multipartFile.getContentType();
assert contentType != null;
return isSupportedContentType(contentType);
}
private boolean isSupportedContentType(String contentType) {
return contentType.equals("application/pdf")
|| contentType.equals("text/xml")
|| contentType.equals("image/jpg")
|| contentType.equals("image/jpeg");
}
}
Output :
Success:
{
"message": "Valid file received"
}
Exception handler
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>("Validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
Failure:
Validation error: Only pdf,xml,jpeg,jpg files are allowed
Below is a small example. I hope it will help.
#Component
public class MultipartFileValidator implements Validator {
#Override
public boolean supports(Class < ? > clazz) {
return MultipartFile.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MultipartFile multipartFile = (MultipartFile) target;
if (multipartFile.isEmpty()) {
// Add an error message to the errors list
errors.rejectValue("file", "required.file");
}
}
}

How to validate a single value but not annotated pojo with specific ConstraintValidator in spring boot

My purpose is to use a specific ConstraintValidator in 2 scenarios below
use annotations on POJO to validate the whole object (the popular way)
validate a single value with specific validator's isValid function (for some configurable dynamic validation request)
The validator must support services injection, so I must get it from spring but not create a new validator instance manually.
followed my test codes:
annotation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { IdNumberValidator.class })
public #interface IdNumber {
String message() default "id number is not available";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
validator
public class IdNumberValidator implements ConstraintValidator<IdNumber, String>, BeanNameAware {
#Autowired
private IUserService usrService;
private String bn;
#Override
public boolean isValid(String value, ConstraintValidatorContext context){
System.out.println("my name is:" + bn);
return usrService.getUserByAccount(value).isPresent();
}
#Override
public void setBeanName(String name) {
this.bn = name;
}
}
pojo
#Getter
#Setter
public class TestPOJO {
private long id;
#IdNumber
private String idn;
}
service
#Service
public class TestValidatorService {
#Autowired
private Validator validator;
#Autowired
private ApplicationContext context;
public void validatePojo(TestPOJO pojo){
BeanPropertyBindingResult e = new BeanPropertyBindingResult(pojo, "TestPOJO");
validator.validate(pojo, e);
if(e.hasErrors()){
for(ObjectError oe : e.getAllErrors()){
System.out.println(oe.toString());
}
}
}
public void validatePojoByDynamicValidator(TestPOJO pojo){
IdNumberValidator validator = context.getBean("com.test.IdNumberValidator", IdNumberValidator.class); // got the name via BeanNameAware but seems not working
System.out.println(validator.isValid(pojo.getIdn(), null));
}
}
In the test case for service, function "validatePojo" passed but "validatePojoByDynamicValidator" did not.
Any solution for this problem? Thanks!

Hibernate validation - autowired returns null

After looking around, I couldn't find any good solution to this.
My autowired didn't work as expected where it returns null. I've autowired this particular class in other classes and it works so it only doesn't work in constraintvalidator classes.
UserService class
#Service
public class UserService {
#Autowired
private UserRepository userRep;
public void addUser(User user) {
userRep.save(user);
}
public void deleteUser(long userId) {
userRep.deleteById(userId);
}
public List<User> retrieveAllUsers(){
Iterable<User>temp =userRep.findAll();
List<User>allUsers = null;
temp.forEach(allUsers::add);
return allUsers;
}
public boolean searchByEmail(String email) {
return userRep.findByEmail(email);
}
public void updateUser(User user) {
userRep.save(user);
}
}
Annotation interface class
#Target(ElementType.FIELD)
//When will the annotation be processed compilation, runtime etc
#Retention(RetentionPolicy.RUNTIME)
//Where is the logic
#Constraint(validatedBy = EmailValidator.class)
#Documented
public #interface ValidEmail {
//Error message
String message() default "Invalid email";
//Required for annotation
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Annotation logic class. The autowired here returns null
public class EmailValidator implements ConstraintValidator<ValidEmail, String> {
#Autowired
private UserService service;
//Actual place to place the logic to check if the data is valid or not
#Override
public boolean isValid(String email, ConstraintValidatorContext context) {
if (email == null) {
return false;
}
List<User> users = service.retrieveAllUsers();
if (users.size() > 0) {
return Pattern.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", email)
&& service.searchByEmail(email);
}
else {
return Pattern.matches("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")#(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])", email);
}
}
#Override
public void initialize(ValidEmail validEmail) {
validEmail.message();
}
}
Main
#SpringBootApplication
#ComponentScan(basePackages = {
"com.Alex.Mains", "com.Alex.UserPackage", "com.Alex.Flights", "com.Alex.Security"
})
#EntityScan( basePackages = {"com.Alex.UserPackage", "com.Alex.Flights"})
#EnableJpaRepositories({"com.Alex.UserPackage", "com.Alex.Flights"})
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
// #Bean
// public Validator validator(final AutowireCapableBeanFactory beanFactory) {
//
// ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
// .configure()
// .constraintValidatorFactory(new SpringConstraintValidatorFactory(beanFactory))
// .buildValidatorFactory();
//
// return validatorFactory.getValidator();
// }
}
Edit: Tried #Componenet
Fixed with adding the following to application.properties. No idea why but it works
spring.jpa.properties.javax.persistence.validation.mode=none
EDIT: My Suggestion
Instead of a custom validator, use the existing #EMail and a unique constraint:
#Entity
public class User {
// ...your properties
#Email
#Column(unique = true)
private String email.
// Rest of class...
}
OLD:
So, first off:
List<User> users = service.retrieveAllUsers();
if (users.size() > 0) {
You are fetching all the Users from the database, just to check whether any users exists? This is very, very inefficient. If you are already using Spring Data, you can just do
#Query("SELECT COUNT(*) > 0 FROM Users")
boolean anyExists();
Furthermore, your Service does not get injected, because EmailValidator is a POJO (plain old java object) and not a Spring managed component. If you annotate it with #Component or #Service Spring will take care of injection.
But I would not recommend that. I'm not sure what your exact use case is, but validators are often used on Entities and as such, they get called when the entity is created or updated. You don't want to issue additional queries in those cases.
Like I said, I don't know what exactly you are trying to achieve, but you could use the existing #Email validator (you can even provide a custom regular expression with the regexp attribute).

Custom annotation for request param is not recognizing while testing the controller method

Request Param validated with custom annotation #ValidNumber which is created by using the below code.
#Constraint(validatedBy=NumberValidater.class)
#Target(Field, Parameter,..)
#Retention(RUNTIME)
#Documented
public #interface ValidNumber {
String message() default
"{javax.validation.constraints.ValidNumber.message}"
Class<?>[] groups() default {};
Class<? extends PayLoad>[] payload() default {};
}
NumberValidator Class
public NumberValidator implements <ConstraintValidator, Integer>{
public void intialize(ValidNumber constraintAnnotation){
}
public boolean isValid(Integer number, ConstraintValidatorContext context){
return(number !=null && number !=0)
}
Configuration Class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Controller Class is:
#RestController
#Validated
public StudentController{
#RequestMapping(value="rollNumber", method=GET)
public Student getStudentHallticket(#ValidNumber (message= "Invalid RollNumber)
#RequestParam(vallue = "rollNumber") Integer rollNumber{
return service.getHallTicket();
}
Java Class executed perfectly and validated the input value, but the test class is not correctly validating the input param.
Test Case:
class StudentSpec extends Specification{
#Autowired
private StudentController studentController;
#Autowired
private LocalValidatorFoctoryBean validator
private MockMvc mockmvc
def setup(){
mockmvc.MockMvcBuilders.standloneSetup(studentController).setValidator(validator).build()
}
def getStudentHallTicket(){
given:
def rollNumber = ""
when:
def response = mockmvc.perform(get("/rollNumber/").param("rollNumber", rollNumber)
then:
response.andExpect(status().isBadRequest())
}
}
When executed it is giving `isOk()` instead of `isBadRequest()` status that means request parameter not validating when it is null.
My question is, how do I make sure the custom annotation is available for testing class?

Custom spring validator not being invoked

I've created a custom constraint validator that works on a list of objects. Unfortunately it doesn't seem to be getting invoked, it worked when I had a wrapper class containing the list with the annotation on the list.
This is the code that worked fine
public class wrapper {
#ValidMyObjectList
List<MyObject> myObjects;
...
}
But now I've got rid of the wrapper class and added the annotation to the parameter in the controller method.
Here's the controller
#RequestMapping(value = "", method = RequestMethod.POST)
public List<MyObject> stopCheque(
#ValidMyObjectList #RequestBody final List<MyObject> myObjects,
final HttpServletResponse httpServletResponse) {
....
}
Here's the constraint annotation
#Constraint(validatedBy = MyObjectListValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
public #interface MyObjectList {
Class<?>[] groups() default {};
String message() default "";
Class<? extends Payload>[] payload() default {};
}
And part of the validator itself
public class MyObjectListValidator implements
ConstraintValidator<MyObjectList, List<MyObject>> {
#Override
public void initialize(final MyObjectList myObjectList) {
}
#Override
public boolean isValid(final List<MyObjectList> myObjectLists, final ConstraintValidatorContext cxt) {
...
}
Would greatly appreciate any help. Thanks
Add to your Spring config class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
and add #Validated to controller class.

Resources