The Java Servlet Specification defines Event Listeners for this purpose. Have a look at this article for an introduction on how to utilize these servlet technologies.
I want to explain here how to integrate HttpSessionListeners smoothly with Grails.
We start by registering a listener in the deployment descriptor - the file web.xml.
By default this file is not visible when developing with Grails since it is generated dynamically. You can get a static version by executing the following command in the application's root directory:
grails install-templates
Instead of executing this command and creating a static web.xml file we are going to choose the more modular approach of extending the web.xml generation process.
The first thing to do is to create the file _Events.groovy in the scripts directory of the application root. Note that this directory was created when executing grails create-app. The contents of _Events.groovy should look something like this:
import groovy.xml.StreamingMarkupBuilder
eventWebXmlEnd = {String tmpfile ->
def root = new XmlSlurper().parse(webXmlFile)
root.appendNode {
'listener' {
'listener-class' (
'org.test.MyHttpSessionListener'
)
}
}
webXmlFile.text = new StreamingMarkupBuilder().bind {
mkp.declareNamespace(
"": "http://java.sun.com/xml/ns/j2ee")
mkp.yield(root)
}
}
This defines a closure that is called by the Grails build mechanism right after creation of web.xml. In the closure Groovy's XmlSlurper is used to parse the web.xml into a tree data structure. Xml elements for listener and listener-class are added and the tree data structure is written back using Groovy's StreamingMarkupBuilder.
The class org.test.MyHttpSessionListener is registered as event listener.
The file src/groovy/org/test/MyHttpSessionListener.groovy contains this class. Obviously the org/test part of the path depends on the package name you want to choose. The file's contents should look like this:
package org.test
import org.codehaus.groovy.grails.commons.ApplicationHolder
import javax.servlet.http.HttpSession
import javax.servlet.http.HttpSessionEvent
import javax.servlet.http.HttpSessionListener
import org.codehaus.groovy.grails.commons.ApplicationHolder
import javax.servlet.http.HttpSession
import javax.servlet.http.HttpSessionEvent
import javax.servlet.http.HttpSessionListener
class MyHttpSessionListener implements HttpSessionListener {
HttpSessionService httpSessionService
HttpSessionService httpSessionService
// called by servlet container upon session creation
void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession()
getHttpSessionService().sessionCreated(session)
}
HttpSession session = event.getSession()
getHttpSessionService().sessionCreated(session)
}
// called by servlet container upon session destruction
void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession()
HttpSession session = event.getSession()
getHttpSessionService().sessionDestroyed(session)
}
}
private synchronized HttpSessionService
getHttpSessionService() {
if (httpSessionService == null) {
httpSessionService =
httpSessionService =
(HttpSessionService) ApplicationHolder
.getApplication().getMainContext()
.getBean("httpSessionService")
}
return httpSessionService
}
}
}
return httpSessionService
}
}
The class implements the sessionCreated and sessionDestroyed methods as defined in the HttpSessionListener interface. These methods are called by the servlet container upon session creation and destruction. However to integrate our listener with the rest of the Grails artefacts it would be nicer if conventional Grails Service methods were called on these events.
To achieve the desired Service calls we cannot utilize Spring injections in our SessionListener. We therefore manually fetch a bean called httpSessionService from the application's MainContext. This bean is a conventional Grails Service that we have to implement in a file called grails-app/services/org/test/HttpSessionService.groovy. The file could have the following contents:
package org.test
import javax.servlet.http.HttpSession
class HttpSessionService {
// method called upon session creation
def sessionCreated(HttpSession session) {
log.info("Session created: "+session.id)
}
//method called upon session destruction
def sessionDestroyed(HttpSession session) {
log.info("Session destroyed: "+session.id)
}
}
Other services can be injected into the HttpSessionService and relevant business logic can be called from the sessionCreated and sessionDestroyed Service methods.
5 comments:
Could you explain this line please?
private synchronized HttpSessionService
The complete method Signaturen is "private synchronized HttpSessionService getHttpSessionService()".
Private means that it cannot be called from outside of this class. Synchronized means that only one thread at a time can execute the method. Thus the assignment in the method is executed exactly once even if two threads try to execute it in parallel.
This is normal Java.
Thanks Oliver! Works out of the box and has an elegant architecture with the delegated service. Jelle van Wieringen
Thank you for the clear and concise example. Please note that the "j2ee" namespace is now "javaee".
Eric G.
That solution doesn't work for Grails 2.0+, which deprecated *Holder classes. In this case, you must create your own holder. Take a look at http://burtbeckwith.com/blog/?p=1017
Post a Comment