Need help with binding Set with Spring MVC form - java

I have been trying for last 3 days still i am not able to solve my problem
I have Person Class
#SuppressWarnings("rawtypes")
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person")
#JoinColumn(name="person_id")
public Set<Book> books = new HashSet<Book>();
class Book
book_id
person_id
In my JSP form i have
<c:forEach items="${BookList}" var="var1" varStatus="counter">
<input type="checkbox" name="books[${counter.index}].book_id" value="${var1.book_id}" >${var1.book_name}</input>
</c:forEach>
I am inserting the books in table depending upon the check boxes
The book list is populated from refrenceData model.
COntroller
#RequestMapping(value = "/persons/add", method = RequestMethod.GET)
public String getAdd(Model model) {
logger.debug("Received request to show add page");
// Create new Person and add to model
// This is the formBackingOBject
model.addAttribute("personAttribute", new Person());
// This will resolve to /WEB-INF/jsp/addpage.jsp
return "hibernate/addpage";
}
#RequestMapping(value = "/persons/add", method = RequestMethod.POST)
public String add(#Valid #ModelAttribute("personAttribute") Person person, BindingResult result) {
logger.debug("Received request to add new person");
if (result.hasErrors())
return "hibernate/addpage";
else
personService.add(person);
// This will resolve to /WEB-INF/jsp/addedpage.jsp
return "hibernate/addedpage";
}
Now if i have single Book object then this works ok and data is entered in DB but if i have set then it says invalid property book[1]
After searching a lot on SO and Google i leart that i have two option
PropertyEditor
AutoPopulatingList
I don't know how to use them in my case. Can anyone help me , where do i have to use them and how to use it

Look at this question Bind objects in a Set collection
You need to use another type of Collection. I'd recommend to use a List instead of a Map. When you send from the form a parameter with a name like:
name="books[0].book_id"
SpringMVC will look in the property called books (which is a Set for you) and then it will try to get the first element by doing books.get(0). Set don't have a get because Set has not an order.
For the implementation of the list you can use AutoPopulatingList. It is an implementation of a lazy List which will create an object if it doesn't exist. For example if you invoke books[0].id and you haven't added a book in the position 0 of the list it will throw a NullPointerException, but if you use AutoPopulatingList it will create a new Book and addd it in that position if that position is empty.
public List<Book> books = new AutoPopulatingList<Book>(new ElementFactory<Book>() {
#Override
public Book createElement(final int index) throws ElementInstantiationException {
//call the constructor as you need
return new Book();
}
});
if you you are going to instanciate it with the default constructor of Book (that is Book()), you can use a syntax like this one:
public List<Book> books = new AutoPopulatingList<Book>(Book.class);

