JSP EL private static class inside JSP - class

I have following jsp:
<%# page import="java.util.Arrays" %>
<%# page import="java.util.List" %>
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html><body>
<%!
private static class Test {
private String val2;
private String val1;
public Test(String v1, String v2) {
val1 = v1;
val2 = v2;
}
public String getVal1() {
return val1;
}
public String getVal2() {
return val2;
}
};
private static List<Test> ITEMS = Arrays.asList(new Test("1","1"),new Test("2","2"));
%>
<%
pageContext.setAttribute("ITEMS",ITEMS);
%>
<c:forEach var="item" items="${ITEMS}">
${item.val1},${item.val2}
</c:forEach>
</body></html>
When TOMCAT executes it - we see exception
javax.el.PropertyNotFoundException: Property 'val1' not readable on type java.lang.String
So EL iterates over the collection but for some reason ${item} inside of forEach becomes a String.
However, when you simply change private static class to public static class - everything works fine.
I undestand it's not a problem, because we have a solution. But I just don't get WHY it DOES REQURE public in this case. JSPs are translated to servlets, so inside translated java code it sees that anyway. Isn't EL just a reflection stuff to get property on object (in our case property is public, so EL should be available to get it without reflection modifiers hacking).
If someone knows - please respond. I would very appreciate!
Thanks in advance.

JSPs are translated by the JSP engine into servlets. EL expressions are evaluated at runtime by the EL engine.
When Test is declared private to the JSP its methods are not (by default) visible through introspection to the Expression Language engine.
Here is a snippet from your forEach as translated by WebSphere Application Server:
do {
out.write(_jsp_string4);
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl
.proprietaryEvaluate("${item.val1}", java.lang.String.class,
(PageContext)pageContext, _jspx_fnmap, false));
out.write(_jsp_string5);
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl
.proprietaryEvaluate("${item.val2}", java.lang.String.class,
(PageContext)pageContext, _jspx_fnmap, false));
out.write(_jsp_string1);
int evalDoAfterBody = _jspx_th_c_forEach_0.doAfterBody();
if (evalDoAfterBody != javax.servlet.jsp.tagext.BodyTag.EVAL_BODY_AGAIN)
break;
} while (true);
EL was designed for use with JavaBeans; beans mandate a public class with a no-args constructor.

