Get slingRequest within a bean in CQ 5.6 - aem

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

Related

How do I get a selector from a sling Resource

I have two Sling Models:
#Model(adaptables = {SlingHttpServletRequest.class, Resource.class}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class VideoGridItem {
#SlingObject
private Resource resource;
#SlingObject
private SlingHttpServletRequest slingHttpServletRequest;
#PostConstruct
public void initVideoGridItem() {
String[] selectors = slingHttpServletRequest.getRequestPathInfo().getSelectors();
insideGrid = selectors == null || selectors.length == 0 ? false : Arrays.stream(selectors).anyMatch("grid"::equals);
url = URLUtils.addHTMLIfPage(resource.getResourceResolver(), linkUrl);
}
}
and
#Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class VideoListing {
private List<String> videoResourcePaths;
#PostConstruct
final void init() {
}
}
I call the VideoGridItem component (technically the resource which references the model) from the video-listing component using HTL:
<sly data-sly-list.videoResourcePath="${model.videoResourcePaths}">
<sly data-sly-resource="${videoResourcePath # wcmmode='disabled', addSelectors='grid'}" data-sly-unwrap="true"></sly>
</sly>
Now, when I debug the code, inside initVideoGridItem, slingHttpServletRequest is null. Fair enough, this resource isn't being directly requested, but I still need to be able to access the selector "grid". Is there a way I can do this from the VideoGridItem.resource?
Use the #org.apache.sling.models.annotations.injectorspecific.Self annotation instead of #SlingObject for the resource and slingHttpServletRequest fields. The self injector will inject the adaptable object itself (i.e. the Sling request) as well as objects that are adaptable from the same (the resource).
Assuming you always need the selector value for your component to function, you should remove Resource.class from the list of adaptable types in your #Model annotation. This will prevent your model class from being adapted from a Resource object, which will cause the slingHttpServletRequest field to be null and your #PostConstruct method will throw a NullPointerException.
Sorry I didn't reply sooner, but I found my defect and moved on. The issue was that I was creating a VideoGridItem by adapting it from a resource in another place in the code and of course Sling couldn't inject a request. I am now accounting for the null request and my code is working well. Thanks for your answer!

JSP EL private static class inside JSP

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.

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.

How to compare and validate 2 fields when persisting an entity using Facelets + JPA?

I am using Seam 2.2 with Facelets and I need to validate 2 fields from a JPA entity comparing both to check its values before insert.
Here is fragment from my enttity:
#Entity
#Scope(ScopeType.CONVERSATION)
#Name("metaAbastecimento")
public class MetaAbastecimento implements Serializable{
private float abastecimentoMinimo;
private float abastecimentoMaximo;
#Column
public float getAbastecimentoMinimo() {
return abastecimentoMinimo;
}
#Column
public float getAbastecimentoMaximo() {
return abastecimentoMaximo;
}
public void setAbastecimentoMinimo(float abastecimentoMinimo) {
this.abastecimentoMinimo = abastecimentoMinimo;
}
public void setAbastecimentoMaximo(float abastecimentoMaximo) {
this.abastecimentoMaximo = abastecimentoMaximo;
}
}
Than I have an xhtml which persists this entity:
<rich:panel>
<f:facet name="header">Detalhe de Meta de Abastecimento</f:facet>
<s:decorate id="abastecimentoMinimo" template="../layout/display.xhtml">
<ui:define name="label">Meta(R$) de Abastecimento Mínimo</ui:define>
<h:outputText value="#{metaAbastecimentoHome.instance.abastecimentoMinimo}">
</h:outputText>
</s:decorate>
<s:decorate id="abastecimentoMaximo" template="../layout/display.xhtml">
<ui:define name="label">Meta(R$) Abastecimento Máximo</ui:define>
<h:outputText value="#{metaAbastecimentoHome.instance.abastecimentoMaximo}"/>
</s:decorate>
<div style="clear:both"/>
</rich:panel>
I need to compare these 2 fields before I persist them and check if they are different than 0f and also if abastecimentoMinimo is smaller than abastecimentoMaximo. How can I do this using Seam + Facelets + JPA?
[]s
Cross field validation in JSF 1 is not possible. So you have to do this manually.
This can in your case either be done by using the #PrePersist and #PreUpdate, or you can do this in your action method manually.
One tip for you. Avoid having Entity beans as seam components. It's better to make a separate seam component.
The reasons for this are:
Entity beans may be bound to a context variable and function as a seam component. Because entities have a persistent identity in addition to their contextual identity, entity instances are usually bound explicitly in Java code, rather than being instantiated implicitly by Seam.
Entity bean components do not support bijection or context demarcation. Nor does invocation of an entity bean trigger validation.
Entity beans are not usually used as JSF action listeners, but do often function as backing beans that provide properties to JSF components for display or form submission. In particular, it is common to use an entity as a backing bean, together with a stateless session bean action listener to implement create/update/delete type functionality.
By default, entity beans are bound to the conversation context. They may never be bound to the stateless context.
Note that it in a clustered environment is somewhat less efficient to bind an entity bean directly to a conversation or session scoped Seam context variable than it would be to hold a reference to the entity bean in a stateful session bean. For this reason, not all Seam applications define entity beans to be Seam components.
#Entity
public class MetaAbastecimento {
.....
//If check fails you can throw exception, thus rollback will occur, and commit will not be made
#PrePersist
#PreUpdate
public void check() {
if(abastecimentoMinimo == 0f || abastecimentoMaximo == 0f && abastecimentoMinimo > abastecimentoMaximo)
throw new RuntimeException("Failed!");
}
}

Can't get JSF input field value on JAVA backend

I have following UI part on JSF - it's simple search form with input field and submit:
<h:form>
<h:commandButton action="#{operation.found}" value="#{msg.search}" />
<h:inputText name="searchParam"/>
</h:form>
And correspondingly, on backend, i attempt to get value of input field next way:
public List<Store> getFound() {
String name = (String) FacesContext.getCurrentInstance()
.getExternalContext().getRequestParameterMap().get(
"searchParam");
SessionFactory sessionFactory = new Configuration().configure()
.buildSessionFactory();
HibernateTemplate hbt = new HibernateTemplate();
hbt.setSessionFactory(sessionFactory);
foundStores = hbt.find(BEAN_PATH + " WHERE name = ?",
new Object[] { name });
return foundStores;
}
And null name is passed to backend.
It seems that problem in .jsf part, but from first glance looks ok...
You must point the <h:inputText> to a managed-bean property:
<h:inputText name="searchParam" value="#{searchBean.searchParam}" />
and define in your bean:
private String searchParam;
public String getSearchParam() {..}
public void setSearchParam(String searchParam) {..}
and then use the searchParam in your getFound() method;
Of course, you need to have the bean defined as managed bean, but I assume you have done it:
<managed-bean>
<managed-bean-name>searchBean</managed-bean-name>
<managed-bean-class>mypackage.SearchBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
You can check a JSF tutorial (like this, for example)