When I have such complicated form i honestly prefer to use JSON and submit it using AJAX.
{"person":{"id":1,"books":[{"person_id":2,"book_id":3},{"person_id":2,"book_id":6},{"person_id":3,"book_id":4}]}
#RequestMapping(value = "/persons/add", method = RequestMethod.POST)
#ResponseBody
public String add(#RequestBody Person person){
//ad your business logic
}
Your code will be validate by de-serializer and you will be able to save it.
You can reed more about that in this post:
http://blog.springsource.com/2010/01/25/ajax-simplifications-in-spring-3-0/

Binding of Set isn't possible with Spring MVC as Sets do not have indexes to work with. Although you can iterate through sets in JSP and show the results.
The solutions might be -
Use another type of collection like List.
Wrap your Set in a POJO, use your Set for showing its containing values in JSP. Once you want to post the form containing your selection, add new property in your POJO which is String(or similar) and provide this property as your PATH in JSP tag, which will get the selection data from JSP. Then in backend code, fill your set with this value.
In case, your POJO is the also an Entity for your database creation using Hibernate, simply put #Transient on top of it. Hibernate will ignore this property while creating table.

Related

Spring Boot handling multiple parameters in a get request

I am new to using Spring boot framework.
I want to create a #GetMapping where based on what user enters in the parameter either Property1 Name(String) or Protery2 Designation(String) or Property3 Salary(Integer) the method should be able to get the List of employees based on one or more properties.
I can create individual methods but I do not want to do that.
I want to do something like this:
#GetMapping("/employee")
public List<Employee> getEmployee(Params parameters)
{
// Filter the list based on parameters provided and return the list
}
Also, I am not understanding how to handle parameters
for example, if it is an integer there is only one column but if the user enters string there are two columns.
If the user does not specify the parameter name I have to handle that.
You can use #RequestParam Map<String, String> params to bind all parameters to one variable
E.g.
#RequestMapping(value="/params", method = RequestMethod.GET)
public ResponseEntity getParams(#RequestParam Map<String, String> params ) {
System.out.println(params.keySet());
System.out.println(params.values());
return new ResponseEntity<String>("ok", HttpStatus.OK);
}
You can define the three parameters using the #RequestParam annotation and check which one is non-empty:
#GetMapping("/employee")
public List<Employee> getEmployee(#RequestParam(defaultValue = "empty") String name, #RequestParam(defaultValue = "empty") String designation, ....
{
// check which one is not empty and perform logic
if (!name.equals("empty")) {
// do something
}
}
Regarding which parameter the user chooses: you can make a drop-down menu or a simple-radio selection, where the user chooses the search criteria himself (and where each criterion is mapped by a request parameter). For example:

How to calculate the outcome of difference between two fields from hibernate and show it on a jsp?

I am making a simple CRUD app, using spring mvc, hibernate and mySQL.
I have a single table in mySQL:
CREATE TABLE `product` (
`id` INT NOT NULL AUTO_INCREMENT,
`product_name` VARCHAR(45) DEFAULT NULL,
`quantity_needed` INT DEFAULT NULL,
`status` INT DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
I have the basics fixes - listing all of the elements on a JSP, adding a new element, updating and deleting.
I want to also show a separate list with just the products, where the status value < quantity_needed with the difference being shown. I created the code to show just the list(without anything else). Below is a part of my productDAOImpl:
#Transactional
#Override
public List<Product> buyList() {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// query the database to find the requested items
Query<Product> theQuery = currentSession.createQuery("from Product p where p.status<p.quantityNeeded");
// get the result list
List<Product> buyList = theQuery.getResultList();
return buyList;
}
and a the controller:
#GetMapping("/buy")
public String buyList(Model theModel) {
// get the requested list of items to be bought
List<Product> theProduct = productDAO.buyList();
// add them to the page
theModel.addAttribute("products", theProduct);
return "buy-list";
}
part of my JSP:
<c:forEach var="tempProduct" items="${products}">
<tr>
<td>${tempProduct.productName}</td>
<td>{tempProduct.difference}</td>
</tr>
In my product entity I created a new field difference
// field for quantityNeeded-status value
#Transient
private int difference;
public int getDifference(Integer quantityNeeded,Integer status) {
difference=quantityNeeded-status;
return difference;
}
As mentioned above, the list I am showing should also have a new parameter, which will be calculated(as a difference of 2 ints from the database).This value will be shown on the page.
How can I do this?
Although it seems a good and straightforward approach, you should never propagate the domain model object outside the business layer, for instance by using entities in the front end.
What you should do, is to create a service layer, with its own dto, where you would put all the information you need to pass to the front end.
So, if your frontend layer needs specific information from the backend, you will create a ProductDTO file with only the fields you need, for instance the productName and the priceDifference.
The service layer will retrieve the information from the DAO, and then translate it into the DTO, to decouple the responsibilities between the datasource and the presentation.
This way, if you change the logic of the calculation of the priceDifference, you just replace the business logic, while the presentation layer will continue working.
I would change:
List<Product> theProduct = productDAO.buyList();
to
List<ProductDTO> theProduct = productService.buyList();
The ProductDTO is a simple POJO with the fields you need and the service would have something like:
public List<ProductDTO> buyList() {
List<Product> myProducts = productDAO.buyList();
//convert the myProduct list to a list of ProductDTO
//calculate the priceDifference
//return the list od DTOs
return dtoList;
}
Quick dirty way -- create the correct getter
public int getDifference() {
return quantityNeeded-status;
}
Proper way is #senape's mentioned above.

How to bind data from request into existing object? Multiple steps form

in my method I have let's say UserDetails object which has some defined value like id, phone etc.
User changing only one value through form, it's email. Is the way how I could use method
userDetailsForm.bindFromRequest(new String[]{"email"}).get()
to not 'loose' previous values? The above example give me new userDetail object with only defined email field. Of course I know that I can use DynamicForm class, or just
userDetailsForm.bindFromRequest().get().getEmail()
but it would be helpfull to have method which does all this binding in one line.
=========EDIT
DynamicForm dynamicForm = Form.form().bindFromRequest();
String email = dynamicForm.get("email");
isn't that what I'm looking for.
=========EDIT======================================
In other words, I want to divide my form to 3 steps, but after every step I am doing update on DB. So for example when I am POSTing step2 I have object with values from previous step:
User [userId=8, createdById=12, name=null, active=false, country=EN]
so now when I am doing:
static Form<User> userForm = Form.form(User.class);
User user = User.find(8);
User user2 = (User) userForm.fill(user).bindFromRequest("name").get();
I am geting new object with empty fields:
User [userId=0, createdById=0, name="value from step 2", active=false, country=null]
I will be very greatfull for any advise.
Try this
Form<UserDetail> submittedForm = form(UserDetail.class).bindFromRequest();
String emailID = submittedForm.data().get("email");
data() will hold name and value pair like following Map<String, String>, further retrieve value by providing its key name inside get() will return you the desired value.
Ok guys, I've figured out how to solve problem.
Here is discussion about this:
https://groups.google.com/forum/#!searchin/play-framework/Form$20bind/play-framework/MtjBV5YNQ3E/QumAmLbMl5sJ
one of possible solution is here:
https://gist.github.com/nraychaudhuri/10590943
import com.fasterxml.jackson.databind.ObjectMapper;
private static Form<Computer> editableForm(final Computer obj) {
ObjectMapper mapper = new ObjectMapper();
Form<Computer> form = Form.form(Computer.class);
Map<String,String> data = mapper.convertValue(obj, Map.class);
Map<String, String> submittedData = form.bindFromRequest().data();
data.putAll(submittedData);
return form.bind(data);
}
and my solution is below:
public T bind(T target, Map<String, String> newValues) {
DataBinder binder = new DataBinder(target);
binder.setAllowedFields(getAllowedFields());
binder.bind(new MutablePropertyValues(newValues));
return target;
}
=================EDIT
Here is important disscussion about security issue: https://groups.google.com/forum/#!searchin/play-framework/Form$20bind/play-framework/uGrSlJMo48c/QnVjzP4ovqcJ
Form.bindFromRequest
without arguments. However, as probably most of you know, invoking
that method without parameters will bind all fields of the
corresponding model object to like-named request parameters, including
fields holding internal state that must never be set from the outside
(e.g. boolean isAuthenticated). That is, an attacker may set any field
and circumvent security if they only know the name of the
corresponding model object field. This is of course a catastrophic
security vulnerability (similar to PHP's notorious and deprecated
register_globals option:
http://www.php.net/manual/en/security.globals.php).

sending javascript object arrays as parameters to controller

Question is pretty self explanatory. I want to send 2 different arrays of objects through a POST form without ajax to my controller.
I changed my question to using ajax and using a get request due to the size of the params. Currently getting a 400 (Bad Request). I have no idea why. Please take a look...
I have objects:
var phone = {phoneId:"", phoneNumber:"", phoneType:""};
var schedule = {scheduleId:"", time:"", day:""};
Which I place into a javascript arrays:
var phones = [phone1, phone2, phone3];
var schedules = [schedule1, schedule2];
and I use ajax to send:
var data = {
index: id,
schedules: schedules,
phones: phones
}
var url = "/myController/myUrl"
$.getJSON(url, data, function(result){
if(result.ok){
$('#messageAlertSuccess').show();
} else {
$('#messageAlertError').show();
}
});
I created wrapping classes to map them like so:
public class PhoneWrapper(){
private String phoneId;
private String phoneNumber;
private String phoneType;
}
And of course the scheduleWrapper follows the same convention.
Here's the method in my controller:
#ResponseBody
#RequestMapping(value="/myUrl", method=RequestMethod.GET)
public Result doSomething(#RequestParam("index") int index,
#RequestParam("phones") Set<PhoneWrapper> phoneWrappers,
#RequestParam("schedules") Set<ScheduleWrapper> scheduleWrappers,
Model model,
HttpSession session){
//do stuff here.
}
I am currently getting a 400. So what's wrong?
Update: here's the url that the .getJSON jquery method is building:
http://localhost:8080/myApp/myController/myUrl?index=9&schedules%5B0%5D%5BscheduleId%5D=1&schedules%5B0%5D%5BfromDay%5D=Monday&schedules%5B0%5D%5BtoDay%5D=Friday&schedules%5B0%5D%5BfromTime%5D=08%3A30%3A00&schedules%5B0%5D%5BtoTime%5D=16%3A00%3A00&schedules%5B1%5D%5BscheduleId%5D=5&schedules%5B1%5D%5BfromDay%5D=Saturday&schedules%5B1%5D%5BtoDay%5D=Monday&schedules%5B1%5D%5BfromTime%5D=09%3A00%3A00&schedules%5B1%5D%5BtoTime%5D=13%3A00%3A00&phones%5B0%5D%5BphoneId%5D=6&phones%5B0%5D%5BphoneNumber%5D=787-788-1111&phones%5B0%5D%5BphoneType%5D=PHONE&phones%5B1%5D%5BphoneId%5D=106&phones%5B1%5D%5BphoneNumber%5D=787-795-4095&phones%5B1%5D%5BphoneType%5D=FAX
I see a few things that don't look right
unless you have getters and setters in your wrappers (DTO is a better name), i don't use them for my DTOs for xhr calls, you need to change
public class PhoneWrapper(){
private String phoneId;
private String phoneNumber;
private String phoneType;
}
to have public fields vs private
public class PhoneWrapper(){
public String phoneId;
public String phoneNumber;
public String phoneType;
}
Your js arrays are not arrays but objects;
var phones = {phone1, phone2, phone3};
var schedules = {schedule1, schedule2};
Here they are as arrays
var phones = [phone1, phone2, phone3];
var schedules = [schedule1, schedule2];
Make sure you naming is the same of both the js and java sides. I find it very helpful to turn on the debugging when troubleshooting these problems. log4j -
<logger name="org.springframework.web.servlet.mvc" >
<level value="debug" />
</logger>
EDIT
So after the question was updated with more info I notice that it was the same problem as Binding a list in #RequestParam
I would say that you are almost there! The first thing the you need is a wrapper to hold the two Set<> parameters since spring is not able to map a collection directly to parameters (yet?).
Also, there are two ways to handle this kind of requests:
use a json request and #Requestbody with a single javascript object in the request body an map this into a java class (automatically by spring). This means you need to change a little how the data is send down and this approach has one side effect: you cannot merge data simply by defining the parameter as a model attribute.
a second possibility is to stay with the post form submit. Also here you need to create the wrapper and use this one as a requestparam. Either one per Set<> parameter like #Sotirios mentioned in his answer or one parameter which holds both sets. Then you need to modify your submit data to send the phone and schedule information like input fields. I haven't used sets in this case but
lists and the parameter names would look like phoneWrapper[0].phoneId.
The advantage of the second approach is that you can merge the request data with existing values so you do not need to send down a complete phone information all the time.
var phones = {phone1, phone2, phone3};
var schedules = {schedule1, schedule2};
These two are not arrays (square brackets), but objects (curly brackets).
Compare with
var phones = ["phone1", "phone2", "phone3"];
var schedules = ["schedule1", "schedule2"];
and if you are to pass actual object references (phone1, phone2, phone3, schedule1 and schedule2 are object variables) then you need to use
var phones = [phone1, phone2, phone3];
var schedules = [schedule1, schedule2];
For spring the map request parameters to Class instance fields, they have to match the name of the parameter.
So with
<input type="hidden" name="someParameter" value="123"/>
and
public class SomeClass {
private String someParameter;
// getters and setters
}
a Spring controller will be able to be injected with a SomeClass instance whose field someParameter has the value 123 that comes from the html hidden input request parameter. This is also known as a command object.
A javascript array has no meaning to either html or http.
As for the solution, I would keep your class PhoneWrapper, use javascript to populate 3 <input> elements, and change the method definition to
#RequestMapping(value=MY_URL, method=RequestMethod.POST)
public String doSomething(#RequestParam("index") int index,
PhoneWrappers phoneWrappers,
ScheduleWrappers scheduleWrappers,
Model model,
HttpSession session){
Notice there are no more array [] brackets. (You would do the same for ScheduleWrappers).

How to explain usages of square brackets ([]) operator in EL expressions

When I read Spring PetClinic sample application, I found they always put the add and modify functionalities into a single JSP file, and they use ${owner['new']} expression to customize elements on current page, for example "New Owner" or "Owner" for a label.
Are there any other usages of [] operator in JSP (Spring) environment?
The Controller file has the following snippet:
#RequestMapping(value = "/owners/new", method = RequestMethod.GET)
public String initCreationForm(Map<String, Object> model) {
Owner owner = new Owner();
model.put("owner", owner);
return "owners/createOrUpdateOwnerForm";
}
#RequestMapping(value = "/owners/{ownerId}/edit", method = RequestMethod.GET)
public String initUpdateOwnerForm(#PathVariable("ownerId") int ownerId, Model model) {
Owner owner = this.clinicService.findOwnerById(ownerId);
model.addAttribute(owner);
return "owners/createOrUpdateOwnerForm";
}
The JSP file has the following snippet:
<h2>
<c:if test="${owner['new']}">New </c:if> Owner
</h2>
The [] will allow you to:
Get a property, if the object is a bean (has getters and setters):
${car['type']}
This will be equivalent to car.getType(); (or car.isType() if the type field is a boolean).
Get a key's value, if the object is a Map:
${carMap['Volvo']}
This will be equivalent to carMap.get('Volvo'); when carMap is a Map.
Get an index, if the object is an array or List:
${cars[1]}
This is equivalent to cars[1] if cars is an array or equivalent to cars.get(1) if cars is a List.
More details/source: http://docs.oracle.com/javaee/6/tutorial/doc/bnahu.html
Edit:
Your question's expression (${owner['new']}) falls into the first case. In the petclinick app, the Owner class is a subclass of Person which is a subclass of BaseEntity. And BaseEntity has a method isNew() (so Owner has that method as well).
This way the snippet ${owner['new']} is equivalent to owner.isNew().
Consider following code
bikesMap.put("honda","cbr250r");
bikesMap.put("yamaha","yzfr15");
request.setAttribute("bikesMap",bikesMap);
request.setAttribute("company","honda");
So if we write ${bikesMap["company"] then it will not evaluate to "cbr250r" because what we are providing in [] is a string literal so container will try to find a key "company" which is not present. But if we write ${bikesMap[company]} then this EL will evaulate to "cbr250r".
${bikesMap[compapny]} will evaulate to "cbr250r" because there is a request attribute named company and the value of company i.e. "honda" is a key to the bikesMap.
${bikesMap["company"]} will not evaluate to "cbr250r" because there is no key named "company".
An advantage of [] operator over dot operator is that it can access lists and arrays effectively. You can write ${bikesList["1"]} but you can't write ${bikesList.1}.
Hope this helps

Categories

Resources