Help

I'm the guy you can blame if this website is crashing. In fact, send me an e-mail if it does, so I can fix it. If you only want to read my entries, subscribe to my blog.

Location: Switzerland, CET
Books I'm currently reading
Google Web Toolkit Applications
Didn't like the first GWT book I read, so this is now an attempt with another book. Halfway through the book and so far it's quite good.
My Books
Java Persistence with Hibernate
with Gavin King
November 2006
Manning Publications
841 pages (English), PDF ebook
703 pages (German)
Hibernate in Action
with Gavin King
August 2004
Manning Publications
408 pages (English), PDF ebook
Unternehmen im Internet
with Ingo Petzke, Michael Mueller
1998
Oldenbourg
300 pages (German)
Tags
Seam Wiki (5)
Seam (4)
Books (1)
> Seam News < (1)
All...
Archive 'Seam Wiki'
Implementing graceful session timeout with Seam, JSF, and jQuery
13. Dec 2007, 00:57 CET, by Christian Bauer

A robust web application should not crash and die when the session times out. I guess we can all agree on that, but thanks to the stateless nature of HTTP and the usual hacks attaching session state onto that protocol, this is quite difficult to accomplish. Just search for session timeout on Google. So here I am with my JSF/Seam/Ajax4JSF/jQuery application, trying to make it more robust.

Session timeout happens on the server

Well, duh. The HTTP session is stored on the server in some fashion. Of course you could be one of the radicals who think that RIA means that the client keeps all the session state, or that you don't need no stinkin' session because you are all REST. Well, show me your app and we'll talk. You just have whitepapers to show? Too bad.

If you keep session state on the server, and naturally it's cleaned up and timing out on the server-side only, your client doesn't know anything about that. So if you want to do anything on the page your user is looking at when the session times out, you need to poll the server.

In my Seam app I need something to poll so I created the following component that runs on the server:

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

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

I know that this is extremely sophisticated. Here is the trick: When you ask the server if the session has timed out, you need to ask it if the current session it has is new. You have no other way to find out if the (previous) session timed out. Because your poll request will create a new session if the previous one timed out. So if you happen to check isNewSession() after the previous session timed out, you get a true result. (By the way, who had the braindead idea that Tomcat should re-use session identifiers? How incompetent can you be?)

Polling the server

At that point I still don't know what I want to do on the client when a new session happens to be present on the server. Well, first I need to find out if that is the case by polling the server:


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

<script type="text/javascript">

    var sessionChecker = Seam.Component.getInstance("httpSessionChecker");
    var timeoutURL = '#{wiki:renderURL(wikiStart)}';
    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);
            jQuery(".ajaxSupport")
                   .removeAttr('onblur')
                   .removeAttr('onchange')
                   .removeAttr('onkeyup')
                   .removeAttr('onclick');
            jQuery(".sessionEventTrigger").hide();
            var answer = confirm("#{messages['lacewiki.msg.SessionTimeout']}");
            if (answer) window.location = timeoutURL;
        }
    }
</script>

First thing, I import the Seam Remoting JavaScript interfaces and the proxy interface of my server-side component, httpSessionChecker. The two main functions I define on the client are startSessionTimeoutCheck() and stopSessionTimeoutCheck(). The start function starts polling the server at an interval. This interval is not just some random millisecond value. I poll the server every sessionTimeoutSeconds plus 3 seconds (3000 milliseconds). So, if the session timeout configured on the server is 30 minutes, I poll it every 30 minutes and 3 seconds, unless somebody resets the interval by calling resetSessionTimeoutCheck(). This basically guarantees that, if no request happened while this browser window is sleeping, the server-side session will be gone when I poll again.

Those three methods (start, stop, reset) will be useful later when I integrate the session timeout checking with the rest of the user interface. Let's look at what I'm doing in the callback of repeated server poll, alertTimeOut(newSession):

  • If the server says that there is no new session, I continue polling it with an unchanged interval.
  • If the server says there is a new session, I stop polling by clearing the interval. Then I react to the new session by modifying the UI on the client side.

So from that point on, I'm handling a client callback and I can do whatever I want to react to the new session.

Adapting the client

My goal is to not just kick the user out when a session timeout occurs. I don't want to just redirect him to some start page, I want to provide a choice. But I also don't want him to do anything dangerous that could throw an exception after his session is gone.

