Spring MultipartFile causing form validation to not be mapped - spring

I'll start by saying my knowledge of Spring is very limited. However, I've been able to work through issues I've faced with it in the past. My newest problem isn't making much sense to me.
So what I've got is a form which takes attributes for an item to be sold at auction. This form has an optional field which can upload a picture of the item being sold. The image uploading works as is. I noticed my form wasn't actually showing the errors given during validation, so I started looking at what might be causing that. If I remove the MultipartFile from the method signature, the web will correctly show form validation errors if they exist. However, now I don't have the image I need.
On the flip side, if I add the required = false attribute to the RequestParam on the MultipartFile, my issue persists and when a form doesn't meet the validations set, I'm met with the following.
The Java side for this method that's supposed to save the item if it's valid or show the validation errors is as follows:
#RequestMapping(method = RequestMethod.POST)
public ModelAndView save(#Valid Item item, #RequestParam(name = "itemImage", required = false) MultipartFile file,
BindingResult result, RedirectAttributes redirect) {
if (result.hasErrors()) {
return new ModelAndView("item/save", "formErrors", result.getAllErrors());
}
boolean isCreate = (null == item.getId());
if (file != null && !file.isEmpty()) {
if (isCreate) {
item = itemService.save(item);
}
Path directory = Paths.get(itemImageDir + "/" + item.getAuction().getId() + "/" + item.getId());
if (!Files.exists(directory)) {
try {
Files.createDirectories(directory);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Files.copy(file.getInputStream(), Paths.get(directory.toString(), file.getOriginalFilename()),
StandardCopyOption.REPLACE_EXISTING);
item.setImageUrl(String.format("/items/image/%s/%s/%s", item.getAuction().getId(), item.getId(), file
.getOriginalFilename()));
itemService.save(item);
} catch (IOException | RuntimeException e) {
result.addError(new ObjectError("imageUrl", "Failed to upload " + file.getOriginalFilename() + " => "
+ e.getMessage()));
return new ModelAndView("item/save", "formErrors", result.getAllErrors());
}
} else {
itemService.save(item);
}
String message = "Successfully created a new item.";
if (!isCreate)
message = "Item has been successfully updated.";
redirect.addFlashAttribute("globalMessage", message);
return new ModelAndView("redirect:/auctions/{item.auction.id}", "item.auction.id", item.getAuction().getId());
}
The view for this page, without all of the extra fluff, looks as so:
<form id="auctionForm" class="col-xs-12" th:action="#{/items/(item)}" th:object="${item}"
action="#" method="post" enctype="multipart/form-data">
<div th:class="'form-group row'">
<label for="itemImage" class="control-label col-sm-2"> Image Upload: </label>
<div class="col-sm-4">
<input id="itemImage" type="file" name="itemImage"/>
</div>
</div>
</form>
Extra context for the issue: If I remove the #Valid annotation, the method will be called and won't fail when the form isn't valid. However, when I have #Valid, the controller method is not even hit. Is there a way I can check where it's failing if it isn't getting to the controller? I compared this one controller with all of the others and it seems to follow the same pattern.
If anyone has any suggestions, I'd be more than grateful. I don't really have any idea what I'm missing, so any suggestions are welcome.

Add in controllers method sigMultipartHttpServletRequest mrequest and check it once

Alright, so I figured out what I was missing. Being new to Spring, I didn't realize parameter order could matter. Apparently BindingResult has to immediately follow the parameter you want to validate. So, I changed the method signature to the following and now all is working as intended:
public ModelAndView save(#Valid Item item, BindingResult result, #RequestParam(name = "itemImage", required = false) MultipartFile file,
RedirectAttributes redirect) {

Related

Spring MVC: How to test whether param exists when there's no value?

I want to display an error message with my custom login.jsp form. When there's an error, the url is ../loginForm?error without any value assigned to error. (This seems to be the behavior of Spring Security.) If there's no error, the url is simply ../loginForm (without the parameter). In the controller I can capture the parameter with #RequestParam, but how do I check whether or not error is passed? In other words, how can I test a parameter alone without a value?
Here's the controller code I have now:
#RequestMapping("/loginForm")
public String showLoginForm(#RequestParam(value="error", defaultValue="false")
boolean error,
Model model)
{
if (error == true)
{
model.addAttribute("loginError", "Invalid username and password.");
}
return "/user/loginForm";
}
...and here's the JSP snippet:
<c:if test="${not empty loginError}">
<tr>
<td><c:out value="${loginError}" /></td>
</tr>
</c:if>
At this point I'm not including the Security configuration I have set up, since everything else seems to be working and I want to keep this focused on the issue at hand.
Thanks in advance for any suggestions!
Ok, I figured it out (while taking a break). The #RequestParam only works when there's actually a parameter available for mapping. If no such parameter is passed in, it's useless. So instead, I checked the Map provided by ServletRequest:
#RequestMapping("/loginForm")
public String showLoginForm(ServletRequest request, Model model)
{
Map<String, String[]> paramMap = request.getParameterMap();
if (paramMap.containsKey("error"))
{
model.addAttribute("loginError", "Invalid username and password.");
}
return "/user/loginForm";
}
It works fine now.
There is another way to do that. Just create one more method where #RequestMapping will check presence of "error" parameter, add required attribute and return view. Both methods could exist together.
#RequestMapping(value = "/loginForm", params = {"error"})
public String loginError(Model model)
{
model.addAttribute("loginError", "Invalid username and password.");
return "/user/loginForm";
}
#RequestMapping(value = "/loginForm")
public String login()
{
return "/user/loginForm";
}

SpringBoot/MVC & Thymleaf form validation on POST with URL parameters

I have a form and validation works. The problem comes in when a url parameter was added. The url parameter is a token and is required. So this is what my controller looks like:
#RequestMapping(value = "/resetpassword", method = RequestMethod.GET)
public String showResetForm(ResetPassword resetPassword, Model model,
#RequestParam(value = "token", required = true) String token,
#RequestParam(value = "msg", required = false) String msg){
model.addAttribute("token", token);
return "resetpassword";
}
#RequestMapping(value = "/resetpassword", method = RequestMethod.POST)
public String setPwd(#ModelAttribute("resetPassword") #Valid ResetPassword resetPassword,// RedirectAttributes reDirectAttr,
BindingResult bindingResult, Model model,
#RequestParam(value = "token", required = true) String token,
#RequestParam(value = "msg", required = false) String msg){
if (bindingResult.hasErrors()) {
//reDirectAttr.addFlashAttribute("org.springframework.validation.BindingResult.resetPassword",bindingResult);
//reDirectAttr.addFlashAttribute("resetPassword",resetPassword);
return "resetpassword?token="+token;
}
else {
if (token == null) {
// TODO: no token, what to do here??
return "redirect:/resetpassword?token=\"\"&msg=notoken";
}
ResetPasswordResponseDto response = super.resetUserPassword(
resetPassword.getUname(), resetPassword.getPassword(),
token);
if (response.getPasswordResetResult() == PasswordResetResult.SUCCESSFUL) {
// TODO: it worked, what now?
return "redirect:/login";
} else if (response.getPasswordResetResult() == PasswordResetResult.INVALID_TOKEN) {
// TODO: bad token
return "redirect:/resetpassword?token="+token+"&msg=badtoken";
} else if (response.getPasswordResetResult() == PasswordResetResult.OUT_OF_POLICY_PW) {
// TODO: out of policy pw
return "redirect:/resetpassword?token="+token+"&msg=outofpolicy";
} else if (response.getPasswordResetResult() == PasswordResetResult.LDAP_FAILURE) {
// TODO: other failure
return "redirect:/resetpassword?token="+token+"&msg=error";
}
}
return "redirect:/resetpassword?token="+token+"&msg=error";
//return new RedirectView("resetpassword?token=\"\"&msg=notoken");
}
So I tried a bunch of things but nothing seems to work. Here is what I would like to happen when the view is requested /resetpassword?token=1232453 the view is displayed. Then if the form has errors the url parameter persists in the url and the form displays the errors. Right now I get an error saying that the template cannot be resolved. Ok fair enough, so I tried doing a redirect instead
return "redirect:/resetpassword?token="+token;
and that seems to work, however the URL parameter is lost and the view loses the bindingResult errors. In the code, I posted I also tried FlashAttributes but I just get an error "Validation failed for object='resetPassword'. Error count: 4" which is correct but I need it to show the form and the errors I coded with Thymeleaf. Any help or suggestions would be great!
Resources I have looked at:
Spring - Redirect after POST (even with validation errors)
&
SpringMVC controller: how to stay on page if form validation error occurs
Have you tried returning a ModelAndView instead of just the redirect string? Attributes on the model will be available as URL query parameters.
ModelAndView redirect = new ModelAndView("redirect:/resetpassword");
redirect.addObject("token", token);
redirect.addObject("msg", "error");
return redirect;

Getting different data in ajax response froms second time

I am encountering a problem which I am not able to find out why.
I am using spring mvc and I am sending ajax request to one of my controller.
$.get("<c:url value="/createcomment" />", {id: pageid , newcomment : newcomment})
.done(function(data){
$("#newcomment"+data.pageId).val('');
var html = '<tr><td>'+
'<div class="pull-left">'+
'<img class="img-rounded" src="resources/profile-pics/male/small.jpg" alt="">'+
'</div><div class="span4"><ul class="nav nav-stacked">'+
'<li><font size="2"><i class="icon-user"></i>'+data.account.firstName+' '+data.account.lastName+'</font></li>'+
'<li><font size="2">'+data.text+'</font></li><li><font size="1">'+data.postingDate+
'</font></li></ul></div></td></tr>';
$(html).inserAfter($("#tr"+data.pageId));
}
When i refresh the page and send the request i get the following desired object.
and when I send it second time again i get Some Document Object.
I don't understand what is happening wrong.
#RequestMapping(value="/createcomment",method=RequestMethod.GET)
public #ResponseBody Comment createComment(#RequestParam(value="id")final String pageId,#RequestParam(value="newcomment")final String text,
final HttpServletRequest request ,final WebRequest req){
final Comment comment = new Comment();
comment.setId(GenerateUID.generate());
comment.setText(text);
comment.setPostingDate(new Date());
comment.setPageId(Long.valueOf(pageId));
try {
return comment;
} catch (NumberFormatException e) {
return null;
} catch (SignInNotFoundException e) {
return null;
}
}
Just for additonal information i am using jQuery JavaScript Library v1.7.1
You might want to check if your method throws a NumberFormatException or SignInNotFoundException, in which case it returns null. Your network log shows that 0 bytes of data have been transferred.

ID in Spring-MVC 2.5 edit form using #Controller

I have a problem with the my Controller code. GET works fine (both empty form + form populated from db), POST works fine only for creating new object, but doesn't work for editing. Part of my #Controller class:
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.GET)
public String setUpForm(#RequestParam(value="id", required = false) Long id, ModelMap model) {
Vehicle v;
if (id == null) {
v = new Vehicle();
} else {
v = vehicleManager.findVehicle(id);
}
model.addAttribute("vehicle", v);
return "vehicle_save";
}
#RequestMapping(value = "/vehicle_save.html", method = RequestMethod.POST)
public String save(#ModelAttribute("vehicle") Vehicle vehicle, BindingResult result, SessionStatus status) {
vehicleValidator.validate(vehicle, result);
if (result.hasErrors()) {
return "vehicle_save";
}
if(vehicle.getId() == null) {
vehicleManager.createVehicle(vehicle);
} else {
vehicleManager.updateVehicle(vehicle);
}
status.setComplete();
return "redirect:vehicle_list.html";
}
The first method creates a vehicle object (including its ID). But the second method gets the same object without the ID field (set to null).
What could I do: manually set vehicle.setID(id from parameters) and then save it to database. This causes JPAOptimisticLockException + I don't like that solution.
Is there a way to pass my Vehicle object with ID to the second method? BTW, I would like to avoid adding hidden ID field to the JSP.
the example you suggested is using session to store the value. the #SessionAttribute is to bind an existing model object to the session. Look at the source code the class is annotated with #SessionAttributes("pet").Which means your model attribute named "pet" is getting stored in session.Also look at the code in processSubmit method of EditPetForm class
#RequestMapping(method = { RequestMethod.PUT, RequestMethod.POST })
public String processSubmit(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/form";
}
else {
this.clinic.storePet(pet);
status.setComplete(); //look at its documentation
return "redirect:/owners/" + pet.getOwner().getId();
}
}
I havnt used something like this before.But i guess putting ur id in session is the way
BTW, I would like to avoid adding hidden ID field to the JSP.
This is common solution. What's wrong with it ? You should create hidden input with id.
May be you can try using session, cause you cant store info between two request. But that will be uglier i guess.
Btw, Can you please explain a little why you want to avoid adding hidden fields? I'm little curious

