How validate domain object in controller input filled by Spring Data? - java

For example I have:
#PostMapping("/person/{id}")
#ResponseBody
public boolean isAdmin(#PathVariable("id") Person person) {
return person != null && person.isAdmin();
}
How I can get same result using Validation?
This not works for me, but I look up for something like this, without manual checking in method body. Is there way for doing so?
#PostMapping("/person/{id}")
#ResponseBody
public boolean isAdmin(#PathVariable("id") #NotNull Person person) {
return person.isAdmin();
}

you need to use javax.validation.Valid and add constraints to your Person pojo: javax.validation.constraints.Email/NotBlank/NotNull;
public class Person {
#NotNull
private Long id;
/* here getters and setters...*/
}
In your controller:
#PostMapping("/person/{id}")
#ResponseBody
public boolean isAdmin(#Valid #RequestBody Person person) {
return person.isAdmin();
}
see my github

Related

How to use validation annotation in setter method with Jackson

I know that I could put the annotations on age field.
But I want to validate age in setAge method, here is my code:
class UserController {
#PutMapping
public ResponseEntity partialUpdate(#RequestBody #Validated UserDTO userDTO) {
return userService.partialUpdate(userDTO);
}
}
public class UserDTO {
private String username;
private Integer age;
public void setAge(#Min(0) #Max(100) Integer age) {
this.age = age;
}
}
It's valid when I give age=101, seems the validations not work.
Seems like you need to use the Validator to check whether you class is valid.
If you are creating a object of UserDTO.
UserDTO user = new UserDTO("My Name", 101);
Then perform the following
Validator validator = Validation.buildDefaultValidatorFactory.getValidator();
Set<ConstraintViolation<SomeEntity>> errors = validator.validate(user);
Then based on your requirement do the necessary things like notify client what was the wrong field, log them. If empty then it seems like everything is valid and proceed further.
if(CollectionUtils.isNotEmpty(errors)){
throw new ConstraintViolationException(errors);
}

Implementatio of mutliple ConstraintValidator and their priority (enable/disable by requests endpoint)

Lets say I have an Object with two fields which should be validated:
public class AnyRQ {
#MerchantAccountValidation
#JsonProperty(value = "merchant-account")
private MerchantAccount merchantAccount;
#RequestIdValidation
#JsonProperty(value = "request-id")
private String requestId;
}
Both of the Annotations #MerchantAccountValidation and #RequestIdValidation implements a ConstraintValidator and including the rules to be valid or not. (Just show one class)
public class RequestIdValidator
implements ConstraintValidator<RequestIdValidation, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && value.length() > 10;
}
}
Now I have a Controller with two endpoints. Endpoint 1 should validate both Fields but Request 2 should just validate requestId.
#RestController
#RequestMapping("/validate")
public class ValidController {
#PostMapping("/endpoint1")
public ResponseEntity<?> register(#Valid #RequestBody AnyRQ req, Errors errors) {
if (errors.hasErrors()) {
}
return null;
}
#PostMapping("/endpoint2")
public ResponseEntity<?> authorization(#Valid #RequestBody AnyRQ req, Errors errors) {
if (errors.hasErrors()) {
}
return null;
}
}
Is there any way to achive a kind of priority or inheritance to get this working? I was thinking about to have the Validation Annotation on the method level of the endpoints. But unfortunately this is not working.
Patrick!
To achieve the desired outcome you can use #GroupSequence. It mostly meant for ordering validations (no need to check that entity exists in database, if id is null f.e.), but would work for your task.
Let's say you have 2 validation groups (better names are required :) ):
public interface InitialValidation {
}
#GroupSequence(InitialValidation.class)
public interface InitialValidationGroup {
}
public interface FullValidation {
}
#GroupSequence(FullValidation.class)
public interface FullValidationGroup {
}
Specify them in the DTO:
public class AnyRQ {
#MerchantAccountValidation(groups = FullValidation.class)
#JsonProperty(value = "merchant-account")
private MerchantAccount merchantAccount;
#RequestIdValidation(groups = {InitialValidation.class, FullValidation.class})
#JsonProperty(value = "request-id")
private String requestId;
}
And in the controller use #Validated instead of #Valid to provide corresponding group:
#RestController
#RequestMapping("/validate")
public class ValidController {
#PostMapping("/endpoint1")
public ResponseEntity<?> register(#Validated(FullValidationGroup.class) #RequestBody AnyRQ req, Errors errors) {
if (errors.hasErrors()) {
}
return null;
}
#PostMapping("/endpoint2")
public ResponseEntity<?> authorization(#Validated(InitialValidationGroup.class) #RequestBody AnyRQ req, Errors errors) {
if (errors.hasErrors()) {
}
return null;
}
}
The other option is to keep one group in DTO, but specify two groups in controller for #Validated.

Java Validating a extended Pojo

I am building project on spring boot and want to add validation that are easy to integrate.
I have Pojo for my project as below:
public class Employee{
#JsonProperty("employeeInfo")
private EmployeeInfo employeeInfo;
}
EmployeeInfo class is as below:
public class EmployeeInfo extends Info {
#JsonProperty("empName")
private String employeeName;
}
Info class is as below:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Info {
#JsonProperty("requestId")
protected String requestId;
}
How to I validate if request Id is not blank with javax.validation
My controller class is as below:
#RequestMapping(value = "/employee/getinfo", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<> getEmployee(#RequestBody Employee employee) {
//need to validate input request here
//for e.g to check if requestId is not blank
}
Request :
{
"employeeInfo": {
"requestId": "",
}
}
Considering you are making use of validation-api:
Please try using below to validate if your String is not null or not containing any whitespace
#NotBlank
In order to validate request parameters in controller methods, you can either use builtin validators or custom one(where you can add any type of validations with custom messages.)
Details on how to use custom validations in spring controller, Check how to validate request parameters with validator like given below:
#Component
public class YourValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return clazz.isAssignableFrom(Employee.class);
}
#Override
public void validate(Object target, Errors errors) {
if (target instanceof Employee) {
Employee req = (Employee) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "employeeInfo.requestId", "YourCustomErrorCode", "yourCustomErrorMessage");
//Or above validation can also be done as
if(req.getEmployeeInfo().getRequestId == null){
errors.rejectValue("employeeInfo.requestId", "YourCustomErrorCode", "YourCustomErrorMessage");
}
}
}
}

How to validate #RequestMapping requests by condition?

I'm using hibernate validations for validating incoming requests in spring #RestController.
Problem: I want to reuse the same DTO object in multiple endpoints. But validate some fields only by condition (eg only on specific endpoints).
#RestController
public class ProductsServlet {
#GetMapping("/avail/product")
public Object avail(#Valid ProductDTO product) {
//should validate id field only
}
#GetMapping("/sell/product")
public Object sell(#Valid(with = GroupFuture.class) ProductDTO product) {
//should validate id + from field
}
}
public class ProductDTO {
#NotNull
#NotBlank
private String id;
#Future(groups = GroupFuture.class)
private Date from;
}
Of course the #Valid(with = GroupFuture.class) statement is invalid. But it shows what I'm trying to achieve.
Is that possible?
Got it. Also having to use the Default.class group to validate any fields not having a group.
#GetMapping("/sell/product")
public Object sell(#Validated({Default.class, GroupFuture.class}) ProductDTO product) {
}

Removing duplication from Spring controllers

I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.

Categories

Resources