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

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

Related

How to check that several fields of an object are not empty with AssertJ?

I have a class with some fields:
class User {
String name;
String id;
// other fields
}
I'm getting a user from the service and want to assert that name and id fields are not empty. The object contains other fields, but those are not needed for this particular case. I tried to extract required fields and apply the allSatisfy {} function:
User user = service.getUser();
assertThat(user)
.extracting("name", "id") // extract only those fields that I need for now
.allSatisy { field -> assertThat(field).isNotEmpty() } // this doesn't compile because `field` is not a String
Am I using the extracting() function wrong?
Or is there other way of asserting multiple fields of an object at once instead of asserting each field separately?
Joop Eggen suggestion is a good solution, extracting multiple values at once returns the list of extract values wrapped in a ListAssert object so that you can (List) chain assertions. The list is a list of object because there is no way to know what type the list will contain (id could be an Integer and name a String so you can't assume you will be given a list of string here).
You can try asIntanceOf to narrow the type as you know more than AssertJ in this case, ex:
assertThat(user)
.extracting("name", "id")
.asInstanceOf(InstanceOfAssertFactories.list(String.class))
.allSatisy(field -> assertThat(field).isNotEmpty());
Whether it is better than casting the field, I'm not completely sure, I leave it to you to decide.
- Edit -
I tried your example and it worked fine with assertj core 3.21.0, this test is passing:
#Test
void test() {
User user = new User();
user.name = "Joe";
user.id = "123";
assertThat(user).extracting("name", "id")
.asInstanceOf(InstanceOfAssertFactories.list(String.class))
.allSatisfy(field -> assertThat(field).isNotEmpty());
}
So I guess this has to do with Kotlin.

Accessing Object Property Directly thymeleaf

Let's say I have a class Factory with 2 fields: fName and fArea. Is it possible to refer to these fields through a single object f1 (an instance of Factory) added to either a Model or a ModelAndView? Resulting to something like this ${f.fArea} (where f is the String attributeName argument of addObject method).
Yes, that's possible. That syntax is the basis of how thymeleaf expressions work...
// Controller
#GetMapping("/whatever")
public String whatever(Map<String, Object> model) {
model.put("f", new Factory());
return "whatever";
}
// Template
</span th:text="${f.fArea}" />
http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#variables

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).

Spring RedirectAttributes: addAttribute() vs addFlashAttribute()

My understanding so far is on our controller request mapping method we can specify RedirectAttributes parameter and populate it with attributes for when the request gets redirected.
Example:
#RequestMapping(value="/hello", method=GET)
public String hello(RedirectAttributes redirAttr)
{
// should I use redirAttr.addAttribute() or redirAttr.addFlashAttribute() here ?
// ...
return "redirect:/somewhere";
}
The redirect attributes will then be available on the target page where it redirects to.
However RedirectAttributes class has two methods:
addAttribute()
addFlashAttribute()
Have been reading Spring documentation for a while but I'm a bit lost. What is the fundamental difference between those two, and how should I choose which one to use?
Here is the difference:
addFlashAttribute() actually stores the attributes in a flashmap
(which is internally maintained in the users session and removed
once the next redirected request gets fulfilled)
addAttribute() essentially constructs request parameters out of
your attributes and redirects to the desired page with the request
parameters.
So the advantage of addFlashAttribute() will be that you can store pretty much any object in your flash attribute (as it is not serialized into request params at all, but maintained as an object), whereas with addAttribute() since the object that you add gets transformed to a normal request param, you are pretty limited to the object types like String or primitives.
Assume you have 2 controllers.If you redirect from one controller to
another controller the values in model object won't be available in the
other controller. So if you want to share the model object values
then you have to say in first controller
addFlashAttribute("modelkey", "modelvalue");
Then second controller's model contains now the above key value pair..
Second question ? What is difference between addAttribute and addFlashAttribute in RedirectAttributes class
addAttribute will pass the values as requestparameters instead of model,so when you add some using addAttribute you can access those values from request.getParameter
Here is the code.I have used to find out what is going on :
#RequestMapping(value = "/rm1", method = RequestMethod.POST)
public String rm1(Model model,RedirectAttributes rm) {
System.out.println("Entered rm1 method ");
rm.addFlashAttribute("modelkey", "modelvalue");
rm.addAttribute("nonflash", "nonflashvalue");
model.addAttribute("modelkey", "modelvalue");
return "redirect:/rm2.htm";
}
#RequestMapping(value = "/rm2", method = RequestMethod.GET)
public String rm2(Model model,HttpServletRequest request) {
System.out.println("Entered rm2 method ");
Map md = model.asMap();
for (Object modelKey : md.keySet()) {
Object modelValue = md.get(modelKey);
System.out.println(modelKey + " -- " + modelValue);
}
System.out.println("=== Request data ===");
java.util.Enumeration<String> reqEnum = request.getParameterNames();
while (reqEnum.hasMoreElements()) {
String s = reqEnum.nextElement();
System.out.println(s);
System.out.println("==" + request.getParameter(s));
}
return "controller2output";
}
Javadoc description:
"A FlashMap provides a way for one request to store attributes intended for use in another. This is most commonly needed when redirecting from one URL to another -- e.g. the Post/Redirect/Get pattern. A FlashMap is saved before the redirect (typically in the session) and is made available after the redirect and removed immediately."

Need help with binding Set with Spring MVC form

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.

Categories

Resources