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