View Javadoc
1   /*******************************************************************************
2    *   Gisgraphy Project 
3    * 
4    *   This library is free software; you can redistribute it and/or
5    *   modify it under the terms of the GNU Lesser General Public
6    *   License as published by the Free Software Foundation; either
7    *   version 2.1 of the License, or (at your option) any later version.
8    * 
9    *   This library is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12   *   Lesser General Public License for more details.
13   * 
14   *   You should have received a copy of the GNU Lesser General Public
15   *   License along with this library; if not, write to the Free Software
16   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
17   * 
18   *  Copyright 2008  Gisgraphy project 
19   *  David Masclet <davidmasclet@gisgraphy.com>
20   *  
21   *  
22   *******************************************************************************/
23  package com.gisgraphy.webapp.listener;
24  
25  import java.util.LinkedHashSet;
26  import java.util.Set;
27  
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletContextEvent;
30  import javax.servlet.ServletContextListener;
31  import javax.servlet.http.HttpSessionAttributeListener;
32  import javax.servlet.http.HttpSessionBindingEvent;
33  
34  import org.springframework.security.Authentication;
35  import org.springframework.security.AuthenticationTrustResolver;
36  import org.springframework.security.AuthenticationTrustResolverImpl;
37  import org.springframework.security.context.HttpSessionContextIntegrationFilter;
38  import org.springframework.security.context.SecurityContext;
39  import org.springframework.security.context.SecurityContextHolder;
40  import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
41  
42  import com.gisgraphy.model.User;
43  
44  /**
45   * UserCounterListener class used to count the current number of active users
46   * for the applications. Does this by counting how many user objects are stuffed
47   * into the session. It Also grabs these users and exposes them in the servlet
48   * context.
49   * 
50   * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
51   */
52  public class UserCounterListener implements ServletContextListener,
53  	HttpSessionAttributeListener {
54      /**
55       * Name of user counter variable
56       */
57      public static final String COUNT_KEY = "userCounter";
58  
59      /**
60       * Name of users Set in the ServletContext
61       */
62      public static final String USERS_KEY = "userNames";
63  
64      /**
65       * The default event we're looking to trap.
66       */
67      public static final String EVENT_KEY = HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY;
68  
69      private transient ServletContext servletContext;
70  
71      private int counter;
72  
73      private Set<User> users;
74  
75      /**
76       * Initialize the context
77       * 
78       * @param sce
79       *                the event
80       */
81      public synchronized void contextInitialized(ServletContextEvent sce) {
82  	servletContext = sce.getServletContext();
83  	servletContext.setAttribute((COUNT_KEY), Integer.toString(counter));
84      }
85  
86      /**
87       * Set the servletContext, users and counter to null
88       * 
89       * @param event
90       *                The servletContextEvent
91       */
92      public synchronized void contextDestroyed(ServletContextEvent event) {
93  	servletContext = null;
94  	users = null;
95  	counter = 0;
96      }
97  
98      synchronized void incrementUserCounter() {
99  	counter = Integer.parseInt((String) servletContext
100 		.getAttribute(COUNT_KEY));
101 	counter++;
102 	servletContext.setAttribute(COUNT_KEY, Integer.toString(counter));
103     }
104 
105     synchronized void decrementUserCounter() {
106 	int counter = Integer.parseInt((String) servletContext
107 		.getAttribute(COUNT_KEY));
108 	counter--;
109 
110 	if (counter < 0) {
111 	    counter = 0;
112 	}
113 
114 	servletContext.setAttribute(COUNT_KEY, Integer.toString(counter));
115     }
116 
117     @SuppressWarnings("unchecked")
118     synchronized void addUsername(User user) {
119 	users = (Set) servletContext.getAttribute(USERS_KEY);
120 
121 	if (users == null) {
122 	    users = new LinkedHashSet<User>();
123 	}
124 
125 	if (!users.contains(user)) {
126 	    users.add(user);
127 	    servletContext.setAttribute(USERS_KEY, users);
128 	    incrementUserCounter();
129 	}
130     }
131 
132     @SuppressWarnings("unchecked")
133     synchronized void removeUsername(User user) {
134 	users = (Set<User>) servletContext.getAttribute(USERS_KEY);
135 
136 	if (users != null) {
137 	    users.remove(user);
138 	}
139 
140 	servletContext.setAttribute(USERS_KEY, users);
141 	decrementUserCounter();
142     }
143 
144     /**
145      * This method is designed to catch when user's login and record their name
146      * 
147      * @see javax.servlet.http.HttpSessionAttributeListener#attributeAdded(javax.servlet.http.HttpSessionBindingEvent)
148      * @param event
149      *                the event to process
150      */
151     public void attributeAdded(HttpSessionBindingEvent event) {
152 	if (event.getName().equals(EVENT_KEY) && !isAnonymous()) {
153 	    SecurityContext securityContext = (SecurityContext) event
154 		    .getValue();
155 	    if (securityContext.getAuthentication().getPrincipal() instanceof User) {
156 		User user = (User) securityContext.getAuthentication()
157 			.getPrincipal();
158 		addUsername(user);
159 	    }
160 	    // Workaround for Jetty bug
161 	    // (http://www.nabble.com/current-user-count-incorrect-tf3550268.html#a9919134)
162 	} else if (event
163 		.getName()
164 		.equals(
165 			AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY)) {
166 	    String username = (String) event.getValue();
167 	    User user = new User(username);
168 	    addUsername(user);
169 	}
170     }
171 
172     private boolean isAnonymous() {
173 	AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
174 	SecurityContext ctx = SecurityContextHolder.getContext();
175 	if (ctx != null) {
176 	    Authentication auth = ctx.getAuthentication();
177 	    return resolver.isAnonymous(auth);
178 	}
179 	return true;
180     }
181 
182     /**
183      * When user's logout, remove their name from the hashMap
184      * 
185      * @see javax.servlet.http.HttpSessionAttributeListener#attributeRemoved(javax.servlet.http.HttpSessionBindingEvent)
186      * @param event
187      *                the session binding event
188      */
189     public void attributeRemoved(HttpSessionBindingEvent event) {
190 	if (event.getName().equals(EVENT_KEY) && !isAnonymous()) {
191 	    SecurityContext securityContext = (SecurityContext) event
192 		    .getValue();
193 	    Authentication auth = securityContext.getAuthentication();
194 	    if (auth != null && (auth.getPrincipal() instanceof User)) {
195 		User user = (User) auth.getPrincipal();
196 		removeUsername(user);
197 	    }
198 	}
199     }
200 
201     /**
202      * Needed for Acegi Security 1.0, as it adds an anonymous user to the
203      * session and then replaces it after authentication.
204      * http://forum.springframework.org/showthread.php?p=63593
205      * 
206      * @see javax.servlet.http.HttpSessionAttributeListener#attributeReplaced(javax.servlet.http.HttpSessionBindingEvent)
207      * @param event
208      *                the session binding event
209      */
210     public void attributeReplaced(HttpSessionBindingEvent event) {
211 	if (event.getName().equals(EVENT_KEY) && !isAnonymous()) {
212 	    SecurityContext securityContext = (SecurityContext) event
213 		    .getValue();
214 	    if (securityContext.getAuthentication() != null) {
215 		if (securityContext.getAuthentication().getPrincipal() instanceof User) {
216 		    User user = (User) securityContext.getAuthentication()
217 			    .getPrincipal();
218 		    addUsername(user);
219 		}
220 	    }
221 	}
222     }
223 }