data-binding Spring - java

I have a Contact object that I put in the request, this object is modified in the
form and then get the modified object. I would like the object that is back is the same object that you send, you keep the value of the attributes that were not in the form.
class Contact{
private String name; // this attributes will be modified
private String lastName;
private Long id;
private Date created; // this atributes will not be modified
// getters and setters ....
}
#RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
public String updateContact(#PathVariable("id") Long id, Model model) {
Contact c = contactDao.get(id);
model.addAttribute("contact", c);
return "contact/form";
}
#RequestMapping(value = "/{id}/edit", method = RequestMethod.POST)
public String update(#PathVariable("id") Long id, #Valid #ModelAttribute Contact contact, BindingResult result, Model model) {
// The contact I get here I want to keep the original attributes of the
// object sent, and have the changes in the fields shown on the form. is that possible?
return "redirect:/contact";
}
<form:form action="${pageContext.servletContext.contextPath}/tags/create" commandName="contact">
<form:input path="name"/>
<form:errors path="name" cssClass="formError"/>
<form:input path="lastName"/>
</form:form>
I do not want to use hidden fields to maintain the value of the attributes that will not be changing

If you only want some of the fields to be handled in a form, make a new class - ContactDTO that contains only them, and then manually (or through reflection) copy them to the original Contact object (which you load by id from the DB)

I found the solution to the problem by stating the contact object as an object that lives in the session
#Controller
#RequestMapping("/contact")
#SessionAttributes("contact")
public class ContactController {
....
....
#RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
public String updateContact(#PathVariable("id") Long id, Model model) {
Contact c = contactDao.get(id);
model.addAttribute("contact", c);
return "contact/form";
}
#RequestMapping(value = "/{id}/edit", method = RequestMethod.POST)
public String update(#PathVariable("id") Long id, #Valid #ModelAttribute Contact contact, BindingResult result, Model model) {
contactDao.update(contact);
return "redirect:/contact";
}
}

What is your persistence framework? is it JPA or Hibernate? If so, annotate the field with #Column(updatable=false)

Related

Problem with form input validation in using #Valid and BindingResult in a Spring Boot App

I am trying to add code-side validation to my form. I am basing on this tutorial: https://www.javacodegeeks.com/2017/10/validation-thymeleaf-spring.html - but without effort.
I have an entity InvoiceData:
#Data
#Document
#NoArgsConstructor
public class InvoiceData {
#Id private String id;
private ContractorData data;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date receptionDate;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date orderDate;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date invoiceIssueDate;
#NotNull
#DateTimeFormat(pattern = "yyyy-MM-dd")
#NotNull
private Date contractDate;
#NotBlank
private String invoiceNumber;
private String additionalCosts;
private String contractorComment;
#NotEmpty
private List<InvoiceTask> invoiceTasks = new ArrayList<>();
And a Controller method:
#RequestMapping(value = "/addinvoice/{contractorId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String addInvoice(#PathVariable("contractorId") String contractorId, #ModelAttribute #Valid InvoiceData data, Model model, BindingResult result, RedirectAttributes attr, HttpSession session) {
if (result.hasErrors()) {
System.out.println("BINDING RESULT ERROR");
attr.addFlashAttribute("org.springframework.validation.BindingResult.data", result);
attr.addFlashAttribute("register", result);
return "redirect:/add";
} else {
Contractor contractor = contractorRepository.findById(contractorId).get();
data.setData(contractor.getContractorData());
if (contractor.getInvoices() == null) {
contractor.setInvoices(new ArrayList<InvoiceData>());
}
contractor.getInvoices().add(data);
invoiceDataRepository.save(data);
contractorRepository.save(contractor);
model.addAttribute("contractor", contractor);
return "index";
}
}
And a small piece of the Thymeleaf for clearness (all other fields look alike this one)
<form action="#" th:action="#{addinvoice/{id}(id=${contractorid})}" th:object="${invoicedata}" method="post">
<ul class="form-style-1">
<li>
<label>Reception date<span class="required">*</span></label>
<input type="date" th:field="*{receptionDate}" id="receptionDate">
</li>
The problem is that when I am trying to send an invalid form, I am not redirected to /add, but I get an error page saying:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='invoiceData'. Error count: 6
And the stacktrace (from just one field, for clearness):
Field error in object 'invoiceData' on field 'invoiceIssueDate': rejected value [null]; codes [NotNull.invoiceData.invoiceIssueDate,NotNull.invoiceIssueDate,NotNull.java.util.Date,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [invoiceData.invoiceIssueDate,invoiceIssueDate]; arguments []; default message [invoiceIssueDate]]; default message [must not be null]
So I presume that this is one of the behaviours that I can exptect from the validator.
But there is one thing, when I set a breakpoint in the controller, at the beginning of the method where the if statement begins, AND I send an invalid form, the debugger never stops there, so it seems that this code is never reached...
But when I send a correctly filled form - everything goes fine, the code works, data is sent to the database etc...
My question is: is this a normal behaviour of the validator? What can I do make the code run when form is invalid, so I can get the BindingResult and show some error output to the user?
You need to move the BindingResult parameter right next to parameter having #Valid annotation.
#RequestMapping(value = "/addinvoice/{contractorId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String addInvoice(#PathVariable("contractorId") String contractorId, #ModelAttribute #Valid InvoiceData data, BindingResult result, Model model , RedirectAttributes attr, HttpSession session) {
if (result.hasErrors()) {
System.out.println("BINDING RESULT ERROR");
attr.addFlashAttribute("org.springframework.validation.BindingResult.data", result);
attr.addFlashAttribute("register", result);
return "redirect:/add";
} else {
Contractor contractor = contractorRepository.findById(contractorId).get();
data.setData(contractor.getContractorData());
if (contractor.getInvoices() == null) {
contractor.setInvoices(new ArrayList<InvoiceData>());
}
contractor.getInvoices().add(data);
invoiceDataRepository.save(data);
contractorRepository.save(contractor);
model.addAttribute("contractor", contractor);
return "index";
}
}
Now the BindingResult variable will be attached to InvoiceData variable. Also if you are Validating multiple parameters in a API, you would require to declare its corresponding BindingResult variable right next to all of these.

