Often it is necessary to be notified, whenever a user logs in or out. This tutorial shows, how this can be achieved with tapestry 5 using form based authentication supported by a web container like tomcat. A really nice design decision of tapestry 5 is not to use XML for configuration, but pure Java. Beside avoiding most of the configuration overhead by following the paradigm Convention over Configuration, there are service classes being responsible for the configuration as defining filters or customized dependency injection and much more.
As we like to be informed when a user logs in, we add a filter to the filter chain being called for every page render request to the AppModule.java
in the services
package of our tapestry project:
/** * A filter, which recognizes a new login. * @param log a logger. * @param globals a gateway to the HTTPServletRequest and some other objects from the * Servlet API. **/ public PageRenderRequestFilter buildLoginFilter(final Logger log, final RequestGlobals globals) { // Anonymous inner class implementing the login filter. return new PageRenderRequestFilter() { public void handle(PageRenderRequestParameters parameters, PageRenderRequestHandler handler) throws IOException { // Retrieve the servlet request. HttpServletRequest httpRequest = globals.getHTTPServletRequest(); if(log.isDebugEnabled()) log.debug("LoginFilter#doFilter() start: " + httpRequest.getUserPrincipal() + ", Requested resource: " + httpRequest.getRequestURI()); Principal principal = null; // Retrieve the principal. If a principal is available, but no // marker object is found in the session, we have a new login. if((principal = httpRequest.getUserPrincipal()) != null && httpRequest.getSession().getAttribute(AppModule.USER_LOGGED_IN) == null) { try { if(log.isInfoEnabled()) log.info("LoginFilter#doFilter() new login for \"" + principal.getName() + "\""); // A new user has logged in. so here comes the code where you can do // something with this information // Next page render request is not a fresh login. httpRequest.getSession().setAttribute(AppModule.USER_LOGGED_IN, principal); } catch(Throwable t) { log.error("LoginFilter#doFilter() " + t.getClass().getName() + "thrown: " + t.getMessage(), t); } } // A call to the filter chain, so that the next filter is executed. handler.handle(parameters); } }; }
The filter tries to retrieve the principal of the currently logged in user. This is the first page render request after a login, if it finds a principal, but there is no marker object in the session. In this case the marker object is stored to the session for following page render requests. The filter can be registered via a so called contribution method situated in AppModule.java
, too:
public void contributePageRenderRequestHandler(OrderedConfiguration<PageRenderRequestFilter> configuration, @InjectService("LoginFilter") PageRenderRequestFilter loginFilter) { configuration.add("loginFilter", loginFilter); }
The Tapestry application is aware of logins, by now. In order to be notified, when a user logs out a HTTPSessionListener can be used. This is not part of Tapestry, but it is available since the application runs in a web container. A session listener has to be configured in the web.xml
:
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <!-- ... --> <listener> <listener-class>some.package.of.mine.SessionListener</listener-class> </listener> <!-- ... --> </web-app>
The implementation of the session listener is as follows:
public class SessionListener implements HttpSessionListener { private static Logger log = Logger.getLogger(SessionListener.class); public void sessionCreated(HttpSessionEvent se) { // Do nothing } public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); Principal principal = (Principal)session.getAttribute(AppModule.USER_LOGGED_IN); if(log.isDebugEnabled()) log.debug("sessionDestroyed() principal: " + principal); session.removeAttribute(AppModule.USER_LOGGED_IN); // Do some stuff here... } }
Whenever the session is destroyed (via session.invalidate()
or session timeout) the method sessionDestroyed()
is called. The marker object will be removed, so that a new login in the same session is recognized again.
nice post. thanks.