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<>();
055
056    /** Weak hashmap with HttpSessions as keys, and WikiSessions as values. */
057    private final Map<String, WikiSession>                 m_sessions   = new WeakHashMap<>();
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<>();
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    @Override
254    public void sessionCreated( HttpSessionEvent se )
255    {
256        HttpSession session = se.getSession();
257        log.debug( "Created session: " + session.getId() + "." );
258    }
259
260    /**
261     * Removes the user's WikiSession from the internal session cache when the web
262     * container destroys an HTTP session.
263     * @param se the HTTP session event
264     */
265    @Override
266    public void sessionDestroyed( HttpSessionEvent se )
267    {
268        HttpSession session = se.getSession();
269        Iterator<SessionMonitor> it = c_monitors.values().iterator();
270        while( it.hasNext() )
271        {
272            SessionMonitor monitor = it.next();
273
274            WikiSession storedSession = monitor.findSession(session);
275
276            monitor.remove(session);
277
278            log.debug( "Removed session " + session.getId() + "." );
279
280            if( storedSession != null )
281            {
282                fireEvent( WikiSecurityEvent.SESSION_EXPIRED,
283                           storedSession.getLoginPrincipal(),
284                           storedSession );
285            }
286        }
287    }
288}