11

I want to display in a <p:growl> that the session has expired.

I found many methods to handle session expiration like:

But I couldn't push a faces message to <p:growl>.

To the point, how can I automatically run some (JavaScript) code in client side when the HTTP session has automatically expired in server side?

ℛɑƒæĿᴿᴹᴿ
  • 4,983
  • 4
  • 38
  • 58

4 Answers4

18

You can use PrimeFaces idle monitor for this. User is redirected to logout action after timeout to invalidate the session. 2 minutes before a countdown dialog is shown to warn user. After moving the mouse again session is extended.

PrimeFaces idle monitor and dialog is placed in a template you can add to every page which is involved:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:p="http://primefaces.org/ui">

<ui:composition>
   <h:form prependId="false">
      <p:idleMonitor
     timeout="#{session.maxInactiveInterval * 1000 - 125000}"
     onidle="startIdleMonitor()"
     onactive="timeoutDialog.hide()" />

      <p:dialog id="timeoutSession"
     header="#{msg['session.expire']}"
     widgetVar="timeoutDialog"
     showEffect="fade" hideEffect="fade"
     modal="true"
     width="400"
     height="110"
     closable="false"
     draggable="false"
     resizable="false"
     appendToBody="true"
     onHide="stopCount()"
     onShow="doTimer()">
     <br />
     <p>
        <span class="ui-icon ui-icon-alert" style="float: left; margin: 8px 8px 0;"/>
        <p:panel>
           #{msg['logoff.soon.1']}
           <span id="dialog-countdown" style="font-weight: bold"></span>
           #{msg['logoff.soon.2']}
        </p:panel>
     </p>
     <br />
     <p style="font-weight: bold;">#{msg['move.cursor']}</p>
      </p:dialog>
      <p:remoteCommand name="keepAlive" actionListener="#{auth.keepSessionAlive}" />
   </h:form>
   <script type="text/javascript">
      var TIME = 120; // in seconds
      var countTimer = TIME;
      var processTimer;
      var timer_is_on = 0;
      var redirectPage = "#{request.contextPath}/auth/j_verinice_timeout";

      var countDownDiv = "dialog-countdown";
      var txtCountDown = null;
      if (!txtCountDown)
        txtCountDown = document.getElementById(countDownDiv);

      function startIdleMonitor() {
        countTimer = TIME;
        txtCountDown.innerHTML = countTimer;
        timeoutDialog.show();
      }
      function timedCount() {
        txtCountDown.innerHTML = countTimer;
        if (countTimer == 0) {
            stopCount();
            window.location.href = redirectPage;
            return;
        }
        countTimer = countTimer - 1;
        processTimer = setTimeout("timedCount()", 1000);
      }
      function doTimer() {
        if (!timer_is_on) {
            timer_is_on = 1;
            timedCount();
        }
      }
      function stopCount() {
        clearTimeout(processTimer);
        timer_is_on = 0;
        keepAlive();
      }
      </script>
</ui:composition>
</html>
  • Line 11: The timeout of the idle monitor is set by system var session.maxInactiveInterval. The value you set in your web.xml or server configuration.
  • Line 12/13: Javascript method startIdleMonitor() is called after timeout without any user interaction. This method opens the dialog. timeoutDialog.hide() is called when user is busy again: Dialog is closed
  • Line 26/27: Two more Javascript methods are called when dialog is shown or hide: doTimer() starts and stopCount() stops the countdown.
  • Line 40: PrimeFaces remote command to keep session alive. By calling an arbitrary method on server the session is extended. Command is called by Javascript method keepAlive() in line 78.
  • Line 59-68: Javascript method timedCount() is called every second to execute the countdown. After timeout redirect is done in line 63.

To activate timeout handling in multiple pages include the timeout template in your layout template:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xml:lang="de-DE">
<h:head>
  ...
</h:head>
<body>
  <ui:include src="/template/sessionTimeOut.xhtml" />
  <ui:include src="/nav.xhtml"/>>
  <ui:insert name="content">Default content</ui:insert>
  <ui:include src="/footer.xhtml"/>>
</body>
</html>

A specific time out for your web application you can set in your web.xml:

<!--?xml version="1.0" encoding="UTF-8"?-->
<web-app>
   ...
   <session-config>
      <!-- Session idle timeout in min. -->
      <session-timeout>30</session-timeout>
    </session-config>
</web-app>

You can read more about this solution in this blog post: JSF and PrimeFaces: Session Timeout Handling

Divyesh Kanzariya
  • 3,629
  • 3
  • 43
  • 44
