Validation of generic parameters in Spring controllers

  • Tutorial
image
We all often write simple methods in controllers working through numeric identifiers.

@RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)
    @ResponseBodypublic Entity get(@PathVariable(value = "entityId") Integer entityId) {
        //возврат значения сущности по ID
    }

The incoming ID must be validated.For example, not all users have access to all IDs.

It is understood that using a UUID is safer and more reliable. But it must be created, stored, indexed and so on. For all entities, doing this is long and difficult. In many cases, it is easier to work with ordinary numeric IDs.

Validation can be done in a simple way:

@RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)
    @ResponseBodypublic Entity get(@PathVariable(value = "entityId") Integer entityId) {
        if(!@dao.validate(entityId))
             return some_error;
        //возврат значения сущности по ID
    }

Plus, in this decision, only one. Simple and fast.
Everything else is bad. Duplication of the code, validation is not compatible with the validation of objects, separate processing is needed in each method.

I want to do this:

@RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)
    @ResponseBodypublic Entity get(@Validated @PathVariable(value = "entityId") Integer entityId) {
        //возврат значения сущности по ID
    }

This simple and logical option does not work. The validator is simply not called. Validation of PathVariable Spring is not supported.

For this option to work, you must turn PathVariable into a ModelAttribute:

@ModelAttributeprivate Integer integerAsModelAttribute(@PathVariable("entityId") Integer id) {
        return id;
}

And get an error when accessing the controller. Wrappers generic types have no default constructor without parameters and no setter. It does this by using Optional. He has both a default constructor and a setter that accepts ordinary inty.

Turn Integer into Optional:

@ModelAttributeprivate Integer integerAsModelAttribute(@PathVariable("entityId") Optional<Integer> id) {
        return id.orElse(null);
    }

And accordingly the controller method itself and the validator declaration:

@InitBinder({"entityId"})
    protectedvoidinitCommissionIdBinder(WebDataBinder binder){
        binder.setValidator(validateEntityIdValidator);
        binder.setBindEmptyMultipartFiles(false);
    }
    @RequestMapping(value = {"/entityName/{entityId}/get"}, method = RequestMethod.GET)
    @ResponseBodypublic Entity get(@Validated @ModelAttribute(value = "entityId") Integer entityId) {
       //Можно смело работать с ID. Оно уже валидировано.
    }

The validator class is absolutely normal:

publicclassValidateIntegerValidatorimplementsValidator{
    @Overridepublicbooleansupports(Class<?> aClass){
        return Integer.class.equals(aClass);
    }
    @Overridepublicvoidvalidate(Object o, Errors errors){
        if(o instanceof Integer) {
            Integer integer = (Integer) o;
            if(!dao.checkId(integer)) {
                errors.reject("-1", "ERROR");
            }
        } else {
            errors.reject("-2","WRONG TYPE");
        }
    }
}

A full working example can be found here .

Also popular now: