I have following situation:
I try to create a simple form to edit information about a movie. Therefor I use spring mvc with jsp.
For the validation I use the JSR 303 hibernate implementation (hibernate-validator 4.2.0).
My problem is that if there are validation errors (BindResults hasErrors method returns true) I can only display the errors with
<form:errors path="*" />
and not fieldspecific. like with:
<form:errors path="title" />
I have no idea why but all errors are displayed in at the path="*" errortag, but none at the fieldspecific ones. The only errors which are displayed right to the field are some which brought an exception i.e.
Failed to convert property value of type java.lang.String to required type int for property runtime; nested exception is java.lang.NumberFormatException: For input string: "")
the important parts of my jsp file look like this:
<%# taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<form:form commandName="movie">
<fieldset>
<form:errors path="*" cssClass="formErrorBlock"/>
<table>
<tr>
<td><form:label path="title">Title:</form:label></td>
<td>
<form:input path="title"/>
<form:errors path="title" cssClass="formFieldError"/>
</td>
</tr>
<tr>
<td><form:label path="runtime">Runtime:</form:label></td>
<td>
<form:input path="runtime"/><br />
<form:errors path="runtime" cssClass="formFieldError" />
</td>
</tr>
<tr>
<td><form:label path="imdbId">IMDB-ID:</form:label></td>
<td>
<form:input path="imdbId"/><br />
<form:errors path="imdbId" cssClass="formFieldError" />
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</form:form>
My Controller:
#Controller
public class MovieController {
#Autowired
private MovieService _movieService;
//some other methods...
#RequestMapping(value="/movie/{pMovieId}/edit", method = RequestMethod.GET)
public String showForm(ModelMap pModel, #PathVariable Integer pMovieId) {
Movie movie = _movieService.getMovieById(pMovieId);
pModel.addAttribute("movie", movie);
return "edit/movie";
}
#RequestMapping(value="/movie/{pMovieId}/edit", method = RequestMethod.POST)
public String editMovieSave(#Valid Movie pMovie, BindingResult pResult, #PathVariable Integer pMovieId, ModelMap pModel) {
Movie movie = _movieService.getMovieById(pMovieId);
if(pResult.hasErrors()) {
return "edit/movie";
}
//save
//redirect to moviepage
return "redirect:/movie/" + pMovieId;
}
}
and finally my movie class:
public class Movie implements Serializable{
private int _id;
#NotEmpty
#Size(min=3)
private String _title;
#NotEmpty
private String _filename;
#Pattern(regexp="tt+[0-9]{7}")
private String _imdbId;
#Min(value=1)
private int _runtime;
//getters and setters....
}
I know this question has been asked a lot of times before, but none of the answers worked with me.
thanks for your help
I solved it by renaming the attributes (removing the underlines). However it was like "jpprade" said (maybe there are accessed by reflection).
For those who can't rename the class attributes (in case of some codingconventions, ...) can annotate the getter methods.
Related
I am trying to use Spring Boot Web with a JSP and JSTL combination. All is well, except the Update method for the JPA queries.
When I submit the form for the edit page, I always get a Type Conversion error for my #Entity which has an #Id column of datatype Long. The control does not even come to the Controller Method with the URL pattern /update, even when I make it hidden in the form:hidden tag.
I am clueless, how to go past this issue?
FYI, I am pasting the relevant sections of the code.
Entity - Quote.java
#Entity
#Data
public class Quote { //implements Auditable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "quote")
#NonNull
private String quote;
...
//other attributes omitted for brevity
}
Controller Method - QuoteController.java
#RequestMapping(value = "/update",method = RequestMethod.POST)
public String updateQuote(#ModelAttribute("quote") Quote quote, Model model,
BindingResult bindingResult) {
LOGGER.info("/update POST request received, quote :: " + quote);
if (bindingResult.hasErrors()) {
LOGGER.error("/update POST request has errors :: " + bindingResult.getAllErrors());
model.addAttribute("error", bindingResult.getAllErrors());
return "qms/edit";
}
quoteService.updateQuote(quote);
return "qms/list";
}
JSP - edit.jsp
<%# include file="header.jsp"%>
<h3>Edit a Quote</h3>
<c:choose>
<c:when test="${error}">
<div>
<span class='error'>No matching quotes. Try again!</span>
</div>
<div>
<b>Errors : </b> : ${error}
</div>
</c:when>
<c:otherwise>
<c:url var="update_quote_url" value="/qms/update"/>
<form:form action="${update_quote_url}" method="post" modelAttribute="quote">
<form:errors path="*" cssClass="errorBox"/>
<table class='table table-striped'>
<form:hidden path="id"/>
<tr>
<td>
<form:label path="id">Id: </form:label>
</td>
<td>
<form:label path="id">${quote.id}</form:label>
</td>
</tr>
<tr>
<td>
<form:label path="quote">Quote: </form:label>
</td>
<td>
<form:input type="text" path="quote" size="50"
placeholder="Beginning is half done!"
value="${quote.quote}"/>
</td>
</tr>
<tr>
<td>
<form:label path="category">Category: </form:label>
</td>
<td>
<form:input type="text" path="category" size="50"
placeholder="Motivation" value="${quote.category}"/>
</td>
</tr>
<tr>
<td>
<form:label path="author">Author Name: </form:label>
</td>
<td>
<form:input type="text" path="author" size="50"
placeholder="Raghavan Muthu" value="${quote.author}"/>
</td>
</tr>
<tr>
` <td colspan="2" class='colspan2'>
<input type="submit" value="Update"/>
</td>`
</tr>
</table>
</form:form>
</c:otherwise>
</c:choose>
<%# include file="footer.jsp"%>
Error
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Sep 20 14:21:03 IST 2021
There was an unexpected error (type=Bad Request, status=400).
Failed to convert value of type 'java.lang.String' to required type 'com.raghsonline.springbootweb.qms.model.Quote'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Long] for value 'When you learn a little, you feel you know a lot. But when you learn a lot, you realize that you know very little.'; nested exception is java.lang.NumberFormatException: For input string: "Whenyoulearnalittle,youfeelyouknowalot.Butwhenyoulearnalot,yourealizethatyouknowverylittle."
org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'com.raghsonline.springbootweb.qms.model.Quote'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Long] for value 'When you learn a little, you feel you know a lot. But when you learn a lot, you realize that you know very little.'; nested exception is java.lang.NumberFormatException: For input string: "Whenyoulearnalittle,youfeelyouknowalot.Butwhenyoulearnalot,yourealizethatyouknowverylittle."
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:79)
at org.springframework.beans.TypeConverterSupport.convertIfNecessary(TypeConverterSupport.java:53)
at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:696)
Question
Why it always considers the #Id Column?
Usage of DTO - a different issue
I have used a DTO which has all the attributes of the main Entity except the #Id column, in the Controller method. However, it has a different issue. It inserts a new record in DB because it does not have the ID attribute.
#RequestMapping(value = "/update",method = RequestMethod.POST)
public String updateQuote(#ModelAttribute("quote") QuoteDTO quoteDTO, Model model,
BindingResult bindingResult) {
LOGGER.info("/update POST request received, quote :: " + quoteDTO);
if (bindingResult.hasErrors()) {
LOGGER.error("/update POST request has errors :: " + bindingResult.getAllErrors());
model.addAttribute("error", bindingResult.getAllErrors());
return "qms/edit";
}
//Convert from the DTO to Model
Quote quote = getModelFromDTO(quoteDTO);
quoteService.updateQuote(quote);
return "qms/list";
}
Any help will be highly appreciated.
Thanks.
A few things solved this issue.
Added the type field for the ID column in the element, where it resolved the type mismatch issue. Thanks to Mohammed Khan for the hint. I kinda ignored it earlier.
Also there was a clash with the actual quote ('quote') of the Model object and also the reference variable of the ModelAttribute ('quote'). I updated the model object to 'quoteObj`, which also helped the issue to be resolved.
Still the /id in the #PathVariable was not working well.. I have updated the #PathVariable in the URL, where instead of having it as /qms/update/{id} , now it is just /qms/update because I get the Model Attribute in the Request Body.
Additionally I enabled the Web Development tool (Network mode/Tab) on the browser to inspect the REQ and RESP elements for each URL being hit. It helped me ensure that the right values are actually passed from the UI to Controller.
My final method now looks like this.
#RequestMapping(value = "/update",method = RequestMethod.POST)
public String updateQuote(#ModelAttribute("quoteObj") Quote quoteObj) {
LOGGER.info("/update POST request received, quote :: " + quoteObj);
quoteService.updateQuote(quoteObj);
return "qms/list";
}
And the form element looks like this.
<tr>
<td>
<form:label path="id">Id: </form:label>
</td>
<td>
<%-- ${quote.id} --%>
<form:input path="id" type="number" value="${quote.id}"/>
</td>
</tr>
I want to bind a List using ModelAttribute. The list contains objects of type Transaction each of which contains transactionid (type int). This is my controller code:
#RequestMapping(value = "/approvecreditdebit.do", method = RequestMethod.POST)
public ModelAndView doActions(HttpServletRequest request,
#ModelAttribute("clc") Clc transactionList, BindingResult result,
ModelMap model) {
/*
* switch (action) { case "approve":
*/
System.out.println("Obj = " + transactionList.getClass());
System.out.println("Val = " + transactionList.getTransactionList());
Users users = new Users();
return new ModelAndView("internal","internal",users);
}
This is my jsp code:
<form:form action="approvecreditdebit.do" method="POST"
modelAttribute="clc">
<table border="1">
<tr>
<th>no</th>
<th>ID</th>
</tr>
<c:forEach items="${clc.transactionList}"
var="transaction" varStatus="status">
<tr>
<td>${status.index}</td>
<td><input name = "transaction[${status.index}].transactionId"
value="${transaction.transactionId}" /></td>
</tr>
</c:forEach>
</table>
<br>
<br>
<center>
<input type="submit" value="approve" />
</center>
</form:form>
This is the Clc class:
public class Clc{
private List<Transaction> transactionList;
public List<Transaction> getTransactionList() {
return transactionList;
}
public void setTransactionList(List<Transaction> transactionList) {
this.transactionList = transactionList;
}
}
The value of transactionList is not being set to the values received from the form. I receive the following error:
Request processing failed; nested exception is java.lang.NullPointerException
I tried searching for the solution on google and got a lot of solutions from stackoverflow but none of them seem to work.
Try something like this (notice the use of <form:input>). I just tried it on a simple Spring MVC app and it works (list is not null and has the values from the form when I try to access it in my POST method).
<c:forEach var="transaction" varStatus="status" items="${clc.transactionList}">
<tr>
<td>${status.index}</td>
<td><form:input path="transactionList[${status.index}].id" /></td>
</tr>
</c:forEach>
I searched everywhere for this error and looked through every single thread here with the same title, changed a lot in my code but still, the error is there.
it says:
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect.
Apache Tomcat/7.0.35
My Controller code is:
#RequestMapping(value = "/manageInventory.htm", method = RequestMethod.GET)
public ModelAndView manageInventory(HttpSession session) {
ArrayList<Product> products = new ArrayList<Product>();
Manufacturer manufacturer = (Manufacturer) manufacturerDAO
.getByUsername(((UserAccount) session.getAttribute("user"))
.getUsername());
products = productDAO.getProductListByManufacturer(manufacturer
.getManufacturerName());
System.out.print(products.get(0).getProductName());
ModelAndView view = new ModelAndView("manageInventory");
view.addObject("products", products);
InventoryItem inventoryItem = new InventoryItem();
view.addObject("inventoryItem", inventoryItem);
return view;
}
#RequestMapping(value = "/manufacture.htm", method = RequestMethod.POST)
public ModelAndView manufactureProduct(HttpSession session, BindingResult result,
#ModelAttribute("inventoryItem") #Valid InventoryItem inventoryItem) {
System.out.print(result.getErrorCount()+" "+result.getAllErrors());
ModelAndView view = null;
if(!result.hasErrors())
{
Manufacturer manufacturer = manufacturerDAO
.getByUsername(((UserAccount) (session.getAttribute("user")))
.getUsername());
inventoryItem.setAvailability(true);
inventoryItem.setManufacturer(manufacturer);
manufacturer.getInventory().add(inventoryItem);
manufacturerDAO.update(manufacturer);
view = new ModelAndView("done");
}
else
{
view = new ModelAndView("manageInventory");
}
return view;
}
The first method adds inventoryItem into the model and I am fetching that for validations afterwards.
I am using tiles so,My tile for this is:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<body>
<h2>Add Inventory</h2>
<br />
<form:form modelAttribute="inventoryItem" action="manufacture.htm" method="post">
<table>
<tr>
<td>Select Product:</td>
<td><form:select path="product">
<c:forEach var="product" items="${products}">
<form:option value="${product}">${product.productName}</form:option>
</c:forEach>
</form:select></td>
<td>Select Quantity:</td>
<td>
<form:input path="quantity" placeholder="Quantity"/><br />
<font color="red"><form:errors path="quantity"/> </font>
</td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="Manufacture">
</td>
</tr>
</table>
</form:form>
</body>
My POJO has the following validation:
#NotNull
private int quantity;
Please help. Thanks in advance.
I forgot to add HttpServletRequest request to the method input parameters.
I added it and it stared working. strange, but worked. :)
I have three fields department_Id,department_Name,department_location in departmentForm act as a model object in this model form.
I have use annotation to validate the fields. Now, I want to only use two fields in different jsp page say create.jsp and one field in different jsp page say getDepartmentById.
When I press submit button of create.jsp, validation is happening but after providing correct information its not submitted cause in this page.
I haven't give one field department_Id which is auto generated by my DAO layer. So, please help me, how to reject this value to execute my create.jsp page for successfully creating department in database.
When I printed the BindingResult object, it shown as follow:
Field error in object 'departmentForm' on field 'departmentId': rejected value [null];
codes [NotEmpty.departmentForm.departmentId,NotEmpty.departmentId,NotEmpty.java.lang.String,NotEmpty];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [departmentForm.departmentId,departmentId]; arguments [];
default message [departmentId],org.hibernate.validator.constraints.NotEmpty.message},
[Ljava.lang.Class;#4fc4a198,[Ljava.lang.Class;#764d2b11];
default message [may not be empty]`
This is how I coded in controller:
#RequestMapping(value = "/createDepartment", method = RequestMethod.POST)
public String createEmployee(#Valid DepartmentForm departmentForm,
BindingResult bindingResult, Map<String, DepartmentForm> model)
throws Exception {
if (bindingResult.hasErrors()) {
System.out.println(bindingResult);
bindingResult.reject(departmentForm.getDepartmentId());
return "departmentForm";
}
System.out.println("mr ankur jadiy");
model.put("departmentForm", departmentForm);
departmentForm.setUpdateStatus('A');
if (departmentForm.getUpdateStatus() == 'A') {
departmentServiceImpl
.actionDecider(convertDeptFormToDeptBO(departmentForm));
}
return "Success";
}
my DepartmentForm code is as follow:
package com.nousinfo.tutorial.model;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
public class DepartmentForm {
#NotEmpty
#Size(min = 1, max = 20,message="")
private String departmentId;
#NotEmpty
private String departmentName;
private String departmentLocation;
private Character updateStatus;
public String getDepartmentId() {
return departmentId;
}
public void setDepartmentId(String departmentId) {
this.departmentId = departmentId;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public String getDepartmentLocation() {
return departmentLocation;
}
public void setDepartmentLocation(String departmentLocation) {
this.departmentLocation = departmentLocation;
}
public Character getUpdateStatus() {
return updateStatus;
}
public void setUpdateStatus(Character updateStatus) {
this.updateStatus = updateStatus;
}
}
and my create.jsp is
<%# page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%# taglib uri="http://jakarta.apache.org/taglibs/input-1.0" prefix="input"%>
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%# taglib uri="http://www.springframework.org/tags" prefix="s"%>
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Create Department</title>
<link rel="stylesheet" href="css/style.css" type="text/css"></link>
</head>
<body>
<table width="1254" height="74" border="0" align="center">
<tr>
<td width="300" height="68" align="center" bgcolor="#99CCFF"><h2>
<span class="style1">Employee Details </span>
</h2></td>
<td width="100" height="68" align="center" bgcolor="#FFFFFF"><img
src="./image/emps.jpg" width="190" height="92" /></td>
</tr>
</table>
<p>
<br />
</p>
<hr size="1" width="786">
<form:form id="form" method="post" action="/EmployeeWebSpring/departmentController/createDepartment"
modelAttribute="departmentForm">
<table>
<tr>
<form:hidden path="updateStatus" />
</tr>
<tr>
<td>
Department_Name:
<font color="red"><form:errors path="departmentName" /></font>
</td>
</tr>
<tr>
<td><form:input path="departmentName" /></td>
</tr>
<tr>
<td>
Department_Location:
<font color="red"><form:errors path="departmentLocation" /></font>
</td>
</tr>
<tr>
<td><form:input path="departmentLocation" /></td>
</tr>
</table>
<br>
<br />
<p> </p>
<br>
<tr>
<td><input type="submit" name="method" value="save" /></td>
<td><input type="submit" name="method" value="cancel" /></td>
</tr>
<hr size="1" width="786">
<p> </p>
</form:form>
</body>
</html>
What the error says is that you're missing value for departmentId, which is not surprising since you defined it as
#NotEmpty
#Size(min = 1, max = 20,message="")
You don't really need to validate departmentId if it's autogenerated by your code. You probably should remove it from the DepartmentForm, especially since it's not in the form, or at least make it optional.
You can make it mandatory in your business object, but the form backing object should reflect what's in the form.
update
If departmentId is a database-generated id, you should set it as disallowed in your controller's InitBinder:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(new String[] { "departmentId" });
}
I'm trying to get Spring annotation based validation working in a simple webapp. I'm using Spring 3.0.5, Tiles 2.2.2. In one specific case I can get the validation error to appear next to the field, however as soon as I add any objects to the model it stops working. Ideally, after the POST, I want to redirect to a GET of the form with the validation errors included. This is the setup:
I have a simple domain object.
public class DomainObject {
#NotEmpty
private String name;
private Date created;
private Date lastModified;
...
}
I have a controller with a GET method which adds all existing DomainObjects to the model and returns a view that displays them, and contains a very simple form for creating them. It also has a POST method for creating a new DomainObject.
#Controller
#RequestMapping("/")
public class DomainObjectController {
#Autowired
private DomainObjectService domainObjectService;
#RequestMapping("form.htm")
public String home(Model model) {
model.addAttribute("objects", domainObjectService.getAll());
model.addAttribute(new DomainObject());
return "form";
}
#RequestMapping(value="new_object.do", method=RequestMethod.POST)
public String newObject(#Valid DomainObject domainObject, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
//model.addAttribute("objects", domainObjectService.getAll());
//model.addAttribute(new DomainObject());
return "form";
}
domainObjectService.saveNew(domainObject);
model.addAttribute("objects", domainObjectService.getAll());
model.addAttribute(new DomainObject());
return "form";
}
}
Here's the view:
<form:form commandName="domainObject" action="new_object.do" method="post>
<spring:message code="name" />: <form:input path="name" />
<input type="submit" value="<spring:message code="create.object"/>" /><form:errors path="name" cssClass="error"/></form:form>
</div>
<table class="centered">
<col width=50 />
<col width=225 />
<col width=200 />
<col width=200 />
<thead>
<tr>
<td id="id" class="sortable"><spring:message code="id" /></td>
<td id="name" class="sortable"><spring:message code="name" /></td>
<td id="created" class="sortable"><spring:message code="created" /></td>
</tr>
</thead>
<tbody>
<c:forEach var="obj" items="${objects}">
<tr>
<td class="id">${obj.id}</td>
<td>${obj.name}</td>
<td>${obj.created}</td>
</tr>
</c:forEach>
</tbody>
</table>
With this setup, if I leave the name field empty, the validation error is picked up and correctly displayed to the right of the field. However, the table is always empty, as no objects are added to the model. If I add objects to the model, by uncommenting the lines
//model.addAttribute("objects", domainObjectService.getAll());
//model.addAttribute(new DomainObject());
the table is populated but the validation error no longer appears. I can't work this one out.
As another unwanted side effect, any relative links I have in the view now no longer work (for example, a link which changes the locale href="?lang=de").
So, what could be causing the validation message to disappear when I add data to the model? And is it possible for me to redirect to the original form while keeping hold of the validation messages?
Thanks,
Russell
The validation error is attached to the object that is invalid. If you replace the object that is invalide by an new one:
model.addAttribute(newDomainObject());
then the error messages is not attached to this object.