Spring Boot, Thymeleaf Validation for Composition Class - java

How can I validate a composition relationship in Thymeleaf/Spring Boot. I have a simple FundTrf class which "has a" Data class. Problem is when I validate form inputs, FundTrf class related fields are getting validated, but the Data class related fields are not getting validated. Is there additional biding needs to be done between these classes. Below is what I have tried.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>HNB CEFT | Test Bed</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Form</h1>
<form action="#" th:action="#{/ceft/fundTrf}" th:object="${fundTrf}" method="post">
<table>
<tr><td>Version </td><td><input type="text" th:field="*{version}" /></td>
<td th:if="${#fields.hasErrors('version')}" th:errors="*{version}">Version Error</td>
</tr>
<tr><td>Bank Code </td><td><input type="text" th:field="*{data.dest_bank_code}" /></td>
<td th:if="${#fields.hasErrors('data.dest_bank_code')}" th:errors="*{data.dest_bank_code}">Bank Code Error</td>
</tr>
<tr><td>Amount </td><td><input type="text" th:field="*{data.amount}" /></td>
<td th:if="${#fields.hasErrors('data.amount')}" th:errors="*{data.amount}">Amount Error</td>
</tr>
</table>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
</body>
</html>
Below is my controller class.
#Controller
public class Hello implements WebMvcConfigurer{
#GetMapping("/ceft/welcome")
public String welcomeForm(Model model) {
model.addAttribute("fundTrf", new FundTrf());
return "welcome";
}
#PostMapping("/ceft/fundTrf")
public String ceftTransaction(#ModelAttribute #Valid FundTrf fundTrf, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "welcome";
} else {
return "result";
}
}
}
Below is my FundTrf class
public class FundTrf {
#NotEmpty
private String version;
private Data data;
..Getters and Setters
}
And this is the Data class.
public class Data {
#NotEmpty
private String reqId;
#NotEmpty
private String frm_hnb_account;
#NotEmpty
private String dest_bank_account;
#NotEmpty
private String benificiary_name;
#NotEmpty
private String dest_bank_code;
#NotEmpty
#Size(min = 2, max = 30)
private String amount;
..Getters and Setters
}
The issue is when I submit the form with empty values the message "Version must not be empty" is coming up, but Amount validation is not working. What am I doing wrong here?

You have to set #Valid on the object Data in order for your Data properties to be also validated.
public class FundTrf {
#NotEmpty
private String version;
#Valid //ADDED VALID HERE
private Data data;
..Getters and Setters
}
The javadoc for javax.validation.Valid says:
Marks a property, method parameter or method return type for
validation cascading. Constraints defined on the object and its
properties are be validated when the property, method parameter or
method return type is validated. This behavior is applied recursively.

Related

I added Lombok, but when I run it, get a getter / setter error

I am using Eclipse on Window. When running the program, an error occurred
An error happened during template parsing (template: "class path resource [templates/add.html]").
Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "add" - line 10, col 40).
Invalid property 'id' of bean class [com.example.demo.models.Book]: Bean property 'id' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Class Entity
#Table(name = "book")
#Entity
#NoArgsConstructor
#Getter
#Setter
#AllArgsConstructor
public class Book implements Serializable{
#Id
private Long id;
#Column
private String name;
#Column
private String image;
}
Class Controller
#Controller
public class BookController {
#Autowired
private BookService bookService;
#GetMapping("/")
public String add(ModelMap model) {
model.addAttribute("book", new Book());
return "add";
}
#PostMapping("/save")
public String save(ModelMap model, Book book) {
bookService.save(book);
model.addAttribute(book);
return list(model);
}
#GetMapping("/list")
public String list(ModelMap model) {
List<Book> list = (List<Book>) bookService.findAll();
model.addAttribute("books", list);
return "list";
}
}
Class Template
<!DOCTYPE html >
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Insert Book</title>
</head>
<body>
<form th:action="#{/save}" th:object="${book}" method="post">
<div th:if="${id == null}">
<input type="text" placeholder="Id" th:field="*{id}" /><br />
</div>
<input type="text" placeholder="Name" th:field="*{name}" /><br /> <input
type="text" th:field="*{image}" placeholder="Image" /><br /> <input
type="submit" value="Confirm"> <a th:href="#{/list}">List</a>
</form>
</body>
</html>
I added and installed Lombok and it didn't get any error but getter / setter error when running the program, so try to replace it manually and run successfully. I think it was Lombok's fault, but nothing was found.
.
The error is on the code below:
<div th:if="${id == null}">
You can fix that by replacing it with:
<div th:if="${book.id == null}">
You might be mislead by the error, but it is not related with Lombok.

Failed to convert property value of type java.lang.String[] to required type java.util.List

