001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.main;
029
030import org.opencms.configuration.CmsSystemConfiguration.UserSessionMode;
031import org.opencms.db.CmsUserSettings;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProject;
034import org.opencms.file.CmsRequestContext;
035import org.opencms.file.CmsUser;
036import org.opencms.security.CmsCustomLoginException;
037import org.opencms.security.CmsRole;
038import org.opencms.security.CmsSecurityException;
039import org.opencms.util.CmsRequestUtil;
040import org.opencms.util.CmsStringUtil;
041import org.opencms.util.CmsUUID;
042import org.opencms.workplace.CmsWorkplaceManager;
043import org.opencms.workplace.tools.CmsToolManager;
044
045import java.io.PrintWriter;
046import java.io.StringWriter;
047import java.util.Collections;
048import java.util.Enumeration;
049import java.util.Iterator;
050import java.util.List;
051
052import javax.servlet.http.HttpServletRequest;
053import javax.servlet.http.HttpSession;
054import javax.servlet.http.HttpSessionEvent;
055
056import org.apache.commons.collections.Buffer;
057import org.apache.commons.collections.BufferUtils;
058import org.apache.commons.collections.buffer.CircularFifoBuffer;
059import org.apache.commons.logging.Log;
060
061/**
062 * Keeps track of the sessions running on the OpenCms server and
063 * provides a session info storage which is used to get an overview
064 * about currently authenticated OpenCms users, as well as sending broadcasts between users.<p>
065 *
066 * For each authenticated OpenCms user, a {@link org.opencms.main.CmsSessionInfo} object
067 * holds the information about the users status.<p>
068 *
069 * When a user session is invalidated, the user info will be removed.
070 * This happens when a user log out, or when his session times out.<p>
071 *
072 * <b>Please Note:</b> The current implementation does not provide any permission checking,
073 * so all users can access the methods of this manager. Permission checking
074 * based on the current users OpenCms context may be added in a future OpenCms release.<p>
075 *
076 * @since 6.0.0
077 */
078public class CmsSessionManager {
079
080    /** The log object for this class. */
081    private static final Log LOG = CmsLog.getLog(CmsSessionManager.class);
082
083    /** Lock object for synchronized session count updates. */
084    private Object m_lockSessionCount;
085
086    /** Counter for the currently active sessions. */
087    private int m_sessionCountCurrent;
088
089    /** Counter for all sessions created so far. */
090    private int m_sessionCountTotal;
091
092    /** Session storage provider instance. */
093    private I_CmsSessionStorageProvider m_sessionStorageProvider;
094
095    /** The user session mode. */
096    private UserSessionMode m_userSessionMode;
097
098    /**
099     * Creates a new instance of the OpenCms session manager.<p>
100     */
101    protected CmsSessionManager() {
102
103        // create a lock object for the session counter
104        m_lockSessionCount = new Object();
105    }
106
107    /**
108     * Checks whether a new session can be created for the user, and throws an exception if not.<p>
109     *
110     * @param user the user to check
111     * @throws CmsException if no new session for the user can't be created
112     */
113    public void checkCreateSessionForUser(CmsUser user) throws CmsException {
114
115        if (getUserSessionMode() == UserSessionMode.single) {
116            List<CmsSessionInfo> infos = getSessionInfos(user.getId());
117            if (!infos.isEmpty()) {
118                throw new CmsCustomLoginException(
119                    org.opencms.security.Messages.get().container(
120                        org.opencms.security.Messages.ERR_ALREADY_LOGGED_IN_0));
121            }
122        }
123
124    }
125
126    /**
127     * Returns the broadcast queue for the given OpenCms session id.<p>
128     *
129     * @param sessionId the OpenCms session id to get the broadcast queue for
130     *
131     * @return the broadcast queue for the given OpenCms session id
132     */
133    public Buffer getBroadcastQueue(String sessionId) {
134
135        CmsSessionInfo sessionInfo = getSessionInfo(getSessionUUID(sessionId));
136        if (sessionInfo == null) {
137            // return empty message buffer if the session is gone or not available
138            return BufferUtils.synchronizedBuffer(new CircularFifoBuffer(CmsSessionInfo.QUEUE_SIZE));
139        }
140        return sessionInfo.getBroadcastQueue();
141    }
142
143    /**
144     * Returns the number of sessions currently authenticated in the OpenCms security system.<p>
145     *
146     * @return the number of sessions currently authenticated in the OpenCms security system
147     */
148    public int getSessionCountAuthenticated() {
149
150        // since this method could be called from another thread
151        // we have to prevent access before initialization
152        if (m_sessionStorageProvider == null) {
153            return 0;
154        }
155        return m_sessionStorageProvider.getSize();
156    }
157
158    /**
159     * Returns the number of current sessions, including the sessions of not authenticated guest users.<p>
160     *
161     * @return the number of current sessions, including the sessions of not authenticated guest users
162     */
163    public int getSessionCountCurrent() {
164
165        return m_sessionCountCurrent;
166    }
167
168    /**
169     * Returns the number of total sessions generated so far, including already destroyed sessions.<p>
170     *
171     * @return the number of total sessions generated so far, including already destroyed sessions
172     */
173    public int getSessionCountTotal() {
174
175        return m_sessionCountTotal;
176    }
177
178    /**
179     * Returns the complete user session info of a user from the session storage,
180     * or <code>null</code> if this session id has no session info attached.<p>
181     *
182     * @param sessionId the OpenCms session id to return the session info for
183     *
184     * @return the complete user session info of a user from the session storage
185     */
186    public CmsSessionInfo getSessionInfo(CmsUUID sessionId) {
187
188        // since this method could be called from another thread
189        // we have to prevent access before initialization
190        if (m_sessionStorageProvider == null) {
191            return null;
192        }
193        return m_sessionStorageProvider.get(sessionId);
194    }
195
196    /**
197     * Returns the OpenCms user session info for the given request,
198     * or <code>null</code> if no user session is available.<p>
199     *
200     * @param req the current request
201     *
202     * @return the OpenCms user session info for the given request, or <code>null</code> if no user session is available
203     */
204    public CmsSessionInfo getSessionInfo(HttpServletRequest req) {
205
206        HttpSession session = req.getSession(false);
207        if (session == null) {
208            // special case for accessing a session from "outside" requests (e.g. upload applet)
209            String sessionId = req.getHeader(CmsRequestUtil.HEADER_JSESSIONID);
210            return sessionId == null ? null : getSessionInfo(sessionId);
211        }
212        return getSessionInfo(session);
213    }
214
215    /**
216     * Returns the OpenCms user session info for the given http session,
217     * or <code>null</code> if no user session is available.<p>
218     *
219     * @param session the current http session
220     *
221     * @return the OpenCms user session info for the given http session, or <code>null</code> if no user session is available
222     */
223    public CmsSessionInfo getSessionInfo(HttpSession session) {
224
225        if (session == null) {
226            return null;
227        }
228        CmsUUID sessionId = (CmsUUID)session.getAttribute(CmsSessionInfo.ATTRIBUTE_SESSION_ID);
229        return (sessionId == null) ? null : getSessionInfo(sessionId);
230    }
231
232    /**
233     * Returns the complete user session info of a user from the session storage,
234     * or <code>null</code> if this session id has no session info attached.<p>
235     *
236     * @param sessionId the OpenCms session id to return the session info for,
237     * this must be a String representation of a {@link CmsUUID}
238     *
239     * @return the complete user session info of a user from the session storage
240     *
241     * @see #getSessionInfo(CmsUUID)
242     */
243    public CmsSessionInfo getSessionInfo(String sessionId) {
244
245        return getSessionInfo(getSessionUUID(sessionId));
246    }
247
248    /**
249     * Returns all current session info objects.<p>
250     *
251     * @return all current session info objects
252     */
253    public List<CmsSessionInfo> getSessionInfos() {
254
255        // since this method could be called from another thread
256        // we have to prevent access before initialization
257        if (m_sessionStorageProvider == null) {
258            return Collections.emptyList();
259        }
260        return m_sessionStorageProvider.getAll();
261    }
262
263    /**
264     * Returns a list of all active session info objects for the specified user.<p>
265     *
266     * An OpenCms user can have many active sessions.
267     * This is e.g. possible when two people have logged in to the system using the
268     * same username. Even one person can have multiple sessions if he
269     * is logged in to OpenCms with several browser windows at the same time.<p>
270     *
271     * @param userId the id of the user
272     *
273     * @return a list of all active session info objects for the specified user
274     */
275    public List<CmsSessionInfo> getSessionInfos(CmsUUID userId) {
276
277        // since this method could be called from another thread
278        // we have to prevent access before initialization
279        if (m_sessionStorageProvider == null) {
280            return Collections.emptyList();
281        }
282        return m_sessionStorageProvider.getAllOfUser(userId);
283    }
284
285    /**
286     * Gets the user session mode.<p>
287     *
288     * @return the user session mode
289     */
290    public UserSessionMode getUserSessionMode() {
291
292        return m_userSessionMode;
293    }
294
295    /**
296     * Kills all sessions for the given user.<p>
297     *
298     * @param cms the current CMS context
299     * @param user the user for whom the sessions should be killed
300     *
301     * @throws CmsException if something goes wrong
302     */
303    public void killSession(CmsObject cms, CmsUser user) throws CmsException {
304
305        OpenCms.getRoleManager().checkRole(cms, CmsRole.ACCOUNT_MANAGER);
306        List<CmsSessionInfo> infos = getSessionInfos(user.getId());
307        for (CmsSessionInfo info : infos) {
308            m_sessionStorageProvider.remove(info.getSessionId());
309        }
310    }
311
312    /**
313     * Destroys a session given the session id. Only allowed for users which have the "account manager" role.<p>
314     *
315     * @param cms the current CMS context
316     * @param sessionid the session id
317     *
318     * @throws CmsException if something goes wrong
319     */
320    public void killSession(CmsObject cms, CmsUUID sessionid) throws CmsException {
321
322        OpenCms.getRoleManager().checkRole(cms, CmsRole.ACCOUNT_MANAGER);
323        m_sessionStorageProvider.remove(sessionid);
324
325    }
326
327    /**
328     * Sends a broadcast to all sessions of all currently authenticated users.<p>
329     *
330     * @param cms the OpenCms user context of the user sending the broadcast
331     *
332     * @param message the message to broadcast
333     */
334    public void sendBroadcast(CmsObject cms, String message) {
335
336        if (CmsStringUtil.isEmptyOrWhitespaceOnly(message)) {
337            // don't broadcast empty messages
338            return;
339        }
340        // create the broadcast
341        CmsBroadcast broadcast = new CmsBroadcast(cms.getRequestContext().getCurrentUser(), message);
342        // send the broadcast to all authenticated sessions
343        Iterator<CmsSessionInfo> i = m_sessionStorageProvider.getAll().iterator();
344        while (i.hasNext()) {
345            CmsSessionInfo sessionInfo = i.next();
346            if (m_sessionStorageProvider.get(sessionInfo.getSessionId()) != null) {
347                // double check for concurrent modification
348                sessionInfo.getBroadcastQueue().add(broadcast);
349            }
350        }
351    }
352
353    /**
354     * Sends a broadcast to the specified user session.<p>
355     *
356     * @param cms the OpenCms user context of the user sending the broadcast
357     *
358     * @param message the message to broadcast
359     * @param sessionId the OpenCms session uuid target (receiver) of the broadcast
360     */
361    public void sendBroadcast(CmsObject cms, String message, String sessionId) {
362
363        if (CmsStringUtil.isEmptyOrWhitespaceOnly(message)) {
364            // don't broadcast empty messages
365            return;
366        }
367        // send the broadcast only to the selected session
368        CmsSessionInfo sessionInfo = m_sessionStorageProvider.get(new CmsUUID(sessionId));
369        if (sessionInfo != null) {
370            // double check for concurrent modification
371            sessionInfo.getBroadcastQueue().add(new CmsBroadcast(cms.getRequestContext().getCurrentUser(), message));
372        }
373    }
374
375    /**
376     * Sends a broadcast to all sessions of a given user.<p>
377     *
378     * The user sending the message may be a real user like
379     * <code>cms.getRequestContext().currentUser()</code> or
380     * <code>null</code> for a system message.<p>
381     *
382     * @param fromUser the user sending the broadcast
383     * @param message the message to broadcast
384     * @param toUser the target (receiver) of the broadcast
385     */
386    public void sendBroadcast(CmsUser fromUser, String message, CmsUser toUser) {
387
388        if (CmsStringUtil.isEmptyOrWhitespaceOnly(message)) {
389            // don't broadcast empty messages
390            return;
391        }
392        // create the broadcast
393        CmsBroadcast broadcast = new CmsBroadcast(fromUser, message);
394        List<CmsSessionInfo> userSessions = getSessionInfos(toUser.getId());
395        Iterator<CmsSessionInfo> i = userSessions.iterator();
396        // send the broadcast to all sessions of the selected user
397        while (i.hasNext()) {
398            CmsSessionInfo sessionInfo = i.next();
399            if (m_sessionStorageProvider.get(sessionInfo.getSessionId()) != null) {
400                // double check for concurrent modification
401                sessionInfo.getBroadcastQueue().add(broadcast);
402            }
403        }
404    }
405
406    /**
407     * Switches the current user to the given user. The session info is rebuild as if the given user
408     * performs a login at the workplace.
409     *
410     * @param cms the current CmsObject
411     * @param req the current request
412     * @param user the user to switch to
413     *
414     * @throws CmsException if something goes wrong
415     */
416    public void switchUser(CmsObject cms, HttpServletRequest req, CmsUser user) throws CmsException {
417
418        // only user with root administrator role are allowed to switch the user
419        OpenCms.getRoleManager().checkRole(cms, CmsRole.ROOT_ADMIN.forOrgUnit(user.getOuFqn()));
420        CmsSessionInfo info = getSessionInfo(req);
421        HttpSession session = req.getSession(false);
422        if ((info == null) || (session == null)) {
423            throw new CmsException(Messages.get().container(Messages.ERR_NO_SESSIONINFO_SESSION_0));
424        }
425
426        if (!OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.WORKPLACE_USER)) {
427            throw new CmsSecurityException(Messages.get().container(Messages.ERR_NO_WORKPLACE_PERMISSIONS_0));
428        }
429
430        // get the user settings for the given user and set the start project and the site root
431        CmsUserSettings settings = new CmsUserSettings(user);
432        String ouFqn = user.getOuFqn();
433        CmsProject userProject = cms.readProject(
434            ouFqn + OpenCms.getWorkplaceManager().getDefaultUserSettings().getStartProject());
435        try {
436            userProject = cms.readProject(settings.getStartProject());
437        } catch (Exception e) {
438            // ignore, use default
439        }
440        String userSiteRoot = settings.getStartSite();
441        CmsRequestContext context = new CmsRequestContext(
442            user,
443            userProject,
444            null,
445            cms.getRequestContext().getRequestMatcher(),
446            userSiteRoot,
447            cms.getRequestContext().isSecureRequest(),
448            null,
449            null,
450            null,
451            0,
452            null,
453            null,
454            ouFqn);
455        // delete the stored workplace settings, so the session has to receive them again
456        session.removeAttribute(CmsWorkplaceManager.SESSION_WORKPLACE_SETTINGS);
457
458        // create a new CmsSessionInfo and store it inside the session map
459        CmsSessionInfo newInfo = new CmsSessionInfo(context, info.getSessionId(), info.getMaxInactiveInterval());
460        addSessionInfo(newInfo);
461        // set the site root, project and ou fqn to current cms context
462        cms.getRequestContext().setSiteRoot(userSiteRoot);
463        cms.getRequestContext().setCurrentProject(userProject);
464        cms.getRequestContext().setOuFqn(user.getOuFqn());
465    }
466
467    /**
468     * @see java.lang.Object#toString()
469     */
470    @Override
471    public String toString() {
472
473        StringBuffer output = new StringBuffer();
474        Iterator<CmsSessionInfo> i = m_sessionStorageProvider.getAll().iterator();
475        output.append("[CmsSessions]:\n");
476        while (i.hasNext()) {
477            CmsSessionInfo sessionInfo = i.next();
478            output.append(sessionInfo.getSessionId().toString());
479            output.append(" : ");
480            output.append(sessionInfo.getUserId().toString());
481            output.append('\n');
482        }
483        return output.toString();
484    }
485
486    /**
487     * Updates the the OpenCms session data used for quick authentication of users.<p>
488     *
489     * This is required if the user data (current group or project) was changed in
490     * the requested document.<p>
491     *
492     * The user data is only updated if the user was authenticated to the system.
493     *
494     * @param cms the current OpenCms user context
495     * @param req the current request
496     */
497    public void updateSessionInfo(CmsObject cms, HttpServletRequest req) {
498
499        if (!cms.getRequestContext().isUpdateSessionEnabled()) {
500            // this request must not update the user session info
501            // this is true for long running "thread" requests, e.g. during project publish
502            return;
503        }
504
505        if (cms.getRequestContext().getUri().equals(CmsToolManager.VIEW_JSPPAGE_LOCATION)) {
506            // this request must not update the user session info
507            // if not the switch user feature would not work
508            return;
509        }
510
511        if (!cms.getRequestContext().getCurrentUser().isGuestUser()) {
512            // Guest user requests don't need to update the OpenCms user session information
513
514            // get the session info object for the user
515            CmsSessionInfo sessionInfo = getSessionInfo(req);
516            if (sessionInfo != null) {
517                // update the users session information
518                sessionInfo.update(cms.getRequestContext());
519                addSessionInfo(sessionInfo);
520            } else {
521                HttpSession session = req.getSession(false);
522                // only create session info if a session is already available
523                if (session != null) {
524                    // create a new session info for the user
525                    sessionInfo = new CmsSessionInfo(
526                        cms.getRequestContext(),
527                        new CmsUUID(),
528                        session.getMaxInactiveInterval());
529                    // append the session info to the http session
530                    session.setAttribute(CmsSessionInfo.ATTRIBUTE_SESSION_ID, sessionInfo.getSessionId().clone());
531                    // update the session info user data
532                    addSessionInfo(sessionInfo);
533                }
534            }
535        }
536    }
537
538    /**
539     * Updates the the OpenCms session data used for quick authentication of users.<p>
540     *
541     * This is required if the user data (current group or project) was changed in
542     * the requested document.<p>
543     *
544     * The user data is only updated if the user was authenticated to the system.
545     *
546     * @param cms the current OpenCms user context
547     * @param session the current session
548     */
549    public void updateSessionInfo(CmsObject cms, HttpSession session) {
550
551        if (session == null) {
552            return;
553        }
554
555        if (!cms.getRequestContext().isUpdateSessionEnabled()) {
556            // this request must not update the user session info
557            // this is true for long running "thread" requests, e.g. during project publish
558            return;
559        }
560
561        if (cms.getRequestContext().getUri().equals(CmsToolManager.VIEW_JSPPAGE_LOCATION)) {
562            // this request must not update the user session info
563            // if not the switch user feature would not work
564            return;
565        }
566
567        if (!cms.getRequestContext().getCurrentUser().isGuestUser()) {
568            // Guest user requests don't need to update the OpenCms user session information
569
570            // get the session info object for the user
571            CmsSessionInfo sessionInfo = getSessionInfo(session);
572            if (sessionInfo != null) {
573                // update the users session information
574                sessionInfo.update(cms.getRequestContext());
575                addSessionInfo(sessionInfo);
576            } else {
577                sessionInfo = new CmsSessionInfo(
578                    cms.getRequestContext(),
579                    new CmsUUID(),
580                    session.getMaxInactiveInterval());
581                // append the session info to the http session
582                session.setAttribute(CmsSessionInfo.ATTRIBUTE_SESSION_ID, sessionInfo.getSessionId().clone());
583                // update the session info user data
584                addSessionInfo(sessionInfo);
585            }
586        }
587    }
588
589    /**
590     * Updates all session info objects, so that invalid projects
591     * are replaced by the Online project.<p>
592     *
593     * @param cms the cms context
594     */
595    public void updateSessionInfos(CmsObject cms) {
596
597        // get all sessions
598        List<CmsSessionInfo> userSessions = getSessionInfos();
599        Iterator<CmsSessionInfo> i = userSessions.iterator();
600        while (i.hasNext()) {
601            CmsSessionInfo sessionInfo = i.next();
602            // check is the project stored in this session is not existing anymore
603            // if so, set it to the online project
604            CmsUUID projectId = sessionInfo.getProject();
605            try {
606                cms.readProject(projectId);
607            } catch (CmsException e) {
608                // the project does not longer exist, update the project information with the online project
609                sessionInfo.setProject(CmsProject.ONLINE_PROJECT_ID);
610                addSessionInfo(sessionInfo);
611            }
612        }
613    }
614
615    /**
616     * Adds a new session info into the session storage.<p>
617     *
618     * @param sessionInfo the session info to store for the id
619     */
620    protected void addSessionInfo(CmsSessionInfo sessionInfo) {
621
622        if (getUserSessionMode() == UserSessionMode.standard) {
623            m_sessionStorageProvider.put(sessionInfo);
624        } else if (getUserSessionMode() == UserSessionMode.single) {
625            CmsUUID userId = sessionInfo.getUserId();
626            List<CmsSessionInfo> infos = getSessionInfos(userId);
627            if (infos.isEmpty()
628                || ((infos.size() == 1) && infos.get(0).getSessionId().equals(sessionInfo.getSessionId()))) {
629                m_sessionStorageProvider.put(sessionInfo);
630            } else {
631                throw new RuntimeException("Can't create another session for the same user.");
632            }
633        }
634    }
635
636    /**
637     * Returns the UUID representation for the given session id String.<p>
638     *
639     * @param sessionId the session id String to return the  UUID representation for
640     *
641     * @return the UUID representation for the given session id String
642     */
643    protected CmsUUID getSessionUUID(String sessionId) {
644
645        return new CmsUUID(sessionId);
646    }
647
648    /**
649     * Sets the storage provider.<p>
650     *
651     * @param sessionStorageProvider the storage provider implementation
652     */
653    protected void initialize(I_CmsSessionStorageProvider sessionStorageProvider) {
654
655        m_sessionStorageProvider = sessionStorageProvider;
656        m_sessionStorageProvider.initialize();
657    }
658
659    /**
660     * Called by the {@link OpenCmsListener} when a http session is created.<p>
661     *
662     * @param event the http session event
663     *
664     * @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent)
665     * @see OpenCmsListener#sessionCreated(HttpSessionEvent)
666     */
667    protected void sessionCreated(HttpSessionEvent event) {
668
669        HttpServletRequest request = OpenCmsServlet.currentRequest.get();
670        String tid = "[" + Thread.currentThread().getId() + "] ";
671        synchronized (m_lockSessionCount) {
672            m_sessionCountCurrent = (m_sessionCountCurrent <= 0) ? 1 : (m_sessionCountCurrent + 1);
673            m_sessionCountTotal++;
674            if (LOG.isInfoEnabled()) {
675                LOG.info(
676                    tid
677                        + Messages.get().getBundle().key(
678                            Messages.LOG_SESSION_CREATED_2,
679                            new Integer(m_sessionCountTotal),
680                            new Integer(m_sessionCountCurrent)));
681            }
682        }
683
684        if (LOG.isDebugEnabled()) {
685            LOG.debug(tid + Messages.get().getBundle().key(Messages.LOG_SESSION_CREATED_1, event.getSession().getId()));
686            if (request != null) {
687                LOG.debug(tid + "Session created in request: " + request.getRequestURL());
688            }
689            StringWriter sw = new StringWriter();
690            new Throwable("").printStackTrace(new PrintWriter(sw));
691            String stackTrace = sw.toString();
692            LOG.debug(tid + "Stack = \n" + stackTrace);
693        }
694    }
695
696    /**
697     * Called by the {@link OpenCmsListener} when a http session is destroyed.<p>
698     *
699     * @param event the http session event
700     *
701     * @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
702     * @see OpenCmsListener#sessionDestroyed(HttpSessionEvent)
703     */
704    protected void sessionDestroyed(HttpSessionEvent event) {
705
706        synchronized (m_lockSessionCount) {
707            m_sessionCountCurrent = (m_sessionCountCurrent <= 0) ? 0 : (m_sessionCountCurrent - 1);
708            if (LOG.isInfoEnabled()) {
709                LOG.info(
710                    Messages.get().getBundle().key(
711                        Messages.LOG_SESSION_DESTROYED_2,
712                        new Integer(m_sessionCountTotal),
713                        new Integer(m_sessionCountCurrent)));
714            }
715        }
716
717        CmsSessionInfo sessionInfo = getSessionInfo(event.getSession());
718        CmsUUID userId = null;
719        if (sessionInfo != null) {
720            userId = sessionInfo.getUserId();
721            m_sessionStorageProvider.remove(sessionInfo.getSessionId());
722        }
723
724        if ((userId != null) && (getSessionInfos(userId).size() == 0)) {
725            // remove the temporary locks of this user from memory
726            OpenCmsCore.getInstance().getLockManager().removeTempLocks(userId);
727        }
728
729        HttpSession session = event.getSession();
730        Enumeration<?> attrNames = session.getAttributeNames();
731        while (attrNames.hasMoreElements()) {
732            String attrName = (String)attrNames.nextElement();
733            Object attribute = session.getAttribute(attrName);
734            if (attribute instanceof I_CmsSessionDestroyHandler) {
735                try {
736                    ((I_CmsSessionDestroyHandler)attribute).onSessionDestroyed();
737                } catch (Exception e) {
738                    LOG.error(e.getLocalizedMessage(), e);
739                }
740            }
741        }
742
743        if (LOG.isDebugEnabled()) {
744            LOG.debug(Messages.get().getBundle().key(Messages.LOG_SESSION_DESTROYED_1, event.getSession().getId()));
745        }
746    }
747
748    /**
749     * Sets the user session mode.<p>
750     *
751     * @param userSessionMode the user session mode
752     */
753    protected void setUserSessionMode(UserSessionMode userSessionMode) {
754
755        m_userSessionMode = userSessionMode;
756    }
757
758    /**
759     * Removes all stored session info objects.<p>
760     *
761     * @throws Exception if something goes wrong
762     */
763    protected void shutdown() throws Exception {
764
765        if (m_sessionStorageProvider != null) {
766            m_sessionStorageProvider.shutdown();
767        }
768    }
769
770    /**
771     * Validates the sessions stored in this manager and removes
772     * any sessions that have become invalidated.<p>
773     */
774    protected void validateSessionInfos() {
775
776        // since this method could be called from another thread
777        // we have to prevent access before initialization
778        if (m_sessionStorageProvider == null) {
779            return;
780        }
781        m_sessionStorageProvider.validate();
782    }
783}