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.Iterator; 026import java.util.Map; 027import java.util.WeakHashMap; 028import java.util.concurrent.ConcurrentHashMap; 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 ConcurrentHashMap<WikiEngine, SessionMonitor> c_monitors = new ConcurrentHashMap<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 monitor = c_monitors.get(engine); 078 if( monitor == null ) 079 { 080 monitor = new SessionMonitor(engine); 081 082 c_monitors.put( engine, monitor ); 083 084 } 085 086 return monitor; 087 } 088 089 /** 090 * Construct the SessionListener 091 */ 092 public SessionMonitor() 093 { 094 } 095 096 private SessionMonitor( WikiEngine engine ) 097 { 098 m_engine = engine; 099 } 100 101 /** 102 * Just looks for a WikiSession; does not create a new one. 103 * This method may return <code>null</code>, <em>and 104 * callers should check for this value</em>. 105 * 106 * @param session the user's HTTP session 107 * @return the WikiSession, if found 108 */ 109 private WikiSession findSession( HttpSession session ) 110 { 111 WikiSession wikiSession = null; 112 String sid = ( session == null ) ? "(null)" : session.getId(); 113 WikiSession storedSession = m_sessions.get( sid ); 114 115 // If the weak reference returns a wiki session, return it 116 if( storedSession != null ) 117 { 118 if( log.isDebugEnabled() ) 119 { 120 log.debug( "Looking up WikiSession for session ID=" + sid + "... found it" ); 121 } 122 wikiSession = storedSession; 123 } 124 125 return wikiSession; 126 } 127 /** 128 * <p>Looks up the wiki session associated with a user's Http session 129 * and adds it to the session cache. This method will return the 130 * "guest session" as constructed by {@link WikiSession#guestSession(WikiEngine)} 131 * if the HttpSession is not currently associated with a WikiSession. 132 * This method is guaranteed to return a non-<code>null</code> WikiSession.</p> 133 * <p>Internally, the session is stored in a HashMap; keys are 134 * the HttpSession objects, while the values are 135 * {@link java.lang.ref.WeakReference}-wrapped WikiSessions.</p> 136 * @param session the HTTP session 137 * @return the wiki session 138 */ 139 public final WikiSession find( HttpSession session ) 140 { 141 WikiSession wikiSession = findSession(session); 142 String sid = ( session == null ) ? "(null)" : session.getId(); 143 144 // Otherwise, create a new guest session and stash it. 145 if( wikiSession == null ) 146 { 147 if( log.isDebugEnabled() ) 148 { 149 log.debug( "Looking up WikiSession for session ID=" + sid + "... not found. Creating guestSession()" ); 150 } 151 wikiSession = WikiSession.guestSession( m_engine ); 152 synchronized( m_sessions ) 153 { 154 m_sessions.put( sid, wikiSession ); 155 } 156 } 157 158 return wikiSession; 159 } 160 161 /** 162 * Removes the wiki session associated with the user's HttpSession 163 * from the session cache. 164 * @param session the user's HTTP session 165 */ 166 public final void remove( HttpSession session ) 167 { 168 if ( session == null ) 169 { 170 throw new IllegalArgumentException( "Session cannot be null." ); 171 } 172 synchronized ( m_sessions ) 173 { 174 m_sessions.remove( session.getId() ); 175 } 176 } 177 178 /** 179 * Returns the current number of active wiki sessions. 180 * @return the number of sessions 181 */ 182 public final int sessions() 183 { 184 return userPrincipals().length; 185 } 186 187 /** 188 * <p>Returns the current wiki users as a sorted array of 189 * Principal objects. The principals are those returned by 190 * each WikiSession's {@link WikiSession#getUserPrincipal()}'s 191 * method.</p> 192 * <p>To obtain the list of current WikiSessions, we iterate 193 * through our session Map and obtain the list of values, 194 * which are WikiSessions wrapped in {@link java.lang.ref.WeakReference} 195 * objects. Those <code>WeakReference</code>s whose <code>get()</code> 196 * method returns non-<code>null</code> values are valid 197 * sessions.</p> 198 * @return the array of user principals 199 */ 200 public final Principal[] userPrincipals() 201 { 202 Collection<Principal> principals = new ArrayList<Principal>(); 203 synchronized ( m_sessions ) { 204 for (WikiSession session : m_sessions.values()) { 205 principals.add( session.getUserPrincipal() ); 206 } 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 destroys 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}