001 /*
002 Licensed to the Apache Software Foundation (ASF) under one
003 or more contributor license agreements. See the NOTICE file
004 distributed with this work for additional information
005 regarding copyright ownership. The ASF licenses this file
006 to you under the Apache License, Version 2.0 (the
007 "License"); you may not use this file except in compliance
008 with the License. You may obtain a copy of the License at
009
010 http://www.apache.org/licenses/LICENSE-2.0
011
012 Unless required by applicable law or agreed to in writing,
013 software distributed under the License is distributed on an
014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 KIND, either express or implied. See the License for the
016 specific language governing permissions and limitations
017 under the License.
018 */
019 package org.apache.wiki.auth;
020
021 import java.security.Principal;
022 import java.util.*;
023
024 import javax.servlet.http.HttpSession;
025 import javax.servlet.http.HttpSessionEvent;
026 import javax.servlet.http.HttpSessionListener;
027
028 import org.apache.log4j.Logger;
029
030 import org.apache.wiki.WikiEngine;
031 import org.apache.wiki.WikiSession;
032 import org.apache.wiki.event.WikiEventListener;
033 import org.apache.wiki.event.WikiEventManager;
034 import org.apache.wiki.event.WikiSecurityEvent;
035 import org.apache.wiki.rpc.json.JSONRPCManager;
036
037 /**
038 * <p>Manages WikiSession's for different WikiEngine's.</p>
039 * <p>The WikiSession's are stored both in the remote user
040 * HttpSession and in the SessionMonitor for the WikeEngine.
041 * This class must be configured as a session listener in the
042 * web.xml for the wiki web application.
043 * </p>
044 */
045 public class SessionMonitor implements HttpSessionListener
046 {
047 private static Logger log = Logger.getLogger( SessionMonitor.class );
048
049 /** Map with WikiEngines as keys, and SessionMonitors as values. */
050 private static Map<WikiEngine, SessionMonitor> c_monitors = new HashMap<WikiEngine, SessionMonitor>();
051
052 /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */
053 private final Map<String, WikiSession> m_sessions = new WeakHashMap<String, WikiSession>();
054
055 private WikiEngine m_engine;
056
057 private final PrincipalComparator m_comparator = new PrincipalComparator();
058
059 /**
060 * Returns the instance of the SessionMonitor for this wiki.
061 * Only one SessionMonitor exists per WikiEngine.
062 * @param engine the wiki engine
063 * @return the session monitor
064 */
065 public static final SessionMonitor getInstance( WikiEngine engine )
066 {
067 if( engine == null )
068 {
069 throw new IllegalArgumentException( "Engine cannot be null." );
070 }
071 SessionMonitor monitor;
072
073 synchronized( c_monitors )
074 {
075 monitor = c_monitors.get(engine);
076 if( monitor == null )
077 {
078 monitor = new SessionMonitor(engine);
079
080 c_monitors.put( engine, monitor );
081 }
082 }
083 return monitor;
084 }
085
086 /**
087 * Construct the SessionListener
088 */
089 public SessionMonitor()
090 {
091 }
092
093 private SessionMonitor( WikiEngine engine )
094 {
095 m_engine = engine;
096 }
097
098 /**
099 * Just looks for a WikiSession; does not create a new one.
100 * This method may return <code>null</code>, <em>and
101 * callers should check for this value</em>.
102 *
103 * @param session the user's HTTP session
104 * @return the WikiSession, if found
105 */
106 private WikiSession findSession( HttpSession session )
107 {
108 WikiSession wikiSession = null;
109 String sid = ( session == null ) ? "(null)" : session.getId();
110 WikiSession storedSession = m_sessions.get( sid );
111
112 // If the weak reference returns a wiki session, return it
113 if( storedSession != null )
114 {
115 if( log.isDebugEnabled() )
116 {
117 log.debug( "Looking up WikiSession for session ID=" + sid + "... found it" );
118 }
119 wikiSession = storedSession;
120 }
121
122 return wikiSession;
123 }
124 /**
125 * <p>Looks up the wiki session associated with a user's Http session
126 * and adds it to the session cache. This method will return the
127 * "guest session" as constructed by {@link WikiSession#guestSession(WikiEngine)}
128 * if the HttpSession is not currently associated with a WikiSession.
129 * This method is guaranteed to return a non-<code>null</code> WikiSession.</p>
130 * <p>Internally, the session is stored in a HashMap; keys are
131 * the HttpSession objects, while the values are
132 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p>
133 * @param session the HTTP session
134 * @return the wiki session
135 */
136 public final WikiSession find( HttpSession session )
137 {
138 WikiSession wikiSession = findSession(session);
139 String sid = ( session == null ) ? "(null)" : session.getId();
140
141 // Otherwise, create a new guest session and stash it.
142 if( wikiSession == null )
143 {
144 if( log.isDebugEnabled() )
145 {
146 log.debug( "Looking up WikiSession for session ID=" + sid + "... not found. Creating guestSession()" );
147 }
148 wikiSession = WikiSession.guestSession( m_engine );
149 synchronized( m_sessions )
150 {
151 m_sessions.put( sid, wikiSession );
152 }
153 }
154
155 return wikiSession;
156 }
157
158 /**
159 * Removes the wiki session associated with the user's HttpSession
160 * from the session cache.
161 * @param session the user's HTTP session
162 */
163 public final void remove( HttpSession session )
164 {
165 if ( session == null )
166 {
167 throw new IllegalArgumentException( "Session cannot be null." );
168 }
169 synchronized ( m_sessions )
170 {
171 m_sessions.remove( session.getId() );
172 }
173 }
174
175 /**
176 * Returns the current number of active wiki sessions.
177 * @return the number of sessions
178 */
179 public final int sessions()
180 {
181 return userPrincipals().length;
182 }
183
184 /**
185 * <p>Returns the current wiki users as a sorted array of
186 * Principal objects. The principals are those returned by
187 * each WikiSession's {@link WikiSession#getUserPrincipal()}'s
188 * method.</p>
189 * <p>To obtain the list of current WikiSessions, we iterate
190 * through our session Map and obtain the list of values,
191 * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference}
192 * objects. Those <code>WeakReference</code>s whose <code>get()</code>
193 * method returns non-<code>null</code> values are valid
194 * sessions.</p>
195 * @return the array of user principals
196 */
197 public final Principal[] userPrincipals()
198 {
199 Collection<Principal> principals = new ArrayList<Principal>();
200 for ( WikiSession session : m_sessions.values() )
201 {
202 principals.add( session.getUserPrincipal() );
203 }
204 Principal[] p = principals.toArray( new Principal[principals.size()] );
205 Arrays.sort( p, m_comparator );
206 return p;
207 }
208
209 /**
210 * Registers a WikiEventListener with this instance.
211 * @param listener the event listener
212 * @since 2.4.75
213 */
214 public final synchronized void addWikiEventListener( WikiEventListener listener )
215 {
216 WikiEventManager.addWikiEventListener( this, listener );
217 }
218
219 /**
220 * Un-registers a WikiEventListener with this instance.
221 * @param listener the event listener
222 * @since 2.4.75
223 */
224 public final synchronized void removeWikiEventListener( WikiEventListener listener )
225 {
226 WikiEventManager.removeWikiEventListener( this, listener );
227 }
228
229 /**
230 * Fires a WikiSecurityEvent to all registered listeners.
231 * @param type the event type
232 * @param principal the user principal associated with this session
233 * @param session the wiki session
234 * @since 2.4.75
235 */
236 protected final void fireEvent( int type, Principal principal, WikiSession session )
237 {
238 if( WikiEventManager.isListening(this) )
239 {
240 WikiEventManager.fireEvent(this,new WikiSecurityEvent(this,type,principal,session));
241 }
242 }
243
244 /**
245 * Fires when the web container creates a new HTTP session.
246 *
247 * @param se the HTTP session event
248 */
249 public void sessionCreated( HttpSessionEvent se )
250 {
251 HttpSession session = se.getSession();
252
253 JSONRPCManager.sessionCreated(session);
254 }
255
256 /**
257 * Removes the user's WikiSession from the internal session cache when the web
258 * container destoys an HTTP session.
259 * @param se the HTTP session event
260 */
261 public void sessionDestroyed( HttpSessionEvent se )
262 {
263 HttpSession session = se.getSession();
264 Iterator<SessionMonitor> it = c_monitors.values().iterator();
265 while( it.hasNext() )
266 {
267 SessionMonitor monitor = it.next();
268
269 WikiSession storedSession = monitor.findSession(session);
270
271 monitor.remove(session);
272
273 log.debug("Removed session "+session.getId()+".");
274
275 if( storedSession != null )
276 {
277 fireEvent( WikiSecurityEvent.SESSION_EXPIRED,
278 storedSession.getLoginPrincipal(),
279 storedSession );
280 }
281 }
282 }
283 }