I am following Spring in Action 5 and have problem with creating Taco model after pressing submit button. This is my design Taco controller class:
#GetMapping
public String showDesignForm(Model model){
List<Ingredient> ingredients = new ArrayList<>();
ingredientRepository.findAll().forEach(i -> ingredients.add(i));
Type[] types = Ingredient.Type.values();
for (Type type : types){
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
return "welcomePage";
}
#ModelAttribute(name = "taco")
public Taco taco(){
return new Taco();
}
#PostMapping
public String processDesign(#Valid Taco taco, Errors errors, #ModelAttribute Order order){
if(errors.hasErrors()) {
return "welcomePage";
}
Taco saved = tacoRepository.save(taco);
order.addDesign(saved);
return "redirect:/orders/current";
}
And the error message which I catch:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'taco' on field 'ingredients': rejected value [CARN]; codes [typeMismatch.taco.ingredients,typeMismatch.ingredients,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [taco.ingredients,ingredients]; arguments []; default message [ingredients]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'ingredients'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.server.models.Ingredient' for property 'ingredients[0]': no matching editors or conversion strategy found]
Taco entity looks like:
#Data
#Entity
public class Taco {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private Date createdAt;
#NotNull
#Size(min = 3, message="Name must be at least 3 characters long")
private String name;
#ManyToMany(targetEntity = Ingredient.class)
#Size(min=1, message="You must choose at least 1 ingredient")
private List<Ingredient> ingredients = new ArrayList<>();
#PrePersist
void createdAt(){
this.createdAt = new Date();
}
}
And my entity with Ingredients:
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
#Entity
public class Ingredient {
#Id
private final String id;
private final String name;
#Enumerated(EnumType.STRING)
private final Type type;
public static enum Type{
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
This is html page which must create new Taco object with picked ingredients:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Testing Firs Page</title>
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="#{/images/taco.jpg}" alt="myImage"/>
<form method="POST" th:object="${taco}">
<span class="validationError"
th:if="${#fields.hasErrors('ingredients')}"
th:errors="*{ingredients}">Ingredient Error</span>
<div class="grid">
<div class="ingredient-group" id="wraps">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<br/>
<button>Submit your taco</button>
</div>
</form>
</body>
</html>
How can I fix it? Thanks for advance.
In Spring in Action, you should add IngredientByIdConverter class. this class is convert Ingredient to String.
#Component
public class IngredientByIdConverter
implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepo;
#Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
#Override
public Ingredient convert(String id) {
return ingredientRepo.findById(id);
}
}
https://github.com/habuma/spring-in-action-5-samples/blob/ff98b2ec36eeb627e4547713c8acbbd26a0eaa33/ch03/tacos-jdbc/src/main/java/tacos/web/IngredientByIdConverter.java
package tacos.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import tacos.Ingredient;
import tacos.data.IngredientRepository;
#Component
public class IngredientByIdConverter implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepo;
#Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
#Override
public Ingredient convert(String id) {
return ingredientRepo.findById(id);
}
}
The error is:
Cannot convert value of type java.lang.String to required type org.server.models.Ingredient for property ingredients[0]
You didn't share the code for Taco or Ingredient or the payload of the POST request, so we cannot say for sure what you need to change.
However, if you add a constructor to Ingredient that takes a String argument, I believe Spring will use that.
How you create an Ingredient object from a String value will of course depend on what the strings content is, so that's entirely up to you to figure out. If you need help with that, create a new question, and include the relevant information, such as the code of your POJO classes and the content of the POST request.

How to work with the object in Thymeleaf dropdown

I am new to Thymeleaf and stuck with a strange issue. Let me tell you what works first. I have two simple class
public class Country {
private long countryid;
private String name;
}
And
public class Person {
private String name;
private long countryId;
}
In the addPerson page I want to select the country from a dropdown. I have manually created a list of countries ( from spring controller) and then my addPerson.html is designed as
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Add Person</h1>
<form action="#" th:action="#{/addPerson}" th:object="${person}"
method="POST">
<p>
Name: <input type="text" th:field="*{name}" />
</p>
<select th:field="*{countryId}" class="form-control">
<option th:each="country: ${countryList}"
th:value="${country.countryid}" th:text="${country.name}"></option>
</select>
<p>
<input type="submit" value="Submit" /> <input type="reset"
value="Reset" />
</p>
</form>
</body>
</html>
When I select a country from dropdown, I get the countryid and everything works fine.
Now I want to change my Person class as below
public class Person {
private String name;
private Country country;
}
So, instead of the countryid, I want to have the country object itself. Keeping everything else same, I have changed my addPerson.html to
<select th:field="*{country}" class="form-control">
<option th:each="country: ${countryList}"
th:value="${country}" th:text="${country.name}"></option>
</select>
Now I can see the dropdown, but upon submitting, I am getting an error
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='person'. Error count: 1
In short : It works with property of the object, what I need to do to work with the entire object itself?
Kindly help.
UPDATE 1: the controller method signature
#GetMapping("/addPerson")
public String addPerson(Model model) {
Country country1 = new Country();
country1.setCountryid(1);
country1.setName("A");
Country country2 = new Country();
country2.setCountryid(2);
country2.setName("B");
List<Country> countryList = new ArrayList<Country>();
countryList.add(country1);
countryList.add(country1);
model.addAttribute("countryList", countryList);
model.addAttribute("person", new Person());
return "addPerson";
}
#PostMapping("/addPerson")
public void processAddPerson(#Valid #ModelAttribute("person") Person person) {
System.out.println(person.getName());
}
UPDATE 2
Upon debugging, I found out that in the second case, on submit, the control is not going to setCountry method of Person class at all!
Add BindingResult Object to your method.
public String processAddPerson( #Valid Person person,BindingResult bindingResult,Model model)
Ensure that the BindingResult has to be immediately after the object annotated with #Valid.

Spring mvc #RequestBody String format

I have one Controller : personController.java
#Controller
public class personController {
private static final Logger LOG = LoggerFactory.getLogger(OcaController.class);
#RequestMapping(value = "/person", method = {RequestMethod.POST, RequestMethod.GET})
public String ocaContract(#RequestBody String requestPerson) {
return requestPerson;
}
1 JSP : person.jsp
<html>
<head>
</head>
<body>
<form class="form-horizontal" METHOD="POST" ACTION="webmvc/person" ENCTYPE="x-www-form-urlencoded">
<div class="controls">
<input type="text" name="name" id="name" value="" placeholder="">
</div>
<div class="controls">
<input type="text" name="surname" id="surname" value="" placeholder="">
</div>
<input type="submit" value="ok"/>
</form>
</body>
</html>
and one Object Class : Person.java
#XmlRootElement(name="Person")
public class Person {
#XmlElement(required = true)
protected String name;
#XmlElement(required = true, nillable = true)
protected String surname;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
} ...
When I populate the JSP and click on the input button, my controller return this "requestPerson" string :
name=&surname=
Is it a way to have this string as a POJO ? My final result must be at the XML format :
<person>
<name>Lisala</name>
<surname>Lili</surname></person>
I hope you ll can help me because i'm on it since 1 day now and i didn't find an easy way to accomplish this.
You can replace #RequestBody with #ModelAttribute and String to Person
public String ocaContract(#ModelAttribute Person requestPerson) {

How to bind Object properties upon page submission in SpringMVC?

After reading tutorials and docs I still do not understand the mechanism behind binding certain Object properties in SpringMVC + hibernate.
Suppose we have a class Poem:
package com.test.poems.model;
import com.tastyminerals.poems.model.Author;
import com.tastyminerals.poems.model.Genre;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name = "P_POEM")
public class Poem {
#Id
#GeneratedValue
#Column(name="ID")
private Integer id;
#Column(name="TITLE")
private String title;
#Column(name="BODY")
private String body;
#Column(name="DATE")
private String date;
#ManyToOne
#JoinColumn(name="ID", referencedColumnName="ID_AUTHOR", insertable = false, updatable = false)
private Author author;
#ManyToOne
#JoinColumn(name="ID", referencedColumnName="ID_GENRE", insertable = false, updatable = false)
private Genre genre;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Author getAuthor() {
return author;
}
public Genre getGenre() {
return genre;
}
public void setAuthor(Author author) {
this.author = author;
}
public void setGenre(Genre genre) {
this.genre = genre;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
and a class Author:
#Entity
#Table(name = "AUTHORS")
public class Author {
#Id
#GeneratedValue
#Column(name="ID_AUTHOR")
private Integer id;
#Column(name="NAME")
private String name;
/* getters and setters */
I need to submit my poem to mysql database via hibernate. For this purpose I created a simple jsp page which has input fields for all Poem properties.
Upon submission RequestMethod.POST returns String values for title, body and author's name.
However that creates a type conversion error like: Failed to convert property value of type 'java.lang.String' to required type 'com.test.model.Author' for property 'author'.
Poem class expects Author object to be set into it but getting String name instead. I wonder why doesn't Spring make the necessary conversions since I explicitely create Author in my controller method? Shouldn't its values be automatically resolved and set after the page submission?
#RequestMapping(value = "/poem/add", method = RequestMethod.GET)
public ModelAndView addPoemPage() {
ModelAndView modelAndView = new ModelAndView("poem-add");
modelAndView.addObject("author", new Author());
modelAndView.addObject("poem", new Poem());
return modelAndView;
}
#RequestMapping(value = "/poem/add", method = RequestMethod.POST)
public ModelAndView addingPoem(#ModelAttribute Poem poem,
#ModelAttribute Author author) {
ModelAndView modelAndView = new ModelAndView("home");
authorService.addAuthor(author);
poem.setAuthor(author);
poemService.addPoem(poem);
return modelAndView;
}
My jsp page:
<%# page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<c:set var="url">${pageContext.request.requestURL}</c:set>
<link
href="${pageContext.request.contextPath}/resources/css/poem-add.css"
rel="stylesheet" />
<title>Writing a poem</title>
</head>
<body>
<h1>New poem</h1>
<p>Here you can write your poem.</p>
<form:form method="POST" commandName="poem"
action="${pageContext.request.contextPath}/poem/add.html">
<table>
<tbody>
<tr>
<td>Title:</td>
<td><input id="" title="Your poem's title" id="title"
name="title" type="text" class="input" /></td>
</tr>
<tr>
<td>Author:</td>
<td><input title="Author's name" id="author" name="author"
type="text" class="input" /></td>
</tr>
<tr>
<td>Date:</td>
<td><input title="Date of creation" id="date" name="date"
type="text" class="input" /></td>
</tr>
<tr>
<td>Text:</td>
<td><textarea title="Your poem goes here" rows="15" cols="50"
class="input"> </textarea></td>
</tr>
</tbody>
</table>
<table class="actions">
<tr>
<td><a
href="${pageContext.request.contextPath}/collection.html"><input
type="button" value="Back" class="button" /></a></td>
<td><a
href="${pageContext.request.contextPath}/collection.html"><input
type="submit" value="Submit" class="button" /></a></td>
</tr>
</table>
</form:form>
</body>
</html>
I know that I need a PropertyEditor or BeanWrapper. But I simply do not understand where and how do I implement them? What is the difference?
Summing my questions up, I need an explanation of what is going on "behind-the-scenes" between hibernate and SpringMVC right after I click submit button. If you could provide a sample of PropertyEditor or BeanWrapper for my case I would be endlessly grateful.
I need an explanation of what is going on "behind-the-scenes" between
hibernate and SpringMVC
: There is no hibernate yet. You are simply mapping a plain bean (model) to a JSP using Spring MVC.
I know that I need a PropertyEditor or BeanWrapper.
: You don't need a PropertyEditor at this stage. PropertyEditor is used for advanced type conversion like when you want an incoming date string "dd-mm-yyyy Zone" to be converted into a java.util.Date object and vice versa.
I wonder why doesn't Spring make the necessary
conversions since I explicitely create Author in my controller method?
Shouldn't its values be automatically resolved and set after the page
submission?
:Spring will automatically resolve if the JSP fields are mapped to the model attribute correctly using Spring Form tags. In your case, JSP form fields are not mapped to the model correctly and it should be as below
<form:form method="POST" modelAttribute="poem"
action="${pageContext.request.contextPath}/poem/add.html">
<table>
<tbody>
<tr>
<td>Title:</td>
<td><form:input path="poem.title" title="Your poem's title"
type="text" class="input" /></td>
</tr>
<tr>
<td>Author:</td>
<td><form:input path="poem.author.name" title="Author's name"
type="text" class="input" /></td>
</tr>
<tr>
<td>Text:</td>
<td><form:textarea path="poem.body" title="Your poem goes here" rows="15" cols="50"
class="input" /></td>
</tr>
</tbody>
</table>
<table class="actions">
<tr>
<td><a
href="${pageContext.request.contextPath}/collection.html"><input
type="button" value="Back" class="button" /></a></td>
<td><a
href="${pageContext.request.contextPath}/collection.html"><input
type="submit" value="Submit" class="button" /></a></td>
</tr>
</table>
</form:form>
You can add many attributes to the model but you can attach only one model attribute to the <form> and not two. Your controller would look like this.
#RequestMapping(value = "/poem/add", method = RequestMethod.GET)
public ModelAndView addPoemPage() {
ModelAndView modelAndView = new ModelAndView("poem-add");
Author author = new Author();
Poem poem = new Poem();
poem.setAuthor(author);
modelAndView.addObject("poem", new Poem());
return modelAndView;
}
#RequestMapping(value = "/poem/add", method = RequestMethod.POST)
public ModelAndView addingPoem(#ModelAttribute("poem") Poem poem) {
ModelAndView modelAndView = new ModelAndView("home");
authorService.addAuthor(poem.getAuthor);
poemService.addPoem(poem);
return modelAndView;
}

Categories

Resources