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 }