Take control of the servlet environment, Part 2

Alternatives to servlet session management

1 2 Page 2
Page 2 of 2
    public void save()
    {
        String[] names = super.getValueNames();
        for( int x = 0; x < names.length; x++ )
        {
            String name = names[x];
            Object value = super.getValue( name );
            writeToDatabase( name, value );
        }
    }
    protected void writeToDatabase( String name, Object value )
    {
        JdbcFacade util = null;
        Date now = new Date( System.currentTimeMillis() );
        String ser = Serialize.objectToString( value );
        try
        {
            util = this._factory.getInstance();
            util.setSQL( "insert into session_master " +
                         "( value, date, session_id, name ) " +
                         "values ( ?, ?, ?, ? )" );
            util.setString( 1, ser );
            util.setDate( 2, now );
            util.setString( 3, getId() );
            util.setString( 4, name );
            util.executeUpdate();
        }
        catch( SQLException e ) // assume a primary key constraint 
violation
        {
            try
            {
                util.reset();
                util.setSQL( "update session_master " +
                             "set value = ?, date = ? " +
                             "where session_id = ? and name = ?" );
                util.setString( 1, ser );
                util.setDate( 2, now );
                util.setString( 3, getId() );
                util.setString( 4, name );
                util.executeUpdate();
            }
            catch( SQLException e2 )
            {
                System.err.println( e2 );
            }
        }
        finally
        {
            if( util != null )
            {
                util.close();
            }
        }
    }

Note: For purposes of simplicity, we assume that any object placed into the session is serializable.

Identification

We've omitted one minor detail: identifying the session. The getId() method referenced by the prior methods must pull off a few tricks. As mentioned in the beginning of this article, most servlet engines use a cookie to handle the browser-to-session mapping. We could, and probably should, assume that our engine does the same and use the superclass implementation of getId(). But we'll roll our own, just in case the server does something fishy that would cause the session ID to differ as the client bounces from one server to the next (thus resulting in unique session states on each server visited).

The logic presented here is simple. We create a unique session ID by taking the current system time in milliseconds and appending a random three-digit number. We add the random number in case two session IDs materialize at the exact same millisecond in time. Although this is highly improbable, Murphy's Law guarantees that it will happen in a mission-critical application. You can use any mechanism you desire to generate the session ID; it must simply be a unique string.

Once you generate a session ID, you must store it in a cookie so that the browser remembers it. That is one of the reasons the SessionWrapper needs a reference to the ResponseWrapper. But that session might have already been accessed and, thus, could already have an ID, so we must check the existing cookies. We've used a utility class to cover the code bloat of looping through each cookie and comparing their names:

    private static final String param = "rudiment.session";
    private static final Random random = new Random();
    private String sessionID;
    public String getId()
    {
        if( null == this.sessionID )
        {
            this.sessionID = Util.getCookieValue( _request, param );
            if( null == this.sessionID )
            {
                this.sessionID = (
                    ( System.currentTimeMillis() * 100 ) +
                    "." +
                    ( 100 + ( Math.abs( random.nextInt() ) % 1000 ) ) );
                if( _response != null )
                {
                    _response.addCookie( new Cookie( param, this.sessionID 
) );
                }
            }
        }
        return( this.sessionID );
    }

With storing and retrieving out of the way, the next logical step is cleaning up. The session interface provides two methods for this purpose: removeValue() deletes a single data pair and invalidate() wipes the entire session.

Our version of removeValue() asks the superclass to remove its in-memory reference and then immediately deletes the database record:

    public void removeValue( String name )
    {
        super.removeValue( name );
        JdbcFacade util = null;
        try
        {
            util = this._factory.getInstance();
            util.setSQL( "delete from session_master " +
                         "where session_id = ? and name = ?" );
            util.setString( 1, getId() );
            util.setString( 2, name );
            util.executeUpdate();
        }
        catch( SQLException e )
        {
            System.err.println( e );
        }
        finally
        {
            if( util != null )
            {
                util.close();
            }
        }
    }

The invalidate() method completes the puzzle. That method tells the session to simply flush itself; it forgets everything. We could simply iterate over all the data and call removeValue() for each, but that would create excess database traffic. Our version of the method must tell the superclass to invalidate and then clean up the database in one fell swoop:

    public void invalidate()
    {
        super.invalidate();
        JdbcFacade util = null;
        try
        {
            util = this._factory.getInstance();
            util.setSQL( "delete from session_master where session_id = ?" 
);
            util.setString( 1, getId() );
            util.executeUpdate();
        }
        catch( SQLException e )
        {
            System.err.println( e );
        }
        finally
        {
            if( util != null )
            {
                util.close();
            }
        }
        Cookie cookie = new Cookie( param, "" );
        cookie.setMaxAge( 1 );
        _response.addCookie( cookie );
    }

Notice that we also wipe the cookie that stores the session ID. This might not seem necessary since the session is now empty, but the database delete might have failed. By forgetting the session ID, we ensure that the next request will receive a fresh session.

Conclusion

Database-stored sessions present a shining example of the power and versatility of the RSEF. You now have the power to control your session management, which is no longer at the whim of another vendor's engine. Next month we'll explain a tricky little problem with cookies and demonstrate how the RSEF comes to the rescue once again.

Thomas E. Davis is a Sun Certified Java Developer and the chief technology officer of his second successful Internet-related company. In addition to being a Java advocate, Thomas is a strong proponent of the extreme programming, design patterns, and refactoring philosophies, which he preaches to his colleagues and supports within his own department. Thomas welcomes constructive criticism and intelligent comments in relation to his articles. Craig Walker is a Sun Certified Java Programmer (awaiting the results of his Developer exam) and senior software engineer. A former consultant to IBM, working on its internal Java initiatives, Craig has pursued the mastery of many Java APIs ranging from the presentation layer to the enterprise and everything in between.

Learn more about this topic

Books

1 2 Page 2
Page 2 of 2