Jim, Very nice. Thanks!
It's something that I've wanted to add to a few applications, but it's never been high enough on the priority list to actually get done. You've just dropped the barrier to adding it immensely. Jonathan On Thu, Mar 31, 2011 at 7:14 AM, Jim O'Callaghan <jc1000...@yahoo.co.uk>wrote: > 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 – you’ll 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 user’s 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; > } > > } > > You’ll 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 it’s 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 > -- Jonathan Barker ITStrategic