How to implement correct data binding in controller? - java

I have a jsp page which combines User object using forms. On the last form I'm trying to get Collection<Permission>. But when I'm trying to pass data to controller I'm getting 400 Error because of:
Field error in object 'user' on field 'permissions':
rejected value [add,view];
codes [typeMismatch.user.permissions,typeMismatch.permissions,typeMismatch.java.util.Collection,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.permissions,permissions];
arguments [];
default message [permissions]];
default message [Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.Collection' for property 'permissions'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [it.marco.javaproject.domain.Permission] for property 'permissions[0]': no matching editors or conversion strategy found]
Here is my jsp form:
<form:form action="/user/permission" method="POST" modelAttribute="user">
<form:checkboxes path="permissions" items="${permissions}" delimiter=<br>"/>
<form:hidden path="email"/>
<form:hidden path="password"/>
<form:hidden path="name"/>
<input type="submit" value="Next" name="next"/>
</form:form>
Part of controller:
public String processRoleForm(#ModelAttribute("user") User user, ModelMap model) {
model.addAttribute("permissions", userService.getPermissions());
return "user/form/permissionForm";
}
Permission class:
#Entity
#Table(name = "permission")
public class Permission implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name")
private String name;
If I'm not mistaken I need to use some kind of data binder in my controller. How to implement it? How to properly translate String[] to Collection of Permission?

I find simple solution. Here it is:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Permission.class, new PropertyEditorSupport() {
#Override
public void setAsText(String id) throws IllegalArgumentException {
setValue(userService.getPermission(Integer.parseInt(id)));
}
});
}

Related

Problem with nested class (Thymeleaf form) - Failed to convert property value

I have a problem with a Thymeleaf form with a nested class. I'm working on Calorie Counter Application and when I'm trying to add a new FoodProduct to an existing meal I get an exception. FoodProduct is a class that handles the template of all FoodProduct i.e.: Apple, Chicken Breast, etc.
FoodProduct class:
#Entity
#Table(name = "food_products")
public class FoodProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "product_name")
private String productName;
#Column(name = "proteins")
private Double proteins;
#Column(name = "fats")
private Double fats;
#Column(name = "carbohydrates")
private Double carbohydrates;
#Column(name = "calories")
private Double calories;
// ...
}
MealFoodProduct class:
#Entity
#Table(name = "meals_food_products")
#IdClass(MealFoodProductPK.class)
public class MealFoodProduct {
#Id
#ManyToOne
#JoinColumn(name = "meal_id")
private Meal meal;
#Id
#ManyToOne
#JoinColumn(name = "food_product_id")
private FoodProduct foodProduct;
#Column(name = "food_product_weight")
private double weight;
//...
}
HTML Form:
<form action="#" th:action="#{/saveMealFoodProduct}" th:object="${mealFoodProduct}" method="post">
<div>
<label>Product name</label>
<div>
<select th:field="*{foodProduct}">
<option th:each="foodProduct : ${foodProducts}" th:text="${foodProduct}" th:value="${foodProduct.id}"></option>
</select>
</div>
</div>
<div>
<label>Weight</label>
<div>
<input type="number" th:field="*{weight}" placeholder="Enter weight">
</div>
</div>
<br>
<div>
<div>
<button type="submit">Save</button>
</div>
</div>
</form>
When I confirm a form I have this exception and I have no idea, what's wrong:
Failed to convert property value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct';
nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct': no matching editors or conversion strategy found
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Integer' to required type 'pl.sosinski.nutritioncounter.model.FoodProduct' for property 'foodProduct': no matching editors or conversion strategy found
If you change the foodProduct selection code to the following, the problem will be fixed.
<select th:field="*{foodProduct.id}">
<option th:each="foodProduct : ${foodProducts}"
th:text="${foodProduct}"
th:value="${foodProduct.id}"></option>
</select>
OR
You need to indicate to your app how convert a Id of FoodProduct returned inside form (String Type) to a FoodProduct entity. For that you have to use a Converter.
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class StringToFoodProductConverter implements Converter<String, FoodProduct> {
#Override
public FoodProduct convert(String arg0) {
Integer id = Integer.valueOf(arg0);
FoodProduct foodProduct = new FoodProduct();
foodProduct.setId(id);
return foodProduct;
}
}

Spring/Thymeleaf - Failed to convert value of type 'java.lang.String' to required type

