Validation in spring - spring

I am using spring mvc and hibernate.I have a form which has three fields id,sum and sum_amount .I have a table in database which has 2 column "id" and "Amount".Now I want to persist amount field in that table if sum(Amount) of a id attribute less than sum_amount field specified in form otherwise it will show an error.How can i do that validation?
<form action="join" method="POST" ajaxForm="ContentReplace" style="margin: 133px;">
<label>Id</label>
<input type="text" name="id" value="" /><br>
<label>Amount</label>
<input type="text" name="amount" value="" /><br>
<label>Sum of Amount</label>
<input type="text" name="sum_amount" value="" /><br>
<input type="Submit" value="Search">
</form>
My model class is
#Entity
public class Amount {
#Id
Integer id;
Integer amount;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
}
Now i know about JSR validation and Spring custom validation but they validate model data.You can see that i need a validation which query database and show error in form field.How can i do that?

It's perfectly reasonable to have injected a Service bean (that has an injected DAO bean) in your custom Validator to perform your lookup and based on that validation success or failure.

Related

Model attribute value is not passed to input type text in Thymeleaf

I'm trying to set model attribute(entryNumber) value to input type text in Thymeleaf. But input value is always empty though the entryNumber value is present.
Empty Entry Number field
Please see my AllocationController, LedgerDto and receive-allocation.html files.
AllocationController
/**
* This method allows to enter receive allocation details.
*/
#GetMapping("/admin/receive-allocation")
public String receiveAllocation(final ModelMap model) {
model.addAttribute("entryNumber", allocationService.getLedgerEntryNumber());
model.addAttribute("votes", allocationService.getAllVotes());
model.addAttribute("divisions", userService.getAllDivisions());
return "receive-allocation";
}
When I'm debugging the application, entryNumber is present in the ModelMap. Refer the below image.
entryNumber is present in the ModelMap
LedgerDto
public class LedgerDto {
#NotEmpty
private String voteNumber;
#NotEmpty
private String division;
#NotEmpty
private String entryNumber;
public String getVoteNumber() {
return voteNumber;
}
public void setVoteNumber(String voteNumber) {
this.voteNumber = voteNumber;
}
public String getDivision() {
return division;
}
public void setDivision(String division) {
this.division = division;
}
public String getEntryNumber() {
return entryNumber;
}
public void setEntryNumber(String entryNumber) {
this.entryNumber = entryNumber;
}
receive-allocation.html
<form th:action="#{/admin/receive-allocation}"
th:object="${ledgerDto}" method="post">
<div class="form-row">
<div class="form-group col-md-2"
th:classappend="${#fields.hasErrors('entryNumber')}? 'has-error':''">
<label for="inputEntryNumber">Entry Number</label> <input
type="text" class="form-control"
th:field="*{entryNumber}">
<p class="error-message"
th:each="error: ${#fields.errors('entryNumber')}"
th:text="${error}">Validation error</p>
</div>
Please help me to solve this.
It is not possible to use th:field when you want to prefill your form with values from your model attribute. In this case you have th:with in combination th:value
<form name='form' action="#" th:action="#{/internal/user/account/datachange}" method='POST' th:object="${userDTO}">
<table>
<tr>
<td><label for="name">Name:</label></td>
<td><input type="text" name="name" id="name" th:with="name=*{name}" th:value="${user.name}" required></td>
</tr>
</table>
</form>

hibernate validation not working while one-to-one mapping in between two entities

During form submission, if there is any validation error then form shows the errors messages under the fields. But the actual problem is in another place. I have two entities User and UserDetails. These entities are mapped with each other by bidirectional one-to-one mapping. Validation is working only with the User entity fields but not with the UserDetails entity.
Spring - 5.0.2`
Hibernate - 5.2.10
User.java
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotEmpty(message="{common.error.msg}")
private String first_name;
#NotEmpty(message="{common.error.msg}")
private String last_name;
#NotEmpty(message="{common.error.msg}")
private String status;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY)
private UserDetails userDetails;
//Getter and setter methods
}
UserDetails.java
#Entity
#Table(name="user_details")
public class UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;
#NotEmpty(message="{common.error.msg}")
private String address;
#NotEmpty(message="{common.error.msg}")
private String mobile;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id")
private User user;
//Getter and setter methods
}
Get and Post methods in the controller class
#GetMapping(value="/create")
public String loadUserForm(Model model) {
model.addAttribute("command", new User());
return "backend/oms/user/form"; //JSP
}
#PostMapping(value="/create")
public String Save(#Valid #ModelAttribute("command") User user, BindingResult br, Model model, HttpSession session, RedirectAttributes ra) {
if(br.hasErrors()) {
return "backend/oms/user/form"; //JSP
} else {
try {
int id = userService.save(user);
if(id > 0) {
ra.addFlashAttribute("flash_msg", "ok|User added!");
return "redirect:/oms/user/edit/"+id;
} else {
return "backend/oms/user/create"; //JSP
}
} catch (ConstraintViolationException ex) {
model.addAttribute("err", "Something wrong! Please try again.");
return "backend/oms/user/form"; //JSP
}
}
}
messages.properties
common.error.msg=This field is required!
form.jsp
<form:form action="/oms/user/create" method="post" modelAttribute="command">
<label>First Name</label>
<form:input path="first_name" class="form-control" placeholder="First Name" value="" />
<form:errors cssClass="error" path="first_name" />
<label>Last Name</label>
<form:input path="last_name" class="form-control" placeholder="Last Name" value="" />
<form:errors cssClass="error" path="last_name" />
<label>Mobile</label>
<form:input path="userDetails.mobile" class="form-control" placeholder="Mobile" value="" />
<form:errors cssClass="error" path="userDetails.mobile" />
<label>Address</label>
<form:textarea path="UserDetails.address" class="form-control" placeholder="Address" value="" />
<form:errors cssClass="error" path="userDetails.address" />
<label>Status</label>
<form:select class="form-control" path="status">
<option value="">Choose...</option>
<option value="E">Enable</option>
<option value="D">Disable</option>
</form:select>
<form:errors path="status" cssClass="error"/>
<input type="submit" class="btn btn-primary" value="Save" />
</form>
Please see the screenshot
As you can see the image only fields from the User entity is validating but fields those are from the UserDetails are not validating. Please help.
It is solved now. #Valid annotation solved my problem. I need to put #Valid annotation before the entity variable declaration like below --
#OneToMany(mappedBy="majorHead", cascade = CascadeType.ALL)
#Valid
private List<MinorHead> minorHead = new ArrayList<MinorHead>();

Using thymeleaf to post form data to a Controller that uses #ModelAttribute (complex objects)

There is the Element class:
public class Element {
private Long id;
private Name name;
// Getters and Setters ...
}
And the Name class:
public class Name {
private String en;
private String fr;
private String de;
// Getters and Setters ...
}
There is a getElementsController:
#GetMapping("/elements/create")
public String getElementsCreate() {
return "private/new-element";
}
There is a NewElementController controller:
#PostMapping("/elements/create")
public String postElementsCreate(#ModelAttribute Element element) {
System.out.println(element)
return null;
}
There is a form that posts data to the NewElementController:
<form method="post" th:object="${element}" th:action="#{/elements/create}">
<input type="text" value="1" name="id" placeholder="Id"/>
// How should I make the input fields for:
element.name.en ?
element.name.fr ?
element.name.de ?
<button type="submit">Save element</button>
</form>
Setting the Id works, but I can not access the name field (it is an object)
I have tried with th:field="*{name}" and with th:field="*{name.en}", but it does not work in that way.
Try following:
<form method="post" th:object="${element}" th:action="#{/elements/create}">
<input type="text" name="id" th:value="*{id}" placeholder="Id"/>
<input type="text" name="name.en" th:value="*{name.en}" placeholder="Name (EN)"/>
<input type="text" name="name.fr" th:value="*{name.fr}" placeholder="Name (FR)"/>
<input type="text" name="name.de" th:value="*{name.de}" placeholder="Name (DE)"/>
<button type="submit">Save element</button>
</form>
Yor controller method for GET should be like this:
#GetMapping("/elements/create")
public String getElementsCreate(Model model) {
Element element = new Element();
Name name = new Name();
element.setName(name);
model.addAttribute("element", element);
return "private/new-element.html";
}

Spring Boot multiple controllers with same mapping

My problem is very similar with this one: Spring MVC Multiple Controllers with same #RequestMapping
I'm building simple Human Resources web application with Spring Boot. I have a list of jobs and individual url for each job:
localhost:8080/jobs/1
This page contains job posting details and a form which unauthenticated users -applicants, in this case- can use to apply this job. Authenticated users -HR Manager-, can see only posting details, not the form. I have trouble with validating form inputs.
What I tried first:
#Controller
public class ApplicationController {
private final AppService appService;
#Autowired
public ApplicationController(AppService appService) {
this.appService = appService;
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.POST)
public String handleApplyForm(#PathVariable Long id, #Valid #ModelAttribute("form") ApplyForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "job_detail"; //HTML page which contains job details and the application form
}
appService.apply(form, id);
return "redirect:/jobs";
}
#RequestMapping(value = "/applications/{id}", method = RequestMethod.GET)
public ModelAndView getApplicationPage(#PathVariable Long id) {
if (null == appService.getAppById(id)) {
throw new NoSuchElementException(String.format("Application=%s not found", id));
} else {
return new ModelAndView("application_detail", "app", appService.getAppById(id));
}
}
}
As you guess this didn't work because I couldn't get the models. So I put handleApplyForm() to JobController and changed a little bit:
#Controller
public class JobController {
private final JobService jobService;
private final AppService appService;
#Autowired
public JobController(JobService jobService, AppService appService) {
this.jobService = jobService;
this.appService = appService;
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.POST)
public ModelAndView handleApplyForm(#PathVariable Long id, #Valid #ModelAttribute("form") ApplyForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return getJobPage(id);
}
appService.apply(form, id);
return new ModelAndView("redirect:/jobs");
}
#RequestMapping(value = "/jobs/{id}", method = RequestMethod.GET)
public ModelAndView getJobPage(#PathVariable Long id) {
Map<String, Object> model = new HashMap<String, Object>();
if (null == jobService.getJobById(id)) {
throw new NoSuchElementException(String.format("Job=%s not found", id));
} else {
model.put("job", jobService.getJobById(id));
model.put("form", new ApplyForm());
}
return new ModelAndView("job_detail", model);
}
}
With this way, validations works but I still can't get the same effect here as it refreshes the page so that all valid inputs disappear and error messages don't appear.
By the way, job_detail.html is like this:
<h1>Job Details</h1>
<p th:inline="text"><strong>Title:</strong> [[${job.title}]]</p>
<p th:inline="text"><strong>Description:</strong> [[${job.description}]]</p>
<p th:inline="text"><strong>Number of people to hire:</strong> [[${job.numPeopleToHire}]]</p>
<p th:inline="text"><strong>Last application date:</strong> [[${job.lastDate}]]</p>
<div sec:authorize="isAuthenticated()">
<form th:action="#{/jobs/} + ${job.id}" method="post">
<input type="submit" value="Delete this posting" name="delete" />
</form>
</div>
<div sec:authorize="isAnonymous()">
<h1>Application Form</h1>
<form action="#" th:action="#{/jobs/} + ${job.id}" method="post">
<div>
<label>First name</label>
<input type="text" name="firstName" th:value="${form.firstName}" />
<td th:if="${#fields.hasErrors('form.firstName')}" th:errors="${form.firstName}"></td>
</div>
<!-- and other input fields -->
<input type="submit" value="Submit" name="apply" /> <input type="reset" value="Reset" />
</form>
</div>
Check thymeleaf documentation here
Values for th:field attributes must be selection expressions (*{...}),
Also ApplyForm is exposed then you can catch it in the form.
Then your form should looks like this:
<form action="#" th:action="#{/jobs/} + ${job.id}" th:object="${applyForm}" method="post">
<div>
<label>First name</label>
<input type="text" name="firstName" th:value="*{firstName}" />
<td th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"></td>
</div>
<!-- and other input fields -->
<input type="submit" value="Submit" name="apply" /> <input type="reset" value="Reset" />
</form>

KeyPairValue in ASP.NET MVC3 Razor

I have a weird problem.
I'm making dynamic form in Razor. I'm using dictionary to store dynamically added inputs.
I generate code like that:
<input type="hidden" value="96" name="Inputs[0].Key">
<input type="text" name="Inputs[0].Value">
I receive in my controller this dictionary. It always has as many elements that I added, but all of them are empty.
This is part of my model:
public class MetriceModelTaskSchedule
{
public IEnumerable<KeyValuePair<long, string>> Inputs { get; set; }
}
What can be wrong here?
What can be wrong here?
The fact that the KeyValuePair<TKey, TValue> class has the Key and Value properties which are readonly. They do not have a setter meaning that the model binder simply cannot set their value.
So as always start by defining a view model:
public class InputViewModel
{
public long Key { get; set; }
public string Value { get; set; }
}
and then:
public class MetriceModelTaskSchedule
{
public IEnumerable<InputViewModel> Inputs { get; set; }
}
Alternatively you could use a Dictionary:
public class MetriceModelTaskSchedule
{
public IDictionary<long, string> Inputs { get; set; }
}
Also make sure that you have respected the standard naming convention for your input fields in the view so that the model binder can successfully bind them to your model:
<div>
<input type="text" name="Inputs[0].Key" value="1" />
<input type="text" name="Inputs[0].Value" value="value 1" />
</div>
<div>
    <input type="text" name="Inputs[1].Key" value="2" />
    <input type="text" name="Inputs[1].Value" value="value 2" />
</div>
...

Resources