Spring portlet mvc Validation fails during submission of the editted form

I have a form with few validations on it.
During new form submission, if validation fails I can see those error messages.
but, during editing the form when I change the field to blank intentionally and submit the form error messages are not shown on Jsp page but I can get the errorcount in controller as 1 .
<portlet:actionURL var="actionUrl">
<portlet:param name="action" value="editCommunity"/>
<portlet:param name="communityID" value="${community.id}"/>
</portlet:actionURL>
<liferay-ui:tabs names="Details" />
<form:form commandName="community" method="post" action="${actionUrl}">
<form:hidden path="id"/>
<div><form:errors cssClass="portlet-msg-error" path="*"/></div>
<table class="manager-detail">
<tr>
<th class="portlet-form-field-label">
<label for="community_label_name"><spring:message code="community.label.name"/></label>
<span class="manager-field-required">*</span>
</th>
<td><form:input id="community_label_name" cssClass="portlet-form-input-field" path="name" size="30" maxlength="80" /></td>
</tr>
My edit controller method.....
rendering edit form
#RequestMapping(params = "action=editCommunity")
public String showEditCommunityForm(final RenderRequest request,
#RequestParam(value="communityID") Long id, final Model model)
throws CommunityNotFoundException {
final ThemeDisplay themeDisplay = (ThemeDisplay) request
.getAttribute(WebKeys.THEME_DISPLAY);
model.addAttribute("community", communityService.getCommunity(id));
return "communityEdit";
}
edited form is submitted
#RequestMapping(params = "action=editCommunity")
public void submitEditCommunityForm(final ActionRequest request,
final ActionResponse response,
#ModelAttribute("community") Community community,
BindingResult result, Model model) throws SystemException, PortalException {
communityValidator.validate(community, result);
if (result.hasErrors()) {
System.out.println("validation errors size..."+result.getErrorCount());
//model.addAttribute("community", community);
response.setRenderParameter("action", "editCommunity");
response.setRenderParameter("communityID", String.valueOf(community
.getId()));
}
}
It is not full code but a block
I have tried couple of things like,
changing the http method from post to POST, but nothing works. Validation perfectly works during form creation, but not during edit.
Am I missing anything? please give me suggestions.
Cheers
Vamshi
Preserving the validation error messages can be a real pain!
I have tried a lot of things - from configuring the redirect behavior of the portlet container to using jsr303 instead of spring validation.
The only solution I have consistently had and success implementing is really ugly:
Do the validation in an action method.
If errors are encountered save the BindingResult/Errors-object with "your own key" in the Spring model and interrupt the action handling.
You are redirected to the render method
There you pick up the Errors-object and put it back to the key where "Spring validation" expects it.
In code this looks something like this:
#ActionMapping
public void invite(#ModelAttribute MyFormBean myFormBean,
BindingResult result, Model model) {
// validate indata
myValidator.validate(myFormBean, result);
// Workaround to preserve Spring validation errors
if (result.hasErrors()) {
model.addAttribute("errors", result);
return;
}
...
}
#RequestMapping
public String showForm(#ModelAttribute("myFormBean") MyFormBean myFormBean,
Model model) {
...
// Workaround to get the errors form-validation from actionrequest
Errors errors = (Errors) model.asMap().get("errors");
if (errors != null) {
model.addAttribute(
"org.springframework.validation.BindingResult.myFormBean", errors);
}
return "myForm";
}
The information stored in the Model under "org.springframework.validation.BindingResult.*" are deleted automatically between the action processing and the render processing, and by preserving it explicitly in "errors" the information will be available to the view.
This is an ugly solution, you have to know more than you want about how the implementation really works, it is counter intuitive and if not properly commented this code could easily be removed by someone not familiar with the problem, but it is not a lot of code and it works.
You can omit the #ModelAttribute in the render phase and retrieve it from the model:
#ActionMapping
public void invite(#ModelAttribute MyFormBean myFormBean,
BindingResult result, Model model) {
// validate indata
myValidator.validate(myFormBean, result);
...
}
#RequestMapping
public String showForm(Model model) {
MyFormBean myFormBean = (MyFormBean)model.asMap().get("myFormBean");
...
return "myForm";
}

Resources