SpringBoot Web, JPA, JSP with JSTL - type conversion error - spring-boot

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>

Related

ModelAttribute not working with lists in spring

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>

Spring form validation - errors are not displayed

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.

How to send back the model data from jsp to controller

I have a controller which sets few values to a model and sends to jsp. In jsp i need to show those values(as labels) along with additional values from user as input values. When i submit the jsp i only get valid values that user has entered and the values set earlier by controller is null.
JSP
<form:form
action="${pageContext.request.contextPath}/admin/deviceAction.html"
modelAttribute="deviceData">
<table class="gridtable" width="500px">
<tr>
<td>Device Name : </td>
<td>${deviceData.deviceName}</td>
</tr>
<tr>
<td>Model Name : </td>
<td>${deviceData.modelName}</td>
</tr>
<tr>
<td>Serial No : </td>
<td>${deviceData.serialNo}</td>
</tr>
<tr>
<td>Device Id : </td>
<td>${deviceData.deviceId}</td>
</tr>
<tr>
<td>Status : </td>
<td>${deviceData.statusCode}</td>
</tr>
<tr>
<td>Action : <span class="required">*</span></td>
<td>
<form:select path="deviceAction" >
<form:option value="" label="--- Select ---" />
<form:options items="${model.actionList}" />
</form:select>
</td>
</tr>
</table>
<input type="submit" value="Submit" id="btn_submit">
</form:form>
Controller:
public ModelAndView beforeSubmit() {
ModelAndView modelView = new ModelAndView();
DeviceData deviceData = new DeviceData();
deviceData.setDevicePk("123");
deviceData.setAccessToken("abcwetrwertewrtetr");
deviceData.setDeviceId("deferterterterterwtetetertg");
deviceData.setDeviceName("test");
deviceData.setEnrolledDate("7-8-13");
deviceData.setModelName("test1");
deviceData.setSerialNo("test2dsfgdfgdfg");
deviceData.setStatusCode("test3");
List<String> actionList = getActionList();
Map<String, List<String>> model = new HashMap<String, List<String>>();
model.put("actionList", actionList);
modelView.addObject("deviceData", deviceData);
modelView.addObject("model", model);
modelView.setViewName("admin/tokenSearchResult");
}
public ModelAndView afterSubmit() {
#ModelAttribute("deviceData") DeviceData deviceData, BindingResult result) {
logger.info("#################device datas are : " + deviceData.getDevicePk() + "###### " + deviceData.getDeviceAction());
return new ModelAndView();
}
deviceData.getDevicePk() is null
Only the drop down value is having valid value. Other values displayed in the screen are received as null.
Edit:
Till now i have found only one solution:
<form:input path="deviceName" readonly="true" />
But this way UI does not looks good. The editable and non editable values mixup in the screen. Looking for a better answer
Finally i am using hidden parameters to solve the problem.
Example:
<td>${deviceData.deviceName}</td>
is replaced by:
<td><form:hidden path="deviceName"</td>
By this way it helps me to avoid any css work(which i am not much comfortable)
If anyone get a better solution kindly post it here
You need to make them into form inputs using the Spring form tags in much the same way as you have for the form:select. If they are not editable by the user, you can always disable them.
You can simple hide those input. For example :
<input type="hidden" name="VehSeriesModelId" value="${vehDetailsVM.id }">
This way, you can get the data to the controller and the user will also not be able to edit the value. On the other hand, your form will also not show it :)

Spring MVC 3 Validation will not work when attributes added to model

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.

Why do I get IllegalArgumentException Cannot convert value of type String to required type Product, in Spring?