I don't have all the answers but I can help you a little.
Tomcat uses BeanELResolver to define property resolution behavior on objects using the JavaBeans component architecture. Since you are not adhering to JavaBean conventions, the behavior is not defined. You can get a more sensible error message if you use
private static class Test implements java.io.Serializable {
which will result in
javax.el.PropertyNotFoundException: Property 'var1' not found on type org.apache.jsp.test_jsp$Test
Look at
http://docs.oracle.com/cd/E17802_01/products/products/jsp/2.1/docs/jsp-2_1-pfd2/javax/el/BeanELResolver.html
http://en.wikipedia.org/wiki/JavaBeans

JSP resolves the EL variables using chain of ELResolver's. The following is the EL Resolver chain hierarchy :
CompositeELResolver
ImplicitObjectELREsolver
CompositeELResolver
PluginELResolver
MapELResolver
ResourceBundleELResolver
ListELResolver
ArrayELResolver
BeanELResolver
ScopedAttributeELResolver
In this hierarchy your item will be resolved using BeanELResolver. BeanELResolver requires the object to follows certain rules to call it as a Bean. One such property is, it should have both setter and getter for each property.
The exception you are seeing is because you dont have the setter defined for the properties in your bean.

Related

Get slingRequest within a bean in CQ 5.6

Including libs/foundation/global.jsp at the top of my jsp winds up calling <cq:defineObjects />, which winds up instantiating the slingRequest or currentNode variables that I can use within my scriptlets. However, I am creating beans and including them like so
<jsp:useBean id="myBean" class="com.foo.bar.MyBean" />
<jsp:setProperty name="myBean" property="request" value="<%= slingRequest %>" />
and in my bean I have a getter/setter
public SlingHttpServletRequest getRequest() {
return resource;
}
public void setRequest(SlingHttpServletRequest request) {
this.resource = resource;
}
Here I'm instantiating the bean and passing in a reference to the current request.
My quesion is, how can I avoid having to pass in this parameter? Is there a way to get a reference to the current request via some sort of static context so that I can set the bean property in the constructor?
There is no way to statically pass the request to newly created bean (and there can't be, as there might be many requests at the same time, so we can't have a shared static variable to store it). You may create a scriptlet:
<% pageContext.setAttribute("myBean", new MyBean(slingRequest)); %>
MyBean property: ${myBean.property}
or get rid of the beans and use one of the frameworks mentioned by Thomas:
Sling Models,
Slice,
NEBA.
You should never pass your request object to Bean classes. I agree with Thomas and Tomek's answer.
But starting to use a framework all of a sudden in the middle of a project to solve this issue is an overkill.
The basic idea here is that the request object should not be passed around. Rather, you should get the resource URI from the request and pass that to your Bean classes.
Additionally you can pass ResourceResolver object to get any resource in your Bean class.
My sample code would look something like this :
<jsp:useBean id="myBean" class="com.foo.bar.MyBean" />
<jsp:setProperty name="myBean" property="resourcePath" value="<%= slingRequest.getResourceURI() %>" />
<jsp:setProperty name="myBean" property="resourceResolver" value="<%= slingRequest.getResourceResolver() %>" />
and my Bean class method would be like this :
public SlingHttpServletRequest getResourcePath() {
return resourcePath;
}
public void setResourceResolver(String resourcePath) {
this.resourcePath = resourcePath;
}
public SlingHttpServletRequest getResourceResolver() {
return resolver;
}
public void setResourceResolver(ResourceResolver resovler) {
this.resolver = resolver;
}
Now in your bean class, you can form the resource object using the resourcePath and ResourceResolver like this :
Resource resource = resourceResolver.resolve(resourcePath);

"value getOrElse is not a member of String" in template

I have the following template code:
views/Login.scala.html:
#(loginForm: Form[views.Data])
#import mytemplates.loginform
#Main("") {
email:#loginform(loginForm("email"))
#*email:#loginForm("email").value.getOrElse("xyz")*#
}
views/mytemplates/loginform.scala.html:
#(emailField: Field)
#emailField.value.getOrElse("xyz")
views/Main.scala.html:
#(page: String)(content: Html)
<!DOCTYPE html>
<html>
<body>
#content
</html>
views/Data.java:
package views;
import play.data.validation.ValidationError;
import java.util.List;
public class Data {
public String email = "";
public Data() { }
public List<ValidationError> validate() {
return null;
}
}
Compiling the above is successful. But if line #*email:#loginForm("email").value.getOrElse("xyz")*# in Login.scala.html is uncommented compiling produces an value getOrElse is not a member of String error.
Why does this happen? I'd like to exclude the template mytemplates.loginform but can't get it to work.
edit: Following estmatic's advice I get the following:
views/Login.scala.html:
#loginForm("email").getClass: class play.data.Form$Field
#loginForm("email").valueOr("").getClass: class java.lang.String
views/mytemplates/loginform.scala.html:
#emailField.getClass: class play.core.j.PlayMagicForJava$$anon$1
#emailField.value.getClass: class scala.None$
I had to use valueOr("") in Login.scala.html otherwise a NullPointer execution exception would be produced. Clearly they are all different classes. I haven't used Play framework much and am not sure what this means.
Since it looks like you have a Java project, the framework is going to do some automatic conversions here and there between the Java classes and their Scala equivalent.
Try this out:
#loginForm("email").getClass()
#loginForm("email").value.getClass()
Make this change on both Login.scala.html and loginform.scala.html and you'll see that you are dealing with different classes in each scenario.
When you go through the loginform template your field.value will be wrapped in a scala.Some object, which is why .getOrElse compiles in that case. When you do it directly in the main view you never leave Java-class-world, so your field.value is returned directly as a String.
If you are using the latest version of Play then you should be able to use the Field.valueOr method instead of getOrElse.
#loginForm("email").valueOr("xyz")

Spring MVC form:checkbox not working

