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}