uı6ʎɹnɯ ꞁəıuɐp
  • 3,431
  • 3
  • 40
  • 49
  • I did not understand the method `#{auth.keepSessionAlive}` –  Oct 01 '13 at 08:03
  • `keepSessionAlive()` is a method in a JSF bean. Calling this method stops idle time for the user session on the application server. Just call an arbitrary getter in this method. I think it can even be empty. – uı6ʎɹnɯ ꞁəıuɐp Oct 01 '13 at 09:16
  • Please do not only post link-only answers. If the link contains the answer, put the relevant part of the answer in your answer on StackOverflow. If the content behind the link changes, or the content is removed, your answer does not answer this question anymore. – Danubian Sailor Oct 01 '13 at 14:39
  • 2
    For those using the example with Primefaces 4.0 or higher, you may need to use Primefaces PF function to show / hide dialog, e.g. onactive="PF('timeoutDialog').hide();" PF('timeoutDialog').show(); – Jonathan L Mar 30 '16 at 21:55
  • JavaScript code processTimer = setTimeout("timedCount()", 1000) can be replaced by processTimer = setInterval("timedCount()", 1000) since timer countdown is a repeat action. Related logic can be simplified a little bit as of the results. – Jonathan L Apr 05 '16 at 22:19
  • 1
    I don't understand this part: timeout="#{session.maxInactiveInterval * 1000 - 125000}" . Could you explain it in more detail please? – raven Apr 07 '16 at 14:34
13

If you're on JSF 2.3, then you can use a session scoped <f:websocket> for this. When the session expires in server side, then the <f:websocket> is automatically closed with close code 1000 ("normal closure").

In other words, just do:

<f:websocket ... scope="session" onclose="sessionScopedSocketCloseListener" />

function sessionScopedSocketCloseListener(code) {
    if (code == 1000) {
        alert("Session has expired!");
    }
}

You can if necessary combine this with JavaScript API of the <p:growl>.

<p:growl widgetVar="growl" ... />

PF("growl").renderMessage({severity: "warn", summary: "Session has expired!" });

If you're not on JSF 2.3 yet, then you can always use <o:socket> instead which offers exactly the same onclose feature as <f:websocket>. See also How can server push asynchronous changes to a HTML page created by JSF?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • I got same idea, i used web socket to detect session expiration, bellow an example `````` but got an issue, I have infinite loop of multiple session initiation from client side, is this a know issue ? – Wafa Sep 17 '20 at 11:39
  • I tried this solution (with session-timeout 1) with the "definitive guide to jsf" example : https://github.com/Apress/definitive-guide-to-jsf-javaee8. After logging and waiting, nothing pops on the screen, neither in the log (with console.log("Session has expired!") after the destruction of the bean "ActiveUser". Tested with wildfly 25.0.1.Final – grigouille Jan 10 '23 at 11:12
  • I'm afraid that with f:websocket, the session never times out. – grigouille Jan 10 '23 at 16:17
  • You can try it (wildfly 25.0.1) in this fork of the "definitive guide to jsf" : https://github.com/ritonglue/definitive-guide-to-jsf-javaee8/tree/session_expire. session-timeout is set to 1 but the session never expires and you never get the alert. – grigouille Jan 10 '23 at 17:12
3