I am having an issue with the Spring form:checkbox tag.
I currently have a JSP page with a form:checkbox tag bound to a Java boolean field. When I put a tick in the checkbox and submit the form the value is false.
Here is the checkbox on my JSP:
<form:checkbox id="field_termsandconditions" path="agreeTermsAndConditions" />
My GET controller:
#RequestMapping(value = "/page1.htm", method = RequestMethod.GET)
public String getPage(HttpServletRequest request, ModelMap model) {
model.addAttribute("MyObject", new MyObject());
return getURL(request);
}
My POST controller:
#RequestMapping(value = "/page1.htm", method = RequestMethod.POST)
public String processPage(HttpServletRequest request,
HttpServletResponse response,
ModelMap model,
MyObject myObject,
BindingResult bindingResult) {
System.out.println(myObject.isAgreeTermsAndConditions);
}
myObject.isAgreeTermsAndConditions is always false when it hits the POST controllers even when checked!
Any ideas?
This might be a little late to answer, but maybe it will help some other person.
When you auto-generate getter and setters for boolean values it is very often generated without 'is' prefix.
For instance, in the case mentioned above the generated setter for 'isAgreeTermsAndConditions' property might be the following: 'setAgreeTermsAndConditions()', note there is no 'is' prefix in the method. The same true for the getters as well.
Since property getter and setters names are used find and bind to the model property, the checkbox might be not shown on the UI or not working properly if there are property name and getters/setters mismatch.
Make sure the property 'isAgreeTermsAndConditions' has the following getters/setters method names: getIsAgreeTermsAndConditions()/setIsAgreeTermsAndConditions(...)

Spring Mvc/Jpa-OneToMany : How to display a list of class associated to another one

I've got a class Module with a OneToMany binding with a class Sequence.
My aim is to show the list of Modules, and by clicking on one of them, display the associated list of Sequences
But it doesn't work, I have a HTTP 500 error.
Here there is my controller :
#RequestMapping(value="formation", method = RequestMethod.GET)
public ModelAndView allModules() {
List<Module> allModules = moduleService.findAll();
return new ModelAndView("formation", "modules", allModules);
}
#RequestMapping(value="sequences/{module}", method = RequestMethod.GET)
public String displaySequences(#PathVariable ("module") Module module, Model model) {
List<Sequence> allSequences = sequenceService.findByModule(module);
model.addAttribute("sequences", allSequences);
return "sequences";
}
and the jsp which show the list of modules to return the list of sequences
<c:forEach items="${modules}" var="module">
<ul>
<li>${module.titre}
<br/>
</li>
</ul>
</c:forEach>
So, where does my error come from?
It works when I do that:
#RequestMapping(value="/sequences/{moduleId}", method = RequestMethod.GET)
public String displaySequences(#PathVariable ("moduleId") Long moduleId, Model model) {
Module module = moduleService.findById(moduleId);
model.addAttribute("module", module);
return "sequences";
}
and I change the link with :
<a href="sequences/${module}">${module.titre}
but I'd like to understand my error.
The reason why you weren't able to display sequences is Spring doesn't know how to parse this
/cmap-web/sequences/com.almerys.jpa.tomcatspring.Module#12b0f0ae
into Module instance.
You can read on this in Spring docs here in the section's 16.3.2.2 URI Template Patterns last paragraph. I paste it here for convenience.
A #PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so. You can also register support for parsing additional data types. See Section 16.3.3.14, “Method Parameters And Type Conversion” and Section 16.3.3.15, “Customizing WebDataBinder initialization”.

Spring List of interface type data binding - how?