The user can either click OK or Cancel on the confirmation dialog. If OK is clicked, I redirect to the start page, otherwise he can stay on the current page. That is going to be a problem because the current page might have all kinds of widgets on it that really really require a session with some data when you click them.

So first, I disable anything that could be dangerous, using jQuery. The first jQuery statement removes all JavaScript events from any elements on the page that have CSS class of ajaxSupport. The second statement hides any elements on the page that have a CSS class of sessionEventTrigger. Elements that have class ajaxSupport are typically input fields that have onblur event triggers, or onclick links.

The user can continue copying stuff out of these input fields (to preserve data that would otherwise be lost) but he can not trigger an AJAX request anymore. He can also not click on any buttons or see any elements that might require a session.

So all of it comes really down to these two questions:

  • Which pages require a session timeout check? Sometimes I just don't care about the server-side session because all actions on a given page might be safe, no matter if they run in the previous or a new HTTP session.
  • Which elements on a page do I want to hide and what actions do I want to disable if the server-side session times out (if the user doesn't accept the redirect to the start page)?

If a page requires a session timeout check, I add the following code to its body:

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

When the page is loaded, I start polling the server (with the given interval). If a new session is present on the server, all elements that are marked as styleClass="ajaxSupport" are stripped of their event handlers with jQuery. This would typically be some input field on a form on the page:

<s:decorate id="userNameDecorate" template="formFieldDecorate.xhtml">
    <ui:define name="label">#{messages['lacewiki.label.commentForm.Name']}</ui:define>
    <h:inputText styleClass="ajaxSupport" tabindex="1" size="40" maxlength="100" required="true"
                 id="userName" value="#{commentHome.instance.fromUserName}">
        <a:support status="commentForm:status" event="onblur" 
                   reRender="userNameDecorate" oncomplete="onAjaxRequestComplete()"/>
    </h:inputText>
</s:decorate>

If a new session is present on the server, the confirmation dialog appears. If the user clicks Cancel, the onblur event on that input field will be disabled. The user can still copy and rescue the value he typed in.

All elements of class sessionEventTrigger will be hidden at that point. These are typically buttons such as Save, Update, etc. Here is an example:

<a:commandLink id="post"
                action="#{commentHome.persist}" tabindex="1"
                reRender="commentDisplayForm, messageBoxContainer"
                accesskey="#{messages['lacewiki.button.commentForm.Post.accesskey']}"
                status="commentForm:status"
                eventsQueue="ajaxEventQueue"
                oncomplete="onAjaxRequestComplete()"
                styleClass="button sessionEventTrigger">
    <h:outputText escape="false" styleClass="buttonLabel" 
                  value="#{messages['lacewiki.button.commentForm.Post']}"/>
</a:commandLink>

Just a coincidence, this is a button that doesn't trigger page navigation put a partial page-rendering after completion of the AJAX request. Now, that is an additional complication we need to take into account.

Dealing with AJAX requests

If you request a page and the session poll interval starts running when the page is rendered, everything is fine. The poll interval will be /session timeout/ + 3 seconds (that's just a safety margin). So, while you are focused on that page and working on it, no request is send to the server and both client and server keep counting the seconds. The server times out after /session timeout/ and 3 seconds later the client asks it if there is a new session present. The server should answer /yes/.

AJAX changes that picture completely. A page that contains AJAX actions supports, by definition, partial re-rendering of that page. So while your session poll interval for the page starts running on the client, you make AJAX requests to the server (trigger onblur events, clicking AJAX buttons, etc.). The client poll interval and the server session timeout are now out of sync. So after every AJAX request completes, you have to reset the client poll interval. That is the job of the oncomplete="onAjaxRequestComplete()" callback you have seen on the code snippets above.

Here is what that JavaScript function does:

function onAjaxRequestComplete() {
    resetSessionTimeoutCheck();
}

I guess you could call the reset directly but I like the indirection. Sometimes I need other things to be done after an AJAX request completes (like, apply some CSS styles to the re-rendered parts of the page). I actually would really like to have a default callback for all Ajax4JSF events.

Finally, I can use the start/stop functions of the session check polling conditionally. Let's say I have a page that doesn't require any session checking, all actions and links are perfectly safe to use with an old or new session.

Except that this page might also, conditionally, include a form with plenty of AJAX magic. So I need to enable session state polling when the form is included and shown, and disable it when the form is not shown. Basically, I need to start and stop polling conditionally on the same page. Easy enough:

<s:fragment rendered="#{commentHome.showForm}">
    <script type="text/javascript">startSessionTimeoutCheck();</script>
</s:fragment>

And the opposite, which would (probably) be the default condition when the page is loaded first time:

<s:fragment rendered="#{not commentHome.showForm}">
    <script type="text/javascript">stopSessionTimeoutCheck();</script>
</s:fragment>

It's fine to stop the polling even if it didn't start, the JS function is safe.

Well, that's about it and I hope we can roll some of that back into the Seam and Ajax4JSF codebase to make it easier.

The new Seam CAPTCHA is great
06. Dec 2007, 19:55 CET, by Christian Bauer

Seam offers some basic infrastructure for CAPTCHA creation and validation, so all you have to do if you want to add CAPTCHA validation to a form is add a single form field and show the picture with <h:graphicImage/>. The only built-in implementation we shipped with Seam 1.x and 2.0 was based on JCaptcha, but you could easily extend it and do your own question/answer thing. This is actually what I did and you can see my simplified math question CAPTCHA if you try to post a comment to this entry.

I'm not the only Seam user who had problems with JCaptcha (search the Seam forum if you want to know more, basically: it's over-engineered, needs seconds to startup, sometimes needs seconds to render an image, the default image generators are hard to read but CAPTCHAs are still easy to break, etc). So Gavin wrote a new Captcha implementation we could ship with Seam 2.0.1. This is in Seam CVS now and not released, so ignore this blog entry if you are not a Seam CVS user and come back to it when we release 2.0.1.

The usage scenario is still the same, add an input field and a picture to your form:

<s:validateAll>
    <h:inputText size="6" maxlength="6" required="true" id="verifyCaptcha" value="#{captcha.response}"/>
    <h:graphicImage value="/seam/resource/captcha"/>
</s:validateAll>

By default Gavins implementation only renders a simple math question as an image with no obfuscation at all. So I extended the built-in classes. This is how my generated CAPTCHA questions look like:

The trick is to tell the user to ignore any circles - and to never use any characters that look like circles (zero, o, O). I don't think people will have issues deciphering these characters, I've been trying myself a few dozen times during testing and failed only once or twice. I think that this CAPTCHA is quite difficult to break automatically though, the grey shades used for obfuscation and real text are the same and circles really destroy the original shape of the characters. And if it's broken, we can simply add more aggressive circles in small incremental steps or increase the rotation range of the characters.

Another thing that makes CAPTCHAs less painful is to store the data in the HTTP session, so that if a user entered a captcha once on your site, you don't require it to be entered a second time. But that's all built-in with the new Seam Captcha stuff.

Here is my custom code for the image generation:


package org.jboss.seam.wiki.core.captcha;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.captcha.Captcha;
import org.jboss.seam.captcha.CaptchaResponse;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

@Name("org.jboss.seam.captcha.captcha")
@Scope(ScopeType.SESSION)
@Install(precedence = Install.APPLICATION)
public class WikiCaptcha extends Captcha {

    Color backgroundColor = new Color(0xf5,0xf5, 0xf5);
    Font textFont = new Font("Arial", Font.PLAIN, 25);
    int charsToPrint = 6;
    int width = 120;
    int height = 25;
    int circlesToDraw = 4;
    float horizMargin = 20.0f;
    double rotationRange = 0.2;
    String elegibleChars = "ABDEFGHJKLMRSTUVWXYabdefhjkmnrstuvwxy23456789";
    char[] chars = elegibleChars.toCharArray();

    @Override
    @Create
    public void init() {
        super.init();

        StringBuffer finalString = new StringBuffer();
        for (int i = 0; i < charsToPrint; i++) {
            double randomValue = Math.random();
            int randomIndex = (int) Math.round(randomValue * (chars.length - 1));
            char characterToShow = chars[randomIndex];
            finalString.append(characterToShow);
        }

        setChallenge(finalString.toString());
        setCorrectResponse(finalString.toString());
    }

    @Override
    public BufferedImage renderChallenge() {

        // Background
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
        g.setColor(backgroundColor);
        g.fillRect(0, 0, width, height);

        // Some obfuscation circles
        for (int i = 0; i < circlesToDraw; i++) {
            int circleColor = 80 + (int)(Math.random() * 70);
            float circleLinewidth = 0.3f + (float)(Math.random());
            g.setColor(new Color(circleColor, circleColor, circleColor));
            g.setStroke(new BasicStroke(circleLinewidth));
            int circleRadius = (int) (Math.random() * height / 2.0);
            int circleX = (int) (Math.random() * width - circleRadius);
            int circleY = (int) (Math.random() * height - circleRadius);
            g.drawOval(circleX, circleY, circleRadius * 2, circleRadius * 2);
        }

        // Text
        g.setFont(textFont);
        FontMetrics fontMetrics = g.getFontMetrics();
        int maxAdvance = fontMetrics.getMaxAdvance();
        int fontHeight = fontMetrics.getHeight();
        float spaceForLetters = -horizMargin * 2 + width;
        float spacePerChar = spaceForLetters / (charsToPrint - 1.0f);

        char[] allChars = getChallenge().toCharArray();
        for (int i = 0; i < allChars.length; i++ ) {
            char charToPrint = allChars[i];
            int charWidth = fontMetrics.charWidth(charToPrint);
            int charDim = Math.max(maxAdvance, fontHeight);
            int halfCharDim = (charDim / 2);
            BufferedImage charImage = new BufferedImage(charDim, charDim, BufferedImage.TYPE_INT_ARGB);
            Graphics2D charGraphics = charImage.createGraphics();
            charGraphics.translate(halfCharDim, halfCharDim);
            double angle = (Math.random() - 0.5) * rotationRange;
            charGraphics.transform(AffineTransform.getRotateInstance(angle));
            charGraphics.translate(-halfCharDim, -halfCharDim);
            int charColor = 60 + (int)(Math.random() * 90);
            charGraphics.setColor(new Color(charColor, charColor, charColor));
            charGraphics.setFont(textFont);
            int charX = (int) (0.5 * charDim - 0.5 * charWidth);
            charGraphics.drawString("" + charToPrint, charX, ((charDim - fontMetrics.getAscent())/2 + fontMetrics.getAscent()));
            float x = horizMargin + spacePerChar * (i) - charDim / 2.0f;
            int y = ((height - charDim) / 2);
            g.drawImage(charImage, (int) x, y, charDim, charDim, null, null);

            charGraphics.dispose();
        }
        g.dispose();

        return bufferedImage;
    }

    @CaptchaResponse(message = "#{messages['lacewiki.label.VerificationError']}")
    public String getResponse() {
        return super.getResponse();
    }
}

(If some of the code looks familiar, you have maybe seen it here before.)

I still need to finish a few other things before I can upgrade the software running this site to the new Captcha though. So if you want to try it out, get Seam CVS and put this class into your codebase, no other configuration necessary.

More on the wiki road map
25. Sep 2007, 08:49 CET, by Christian Bauer

I was traveling last week (see picture) when Gavin posted about the wiki road map and didn't have much time to elaborate.

What we actually call wiki is a small platform that we are building to run some of our websites, most notably the website you are looking at right now and the upcoming /seamframework.org/ community site. Gavin already explained why we are building this from scratch, the primary reason being that we want to build it on and with Seam.

For me it is also about learning how to write a real application with Seam and JSF. There are some unique and new challenges you have to face if you have the typical Struts-HttpSession-StatelessFacade-DAO background. We are chatting about this frequently and it's obvious that the introduction of new contexts and contextual wiring of components at runtime radically changes the programming model. This is a chat excerpt from yesterday:

Christian: there is some hope that it will all sort itself out with more patterns
Gavin: it takes time to develop the intuition about what is good to do
Gavin: remember the first time you wrote OO code :)
Christian: well the thing is, it's difficult to understand the state your system is in
Christian: which is now so much more important
Christian: and it's already a pain with imperative programming
Gavin: interesting thing to say
Gavin: i dont know what you mean
Gavin: you mean there is no central orchestration?
Christian: when you write code, you push the state of the system forward in little steps
Christian: and for every line you need to visualize in your head what it is now
Christian: which is with seam no longer limited to your current stack of operations
Christian: you need to think about the contexts as well
Gavin: don't you need to anyway?
Gavin: even if you don't have a proper model of these contexts
Gavin: thats what i always found....
Christian: hm
Christian: well, no
Christian: session is easy to do - even if it ends up being buggy
Christian: but page and conversation are a challenge if you add the JSF lifecycle as well

