1

Is there any possibility to validate StandardMultipartHttpServletRequest using standard @Valid annotation and custom Validator?

I've implemented such validator, annotated method param in controller the validator is not invoked.

Slava Semushin
  • 14,904
  • 7
  • 53
  • 69
Opal
  • 81,889
  • 28
  • 189
  • 210
  • I've implemented validator for `MultipartFile` interface and it works. Why do you need to validate exactly `StandardMultipartHttpServletRequest`? – Slava Semushin Aug 09 '16 at 15:45
  • @SlavaSemushin, since this is the argument of the method in controller. From it I extract the upload files. – Opal Aug 09 '16 at 16:54
  • Yes, and `StandardMultipartHttpServletRequest` implements `MultipartFile`. If you don't use some implementation specific methods then you can replace argument type by this interface. I don't see what is your problem here. Implement custom validator, put annotation, add `@Valid` annotation, etc. – Slava Semushin Aug 09 '16 at 17:18
  • @SlavaSemushin, could you please provide an example? I need to validate an `List` object. – Opal Aug 10 '16 at 09:26
  • I can provide example (https://github.com/php-coder/mystamps/blob/d8da294f991ccdc7495b150f87b260a635d8d248/src/main/java/ru/mystamps/web/validation/jsr303/NotEmptyFileValidator.java) but it's for validation single object. Unfortunately you didn't mention that fact in your question. Validating collection is a bit different. Dod you saw this question http://stackoverflow.com/questions/4308224/hibernate-validation-of-collections-of-primitives ? – Slava Semushin Aug 10 '16 at 10:35
  • @SlavaSemushin, thanks. I've already resolved it. Will add my answer later on. – Opal Aug 10 '16 at 10:37
  • @SlavaSemushin, answered. – Opal Aug 10 '16 at 11:29

1 Answers1

1

I've figured it out myself. To make it work you need a DTO:

import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Getter
@Setter
public class NewOrderFilesDTO {
    List<MultipartFile> files;
}

Then, a validator:

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import static org.springframework.util.CollectionUtils.isEmpty;

@Component
public class NewOrderFilesValidator implements Validator {

    private static final String MIME_TYPE_PDF = "application/pdf";
    private static final long ALLOWED_SIZE = 3 * 1024 * 1024;

    @Override
    public void validate(Object target, Errors errors) {
        if (target == null) {
            return;
        }

        NewOrderFilesDTO newOrderFilesDTO = (NewOrderFilesDTO) target;
        List<MultipartFile> newOrderFiles = newOrderFilesDTO.getFiles();

        if (isEmpty(newOrderFiles)) {
            return;
        }

        for (MultipartFile file : newOrderFiles) {
            if (!MIME_TYPE_PDF.equals(file.getContentType())) {
                errors.rejectValue(file.getName(), file.getName(), "'application/pdf' files allowed only!");
            }

            if (file.getSize() > ALLOWED_SIZE) {
                errors.rejectValue(file.getName(), file.getName(), "File size allowed up to 3MB!");
            }
        }
    }

    @Override
    public boolean supports(Class<?> cls) {
        return NewOrderFilesDTO.class.equals(cls);
    }
}

And finally a controller:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.validation.Valid;

import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@Controller
class OrderController {

    private final NewOrderFilesValidator newOrderFilesValidator;

    @Autowired
    OrderController(NewOrderFilesValidator newOrderFilesValidator) {
        this.newOrderFilesValidator = newOrderFilesValidator;
    }

    @InitBinder("newOrderFiles")
    void initOrderFilesBinder(WebDataBinder binder) {
        binder.addValidators(newOrderFilesValidator);
    }

    @ResponseStatus(NO_CONTENT)
    @RequestMapping(value = ORDERS_PATH, method = POST, consumes = MULTIPART_FORM_DATA_VALUE)
    void createOrder(
            @Valid @ModelAttribute NewOrderFilesDTO newOrderFiles
    ) {

    }
}

With the configuration above the DTO will be validated automatically by spring.

Opal
  • 81,889
  • 28
  • 189
  • 210
  • Just to let you know that check for a file type is weak because it's based on the headers from user's request. – Slava Semushin Aug 10 '16 at 12:23
  • @SlavaSemushin, yes I know that. Maybe will verify it using an another way. – Opal Aug 10 '16 at 12:27
  • Another room for improvement would be to transform your validator to JSR-303 validator. In this case it can be reused easily and wouldn't depend on Spring. – Slava Semushin Aug 10 '16 at 14:17