How to use gorm after a http session timeout

In the previous part we’ve already discussed how to get informed about “regular” logout- and timeout-events in order to maintain a list of currently logged in users. But in our use-case we not only want to maintain a simple list, but also want to do some database cleanup queries whenever a user logs out (in our case: we have to remove some “locking” entries, identifiying resources exclusively locked by the current user).

We’ve already identified the two different places where we have to handle the logout event:

  1. The “onLogout” method of the RewooAuthenticationListener (informed by shiro when a regular logout occurres)
  2. The “sessionDestroyed” method of the RewooHttpSessionListener (informed by the servlet container when a timeout occurres)

The first scenario can be handled quite easily: because we are informed about the event by the shiro plugin we will have a fully initialized hibernate session which can be used to clean up your tables in the usual way:

public class RewooAuthenticationListener implements AuthenticationListener {
...
    public void onLogout(PrincipalCollection principalCollection) {
        // Maintain your user list as described in the previous post
        // ...
        // Do the clean up now
        SystemUser user = SystemUser.findByName(userName)
        ExclusiveLocks.findAllByUser(user) {
            it.delete()
        }
    }
...
}

The second one is the tricky part: because the method is called by the servlet container we will not have any valid hibernate session bound to the SessionHolder of the thread-context. So, the Gorm calls will break with a “No hibernate session bound to thread” exception The first solution which came into our minds was, to retrieve a new session by wrapping the routine in a simple withNewSession-closure call, like this:

public class RewooHttpSessionListener implements HttpSessionListener {
...
    public void sessionDestroyed(HttpSessionEvent event) {
        // Maintain your user list as described in the previous post
        // ...
        // Do the clean up now
        ExclusiveLocks.withNewSession {
            SystemUser user = SystemUser.findByName(userName)
            ExclusiveLocks.findAllByUser(user) {
                it.delete()
            }
        }
    }
    ...
}

In fact this solution will work for the FIRST timeout event. But afterwards you will get the following exception each time a new timeout occurres:

org.springframework.dao.DataAccessResourceFailureException: Could not open Hibernate Session; nested exception is org.hibernate.SessionException: Session is closed!

After some research and digging into the grails / hibernate code we discovered that after leaving the withNewSession closure (which resides within a grails class called “HibernatePluginSupport”) the session is closed but not removed from the SessionHolder. When the next timeout occurres the old session will be found by hibernate and the exception will be thrown. Interestingly enough the clean up code used by grails 1.3.5 is significantly different to the new cleanup algorithm used by HibernateGormEnhancer (which replaces HibernatePluginSupport in newer grails versions). Because of some compatibility issues we can not upgrade to a newer version of grails at the moment so we can not say if the problems described here will vanish with a newer grails release.

We’ve searched around for a while to find a possibility to remove the outdated session from the session holder, but we did not found any proper way to accomplish this:

  • you can not access the underlying sessionMap directly, because it is a private variable and for encapsulation purposes there is no getter
  • you can not use the removeSession method, because you have to pass in the key of the session to remove: unfortunately this key is not the session itself but a private object created by SessionHolder during instantiation of the holder and there is no way to retrieve it
  • even calling the clean method of the SessionHolder will not clean the internal map

Finally we found a solution: by calling the SessionHolder#getValidatedSession you will only get the current session, if the session is not already closed. In case it is closed, it will be removed from the internal map as a side effect. So, that is exactly what we want to have in our case but be aware that this is a very ugly workaround. It depends upon an implementation detail of a class which is not under your control.

Our solution leads us to the following method which can be used to clean up the hibernate session:

private void cleanUpHibernateSession() {
    SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(SessionGrabber.getSessionFactory())
    if (sessionHolder != null && !sessionHolder.isEmpty()) {
        // Calling this method will "remove" already closed sessions as a side-effect (so we don't care about the return-value)
        sessionHolder.getValidatedSession()
        if (sessionHolder.isEmpty()) {
            sessionHolder.clear()
        }
    }
}

The method will be called after the withNewSession closure call like this:

public class RewooHttpSessionListener implements HttpSessionListener {
...
    public void sessionDestroyed(HttpSessionEvent event) {
        // Maintain your user list as described in the previous post
        // ...
        // Do the clean up now
        ExclusiveLocks.withNewSession {
            SystemUser user = SystemUser.findByName(userName)
            ExclusiveLocks.findAllByUser(user) {
                it.delete()
            }
        }
        cleanUpHibernateSession()
    }
    ...
}

This will make sure that the next call of withNewSession will not be disturbed by an already closed session.

Conclusion

The solution we’ve presented here give you the possibility to clean up parts of your database each time a user logs out. We don’t feel very comfortable with our workaround for the SessionHolder problem: if you know a better solution we would be glad to hear about it!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s