I have the following PUT method in my Spring Controller class. There is a parameter annotated with #PathVariable. There is another parameter annotated with #RequestBody #Valid. The problem is RequestBody is getting validated first and the method returns in case of exception. But I want #Pathvariable to be validated first and return in case of exception.
#RestController
#Validated
#RequestMapping(value = "/v1/order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderMappingController {
#PutMapping(value = "/{id}/order-mapping", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> UpdateOrderMapping(#PathVariable(value = "id") #NotBlank(message = "InvalidID")
#ValidOrderId String id,
#RequestBody #Valid OrderMappingRequest request) {
//...
}
}
Thanks in Advance!
You can write custom annotation and validator to validate the #Pathvariable
#RestController
#Validated
#RequestMapping(value = "/v1/order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderMappingController {
#PutMapping(value = "/{id}/order-mapping", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> UpdateOrderMapping(#PathVariable(value = "id") #OrderId String id,
#RequestBody #Valid OrderMappingRequest request) {
//...
}
}
Create custom annotation OrderId which can validate with custom validator class OrderIdValidator
#Constraint(validatedBy = OrderIdValidator.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.METHOD,PARAMETER })
public #interface OrderId{
public String message() default "order id not found ";
public Class<?>[] groups() default {};
public Class<? extends Payload>[] payload() default{};
}
Then you can write the custom validator class which can check the business logic if the id is present in db or any other validation
public class OrderValidator implements ConstraintValidator<OrderId, String> {
#Autowired
private OrderRepository repository;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return (repository.existsByPeriod(value));
}
}
I suggest to try a different approach. Try not to annotate #Valid on your OrderMappingRequest, then implement a custom org.springframework.validation.Validator for your OrderMappingRequest bean which will then be injected and invoke in your OrderMappingController#UpdateOrderMapping method.
public class OrderMappingController {
#Autowired
private OrderMappingRequestValidator validator
#PutMapping("/{id}/order-mapping")
public ResponseEntity<?> UpdateOrderMapping(#PathVariable
#NotBlank(message = "InvalidID")
#ValidOrderId String id,
#RequestBody OrderMappingRequest request,
BindingResult result) {
validator.validate(request, result);
if(result.hasErrors()){
... do stuff or throw
}
}
}
Related
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!
I'm implementing a #RestController and I realized that #Valid is working with #RequestBody or #ModelAttribute params of a #GetMapping method, but not with a #RequestAttribute parameter.
To get validated the #RequestAttribute annotated param I have to annotate my Controller class with #Validated.
Following my code:
Controller
#Log4j2
#RestController
#RequestMapping("/test/api/v1/entity")
public class MyController extends SomeController {
#GetMapping("/getInfo")
public ResponseEntity<<MyResponse>> infoStatus (RequestParam(required = false) String inputStr,
#Valid #RequestAttribute ObjectToValidate objToValidate){
//Any stuff here
}
}
Bean to validate
#Getter
#Setter
#Valid
public class ObjectToValidate {
#NotNull
#NotEmpty
private String anyCode;
}
The result is anyCode is not checked to be not null nor empty.
If I annotate MyController with #Validate, the ObjectToValidate param is validate as expected.
If I change controller as follows, the validation also works.
#Log4j2
#RestController
#RequestMapping("/test/api/v1/entity")
public class MyController extends SomeController {
#ModelAttribute
public ObjectToValidate addToModel(#RequestAttribute ObjectToValidate
objToValidate) { return objToValidate; }
#GetMapping("/getInfo")
public ResponseEntity<MyResponse> infoStatus (
#RequestParam(required = false) String inputStr,
#Valid #ModelAttribute ObjectToValidate objToValidate
){
//Any stuff here
}
}
Please, could you explain why?
#Valid can be used on #RequestBody Controller Method Arguments.
That is #RequestBody method argument can be annotated with #Valid to invoke automatic validation.
It will be no use if you annotate ObjectToValidate class with #Valid.
#PostMapping("/notes")
Note getNote(#Valid #RequestBody Note note) {
return repository.save(note);
}
To validate the path variable, the controller class should be annotated with #Validated
#RestController
#Validated // class level
public class NoteController {
#GetMapping("/note/{id}")
Note findOne(#PathVariable #NotBlank(message = "Id must not be empty") String id) {
return repository.findById(id)
.orElseThrow(() -> new NotekNotFoundException(id));
}
}
Hope it helps!!
How can I achive that the #ResponseBody (in my case a class of type SomePojoInterface) is automatically validated (lets say through JSR-303 validation). Nice to have would be, that in case of a validation-failure the handler would throw an Exception which can be handled in some #ControllerAdvice annotated class.
My code so far.
#RestController
public class MyRestController {
#GetMapping(value = "validate", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
protected SomePojo validateResponse() {
return new SomePojo();
}
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.XXX)
#ExceptionHandler(MyResponseValidationException.class)
public void handleResponseValidationException() {
// ...
}
}
public class SomePojo implements SomePojoInterface {
#NotNull
private String someValue;
// getter / setter
}
If you have annotated your class SomePojo, then:
#GetMapping(value = "validate", produces = MediaType.APPLICATION_JSON_VALUE)
protected SomePojo validateResponse() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
SomePojo somePojo = new SomePojo(null);
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(somePojo);
// Other stuff
}
#Valid annotation is for request. More examples from their docs. I am not sure what all you want to validate
I managed to achieve this through the #RestControllerAdvice.
#RestControllerAdvice
public class RestPostProcessingAdvice implements ResponseBodyAdvice<SomePojoInterface> {
#Inject
private Validator validator;
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if(doSomeChecksIfEligiable(returnType, converterType)) {
return true;
}
return false;
}
#Override
public SomePojoInterface beforeBodyWrite(SomePojoInterface body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(body);
if(constraintViolations.size() > 0) {
response.setStatusCode(HttpStatus.XXX);
LOG.fatal("Sorry, I'm sending crap");
}
return body;
}
}
Be aware that throwing an Exception and catching it in an #ExceptionHandler that is sending the same (mofified) object out in the #ResponseBody could lead to an endless loop, since the object will be checked again this #RestControllerAdvice.
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.
Service Class
#Service
#RequestMapping(value = "employees")
public interface EmployeeService {
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public #ResponseBody Employee getEmployee(#PathVariable("id") int employeeId) throws EmpException;
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody List<Employee> getAllEmployees() throws EmpException;
#RequestMapping(method = RequestMethod.POST)
public #ResponseBody Employee createEmployee(#RequestBody Employee employee) throws EmpException;
#RequestMapping(value ="{id}", method = RequestMethod.DELETE)
public #ResponseBody UserInfo deleteEmployee(#PathVariable("id") int employeeId) throws EmpException;
#RequestMapping(value="{id}", method = RequestMethod.PUT)
public #ResponseBody Employee updateEmployee(#RequestBody Employee employee,#PathVariable("id") int employeeId) throws EmpException;
}
Implementation class
#Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
#Autowired
private Employee employee;
private static final Logger logger = LoggerFactory.getLogger(EmployeeServiceImpl.class);
public Employee getEmployee(#PathVariable("id") int employeeId) throws EmpException {
logger.info("Start getEmployee. ID="+employeeId);
employee = employeeDao.getEmployee(employeeId);
if(employee != null) {
return employee;
} else {
throw new EmpException("ID: "+employeeId+" is not available");
}
}
}
in implementation class also i used #pathvariable annotation then only value for employeeId will be pass from interface to implementation otherwise null pointer expression will be occur.any other way to pass the value from interface to implementation without using #pathvariable .
Request mappings don't go on service class, they go on controller.
For which you'll need #Controller annotation.