Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

This article is currently a starting place for how to do this. There are still some issues to work out, but it is a good start.

The goal here is to say if I am on page http://localhost:8080/mywebapp/somepage.seam?someid=5 and I leave the browser sit, when the session times out I should be redirected to the login page and then back to the page I was viewing. If one uses restful URLs everything resumes like always and has the least impact on the user.

The starting point for this article is from Bauer's http://in.relation.to/Bloggers/ImplementingGracefulSessionTimeoutWithSeamJSFAndJQuery

Let's go through the code, the layout/template.xhtml should include the header:

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>My Web App</title>
    <link href="stylesheet/theme.css" rel="stylesheet" type="text/css" />

    <script type="text/javascript" src="#{basePath}/seam/resource/remoting/resource/remote.js"></script>
    <script type="text/javascript" src="#{basePath}/seam/resource/remoting/interface.js?httpSessionChecker"></script>

    <script type="text/javascript">

        // ###################### Seam Remoting #################################

        Seam.Remoting.displayLoadingMessage = function() {};
        Seam.Remoting.hideLoadingMessage = function() {};

        // ###################### Session timeout alert #################################

        var sessionChecker = Seam.Component.getInstance("httpSessionChecker");
        var timeoutURL = escape(window.location);
        var timeoutMillis = '#{sessionTimeoutSeconds}'*1000+3000;
        var sessionTimeoutInterval = null;

        function startSessionTimeoutCheck() {
            sessionTimeoutInterval = setInterval('sessionChecker.isNewSession(alertTimeout)', timeoutMillis);
        }

        function stopSessionTimeoutCheck() {
            if (sessionTimeoutInterval) clearInterval(sessionTimeoutInterval);
        }

        function resetSessionTimeoutCheck() {
            stopSessionTimeoutCheck();
            startSessionTimeoutCheck();
        }

        function alertTimeout(newSession) {
            if (newSession) {
                clearInterval(sessionTimeoutInterval);
                var answer = confirm("Login and return to existing Page");
                if (answer) window.location = '#{contextPath}/login.seam?referer=' + timeoutURL;
            }
        }

    </script>
</head>

The above is nearly the same as the Graceful timeout article however I removed the jquery references for testing purposes. Also I changed the timeoutURL to reference the current url which is added to the login.seam. Optimally the #{contextPath}/login.seam would be factorized and put in the components.xml as not everyone has this path.

Also, if you wanted to make this operation transparent, you could remove the alert, and simply set the window.location.

Next open components.xml and add:

<factory name="sessionTimeoutSeconds" 
   		    scope="SESSION" 
   		    value="#{facesContext.externalContext.getSession(true).getMaxInactiveInterval()}"/>
   		    
<factory name="basePath"
             value="#{facesContext.externalContext.request.scheme}://#{facesContext.externalContext.request.serverName
                     }:#{facesContext.externalContext.request.serverPort}#{facesContext.externalContext.request.contextPath}"/>
      
<factory name="contextPath"
             value="#{facesContext.externalContext.request.contextPath}"/>

This will take care of resolving the necessary el in the javascript.

Now create the HttpSessionChecker.java:

@Name("httpSessionChecker")
@Scope(ScopeType.APPLICATION)
public class HttpSessionChecker {

    @WebRemote
    public boolean isNewSession() {
        return ServletContexts.instance().getRequest().getSession().isNew();
    }
}

Modify login.page.xml to be:

<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd">

	<param name="referer" value="#{redirector.referer}" />

   <!-- Default seam-gen code
   <navigation from-action="#{identity.login}">
      <rule if="#{identity.loggedIn}">
         <redirect view-id="/home.xhtml"/>
      </rule>
   </navigation>
    -->

   <!-- New code to deal with the existing url -->
   <navigation from-action="#{identity.login}">
      <rule if="#{identity.loggedIn and not empty redirector.referer}">
      	<raise-event type="redirectToPreviousUrlEvent"/>
      </rule>
      <rule if="#{identity.loggedIn and empty redirector.referer}">
      	<redirect view-id="/home.xhtml"/>
      </rule>
   </navigation>

</page>

You'll see this references a redirector Seam component, that needs to be created also. It will handle the referer binding and also the external redirect:

@Name("redirector")
@Scope(ScopeType.CONVERSATION)
public class Redirector {

	private String referer;
	public String getReferer() { return referer; }
	public void setReferer(String referer) { this.referer = referer; }
	
	@Observer("redirectToPreviousUrlEvent")
	public void test() throws IOException {
		FacesContext.getCurrentInstance().getExternalContext().redirect(referer);
	}
}

Now the only thing left to do is add the following to the body of an existing xhtml page:

<script type="text/javascript">startSessionTimeoutCheck();</script>

And if you don't want to sit in front of the computer staring at the screen for 30 minutes add the following to web.xml to test it out:

 <session-config>
  <session-timeout>1</session-timeout>
 </session-config>