ICEfaces
  1. ICEfaces
  2. ICE-6906

Liferay's client side session tracker is not updated when ICEfaces sends Ajax requests

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Won't Fix
    • Affects Version/s: EE-2.0.0.GA, 2.0.2
    • Fix Version/s: 3.0, EE-2.0.0.GA_P01
    • Component/s: Framework
    • Labels:
      None
    • Environment:
      ICEfaces 2 Liferay 5 and 6

      Description

      Liferay has a client-side mechanism for tracking user activity and presenting a warning banner before the session times out and an expired banner after the session has time out. ICEfaces' Ajax requests do not update this mechanism which results in the banner displaying prematurely even though client activity has taken place. Liferay recommends using the client-side API Liferay.Session.setCookie() for each request that should update the timer values.

        Activity

        Hide
        Deryk Sinotte added a comment -

        Assigning to me and linking to related SalesForce case.

        Show
        Deryk Sinotte added a comment - Assigning to me and linking to related SalesForce case.
        Hide
        Deryk Sinotte added a comment -

        After a fair amount of testing, it turned out that Liferay.Session.setCookie() has a couple of issues and is not feasible for our purposes:

        1) It behaves differently depending on which user you are logged in as. For example, it works when logged in as Bruno (Administrator) but not as John (Regular User). There is some security component here that prevents this from being a general solution.

        2) When logged in as the proper user, setCookie works fine until you allow the warning banner to appear. For example, you can maintain activity with the portlet and the warning banner is suppressed. If you cease activity, the warning banner will appear at the set time (by default this is 1 minute before session expiry) as designed. However, if you click the 'Extend' button, the setCookie method no longer has the desired effect, the warning banner will appear no matter what level of user activity is done.

        The only other item of note is the setting of the session-timeout value. This is normally done in the web.xml of the portlet .war files. However the value used by the client-side timer is retrieved from Liferay's web.xml file (for the Tomcat distribution, this is webapps/ROOT/WEB-INF/web.xml). In order to align the session timeout values for the client and the backend, you must ensure that these values are the same.

        Show
        Deryk Sinotte added a comment - After a fair amount of testing, it turned out that Liferay.Session.setCookie() has a couple of issues and is not feasible for our purposes: 1) It behaves differently depending on which user you are logged in as. For example, it works when logged in as Bruno (Administrator) but not as John (Regular User). There is some security component here that prevents this from being a general solution. 2) When logged in as the proper user, setCookie works fine until you allow the warning banner to appear. For example, you can maintain activity with the portlet and the warning banner is suppressed. If you cease activity, the warning banner will appear at the set time (by default this is 1 minute before session expiry) as designed. However, if you click the 'Extend' button, the setCookie method no longer has the desired effect, the warning banner will appear no matter what level of user activity is done. The only other item of note is the setting of the session-timeout value. This is normally done in the web.xml of the portlet .war files. However the value used by the client-side timer is retrieved from Liferay's web.xml file (for the Tomcat distribution, this is webapps/ROOT/WEB-INF/web.xml). In order to align the session timeout values for the client and the backend, you must ensure that these values are the same.
        Hide
        Deryk Sinotte added a comment -

        A fix for issue #1 noted above looks to be covered by this case:

        http://issues.liferay.com/browse/LPS-16371

        I've opened a case in Liferay's system for issue #2:

        http://issues.liferay.com/browse/LPS-17624

        In any event, neither problem is solved for current users of ICEfaces. Following a variety of testing, the following solution has been implemented.

        A class called PortletListener has been implemented as a SystemEventListener, much like how we use BridgeSetup to add Javascript to the page already. It checks to ensure that the current request is an ICEfaces view, a PortletRequest, and more specifically, the Liferay is involved and then adds the following snippet of JavaScript:

        if(!extendLiferayClientTimer){

        var extendLiferayClientTimer = function() {
        if(Liferay.Session)

        { Liferay.Session.extend(); }

        };

        if (Liferay)

        { ice.onSubmitSend(extendLiferayClientTimer); }

        }

        The code registers a small function that, if possible, calls Liferay.Session.extend() with each client submission. This ensures that the banner is reset for every client interaction that ICEfaces does.

        Note: The one drawback of this is that the Liferay.Session.extend() call makes a separate request to the server. This is part of the Liferay code and is currently unavoidable. The request however is small and innocuous. This change works with both Liferay 6.0.6 as well as 5.2.3 (the community versions) so should help customers running on existing implementations.

        Show
        Deryk Sinotte added a comment - A fix for issue #1 noted above looks to be covered by this case: http://issues.liferay.com/browse/LPS-16371 I've opened a case in Liferay's system for issue #2: http://issues.liferay.com/browse/LPS-17624 In any event, neither problem is solved for current users of ICEfaces. Following a variety of testing, the following solution has been implemented. A class called PortletListener has been implemented as a SystemEventListener, much like how we use BridgeSetup to add Javascript to the page already. It checks to ensure that the current request is an ICEfaces view, a PortletRequest, and more specifically, the Liferay is involved and then adds the following snippet of JavaScript: if(!extendLiferayClientTimer){ var extendLiferayClientTimer = function() { if(Liferay.Session) { Liferay.Session.extend(); } }; if (Liferay) { ice.onSubmitSend(extendLiferayClientTimer); } } The code registers a small function that, if possible, calls Liferay.Session.extend() with each client submission. This ensures that the banner is reset for every client interaction that ICEfaces does. Note: The one drawback of this is that the Liferay.Session.extend() call makes a separate request to the server. This is part of the Liferay code and is currently unavoidable. The request however is small and innocuous. This change works with both Liferay 6.0.6 as well as 5.2.3 (the community versions) so should help customers running on existing implementations.
        Hide
        Deryk Sinotte added a comment -

        Changes have been checked into both:

        ossrepo/icefaces2/trunk/icefaces
        ossrepo/icefaces2/branches/icefaces-2.0.x-maintenance/icefaces

        Resolving as fixed.

        Show
        Deryk Sinotte added a comment - Changes have been checked into both: ossrepo/icefaces2/trunk/icefaces ossrepo/icefaces2/branches/icefaces-2.0.x-maintenance/icefaces Resolving as fixed.
        Hide
        Deryk Sinotte added a comment - - edited

        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>

        Show
        Deryk Sinotte added a comment - - edited 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>
        Hide
        Ken Fyten added a comment -

        Since this issue could be fixed at any time with a Liferay release, and that the Liferay release would likely necessitate additional changes to the technique being used to ensure the portlet session doesn't expire erroneously when only JSF-ajax updates are occurring, it is not appropriate for inclusion directly in either ICEfaces or the PortletFacesBridge at this time.

        Users are recommended to use the documented work-around until such time as Liferay resolves and releases a fix for http://issues.liferay.com/browse/LPS-17624.

        Show
        Ken Fyten added a comment - Since this issue could be fixed at any time with a Liferay release, and that the Liferay release would likely necessitate additional changes to the technique being used to ensure the portlet session doesn't expire erroneously when only JSF-ajax updates are occurring, it is not appropriate for inclusion directly in either ICEfaces or the PortletFacesBridge at this time. Users are recommended to use the documented work-around until such time as Liferay resolves and releases a fix for http://issues.liferay.com/browse/LPS-17624 .
        Hide
        Keith Garry Boyce added a comment -

        Should script be updated to call Liferay.Session.set('sessionState', 'active');

        as per http://issues.liferay.com/browse/LPS-17624

        ?

        Show
        Keith Garry Boyce added a comment - Should script be updated to call Liferay.Session.set('sessionState', 'active'); as per http://issues.liferay.com/browse/LPS-17624 ?
        Hide
        Keith Garry Boyce added a comment -

        Also api has changed to onBeforeSubmit instead of onSubmitSend

        Show
        Keith Garry Boyce added a comment - Also api has changed to onBeforeSubmit instead of onSubmitSend
        Hide
        Deryk Sinotte added a comment -

        Thanks for the comments. I've added a Wiki page that includes an example project http://wiki.icesoft.org/display/ICE/Liferay+Client+Session+Tracker that hopefully points out all the potholes.

        Show
        Deryk Sinotte added a comment - Thanks for the comments. I've added a Wiki page that includes an example project http://wiki.icesoft.org/display/ICE/Liferay+Client+Session+Tracker that hopefully points out all the potholes.

          People

          • Assignee:
            Deryk Sinotte
            Reporter:
            Deryk Sinotte
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: