I have the following classes used for validating a password.
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
#Override
public void initialize(ValidPassword constraintAnnotation) {
}
#Override
public boolean isValid(String password, ConstraintValidatorContext context) {
PasswordValidator validator = new PasswordValidator(Arrays.asList(
// at least 8 characters
new LengthRule(8, 30),
// at least one upper-case character
new CharacterRule(EnglishCharacterData.UpperCase, 1),
// at least one lower-case character
new CharacterRule(EnglishCharacterData.LowerCase, 1),
// at least one digit character
new CharacterRule(EnglishCharacterData.Digit, 1),
// at least one symbol (special character)
new CharacterRule(EnglishCharacterData.Special, 1),
// no whitespace
new WhitespaceRule()
));
RuleResult result = validator.validate(new PasswordData(password));
if (result.isValid()) {
return true;
}
List<String> messages = validator.getMessages(result);
String messageTemplate = messages.stream().collect(Collectors.joining(","));
context.buildConstraintViolationWithTemplate(messageTemplate)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
}
#Documented
#Constraint(validatedBy = PasswordConstraintValidator.class)
#Target( {ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.LOCAL_VARIABLE, ElementType.TYPE_PARAMETER, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidPassword {
String message() default "Invalid Password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Adding the #ValidPassword annotation for a field in a class works.
However when I try adding the annotation to a parameter in a function, the validator is never called/reached.
public void resetUserPassword(Integer userId, #ValidPassword String newPassword) {
}
Also adding the annotation here doesn't work either:
#PostMapping("/user/resetPassword/{id}")
public ResponseEntity<?> resetUserPassword(#PathVariable("userId") Integer userId, #Valid #ValidPassword #RequestBody String newPassword) {
userService.resetUserPassword(userId, newPassword)
return ResponseEntity.ok().build();
}
I don't think I am missing any dependencies, so I'm not sure where the problem is.
The annotation #Validated defined at class-level annotation is
necessary to trigger method validation for a specific bean to begin
with.
Also in other words
The #Validated annotation is a class-level annotation that we can
use to tell Spring to validate parameters that are passed into a
method of the annotated class.
Refer this link https://github.com/spring-projects/spring-framework/issues/11039 to find out the origin for #Validated
Usage:
As you have the below method with your custom annotation #ValidPassword with your #Valid
#PostMapping("/user/resetPassword/{id}")
public ResponseEntity<?> resetUserPassword(#PathVariable("userId") Integer userId, #Valid #ValidPassword #RequestBody String newPassword) {
userService.resetUserPassword(userId, newPassword)
return ResponseEntity.ok().build();
}
#Valid
It is used for enabling the whole object validation As you can see in the below example the #NotNull #Size and #NotBlank will be invoked to validate the user input or supplied values present in the object.
Eg:
public class DummyUser{
#NotNull
#Size(min =8)
private String password;
#NotBlank
private String username;
}
#Validated
But, As per your case you want your custom validation to be invoked on the method parameter , Hence you need to provide the hint to spring to invoke the custom validation. So, to do that you have declare the #Validated annotation at class level in your controller.
So these are the reasons for your validation to start working after you have annotated your controller class with #Validated annotation.
You need to add #Validated annotation on the controller class or another class, where you want to validate method parameter with your custom validation.
There was an explanation in spring-boot 2.1.x documentation about this kind of method-level validation, but I couldn't find it in current 2.7.x docs.
In general it's a spring-framework feature, that can be found here. In a non-boot project you'll need to create a bean of type MethodValidationPostProcessor manually, but spring-boot auto-configurates this bean for you - the autoconfiguration can be found in ValidationAutoConfiguration class.
According to java-docs of MethodValidationPostProcessor, target classes with JSR-303 constraint annotated methods need to be annotated with Spring's #Validated annotation at the type level, for their methods to be searched for inline constraint annotations. Validation groups can be specified through #Validated as well. By default, JSR-303 will validate against its default group only.
Related
In RestController, I am using #Valid annotation to validate custom class's members as below
#PostMapping("/path1")
public ResponseEntity<String> function1(#Valid CustomClass customClass) {
...
}
I defined custom validator to CustomClass's specific member, for instance, #CustomValid.
And, I have to use #Validated for other function's #RequestParam
#PostMapping("/path2")
public ResponseEntity<String> function2(#RequestParam("param") #NotBlank String param) {
...
}
To run second validator, I have to add #Validated annotation at class level.
But when I add #Validated, the #CustomValid called twice. When I remove #Validated, #NotBlank does not called.
How can I fix this?
I want to know if there is a way to create my own custom annotation.
What I want to do is to create an annotation that with a given name and a value, puts that key on my headers.
This is custom annotation prototype:
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ResponseHeader {
String value() default "";
String name() default "";
}
And this is what I want to call:
public #ResponseHeader(name="Location", format = "/test/id/%s") String test() {
return UUID.randomUUID();
}
What I want to do is by using my custom annotation, overriding the spring functionality to add the to the headers the entry (Location->/test/id/UUID generated) and create a response entity with that.
Is this possible?
I know that maybe my answer doesn't directly answer your question, but to achieve your target, you could :
Use HandlerInterceptorAdapter
Use ResponseEntity as your API response
ResponseEntity.status(HttpStatus.CREATED).header(HttpHeaders.LOCATION, "/test/id/" + randomId).body(...);
Given multiple REST resources to gather order information.
/order/{id}
/order/{id}/positions
/order/{id}/invoice
/order/{id}/shipment
Within the a Srping Boot 2 application it's implemented across multiple controllers, e.g. OrderController, InvoiceController, etc.
Currently every controller uses the OrderRepository to ensure the the order with the given id exists. Otherwise it throws an exception. It's always the same replicated code.
#RestController
public class OrderController {
// ...
#GetMapping("order/{id}")
public Order getCustomer(#PathVariable final Integer id) {
return this.orderRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("order not found"));
}
}
Does the framework provide a callback to write the order id check just once?
I found the AntPathMatcher but it seems not the right way, since it provides just an boolean interface.
This is usually a good case for bean validation. While there is already builtin support for many cases of validation (#Size, #NotNull, ...), you can also write your own custom constraints.
First of all, you need to define an annotation to validate your ID, for example:
#Documented
#Constraint(validatedBy = OrderIdValidator.class)
#Target({PARAMETER})
#Retention(RUNTIME)
public #interface ValidOrderId {
String message() default "Invalid order ID";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Since your order ID is always a parameter for your controller mappings, you could use ElementType.PARAMETER to only allow the annotation for method parameters.
The next step is to add the #Constraint annotation to it and point to a custom validator class (eg. OrderIdValidator):
#Component
public class OrderIdValidator implements ConstraintValidator<ValidOrderId, Integer> {
private OrderRepository repository;
public OrderIdValidator(OrderRepository repository) {
this.repository = repository;
}
#Override
public boolean isValid(Integer id, ConstraintValidatorContext constraintValidatorContext) {
return repository.existsById(id);
}
}
By implementing the isValid method, you can check whether or not the order exists. If it doesn't exist, an exception will be thrown and the message() property of the #ValidOrderId annotation will be used as a message.
The last step is to add the #Validated annotation to all of your controllers, and to add the #ValidOrderId annotation to all order ID parameters, for example:
#Validated // Add this
#RestController
public class OrderController {
#GetMapping("order/{id}")
public Order getCustomer(#PathVariable #ValidOrderId final Integer id) { // Add #ValidOrderId
// Do stuff
}
}
If you prefer to use a different response status for your validations, you could always add a class annotated with the #ControllerAdvice annotation and use the following method:
#ExceptionHandler(ConstraintViolationException.class)
public void handleConstraints(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
I am newbie in Java world (came from .Net background). I created a RESTful service using Jersey framework. it has couple of methods. Following is the sample code snippet for Customer service. There are couple more services in my code.
#Path("/CustomerService")
public interface ICustomerService {
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Path("/getCustomerInfo")
Response query(String constraints);
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#Path("/getCustomerDetails")
Response fetchDetails(String customerID);
}
I have some validation logic which I would like to perform at each API which is exposed to the client. I C# world we can define own validation logic. Something like following, This can be applied at method or controller level.
[MyValdationLogic()]
What is the equivalent in Java? How can I write code which can be applied at multiple places over the method.
Also I do not want to allow admin to play with that configuration. I found there is something called as filters but this gets configured in config file. admin can disable it.
You can create a Validator class and use that Validator class on your bean for validation. Although, the process is bit lengthy.
Following is one example of doing this -
Jersey Resource
#POST
#Path("/addEmp")
#Produces("text/plain")
public String doOrder(#BeanParam final #Valid Employee emp) {
// Some implementation here
}
Sample Bean -
Suppose, I want to apply validation on address i.e. either address or city or postcode must be there.
#Address
public final class Employee {
#FormDataParam("id")
private String id;
#FormDataParam("address")
private String address;
#FormDataParam("city")
private String city;
#FormDataParam("postcode")
private String postcode;
// Other member variables
// Getters and setters
}
Address Annotation -
Define custom Address Annotation
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
#Retention(RUNTIME)
#Constraint(validatedBy = AddressValidator.class)
#Documented
public #interface Address {
String message() default "Address required";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator Class -
Here is the validator class contains actual validation logic -
public class AddressValidator implements ConstraintValidator<Address, Employee> {
#Override
public boolean isValid(Employee emp, ConstraintValidatorContext constraintValidatorContext) {
// Check for at least one value
if((emp.getAddress() != null && !emp.getAddress().equals("") ||
(emp.getCity() != null && !emp.getCity().equals("")) ||
(emp.getPostcode() != null && !emp.getPostcode().equals("")))) {
return true;
}
return false;
}
public void initialize(Address emp) {
...
}
}
By this way, you can create reusable Validator class. Instead of taking Employee directly in this Validator class, you can take Object class or some parent class and then change the logic accordingly.
You can check more details in bean-validation
I've a form I want to validate. It contains 2 Address variables. address1 has always to be validated, address2 has to be validated based on some conditions
public class MyForm {
String name;
#Valid Address address1;
Address address2;
}
public class Address {
#NotEmpty
private String street;
}
my controller automatically validates and binds my form obj
#RequestMapping(...)
public ModelAndView edit(
#ModelAttribute("form")
#Valid
MyForm form,
BindingResult bindingResult,
...)
if(someCondition) {
VALIDATE form.address2 USING JSR 303
the problem is that if I use the LocalValidatorFactoryBean validator i can't reuse the BinidingResult object provided by Spring. The bind won't work as the target object of 'result' is 'MyForm' and not 'Address'
validate(form.getAddress2(), bindingResult) //won't work
I'm wondering what's the standard/clean approach to do conditional validation.
I was thinking in programmatically create a new BindingResult in my controller.
final BindingResult bindingResultAddress2 = new BeanPropertyBindingResult(address2, "form");
validate(form.getAddress2(), bindingResultAddress2);
but then the List of errors I obtain from bindingResultAddress2 can't be added to the general 'bindingResult' as the field names are not correct ('street' instead of 'address2.street') and the binding won't work.
Some dirty approach would be to extend BeanPropertyBindingResult to accept some string to append to the fields name.. do you have a better approach?
The standard approach for validating hierarchical structures is to use pushNestedPath()/popNestedPath(), though I'm not sure how it plays with JSR-303:
bindingResult.pushNestedPath("address2");
validate(form.getAddress2(), bindingResult);
bindingResult.popNestedPath();
I've never tried myself, but I think the correct approach is using validator groups.
First of all, let's see #javax.validation.Valid API
Mark an association as cascaded. The associated object will be validated by cascade.
When Spring framework uses #Valid as a marker to validate its command objects, it corrupts its purpose. Spring should instead create your own specific annotation which specifies the groups which should be validated.
Unfortunately, you should use Spring native Validator API if you need to validate some groups
public void doSomething(Command command, Errors errors) {
new BeanValidationValidator(SomeUserCase.class, OtherUserCase.class)
.validate(command, errors);
if(errors.hasErrors()) {
} else {
}
}
BeanValidationValidator can be implemented as
public class BeanValidationValidator implements Validator {
javax.validation.Validator validator = ValidatorUtil.getValidator();
private Class [] groups;
public BeanValidationValidator(Class... groups) {
this.groups = groups;
}
public void validate(Object command, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(command, groups);
for(ConstraintViolation<Object> constraintViolation: constraintViolationSet) {
errors.rejectValue(constraintViolation.getPropertyPath().toString(), null, constraintViolation.getMessage());
}
}
}