Jonathan, Sample HTTPSessionListener and SessionMonitor classes below if you are using a clustered environment you would want to adjust this to leverage a db oriented solution youll probably find some holes in it but for my purposes it works as required. I use it to display a grid of SessionMonitors that auto refreshes every x seconds. It displays some useful user info and allows a kill of the users session. I call the updateLastSeen method each time a user hits a new page. You can get most of this information from the http log anyway, but you might find it a useful base to perform other operations with.
package com.a.b.util.beans; import java.io.Serializable; import java.util.Date; /** * Utility class to store details about session creation * @author Jim * */ public class SessionMonitor implements Serializable, Comparable { /** * */ private static final long serialVersionUID = -7043353042185132418L; private Date createTime; private String sessionId; private String iPAddress; private String UserName; private String lastSeen; private boolean active; private transient String createTimeStr; // A convenience placeholder to replace with an actionlink in a grid to allow kill of a session private transient String killSession; public String getCreateTimeStr(){ return createTime.toString(); } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public String getIPAddress() { return iPAddress; } public void setIPAddress(String address) { iPAddress = address; } public SessionMonitor(Date createTime, String sessionId) { this.createTime = createTime; this.sessionId = sessionId; this.active = true; } public String getUserName() { return UserName; } public void setUserName(String userName) { UserName = userName; } public String getLastSeen() { return lastSeen; } public void setLastSeen(String lastSeen) { this.lastSeen = lastSeen; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public String getKillSession() { return killSession; } public void setKillSession(String killSession) { this.killSession = killSession; } public int compareTo(Object o) { return (this.createTime.compareTo(((SessionMonitor)o).createTime) < 0) ? 0 : -1; } } package com.a.b.listeners; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import org.apache.tapestry5.TapestryFilter; import org.apache.tapestry5.ioc.Registry; import org.apache.tapestry5.services.ApplicationGlobals; import org.apache.tapestry5.services.RequestGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.a.b.entities.core.SessionContext; import com.a.b.entities.core.user.UserAccessLog; import com.a.b.services.UserAccessLogEntityManager; import com.a.b.util.DateUtils; import com.a.b.util.beans.SessionMonitor; /** * jc1 - 2010.02.04 Used to count the number of sessions * * @author jc1 * */ public class MySessionListener implements HttpSessionListener { private static final String HASHMAP_NAME = "sessionMap"; // The number of days after which to purge old sessions from the session map public static final int PURGE_DAYS = 1; private final static Logger log = LoggerFactory.getLogger(MySessionListener.class); LinkedHashMap<String, SessionMonitor> sessionMap = null; ServletContext context = null; /** * TODO: - rethink this for clustering - a DB persisted entity will probably have to be used to * CRUD the SessionMonitor objects as the ServletContext may not be replicated depending on the * cluster provider. Other relevant notes: * * - in a cluster the sessionId often has an affinity node ID appended - this will need truncation * for correct lookups * * - in a cluster if we are in a different session context we will lose the ability to kill the * HttpSession - a DB oriented solution is required to kill the user */ @SuppressWarnings("unchecked") public void sessionCreated(HttpSessionEvent event) { HttpSession session = event.getSession(); if (context == null){ context = session.getServletContext(); } sessionMap = (LinkedHashMap<String, SessionMonitor>) context.getAttribute(HASHMAP_NAME); if (sessionMap == null) { context.setAttribute(HASHMAP_NAME, new LinkedHashMap()); sessionMap = (LinkedHashMap<String, SessionMonitor>) context.getAttribute(HASHMAP_NAME); } sessionMap.put(session.getId(), new SessionMonitor(new Date(), session.getId())); // put a key -> value pair of the session into the servlet context - this will allow us to look it up and kill it from another session context.setAttribute(session.getId(), session); } public void sessionDestroyed(HttpSessionEvent event) { HttpSession session = event.getSession(); try { ((SessionMonitor)sessionMap.get(session.getId())).setActive(false); } catch (Exception e) { // ignore - this was triggering an NPE if executed after natural session timeout } //Remove the reference to the session in the servlet context if (context == null){ context = session.getServletContext(); } context.removeAttribute(session.getId()); // Log when the user's session times out Registry registry = (Registry) context.getAttribute(TapestryFilter.REGISTRY_CONTEXT_NAME); if (registry != null){ UserAccessLogEntityManager ualem = registry.getService(UserAccessLogEntityManager.class); if (ualem != null){ try { // The sso: prefix below was taken from the Persist.java source used to do session persistence in T5.2.0 - if this breaks probably the prefix / strategy has changed and the new source should be checked for a solution. ualem.saveUserAccessLogWithCommit(new UserAccessLog("User Session Timeout / Killed"), (SessionContext)session.getAttribute("sso:"+ SessionContext.class.getName())); } catch (Exception e) { log.error("Unable to log User Session Timeout / Killed event", e); } } } } /** * TODO - should this be synchronised? - sessionId should be unique even across a cluster so no concurrent modification events should take place? * @param applicationGlobals * @param sessionId * @param lastSeen */ public static void updateLastSeen(ApplicationGlobals applicationGlobals, String sessionId, String lastSeen){ ServletContext servletContext = applicationGlobals.getServletContext(); Map sessionMap = (Map)servletContext.getAttribute(MySessionListener.HASHMAP_NAME); if (sessionMap == null) {return;} SessionMonitor sessionMonitor = (SessionMonitor)sessionMap.get(sessionId); if (sessionMonitor == null) {return;} sessionMonitor.setLastSeen(lastSeen); } public static void setUserName(ApplicationGlobals applicationGlobals, RequestGlobals requestGlobals, String sessionId, String userName){ ServletContext servletContext = applicationGlobals.getServletContext(); Map sessionMap = (Map)servletContext.getAttribute(MySessionListener.HASHMAP_NAME); SessionMonitor sessionMonitor = (SessionMonitor)sessionMap.get(sessionId); sessionMonitor.setUserName(userName); HttpServletRequest req = requestGlobals.getHTTPServletRequest(); sessionMonitor.setIPAddress(req.getRemoteAddr() + '/' + req.getHeader("VIA") + '/' + req.getHeader("X-FORWARDED-FOR")); } public static void setInActive(ApplicationGlobals applicationGlobals, String sessionId){ ServletContext servletContext = applicationGlobals.getServletContext(); Map sessionMap = (Map)servletContext.getAttribute(MySessionListener.HASHMAP_NAME); ((SessionMonitor)sessionMap.get(sessionId)).setActive(false); } synchronized public static void removeOldSessionsFromMap(ApplicationGlobals applicationGlobals){ try { ServletContext servletContext = applicationGlobals.getServletContext(); Map sessionMap = (Map)servletContext.getAttribute(MySessionListener.HASHMAP_NAME); Collection sessionMonitors = sessionMap.values(); Iterator sessionMonitorsIterator = sessionMonitors.iterator(); Date now = new java.util.Date(); while (sessionMonitorsIterator.hasNext()){ SessionMonitor sessionMonitor = (SessionMonitor)sessionMonitorsIterator.next(); if (DateUtils.getDifferenceInDays(sessionMonitor.getCreateTime(), now) > PURGE_DAYS){ sessionMonitorsIterator.remove(); } } } catch (Exception e){ log.error("Could remove old sessions from map", e); } } synchronized public static void removeDeadSessionsFromMap(ApplicationGlobals applicationGlobals){ try { ServletContext servletContext = applicationGlobals.getServletContext(); Map sessionMap = (Map)servletContext.getAttribute(MySessionListener.HASHMAP_NAME); Collection sessionMonitors = sessionMap.values(); Iterator sessionMonitorsIterator = sessionMonitors.iterator(); while (sessionMonitorsIterator.hasNext()){ SessionMonitor sessionMonitor = (SessionMonitor)sessionMonitorsIterator.next(); if (sessionMonitor.isActive() == false){ sessionMonitorsIterator.remove(); } } } catch (Exception e){ log.error("Could remove dead sessions from map", e); } } synchronized public static void invalidateSession(ApplicationGlobals applicationGlobals, String sessionId){ ServletContext servletContext = applicationGlobals.getServletContext(); try { HttpSession session = (HttpSession)servletContext.getAttribute(sessionId); session.invalidate(); log.info("Forced kill of session: " + sessionId); // TODO: look into decrementing db login count etc. } catch (Exception e){ log.error("Could not force kill session: " + sessionId + " - session may be already invalidated", e); } } public static List getSessionList(ApplicationGlobals applicationGlobals){ ServletContext servletContext = applicationGlobals.getServletContext(); // Using LinkedHashMap as the key order is preserved. LinkedHashMap sessionMap = (LinkedHashMap)servletContext.getAttribute(MySessionListener.HASHMAP_NAME); if (sessionMap == null){ sessionMap = new LinkedHashMap(); } List retVal = new ArrayList(); for (Iterator i = sessionMap.keySet().iterator(); i .hasNext();){ retVal.add(sessionMap.get(i.next())); } return retVal; } } Youll also need to register the listener in your web.xml: <listener> <listener-class>com.a.b.listeners.MySessionListener</listener-class> </listener> Regards, Jim. From: Jonathan Barker [mailto:jonathan.theit...@gmail.com] Sent: 31 March 2011 03:25 To: Tapestry users Subject: Re: Logon notification Jim, I would be interested in seeing your HTTPSessionListener implementation. David, I'm going to hazard a guess that indeed you would need to do some configuration through tapestry-ioc. Can I assume that the events that you do see are from services that you have configured entirely in Spring? Regards, Jonathan On Wed, Mar 30, 2011 at 12:14 PM, Jim O'Callaghan <jc1000...@yahoo.co.uk>wrote: > Hi David, > > Is there something specific in the ApplicationListener approach you > require? I use TSS and have a login page that in its onSuccess method > sends > a redirect like so: > > @Inject > private HttpServletResponse response; > . > . > . > Object onSuccess() throws IOException > { > response.sendRedirect(request.getContextPath() + checkUrl + > "?j_username=" + > login.getUserName() + "&j_password=" + > login.getPassword() + > "&_spring_security_remember_me=" + > (login.isRememberMe() ? "checked" : "")); > > and then in its onActivate method: > > void onActivate(String extra) { > if (extra.equals("failed")) { > failed = true; > > form.recordError(messages.get("login.invalidUsernameOrPassword")); > } else { > // do your thing > . > . > . > } > > > I use a registered listener that implements HTTPSessionListener to update a > hashtable of where a user has last been etc. for a kind of dashboard page, > and some db calls to update a persisted logged on flag on a user table > would this approach be of any use to you? > > Regards, > Jim. > > From: David Uttley [mailto:dutt...@democracysystems.com] > Sent: 30 March 2011 16:29 > To: Tapestry users > Subject: Re: Logon notification > > Hi Peter, > > If it was as easy as what you suggest I wouldn't have bothered with the > mailing list, this is a last resort I don't use these mailing lists lightly > only when I start banging my head against a wall, I had already done the > registering of the ApplicationListener. As for lack of detail I am not > really sure what to post that would be of help. I would also argue that it > is a Tapestry question as I am using Tapestry Spring Security which is > configured directly by T5. Spring is reporting events to the > ApplicationListener its the events that occur in Tapestry Spring Security > that are not appearing. If I use the first example then I get other events > from the container but not from TSS. > > I have been in contact with the guy from TSS and he told me his Spring > knowledge was limited and directed me to the Tapestry mailing board. > > From looking through the debugger it appears that TSS has no application > listeners to notify when it wishes to send an event. However, Spring has an > application listener as I am getting other events from it. Therefore, I am > confused how the ApplicationListener is set on TSS, I wonder if I have to > specifically add the ApplicationListener for Tapestry in the AppModule. It > seems like the TSS is initialised before the Spring context files and > annotations. > > It looks like TSS has little support and I probably should cut my losses > now > and remove it and just go for a pure Spring based implementation. > > Thanks > David > > > On 30 Mar 2011, at 16:00, p.stavrini...@albourne.com wrote: > > > Hi David, > > > > Apart from the lack of details in your question, this is also hardly a > Tapestry question. You should direct it to the Spring forums instead, but > if > this reply helps so be it: > > > > You will need in your applicationContext.xml to define a bean that will > automatically start receiving events (I assume you know how)... I am not > familiar with Spring security, but the API docs are quite clear: > > > > > > http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframe > work/context/ApplicationListener.html > > > > So code that might have looked something like this in the past: > > > > public void onApplicationEvent(ApplicationEvent applicationEvent) > > { > > if (applicationEvent instanceof AuthenticationSuccessEvent) > > { > > AuthenticationSuccessEvent event = (AuthenticationSuccessEvent) > applicationEvent; > > UserDetails userDetails = (UserDetails) > event.getAuthentication().getPrincipal(); > > > > //notify here now, etc. > > } > > } > > > > 'In theory' (untested of course) can now be replaced by: > > > > public void onApplicationEvent(AuthenticationSuccessEvent successEvent) > > { > > UserDetails userDetails = (UserDetails) > successEvent.getAuthentication().getPrincipal(); > > //notify here etc. > > > > } > > > > And thats all?! > > > > Cheers, > > Peter > > > > > > > > ----- Original Message ----- > > From: "David Uttley" <dutt...@democracysystems.com> > > To: "Tapestry users" <users@tapestry.apache.org> > > Sent: Wednesday, 30 March, 2011 16:50:26 GMT +02:00 Athens, Beirut, > Bucharest, Istanbul > > Subject: Logon notification > > > > So do I have any takers for this problem? > > > > Somebody must be recording logins somewhere, I don't have to use Spring > to > do it. > > > > ------Original message > > > > I am trying to get spring security to notify me of a successful logon > using the Spring ApplicationListener. However, the ApplicationListener > doesn't seem to be notified of the events. > > > > Any help or pointers will be appreciated. > > > > I am using t5.2 Spring 3.0 and the 3.0.0-snapshot of t5 Spring Security. > > > > > > ------- > > > > Thanks > > David > > > > --------------------------------------------------------------------- > > To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org > > For additional commands, e-mail: users-h...@tapestry.apache.org > > > ________________________________________ > No virus found in this message. > Checked by AVG - www.avg.com > Version: 10.0.1209 / Virus Database: 1500/3538 - Release Date: 03/29/11 > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org > For additional commands, e-mail: users-h...@tapestry.apache.org > > -- Jonathan Barker ITStrategic ________________________________________ No virus found in this message. Checked by AVG - www.avg.com Version: 10.0.1209 / Virus Database: 1500/3540 - Release Date: 03/30/11