How can I validate size of Pageable?

Viewed 3693

I'm using org.springframework.data.domain.Pageable with my @RestController.

How can I validate or limit the page size?

Without any validation, when clients call with size of 10000. The actual pageSize is 2000.

This could lead wrong signal for last page, I think.

How can I validate it and notify clients about it? Say with 400?

3 Answers

You can write a custom annotation to validate Pageable object

@Constraint(validatedBy = PageableValidator.class)
@Target( { ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface PageableConstraint {
    String message() default "Invalid pagination";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    int maxPerPage() default 100;;
}

and Its implementation

public class PageableValidator implements
        ConstraintValidator<PageableConstraint, Pageable> {

    private int maxPerPage;

    @Override
    public void initialize(PageableConstraint constraintAnnotation) {
        maxPerPage=constraintAnnotation.maxPerPage();
    }

    @Override
    public boolean isValid(Pageable value, ConstraintValidatorContext context) {
        return value.getPageSize()<=maxPerPage;
    }
}

and you can use it over your controller like any other javax validation annotations.

@RestController
@RequestMapping("/api")
@Validated
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List<Employee> getAllEmployees(@PageableConstraint(message = "Invalid page size",maxPerPage = 400) Pageable pageable) {
        return employeeService.getAllEmployees();
    }
}

Try using Custom Annotations to validate the Pageable object. If it is not working, try the argument resolver as shown below.

You can create Argument Resolver in your Spring Configuration that is extending WebMvcConfigurerAdapter

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver() {

        @Override
        public Pageable resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
            Pageable p = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
            if (webRequest.getParameter("per_page") != null) {
                int pageSize = Integer.parseInt(webRequest.getParameter("per_page"));
                if (pageSize < 1 || pageSize > 2000) {
                    message = "Invalid page size";
                }
            }
            if (message != null) {
                Set<CustomConstraintViolation> constraintViolations = new HashSet<>();
                constraintViolations.add(new CustomConstraintViolationImpl(message));
                throw new ConstraintViolationException(constraintViolations);
            }
            return new PageRequest(p.getPageNumber(), p.getPageSize());
        }
    };
    resolver.setMaxPageSize(2000);
    argumentResolvers.add(resolver);
    super.addArgumentResolvers(argumentResolvers);
}

This resolver will make sure the max page size is 2000 for every request to your controller.

You need to throw a ConstraintViolationException when the size is more than 2000. For that you need to create a custom ConstraintViolation interface and implement it

public CustomConstraintViolation implements ConstraintViolation<CustomConstraintViolation> {}

public CustomConstraintViolationImpl implements CustomConstraintViolation {

...

}
Related