Tried to find the answer on the Web but failed. Should be simple for pro Spring Devs... so here it comes:
In few words I want to bind the List of interface type: List to the form and get the data back (possibly modified by user via form. The problem is that it doesn't work :(
my code (short version) - command/model class which is passed to the form:
public class RoomsFormSearchResultCommand extends RoomsFormSearchCommand {
#SuppressWarnings("unchecked")
private List<IRoom> roomsList = LazyList.decorate(new ArrayList<Room>(),
FactoryUtils.instantiateFactory(Room.class));
public List<IRoom> getRoomsList() {
return roomsList;
}
public void setRoomsList(final List<IRoom> roomsList) {
this.roomsList = roomsList;
}
(...)
then in the form I use it like that (short version):
<form:form method="post" action="reserve" commandName="roomsResultsCmd">
(...)
<c:forEach var="room" items="${roomsResultsCmd.roomsList}"
varStatus="status">
<tr>
<td><form:input path="roomsList[${status.index}].roomNumber" readonly="true"/>
(...)
The form is displayed fine but after submitting it I get:
2012-01-22 21:31:55 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [wyspa] in context with path [/wyspa] threw exception [Request processing failed; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'roomsList[0]' of bean class [com.wyspa.controller.command.RoomsFormSearchResultCommand]: Illegal attempt to get property 'roomsList' threw exception; nested exception is org.springframework.beans.NullValueInNestedPathException: Invalid property 'roomsList' of bean class [com.wyspa.controller.command.RoomsFormSearchResultCommand]: Could not instantiate property type [com.wyspa.entity.IRoom] to auto-grow nested property path: java.lang.InstantiationException: com.wyspa.entity.IRoom] with root cause
org.springframework.beans.NullValueInNestedPathException: Invalid property 'roomsList' of bean class [com.wyspa.controller.command.RoomsFormSearchResultCommand]: Could not instantiate property type [com.wyspa.entity.IRoom] to auto-grow nested property path: java.lang.InstantiationException: com.wyspa.entity.IRoom
at org.springframework.beans.BeanWrapperImpl.newValue(BeanWrapperImpl.java:633)
at org.springframework.beans.BeanWrapperImpl.growCollectionIfNecessary(BeanWrapperImpl.java:863)
at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:770)
at org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperImpl.java:555)
(...)
The deal is then when I change the List to "instances" list everything works fine!
public class RoomsFormSearchResultCommand extends RoomsFormSearchCommand {
#SuppressWarnings("unchecked")
//notice that the List is now List<Room>
private List<Room> roomsList = LazyList.decorate(new ArrayList<Room>(),
FactoryUtils.instantiateFactory(Room.class));
In this case data is passed to the controller in proper way.
Since I am used to devlop on interfaces and I am pretty crazy about it I would REALLY prefer not to translate the List<IRoom> (which comes back from services) to List<Room> which seems to suit Spring. Is it possible to work with List<IRoom> in this case or Spring just doesn't support it?
//Of course Room implements IRoom - but I guess you already got that...
I would be VERY happy for any help/suggestions!
Best Regards,
Nirwan
I have exact the same problem. Changing to following won't fix the problem. It looks spring binding ignores the factory utils and tries to instantiate the null object itself:
#SuppressWarnings("unchecked")
private List<IRoom> roomsList = LazyList.decorate(new ArrayList<IRoom>(),
FactoryUtils.instantiateFactory(Room.class));
The workaround is to set auto grow nested path off in your controller:
#InitBinder protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.setAutoGrowNestedPaths(false);
super.initBinder(request, binder);
}
The problem is you'll lose the handy nested path like user.account.address.street. You have to make sure none of user, account, addresss is null. It does cause a lot of problems. That's why I came here, see if I can find better solution.
If you don't actually need the list to auto-grow, you can store the form object in the session to avoid the nasty side effects of disabling auto-growing nested paths.
#Controller
#SessionAttributes(types = RoomsFormSearchResultCommand.class)
public final class SearchController {
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.setAutoGrowNestedPaths(false);
}
#RequestMapping(method = RequestMethod.GET)
public String showForm(final Model model) {
RoomsFormSearchResultCommand form = ... // create or load form
model.addAttribute(form);
}
#RequestMapping(method = RequestMethod.POST)
public String onSubmitUpdateCart(
#ModelAttribute final RoomsFormSearchResultCommand form,
final BindingResult result,
final SessionStatus status) {
// if result has no errors, just set status to complete
status.setComplete();
}
}
Try the following lines
#SuppressWarnings("unchecked")
private List<IRoom> roomsList = LazyList.decorate(new ArrayList<IRoom>(),
FactoryUtils.instantiateFactory(Room.class));
don't have time to try that myself, but it would make sense.