The code posted by @uı6ʎɹnɯ lǝıuɐp i's very useful, but i will add some observations:

  • @Jonathan L sugests replace processTimer = setTimeout("timedCount()", 1000) to setInterval("timedCount()", 1000). I think makes sense, but need some modifications:

    function doTimer() {
        if (!timer_is_on) {
            timer_is_on = 1;
            processTimer = setInterval("timedCount()", 1000);
        }
    }
    
    function stopCount() {
        clearInterval(processTimer);
        timer_is_on = 0;
        keepAlive();
    }
    

  • The method timedCount() can be changed to:

    function timedCount() {
        txtCountDown.innerHTML = countTimer;
        if (countTimer > 0) {
            countTimer = countTimer - 1;
        } else {
            clearInterval(processTimer);
            doLogout();
        }
    }
    

    And you need add under the keepAlive remoteCommand something like this:

    <p:remoteCommand name="doLogout" action="#{loginMB.logout()}" 
                     process="@this" partialSubmit="true" immediate="true" />
    

    Inside Managed Bean:

    public void logout() {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        validUser = false;
        loggedUser = false;
        redirectToPage("/login.xhtml");
    }
    
    private void redirectToPage(String page) {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        context.redirect(context.getRequestContextPath() + page);
    }
    

    Remember the atribute name="doLogout" of p:remoteCommand is converted in a javascript function that you can call anywhere. It's perfect to iterate the view with managed bean.

    This approach will prevent your system stacks the ViewExpiredException.


  • Some people doesn't understand the keepAlive concept, but it's very easy, just add a simple function inside your managed bean. Example keepSessionAlive action:

    public void keepSessionAlive () {
        System.out.println(">>> Session is alive... ");
    }
    

    Remember the p:remoteCommand named keepAlive is a javascript function called keepAlive() and that call an action keepSessionAlive inside your managed bean.

    This action signs you are not idle.


  • The session.maxInactiveInterval of idleMonitor references <session-timeout> of web.xml

    <p:idleMonitor timeout="#{session.maxInactiveInterval * 1000 - 125000}"
                   onidle="startIdleMonitor()" onactive="timeoutDialog.hide()" />
    

    I recommend put session monitor to execute 5 seconds before finish the session to prevent run doLogout() after session end and get the ViewExpiredException.

    The first line of javascript code is var TIME = 120; // in seconds, this represents the time to finalize the session. The original code # {session.maxInactiveInterval * 1000 - 125000} will use session-timeout mutiplicated by 1.000 (because it's miliseconds) and subtract 125.000 that represents 125 seconds, 5 seconds less than counter, therefore doesn't need changes.


I hope this helps... Good Luck!

ℛɑƒæĿᴿᴹᴿ
  • 4,983
  • 4
  • 38
  • 58
2

I just wanted to post here for future visitors, I was able to find a slightly different but working approach with idlemonitor and timer components. Basically, if the session timeout is set to 30 mins in web.xml, this code will open a dialog after 28 min of user being idle, with the following components: Message - You are about to be logged off in mins. Please click the ok button to keep the session alive. Timer - Primefaces extensions timer which will have a 2 min countdown. Ok button - keeps session alive Log Out button - Logs the user out. Here is the code for this change:

sessionTimeOut.xhtml

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:p="http://primefaces.org/ui"
       xmlns:pu="http://primefaces.org/ultima"
       xmlns:pe="http://primefaces.org/ui/extensions">


       <h:form>
           <p:idleMonitor timeout="#{session.maxInactiveInterval * 1000 - 130000}" onidle="PF('idleDialog').show();PF('timeoutTimer').start();" />

           <p:dialog id="timeoutDialog" header="Are you there?" widgetVar="idleDialog" modal="true" closable="false" draggable="false" resizable="false" >
                <p:panelGrid columns="1" styleClass="ui-noborder">

                    <p:panel>
                        <h:outputText value="You are about to be logged off in " />
                        <p:spacer width="10"/>
                        <pe:timer id="timeoutTimer" widgetVar="timeoutTimer" singleRun="true" timeout="120" format="mm:ss" autoStart="false" listener="#{userSessionBean.logout()}"/>
                        <p:spacer width="10"/>
                        <h:outputText value=" mins." />

                    </p:panel>

                    <p:panel>
                        <h:outputText value="Please click 'Ok' to keep the session alive" />
                    </p:panel>

                    <p:panel style="text-align: center;">
                        <p:commandButton id="confirm" value="Ok" actionListener="#{userSessionBean.keepAlive()}" onclick="PF('timeoutTimer').stop(true);" oncomplete="PF('idleDialog').hide();" process="@this"/>
                        <p:spacer width="10"/>
                        <p:commandButton id="timeoutLogout" value="Log Out" widgetVar="timeoutLogoutWV"
                               actionListener="#{userSessionBean.logout()}" oncomplete="PF('timeoutTimer').stop(true); PF('idleDialog').hide(); location.reload(true);"/>
                    </p:panel>

                </p:panelGrid>
           </p:dialog>
       </h:form>

</ui:composition>

insert this into your base template as this

<ui:include src="./sessionTimeOut.xhtml" />

Here is the java side code

public void keepAlive() {
    logger.info("User " + loggedInUser.getUserLogin() + " requested the Session " + getCurrentHttpSessionId() + "  to be kept alive at " + new Date().toString());

    /**
     * Do nothing
     */
}


public String logout() throws IOException {
    FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    externalContext.getContextName();

    logger.info(FacesContext.getCurrentInstance().getExternalContext().getContextName());

    facesContext.getExternalContext().redirect("/Project/views/login.xhtml");

    logger.info("Logout");

}

I hope this helps. Thanks.

AmeyK
  • 21
  • 2