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