I am new to Spring and Thymeleaf, and I do not know what went wrong here.
When submitting my form, I get THIS error:
There was an unexpected error (type=Bad Request, status=400). Failed to convert value of type 'java.lang.String' to required type 'br.com.teste.segware.domain.post.Post'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'Some text...'; nested exception is java.lang.NumberFormatException: For input string: "Sometext..." org.springframework.beans.TypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'br.com.teste.segware.domain.post.Post'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'Some text...'; nested exception is java.lang.NumberFormatException: For input string: "Sometext..."
Here is my Post class: (I use Lombok, so the getters and setters are self-generated)
#Getter
#Setter
#Entity
#Table(name = "post")
public class Post implements Serializable {
#EqualsAndHashCode.Include
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Integer id;
#NotBlank
#Column(nullable = false)
private String nome;
#NotBlank
#Size(max = 800)
#Column(nullable = false)
private String post;
}
My Controller:
#Controller
public class IndexController {
#Autowired
private PostService postService;
#PostMapping("/savePost")
String savePost(#ModelAttribute("post") Post post) {
postService.savePost(post);
return "redirect:/";
}
}
And my HTML form:
<form method="post" th:object="${post}" th:action="#{/savePost}">
<fieldset>
<input type="hidden" th:field="*{id}" />
<label for="name">Nome:</label> <br/>
<input type="text" id="name" name="name" th:field="*{nome}" placeholder="Nome..." /> <br/><br/>
<label for="post">O que vocĂȘ gostaria de dizer?</label> <br/>
<textarea id="post" name="post" th:field="*{post}" ></textarea> <br/><br/>
<input type="submit" value="Postar" />
</fieldset>
</form>
Why is this thing trying to convert the <textarea> from String to some number with NumberFormat stuff ?
The Entity named Post clearly declares the field post as a String. So why does Spring thinks it is some kind of Number when submitting? Obviously, when I put some numbers in textarea, it saves to the database. But I need String to be saved...
Someone please enlighten me.
Thanks in advance!
EDIT
Here is the Repository AND Service Class, just to be sure.
Service...
#Service
public class PostService {
#Autowired
private PostRepository postRepository;
public void savePost(Post post) {
postRepository.save(post);
}
}
Repo...
public interface PostRepository extends JpaRepository<Post, Integer> {
}
There seems to be a name collision since both the object name and the variable name are same (in your case, post).
Either change the column name in the entity class to other than post and then change in the html. Or, change the backing object name in the controller and html to something other than post. Both are working for me.

LocalDate in form

I have a question about Spring + Thymeleaf date format.
I have a simple entity with LocalDate date field. I want to get this date from a user in form and save it to MySQL database.
I'm getting such an error:
Failed to convert property value of type java.lang.String to required type java.time.LocalDate for property date; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type java.time.LocalDate for value 2019-04-30; nested exception is java.time.format.DateTimeParseException: Text 2019-04-30 could not be parsed at index 2
My entity:
#Entity
#Table(name="game")
public class Game{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Transient
private User gameOwner;
private LocalDate date;
private LocalTime time;
//other fields
Thymeleaf view / form:
<form action="#" th:action="#{/games/addForm}" th:object="${gameForm}" method="post">
<p>Date: <input type="date" th:field="*{date}" /></p>
</form>
What's the reason for this problem? Maybe there is the other, better way to storage date?
Problem solved..
I don't know why but changing my template to:
<input type="date" th:value="*{date}" th:field="*{date}" />
and adding #DateTimeFormat(pattern = "yyyy-MM-dd") to entity field solved the problem.
import org.springframework.format.annotation.DateTimeFormat;
add the following annotation above date.
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
put additional curly bracket around date. It will convert date into string
<form action="#" th:action="#{/games/addForm}" th:object="${gameForm}" method="post">
<p>Date: <input type="date" th:field="*{{date}}" /></p>
</form>
I'm unable to reproduce the exact error, but I believe that adding a custom editor for the LocalDate class should fix this. Add this method to your controller:
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException{
setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
#Override
public String getAsText() throws IllegalArgumentException {
return DateTimeFormatter.ofPattern("yyyy-MM-dd").format((LocalDate) getValue());
}
});
}
It's also possible to add this globally, you'll have to create a ControllerAdvice class and add the method there instead.
Thymeleaf provides an extra module for that: https://github.com/thymeleaf/thymeleaf-extras-java8time
Adding the following dependency (maven) should be enough:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