I receive the exception
Failed to convert property value of
type [java.lang.String] to required
type [beans.Product] for property
product; nested exception is
java.lang.IllegalArgumentException:
Cannot convert value of type
[java.lang.String] to required type
[beans.Product] for property product:
no matching editors or conversion
strategy found
in the Errors errors object even before my DetailProductValidator starts validating through the validate method.
I don't understand why Spring does that. I don't have any input field that is mapped directly to the product property/object. I just use the product object's properties in the jsp. For example, I use:
<form:options items="${dpBackObj.product.colorMap}"/>
<!-- or -->
${dpBackObj.product.priceInDollars}
but I never use:
<form:input path="product"/>
Can anyone please explain why this happens? And maybe inform me of a simple solution?
The bean configuration for the controller is:
<!-- DETAIL PRODUCT FORM CONTROLLER -->
<bean id="productDetailFormController" name="/detail.htm /addToCart.htm"
class="detailProduct.DetailProductFormController">
<property name="sessionForm" value="true" />
<property name="commandName" value="dpBackObj" />
<property name="commandClass" value="detailProduct.DetailProductBackingObject" />
<property name="validator">
<bean class="detailProduct.DetailProductValidator" />
</property>
<property name="formView" value="detail" />
<property name="successView" value="redirect:/viewCart.htm" />
<property name="cartService" ref="cartServiceImpl"/>
</bean>
The backing object for the DetailProductFormController is:
public class DetailProductBackingObject {
private String quantityOverflowError;
private Product product;
private int quantity;
private ShoppingCart shoppingCart;
private long sizeId;
private long colorId;
public DetailProductBackingObject() {
this.product = new Product();
this.sizeId = -1;
this.colorId = -1;
}
//getters and setters
}
If you need some other info, I will provide. I am using Spring 2.5.5.
Kind Regards,
Despot
EDIT1 (due to request from axtavt):
<form:form method="post" commandName="dpBackObj">
<table width="730" border="0" cellspacing="0" cellpadding="0">
<c:if test="${!empty dpBackObj.quantityOverflowError}">
<tr>
<td>
<c:out value="${dpBackObj.quantityOverflowError}"/>
</td>
</tr>
</c:if>
<spring:bind path="dpBackObj.*">
<c:if test="${not empty status.errorMessages}">
<div class="val-summary text-error" id="errorDivId">
<div style="" class="val-summary text-error" id="errorDivId">
<fmt:message key="detail.error.header"/>
<ul>
<c:forEach items="${status.errorMessages}" var="error">
<li><c:out value="${error}"/></li>
</c:forEach>
</ul>
</div>
</div>
</c:if>
</spring:bind>
<tr>
<td width="310" align="left" valign="top">
<img src="${imagesPath}/${dpBackObj.product.largeImageUrl}" alt="${dpBackObj.product.description}" />
</td>
<td width="420" align="left" valign="top">
<div id="tls_detPName">
<c:out value="${dpBackObj.product.name}"></c:out>
</div>
<div >
<strong class="numeric">${dpBackObj.product.priceInDollars}</strong>
</div>
<div id="tls_detPDescLong">
${dpBackObj.product.largeDescription}
<br />
</div>
<div >
<table cellpadding="2" border="0">
<tr>
<td align="right">
<label for="p_sizes" class="label"><fmt:message key="viewCart.Size"/></label>
</td>
<td>
<form:select path="sizeId" >
<form:option value="-1" label="x"/>
<form:options items="${dpBackObj.product.sizeMap}"/>
</form:select>
</td>
</tr>
<tr>
<td align="right">
<label for="p_colors" class="label"><fmt:message key="viewCart.Color"/></label>
</td>
<td>
<form:select path="colorId" >
<form:option value="-1" label="y"/>
<form:options items="${dpBackObj.product.colorMap}"/>
</form:select>
</td>
</tr>
</table>
</div>
<div id="tls_addToCart">
<div >
<label for="quantityId" class="label"><fmt:message key="viewCart.Quantity"/>:</label>
<form:input path="quantity" onkeypress="return checkForNumber(this, event)" maxlength="10" size="3" id="quantityId" cssClass="textbox-center"/>
<input type="image" name="addToCartButtonName" src="${imagesPath}/addToCartBtn.jpg" />
</div>
</div>
</td>
</tr>
</table>
</form:form>
EDIT2 (due to JacobM's request):
This is my Validator:
public class DetailProductValidator implements Validator {
public boolean supports(Class clazz) {
return DetailProductBackingObject.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
DetailProductBackingObject detailProductBackingObject = (DetailProductBackingObject) obj;
if (detailProductBackingObject.getSizeId() == -1) {
errors.rejectValue("sizeId", "error.detail.jsp.choose.size", null, "Input size.");
}
}
}
When I reach the line DetailProductBackingObject detailProductBackingObject = I already have the error.
The converting of the request parameters to the backing object properties happens in http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/web/servlet/mvc/BaseCommandController.html . This is what Spring says about the conversion:
Populating using request parameters
and PropertyEditors: Upon receiving a
request, any BaseCommandController
will attempt to fill the command
object using the request parameters.
This is done using the typical and
well-known JavaBeans property
notation. When a request parameter
named 'firstName' exists, the
framework will attempt to call
setFirstName([value]) passing the
value of the parameter. Nested
properties are of course supported.
For instance a parameter named
'address.city' will result in a
getAddress().setCity([value]) call on
the command class.
It's important to realise that you are
not limited to String arguments in
your JavaBeans. Using the
PropertyEditor-notion as supplied by
the java.beans package, you will be
able to transform Strings to Objects
and the other way around. For instance
setLocale(Locale loc) is perfectly
possible for a request parameter named
locale having a value of en, as long
as you register the appropriate
PropertyEditor in the Controller (see
initBinder() for more information on
that matter.
Validators: After the controller has
successfully populated the command
object with parameters from the
request, it will use any configured
validators to validate the object.
Validation results will be put in a
Errors object which can be used in a
View to render any input problems.
Since I can't see anything wrong with the form, the only possible reason I can imagine is that you have a parameter named product in the URL of your form page.
If so, you can change your URLs or use DataBinder.setDisallowedFields() to disable the attempt to bind that parameter.

Resources