spring 3 auto form field validation with file upload - java

I was using JSR 303 validation with hibernate validator to have fields validated automatically by specifying #Valid on the controller method. Validation was working fine. I have know added an upload field to the form and have added a #RequestParam("file") as a MultipartFile file. Now it works only if all fields are valid on submission otherwise I get a 404 (Bad Request). If I remove the #Valid annotation I get the javax.validation.ConstraintViolationException with all the validation violations with Status 500.
I'm using Spring 3.2
my form:
<form action="#springUrl("/admin/stores/save")" method="POST" enctype="multipart/form-data">
Name:
#springBind( "store.name" )
<input type="text"
name="${status.expression}"
value="$!status.value" /><br>
......
<input type="file" name="file" accept="image/*">
<input type="submit" value="submit"/>
</form>
Controller:
#RequestMapping(value="/save", method = RequestMethod.POST)
#Transactional
public String save(#Valid #ModelAttribute Store store, #RequestParam("file") MultipartFile file, BindingResult bindingResult, ModelMap model) {
if (bindingResult.hasErrors()) {
model.addAttribute("message", "Failed");
model.addAttribute("store", store);
return "admin/stores/form";
} else {
.....

your problem is in method argument order. #ModelAttribute must be followed by BindingResult argument. Look at Spring documentation and check also Example 17.1. Invalid ordering of BindingResult and #ModelAttribute.
. You also should add MultipartFile to form class (Store) if it is part of it.
Store {
/* Your other fields */
#NotNull
private MultipartFile file;
public MultipartFile getFile() {
return file;
}
public void setFile(MultipartFile file) {
this.file= file;
}
}

Related

One validator redirecting correctly, another one not redirecting at all?

I have two Validators registered in my Controller. The problem is - InvoiceFormValidator works perfectly, returns errors when there are any, and redirects to desired page, with the form filled out with data.
The second one is not redirecting at all, it is just redirecting me to my error page, showing me:
Date Thu Apr 25 15:22:57 CEST 2019 Path
/contractor/update/5cc193e581c7dc75cfb7bcff Error Bad Request Status
400 Message Validation failed for object='contractor'. Error count:
1
Now the code: (I'm presenting just the important part of the code)
#Autowired private InvoiceFormValidator invoiceFormValidator;
#Autowired private PersonFormValidator personFormValidator;
#InitBinder({"invoicedata"})
protected void initPersonFormBinder(WebDataBinder binder) {
binder.addValidators(invoiceFormValidator);
}
#InitBinder({"contractor"})
protected void initContractorFormBinder(WebDataBinder binder) {
binder.addValidators(personFormValidator);
}
This is how it looks on the controller side (for initPersonFormBinder:)
#RequestMapping(value = "/addinvoice", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String addInvoice(#CurrentUser Contractor contractor, #ModelAttribute("invoicedata") #Validated InvoiceData invoicedata, BindingResult result, Model model, RedirectAttributes attr) {
if (result.hasErrors()) {
return "add";
} else {
And for initContractorFormBinder:
#RequestMapping(value = "/contractor/update/{id}", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String updateContractor(#PathVariable("id") String id, #ModelAttribute("contractor") #Validated Contractor contractor, Model model, BindingResult result, RedirectAttributes attr) {
if (result.hasErrors()) {
logger.error("BINDING RESULT ERROR");
return "index";
} else {
The problem here is that the code never enters this method, NEVER reaches the if statement.
Now the code side of InvoiceFormValidator:
#Component
public class InvoiceFormValidator implements Validator {
Logger logger = LoggerFactory.getLogger(InvoiceFormValidator.class);
#Override
public boolean supports(Class<?> clazz) {
return InvoiceData.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "receptionDate", "empty");
ValidationUtils.rejectIfEmpty(errors, "orderDate", "empty");
ValidationUtils.rejectIfEmpty(errors, "invoiceIssueDate", "empty");
ValidationUtils.rejectIfEmpty(errors, "contractDate", "empty");
ValidationUtils.rejectIfEmpty(errors, "invoiceNumber", "empty");
}
}
And the same time, PersonFormValidator looks like this:
#Component
public class PersonFormValidator implements Validator {
Logger logger = LoggerFactory.getLogger(com.look4app.generator.component.impl.PersonFormValidator.class);
#Override
public boolean supports(Class<?> clazz) {
return Contractor.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Contractor contractor = (Contractor) target;
if (contractor.getContractorData().getNip() == null || contractor.getContractorData().getNip().equals("")) {
errors.rejectValue("contractorData.nip", "empty");
}
logger.error("CONTRACTOR VALIDATION ERROR");
}
}
And a small part of the Thymeleaf template here, for the part which is not working:
<form action="#" th:action="#{/contractor/update/{id}(id=${contractor.id})}" th:object="${contractor}" method="post">
<ul class="form-style-1">
<li>
<label>NIP<span class="required">*</span></label>
<input type="text" th:field="*{contractorData.nip}" id="nip" th:value="${contractor.contractorData?.nip}" >
<span class="error" th:if="${#fields.hasErrors('contractorData.nip')}" th:errors="*{contractorData.nip}">Generic error</span>
</li>
<li>
<input type="submit" value="Zapisz dane firmy" />
</li>
Same HTML for the WORKING validation is a bit different, but not that much:
<form action="#" th:action="#{addinvoice}" th:object="${invoicedata}" method="post">
<ul class="form-style-1">
<li>
<label>Komentarz</label>
<input type="text" th:field="*{contractorComment}" id="contractorComment">
</li>
<li>
<input type="submit" value="Zapisz protokół" />
</li>
What is wrong with this redirecting?
Notice the subtle difference in the order of the method parameters:
public String addInvoice (... #ModelAttribute("invoicedata") #Validated InvoiceData invoicedata, BindingResult result, Model model, ...) {
public String updateContractor(... #ModelAttribute("contractor") #Validated Contractor contractor, Model model, BindingResult result, ...) {
The BindingResult parameter should come right after the parameter with the #Validated. If you change your method signature to:
public String updateContractor(#PathVariable("id") String id, #ModelAttribute("contractor") #Validated Contractor contractor, BindingResult result, Model model, RedirectAttributes attr) {
You should be able to access your method. If a BindingResult parameter is present directly after the #ModelAttribute, Spring puts the validation result in the BindingResult and calls the method, where you can process the BindingResult manually. Otherwise, Spring handles the validation and skips the method.

Spring MVC file upload is passing null to the controller

i've a spring MVC application and when i try to upload a multipart file, a null value is passed to the controller. all the other text parameters are passed properly only the file input is passed as null. I've included the MultipartResolver bean and commons-io plus commons-fileupload dependencies in my project. i've checked it's passed in the browser's request but it's not bound in the modelAttribute.
here is a snippet code from my view
<form:form method="post" enctype="multipart/form-data" action="${pageContext.request.contextPath}/profile/secure/saveIdentity" commandName="profileModel">
<span><b>Upload your passport photo</b></span>
<form:input path="passportPhotograph" type="file" id="passportPhoto"/>
</form:form>
and here is a snippet from my controller method
#RequestMapping(value = "/secure/saveIdentity", method = RequestMethod.POST)
public ModelAndView saveIdentity(#ModelAttribute("profileModel") #Valid ProfileModel profileModel,HttpServletRequest request){
MultipartFile photo = profileModel.getPassportPhotograph();
if(photo != null){ do something.... }
}
here is my ProfileModel.java class snippet
public class ProfileModel{
private MultipartFile passportPhotograph;
public MultipartFile getPassportPhotograph() {
return passportPhotograph;
}
public void setPassportPhotograph(MultipartFile passportPhotograph) {
this.passportPhotograph = passportPhotograph;
}
............
}
And in my dispatcher-servlet file I have declared MultipartResolver bean:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="99999999999"/>
</bean>
and finally in my build.gradle file i added these dependencies
compile('commons-io:commons-io:2.0.1')
compile('commons-fileupload:commons-fileupload:1.3.1')
after all this it's passing null to my controller even though it's included in the HttpServletRequest. what should i do to fix this. Thanks in advance for you help.
you need to use #RequestMapping(value = "/secure/saveIdentity", method = RequestMethod.POST, headers = ("content-type=multipart/*"), produces = "application/json", consumes = "image/*").
Solved my issue by using the StandardServletMultipartResolver which comes with the spring web framework. But you have to use Servlet version 3.0+ for this to work.
This sample code. point is input tag name binding file in model. maybe you need library json and file. ex) commons-io, commons-fileupload and jackson, gson use to parsing in controller.
HTML
<form:form method="post" enctype="multipart/form-data" action="${pageContext.request.contextPath}/profile/secure/saveIdentity">
<span><b>Upload your passport photo</b></span>
<form:input type="file" name="file"/>
</form:form>
Controller
#RequestMapping(value = "test", method = RequestMethod.POST)
public String testFormData(FileAndContentModel model) {
// break point and check model.
return "success";
}
Model
public class FileAndContentModel {
private MultipartFile file;
public FileAndContentModel () {
}
// getter, setter
}
This is not the solution, I just type here because is easier to read than a comment. Please try the minimum requeriment, just upload a file and verify if works.
Form:
<form:form method="post" enctype="multipart/form-data" action="${pageContext.request.contextPath}/profile/secure/saveIdentity">
<span><b>Upload your passport photo</b></span>
<input name="file" type="file" id="passportPhoto"/>
</form:form>
Controller:
#RequestMapping(value = "/secure/saveIdentity", method = RequestMethod.POST)
public void saveIdentity(#RequestParam("file") MultipartFile file) {
//please verify here if the file is null

Spring controller get empty object

I'm sending an object to spring controller via jsp form.
JSP:
<form:form modelAttribute="uploadItem" action="/uploadObject" method="post" enctype="multipart/form-data">
<form:input path="fileData" accept="audio/mpeg" type="file" id="file-upload" name="file-upload" />
<form:input type="text" path="title" id="upload-title" name="upload-title"/>
<input type="image" src="..." alt="Upload"/>
</form:form>
ModelService:
public void fillUploadMelodyModel(Model model) {
fillAdminRootModel(model);
model.addAttribute("uploadItem", new UploadedItem());
}
UploadedItem:
public class UploadedItem {
private CommonsMultipartFile fileData;
private String title;
}
Controller:
#RequestMapping(value = "/uploadObject", method = RequestMethod.POST)
public String doUpload(UploadedItem uploadItem, BindingResult result, Principal principal) {
//at this point I get an empty object (null null values)
}
What is the problem? How to pass object to controller in jsp?
Try changing then your controller like this
#RequestMapping(value = "/uploadObject", method = RequestMethod.POST)
public String doUpload(UploadedItem uploadItem,
BindingResult result,
#RequestParam("fileData") MultipartFile file,
#RequestParam("title") String title,
Principal principal) {
//Here you should receive your parameters
}
I think the names you have used for the file (file-upload) and title (upload-title) are not in sync with your domain object attribute names. Change your names to fileData and title in your Jsp page.

How to retrieve input text from thymeleaf?

I am trying to get a value from thymeleaf input into my java class.
Simple script from thymeleaf
<h1>Form</h1>
<form action="#" th:action="#{index}" th:object="${emails}" method="post">
<p>Emails: <input id="emailbox" type="text" th:field="*{email}"
placeholder="Enter e-mails: eg; Foo#gmail.com, Bar#yahoo.com"/></p>
<p><input type="submit" value="Submit"/> <input type="reset" value="Reset"/></p>
</form>
How would I be able to retrieve the inputted text into my java class?
Controller
#Controller
public class IndexController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView getdata() throws IOException {
ModelAndView model = new ModelAndView("index");
model.addObject("emails", new MailModel());
return model;
}
#PostMapping("/index")
public String emailSubmit(#ModelAttribute MailModel emails) {
System.out.println(emails.getEmail());
return "index";
}
I am able to run my application and see thymeleaf view. When I enter some text into the text box and hit submit. I receive an error.
Error Message
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'emails' available as request attribute
My Application is created with Springboot, Java, and Thymeleaf. What am I doing wrong? Is it possible that ModelandView does not work with PostMapping? I also followed https://spring.io/guides/gs/handling-form-submission/ and I got that sample working, but when I tried to follow the logic and implement into my project. It did not work.
In your HTML, change th:action="#{index}" to th:action="#{/index}". This will allow Thymeleaf to resolve it properly.

Spring validator annotation and error redirection

I m actually studying the Spring framework with Spring Boot and Sprint MVC.
I m doing a form that post some data to complete an object, and I need to validate some values.
The fact is that the validation works, in fact, if I dont respect the validation, I m "redirected" (not really in fact, the URL doesn't change) to the error.html content.
How should I do to manage my redirection correctly ? This doesn't work this way :
#RequestMapping(value = "/print", method = RequestMethod.POST)
public String printPost(#ModelAttribute("printerentity") #Valid PrinterEntity print, Model model, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "redirect:/formPrinter";
}
model.addAttribute("printed", print.getName());
model.addAttribute("printerentity", new PrinterEntity());
return "index";
}
And the form :
<form method="post" th:action="#{/print}" th:object="${printerentity}">
<input type="text" th:field="*{name}"/>
<button type="submit">Valider</button>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</p>
</form>
What am I doing wrong ?
EDIT : It seems that when I have an error, I dont pass in the controller code :o =>
#RequestMapping(value = "/print", method = RequestMethod.POST)
public String printPost(#ModelAttribute("printerentity") #Valid PrinterEntity print, Model model, BindingResult bindingResult) {
System.out.println("I dont passe here when error but I m redirected");
if (bindingResult.hasErrors()) {
return "formPrinter";
}
model.addAttribute("printed", print.getName());
model.addAttribute("printerentity", new PrinterEntity());
return "index";
}
thanks for advance
How should I do to manage my redirection correctly ?
When you redirect a request, then the current request destroyed, and a new request object is created to process the request, so as per Sotirios Delimanolis has mentioned in comment, that model attributes are request attributes, which are available for per request only, If you want to store model attributes in the HTTP session between requests use #SessionAttributes, or a FlashAttributes.
As per your html form you have:
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</p>
you mean you want to show validation errors in the same page, then don't redirect return the same view.
if (bindingResult.hasErrors()) {
//return "redirect:/formPrinter";
return "your html form view";
}
then, on your view you can render all validation error messages like:
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
I had the same problem and it was solved by reordering the parameters in my #PostMapping method to (#Valid #ModelAttribute Form form, BindingResult bindingResult, Model model)
When the params were in the same order as your controller (#Valid #ModelAttribute Form form, Model model, BindingResult bindingResult) I was also redirected to /error without the controller #PostMethod being called.

Categories