Creating drop down list using Spring, Hibernate, JSP

Applications: Hibernate, Spring 3.0 MVC, JSP (used Spring forms)
Requirement: To select a table data from the database using Hibernate and display that as a drop-down list in JSP page using Spring MVC.
Code:
Hibernate / Dao code is
Cuisine class
#Entity
#Table(name = "cuisine")
public class Cuisine {
#Id
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
.. getters and setters
RecipeDaoImpl class
public List<Cuisine> getCuisine() {
String hql = "SELECT id, name FROM Cuisine";
return getSession().createQuery(hql).list();
}
Spring MVC
#Controller
public class RecipeController {
...
#RequestMapping("/add")
public String newRecipe(Map<String, Object> map) {
/* Get cuisine list and new object for cuisine */
List<Cuisine> cuisines = recipeServices.getCuisine();
System.out.println(cuisines);
map.put("cuisineList", cuisines);
map.put("cuisine", new Cuisine());
return "recipes/new";
}
JSP page:
<%# taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<tr>
<th><sf:label path="cuisine">Cuisine</sf:label></th>
</tr>
<tr>
<td><sf:select path="${cuisineList}">
<sf:options items="${cuisine}"></sf:options>
</sf:select></td>
</tr>
On doing it, I am getting following error:
org.springframework.beans.NotReadablePropertyException: Invalid property '[Cuisine [id=1, name=Continental][id=2, name=Italian]' of bean class [com.recipe.tables.Recipe]: Bean property '[Cuisine [id=1, name=Continental][id=2, name=Italian]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:729)
org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:721)
org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99)
org.springframework.validation.AbstractBindingResult.getFieldValue(AbstractBindingResult.java:219)
org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:120)
Can someone please suggest how to fix this? I have checked couple of articles and tried to replicate them, but no luck.
I think jsp should be
<td><sf:select path="${cuisine}">
<sf:options items="${cuisineList}" id="id" itemValue="name"></sf:options>
</sf:select></td>
</tr>

Spring MVC + Hibernate: How to handle form with relation

I have problem with adding/updating records with relations. Could please some advice how it should work?
I have two entities: Question and Category:
public class Question {
#Id
#GeneratedValue
private Long questionId;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category")
private Category category;
and
#Entity
#Table(name = "category")
public class Category {
#Id
#GeneratedValue
private Long categoryId;
private String name;
I have some list of categories and I would like to add new Question with selected Category. So in my QuestionController I have add method:
#RequestMapping(value = "/add", method = RequestMethod.GET)
public ModelAndView add() {
ModelAndView mav = new ModelAndView("question/add");
mav.addObject("question", new Question());
mav.addObject("categoryList", categoryService.getAll());
return mav;
}
and form:
<form:form modelAttribute="question" method="POST" >
Name: <form:input path="name" value="${ques.name}" />
Category: <form:select path="category" items="${categoryList}" />
<input type="submit" value="Add" />
</form:form>
Everything looks good for now (I can fill question name and select category). But I don't know how add POST method should work
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String added(#ModelAttribute Question question, BindingResult bindingResult) {
}
When I try to use above method I have error: Failed to convert property value of type 'java.lang.String' to required type model.Category
I've tried to look for similar problem but I coudln't find anything.. So if someone can help/advice or show link to similar issue I would be grateful!
Cheers!
You need to provide code for Spring that tells it how to convert the string value from the web page back into a Category object. This is done by either:
Adding a PropertyEditor to the DataBinder.
Creating a Converter.
It is a bad practice using hibernate objects to map the form items. There are two solutions
Add another property private transient String categoryString; to the 'Question' class. and map the UI category to this <form:select path="categoryString" items="${categoryList}" />
That way you can avoid the error.
Do not use the hibernate mapping classes for mapping the form items, use POJOs for doing this. and later somewhere in your application map this simple pojo elements onto the hibernate entity.
try changing this line:
<form:select path="category" items="${categoryList}" />
to:
<form:select path="category.categoryId" items="${categoryList}" itemLabel="name" itemValue="categoryId"/>
Then in the added method (post method), retrieve the Category object back from hibernate and set back on question object before saving:
Category selectedCategory = yourHibernateService.getCategoryById(question.getCategory().getCategoryId());
question.setCategory(selectedCategory);

Categories