#ModelAttribute for complex objects

I have a user entity that has many attributes (some fields not shown here):
#Entity
public class User {
#OneToOne(cascade = ALL, orphanRemoval = true)
private File avatar; // File is a custom class I have created
#NotEmpty
#NaturalId
private String name;
#Size(min = 6)
private String password;
#Enumerated(EnumType.STRING)
private Role role;
}
In my thymeleaf template I have a form that submits username, password and avatar (MultipartFile). Now in my controller instead of these parameters...
#PostMapping("/register")
public String register(#RequestParam String username,
#RequestParam String password,
#RequestParam MultipartFile avatar) { ...
...I want to use #ModelAttribute #Valid User user. My problem is that:
password first should be encrypted then passed to the user entity,
bytes[] from MultipartFile should be extracted then stored in user entity (as a custom File object),
some other fields such as Role should be set manually in the service class.
How can I take advantage of #ModelAttribute?
Instead of trying to shoehorn everything into your User class, write a UserDto or UserForm which you can convert from/to a User. The UserForm would be specialized for the web and converted to a User later on.
The conversions you are talking about should be done in your controller (as that is ideally only a conversion layer before actually talking to your business services).
public class UserForm {
private MultipartFile avatar;
#NotEmpty
private String username;
#Size(min = 6)
#NotEmpty
private String password;
public UserForm() {}
public UserForm(String username) {
this.username = username;
}
static UserForm of(User user) {
return new UserForm(user.getUsername());
}
// getters/setters omitted for brevity
}
Then in your controller do what you intended to do (something like this):
#PostMapping("/register")
public String register(#ModelAttribute("user") UserForm userForm, BindingResult bindingResult) {
if (!bindingResult.hasErrors()) {
User user = new User();
user.setName(userForm.getUsername());
user.setPassword(encrypt(userForm.getPassword());
user.setAvataor(createFile(userForm.getAvatar());
userService.register(user);
return "success";
} else {
return "register";
}
}
This way you have a specialized object to fix your web based use cases, whilst keeping your actual User object clean.
Maybe you can just use a setter to make all these actions. When Spring is mapping data to fields, and you have setters in the entity, it will use them to pass data. You can preprocess data in this way and set final values to fields.
#PostMapping("/register")
public String register(#ModelAttribute User user, Model model) { // remember if You have another name for parameter and backing bean You should type this one #ModelAttribute(name="userFromTemplate") User user
encryptPassword(user.getPassword); //remember that is sample code, You can do it however You want to
extractBytes(user.getAvatar); //same here
user.setRole(manuallySetRole);
model.addAttribute("user", user);
return "success"; // here u can redirect to ur another html which will contain info about ur user
} else
return "redirect:sorry";
}
encryptPassword(String password) { ... }
same for the rest methods
Here i give You sample code how to use #ModelAttribute in your example. If You have questions feel free to comment.

Spring binds form data to multiple (wrong) objects

I experience a weired error where the form data is bound to a completely wrong object upon submission.
I am using spring with thymeleaf and have the following form:
<form method="post" th:action="#{/backend/user/create}"
th:object="${userInCreation}" id="userCreateForm">
<input th:field="*{firstName}" />Create user</button>
</form>
The object I want to bind to is
public class UserInCreation implements Serializable {
private String firstName;
public UserInCreation() {}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
Binding happens in the controller
#Controller
#RequestMapping("/backend/user")
public class UserController {
#RequestMapping(value = "/create", method = RequestMethod.GET)
public String createUserForm(UserInCreation userInCreation) {
return "backend/user/create";
}
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String createUser(#Valid UserInCreation userInCreation, BindingResult result, Model model) {
return "backend/user/index";
}
}
This works fine, despite a big issue: The data I type into the firstName field is also bound to the Spring-Security Principal, which I make available as a ModelAttribute:
#ModelAttribute("currentAuthor")
public User getCurrentAuthor(Principal principal, HttpSession session) {
User author = (User) ((Authentication) principal).getPrincipal();
return author;
}
The class User also has a field firstName, and this is changed. So when I type "Some name" into the form and submit, suddenly the first name of the principal will be "Some name". Any thoughts?
I found out what the problem is. Actually, I had a function of the type
#ModelAttribute("foo")
public Foo xyz(#ModelAttribute("bar") Bar bar) {
...
}
This is against the specification, but it seemed to work at first. But it also seems to mess up the data binding system completely.

Handle update forms in Spring MVC

What is the proper way to handle editing objects in Spring MVC. Let's say I have user object:
public class User {
private Integer id;
private String firstName;
private String lastName;
//Lets assume here are next 10 fields...
//getters and setters
}
Now in my controller I have GET and POST for url: user/edit/{id}
#RequestMapping(value = "/user/edit/{user_id}", method = RequestMethod.GET)
public String editUser(#PathVariable Long user_id, Model model) {
model.addAttribute("userForm", userService.getUserByID(user_id));
return "/panels/user/editUser";
}
#RequestMapping(value = "/user/edit/{user_id}", method = RequestMethod.POST)
public String editUser(#Valid #ModelAttribute("userForm") User userForm,
BindingResult result, #PathVariable String user_id, Model model) {
if(result.hasErrors()) {
User user = userService.getById(user_id);
user.updateFields(userForm);
}
userService.update(user);
}
Now the question is do I really need to get my user from database in POST method and update every field one by one in some update method or is there better way for that?
I am thinking about using #PathVariable for User and get User from database with converter and then in some way inject parameters from POST method into that object automatically. Something like this:
#RequestMapping(value = "/user/edit/{user}", method = RequestMethod.POST)
public String editUser(#Valid #PathVariable("user") User userForm,
BindingResult result, Model model)
But when I try this I got error with BindingResults:
java.lang.IllegalStateException: An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the #RequestBody or the #RequestPart arguments
Is there any easy way to create controller to handle objects editing or do I need to copy fields which could change one by one??
By the way, I can't use SessionAttributes because it causes problems for multiple tabs.
I believe you are sending "userForm" as a model attribute. If so try with following pattern,
#RequestMapping(value = "/user/edit/{user_id}", method = RequestMethod.POST)
public String editUser(#PathVariable String user_id, #Valid #ModelAttribute("userForm") User userForm,
BindingResult result, Model model)
Thanks
You keep user id in a input hidden inside your edit form.
#RequestMapping(value = "/user/edit", method = RequestMethod.POST)
public String editUser(#Valid #ModelAttribute("userForm") User userForm,
BindingResult result,Model model){
if(result.hasErrors()){
User user = userService.getById(userForm.getId());
user.updateFields(userForm);
}
userService.update(user);
return "redirect:.......";
}

How to effectively handle multiple user inputs with #RequestParam

I am using #RequestParam to catch the front-end user input and pass it to the back end through controller and save it to the database.
So far controller handles the request like this:
#RequestMapping(value = "/myURL", method = RequestMethod.POST)
public String saveCustomer(
#RequestParam("customerFirstName") String customerFirstName,
#RequestParam("customerLastName") String customerLastName,) {
Customer customer = customerService.saveCustomer(
customerFirstName, customerLastName);
return null;
}
Well I guess this is fine when I only have two #RequestParam for two arguements, but I am facing some table that has more than 10 params, I think by using #RequestParam is apparently not realistic, is there another around this?
You can save the customer directly.
#RequestMapping(value = "/myURL", method = RequestMethod.POST)
public String saveCustomer(Customer customer) {
customerService.saveCustomer(customer);
return null;
}
Spring can databind POJOs as long as you have a no-args constructor and setters for your properties.
You should create a provider to use JSON, so you can send complex Java\JS objects and not just primitives.
If you are going to deal with multiple #RequestParam you can always opt for a bean class approach.
Controller:
#RequestMapping(value = "/myURL", method = RequestMethod.POST)
public String saveCustomer(
#RequestBody Customer customer) {
return null;
}
Bean:
public class Customer{
private String customerFirstName;
private String customerLastName;
//constructors, getters and setters
}
Check this link out for more info on #RequestBody

Categories