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