There are so many new ways of solving architectural problems that inevitably some of them turn out to be bad ideas in the long run while others work very nicely. This is also becoming an FAQ on the Seam forum, so we as a developer team need to provide more guidelines (and probably a pattern catalogue) for Seam users.

Seam as a project is now more than 2 years old, the first presentations Gavin was showing internally at JBoss - and I don't think many people understood what he wanted to say at the time :) - date back to a developer meeting in 2004. So the good news is that some of the more obvious design patterns are already emerging and that we just need to finish formalizing them. Others are still open for debate.

For example, the code running this website has about 130 classes and 50 TODO items. Most of the TODOs are not minor problems but go something like this:


    @Observer("Wiki.started")
    public void scanForSearchSupportComponents() {
        log.debug("initializing search registry");

        // Fire an event and let all listeners add to the given collection
        // TODO: Is this smarter than PreferenceRegistry scanning approach?
        Set<SearchSupport> searchSupportComponents = new HashSet<SearchSupport>();
        Events.instance().raiseEvent("Search.addSearchSupport", searchSupportComponents);

This scans the application space after deployment and startup and finds every component that wants to integrate with the full text indexing/search capability of the platform (based on the excellent Hibernate Search integration with Lucene). The way this is done is a registry that fires an event on startup and any component that wishes to register adds itself to the collection that is passed around as the event payload.

We need similar functionality for preferences, every component that brings its own set of customizable properties needs to be known. But there I used a completely different approach:


    @Observer("Wiki.started")
    public void scanForPreferenceComponents() {
        log.debug("initializing preference registry");

        // Register the meta model by scanning components with @Preference annotation
        String[] boundNames = Contexts.getApplicationContext().getNames();

        for (String boundName : boundNames) {
            if (boundName.endsWith(".component") && !boundName.startsWith("org.jboss.seam")) {
                Component component = (Component) Contexts.getApplicationContext().get(boundName);

                if (component.getBeanClass().getAnnotation(Preference.class) != null &&
                    !preferenceComponentsByName.containsKey(component.getName())) {

                    log.debug("Registering preference component: " + component.getName());

What happens here is that I get all the components from the application context (Seam registers component metadata there), find out which have a @Preference annotation, and then build my own metadata for the registry. I'd argue that the event/listener approach is more elegant. I think many applications need an application-level registry of metadata and it comes up regularly on the forums as well. So what is the pattern going to be and which variations are good and bad practice?

P.S. If you want to try out this application or just look at the code - beware, the example above should make it clear that it's not all very beautiful - here are some pointers: It's released under the LGPL and you can find it in the examples/wiki/ directory of the Seam 2.0 package. You can look at the source online or get it from Seam CVS. We have a separate JIRA component called Wiki for any bug or improvement reports.

Fun with MySQL case sensitivity
30. Aug 2007, 15:45 CET, by Christian Bauer

So I was preparing the data for this website locally on my machine. My staging environment was pretty much the same (same Java VM, JBoss AS, MySQL version, etc.) The only difference is the operating system, it's OS X and the live site is running on CentOS. I created a package to deploy and everything looked fine. Until I edited documents on the live site. MySQL started to throw constraint violation errors it didn't show in the staging environment.

Now, it took me about half an hour to realize what was going on and I hope this will save someone some time:

  • MySQL is case sensitive or not wrt identifiers in the database catalog.
  • This depends on the operating system it is running on, which clearly doesn't make any sense but you learn it at some point. Table names, for example in SQL queries or even in foreign key constraint declarations (references FOO...) have to be specified in the correct case if you run MySQL on Linux but not if you run it on OS X.

I know this and I have written all database identifiers in uppercase everywhere, in all source and scripts. What I didn't expect was that the mysqldump tool, which I used to export the package for deployment from the staging database, generates all constraint declarations in the exported file with lowercase references 'foo'.

This you'd usually consider a regular bug in a tool, if MySQL would have caught it on import. But no, it silently ignored these obviously illegal constraint declarations (because the target table can't be found), imported them anyway, and then failed at runtime with a constraint violation. Nice, eh?

New weblog is live
30. Aug 2007, 04:24 CET, by Christian Bauer

I just completed migration of the data from the old Hibernate weblog to the new site. This is actually the first real entry using the new software. Hurray!