It was decided that, since these were basically Liferay bugs that we were trying to workaround, we would be better served to have it in the PortletFaces Bridge if possible and, until such time as it was generally available, we'd offer the solution written up as a workaround in this JIRA. I've reverted the originally committed changes.
Overview
--------
There is basically one thing that we are trying to do and 2 bugs that are impacting it. We need to be able to update Liferay's client-side system for notifying users when the session is about to expire. By default, the session timeout value is 30 minutes and the session timeout warning is 1 minutes. So in the absence of any activity, after 29 minutes, Liferay will display a banner warning you that the session will expire in 1 minute and provide an "Extend" button which resets the timer.
The challenge is that certain requests, like ICEfaces Ajax requests, do not automatically re-adjust Liferay's client timer. So interacting with an ICEfaces portlet will not prevent the warning banner from displaying after 29 minutes no matter how much activity there is. Liferay's recommendation is to call Liferay.Session.setCookie() so that the cookie used to adjust the timer is properly updated. The way to do this with ICEfaces is to register a function with the ICEfaces JavaScript API that gets called for each request. For example:
<script>
if(!extendLiferayClientTimer){
var extendLiferayClientTimer = function() {
if(Liferay.Session)
{
Liferay.Session.setCookie();
}
};
if (Liferay)
{
ice.onSubmitSend(extendLiferayClientTimer);
}
}
</script>
The script ensure that the function is only registered once even if there are multiple portlets involved. You can manually add this script using whatever technique suits you environment.
Issues
------
As noted early, there are 2 bugs that impact our approach:
1) Calling setCookie behaves differently depending on which user you are logged in as. For example, it works to adjust the cookie value when logged in as Bruno (Administrator) but not as John (Regular User). There is some security issue here that prevents this from being a general solution for current versions of Liferay. The issue is documented in
http://issues.liferay.com/browse/LPS-16371 and will be fixed in future releases of Liferay. The workaround would be to ensure that your users have sufficient privileges to properly call setCookie().
2) As per
http://issues.liferay.com/browse/LPS-17624, calling setCookie() works fine to prevent the warning banner initially. But if the warning banner does get displayed and you click "Extend", additional calls to setCookie() no longer have the desired effect. In the scenario described above, the warning banner will appear every 29 minutes no matter how much client activity there is. The way to circumvent that issue is to call Liferay.Session.extend() rather than setCookie(). So the script would look like this:
<script>
if(!extendLiferayClientTimer){
var extendLiferayClientTimer = function() {
if(Liferay.Session){
Liferay.Session.extend();
}
};
if (Liferay) {
ice.onSubmitSend(extendLiferayClientTimer);
}
}
</script>
NOTE: It's important to know that call to extend() makes a small Ajax request to the server to ensure that the server session is updated at the same time as the client. This extra request is in addition to the ICEfaces request so it's not as efficient a network bandwidth perspective as simply calling setCookie().
Implementation
--------------
There are a number of different ways to add the script to your portal page. You can use Liferay's themes or simply manually add the script to any views that require it. One way we suggest to implement this feature in your own applications is to create a SystemEventListener that automatically adds the script to the head section of the page during the PreRenderViewEvent. Below is an example of a Java class that implements the SystemEventListener interface and adds the code to the <head> section of the page. The PortletFaces Bridge takes care of ensuring that it gets properly inserted using Liferay APIs:
package org.icefaces.impl.event;
import org.icefaces.util.EnvUtils;
import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import java.io.IOException;
public class PortletListener implements SystemEventListener {
public void processEvent(SystemEvent systemEvent) throws AbortProcessingException {
final FacesContext fc = FacesContext.getCurrentInstance();
if (!EnvUtils.isICEfacesView(fc))
{
//If ICEfaces is not configured for this view, we don't need to process this event.
return;
}
if (!EnvUtils.instanceofPortletRequest(fc.getExternalContext().getRequest()))
{
//If we're not running in a portlet, we don't need to process this event.
return;
}
if (EnvUtils.isLiferay())
{
//We currently only have a special script snippet for Liferay
UIViewRoot root = fc.getViewRoot();
root.addComponentResource(fc, getLiferayScriptComponent(), "head");
}
}
public boolean isListenerForSource(Object o)
{
return true;
}
private UIComponent getLiferayScriptComponent() {
UIOutput liferayScript = new UIOutputWriter() {
public void encode(ResponseWriter writer, FacesContext context) throws IOException {
writer.startElement("script", this);
writer.write("if(!extendLiferayClientTimer){");
writer.write(" var extendLiferayClientTimer = function() {");
writer.write(" if(Liferay.Session)
{");
// writer.write(" Liferay.Session.setCookie();");
writer.write(" Liferay.Session.extend();");
writer.write(" }
");
writer.write(" };");
writer.write(" if (Liferay)
{");
writer.write(" ice.onSubmitSend(extendLiferayClientTimer);");
writer.write(" }
");
writer.write("}");
writer.endElement("script");
}
};
liferayScript.setTransient(true);
return liferayScript;
}
}
Once you've added the class to your code, you also need to specify the listener in your faces-config.xml file:
<application>
...
<system-event-listener>
<system-event-listener-class>org.icefaces.impl.event.PortletListener</system-event-listener-class>
<system-event-class>javax.faces.event.PreRenderViewEvent</system-event-class>
</system-event-listener>
...
</application>
Assigning to me and linking to related SalesForce case.