/** * Retrieves the session ID from the session cookie in the given {@link ToadletContext}, checks if * it contains a valid (existing and not expired) session and if yes, returns the {@link Session}. * * <p>If the session was valid, then its validity is extended by {@link MAX_SESSION_IDLE_TIME}. * * <p>If the session is not valid anymore, <code>null</code> is returned if the new constructor * was used (for example by PluginRespirator). If the deprecated constructor was used, a * RedirectException to the login URI is thrown. * * @throws RedirectException if login redirect URI was set */ public synchronized Session useSession(ToadletContext context) throws RedirectException { UUID sessionID = getSessionID(context); if (sessionID == null) { if (mLogInRedirectURI == null) return null; throw new RedirectException(mLogInRedirectURI); } // We must synchronize around the fetching of the time and mSessionsByID.push() because // mSessionsByID is no sorting data structure: It's a plain // LRUHashtable so to ensure that it stays sorted the operation "getTime(); push();" must be // atomic. long time = CurrentTimeUTC.getInMillis(); removeExpiredSessions(time); Session session = mSessionsByID.get(sessionID); if (session == null) { if (mLogInRedirectURI == null) return null; throw new RedirectException(mLogInRedirectURI); } session.updateExpiresAtTime(time); mSessionsByID.push(session.getID(), session); setSessionCookie(session, context); return session; }
/** * Deletes the session with the given ID. * * @return True if a session with the given ID existed. */ private synchronized boolean deleteSession(UUID sessionID) { Session session = mSessionsByID.get(sessionID); if (session == null) return false; mSessionsByID.removeKey(sessionID); mSessionsByUserID.remove(session.getUserID()); return true; }
/** * Garbage-collects any expired sessions. Must be called before client-inteface functions do * anything which relies on the existence a session, that is: creating sessions, using sessions or * checking whether sessions exist. * * <p>FIXME: Before putting the session manager into fred, write a thread which periodically * garbage collects old sessions - currently, sessions will only be garbage collected if any * client continues using the SessiomManager * * @param time The current time. */ private synchronized void removeExpiredSessions(long time) { for (Session session = mSessionsByID.peekValue(); session != null && session.isExpired(time); session = mSessionsByID.peekValue()) { mSessionsByID.popValue(); mSessionsByUserID.remove(session.getUserID()); } // FIXME: Execute every few hours only. verifyQueueOrder(); verifySessionsByUserIDTable(); }
/** * Deletes the session associated with the given user ID. * * @return True if a session with the given ID existed. */ private synchronized boolean deleteSessionByUserID(String userID) { Session session = mSessionsByUserID.remove(userID); if (session == null) return false; mSessionsByID.removeKey(session.getID()); return true; }
/** * Returns true if the given {@link ToadletContext} contains a session cookie for a valid * (existing and not expired) session. * * <p>In opposite to {@link getSessionUserID}, this function does NOT extend the validity of the * session. Therefore, this function can be considered as a way of peeking for a session, to * decide which Toadlet links should be visible. */ public synchronized boolean sessionExists(ToadletContext context) { UUID sessionID = getSessionID(context); if (sessionID == null) return false; removeExpiredSessions(CurrentTimeUTC.getInMillis()); return mSessionsByID.containsKey(sessionID); }
/** * Debug function which checks whether the sessions by user ID table does not contain any sessions * which do not exist anymore; */ private synchronized void verifySessionsByUserIDTable() { Enumeration<Session> sessions = mSessionsByUserID.elements(); while (sessions.hasMoreElements()) { Session session = sessions.nextElement(); if (mSessionsByID.containsKey(session.getID()) == false) { Logger.error( this, "Sessions by user ID hashtable contains deleted session, removing it: " + session); mSessionsByUserID.remove(session.getUserID()); } } }
/** Debug function which checks whether the session LRU queue is in order; */ private synchronized void verifyQueueOrder() { long previousTime = 0; Enumeration<Session> sessions = mSessionsByID.values(); while (sessions.hasMoreElements()) { Session session = sessions.nextElement(); if (session.getExpirationTime() < previousTime) { long sessionAge = (CurrentTimeUTC.getInMillis() - session.getExpirationTime()) / (60 * 60 * 1000); Logger.error( this, "Session LRU queue out of order! Found session which is " + sessionAge + " hour old: " + session); Logger.error(this, "Deleting all sessions..."); mSessionsByID.clear(); mSessionsByUserID.clear(); return; } } }
/** * Creates a new session for the given user ID. * * <p>If a session for the given user ID already exists, it is deleted. It is not re-used to * ensure that parallel logins with the same user account from different computers do not work. * * @param context The ToadletContext in which the session cookie shall be stored. */ public synchronized Session createSession(String userID, ToadletContext context) { // We must synchronize around the fetching of the time and mSessionsByID.push() because // mSessionsByID is no sorting data structure: It's a plain // LRUHashtable so to ensure that it stays sorted the operation "getTime(); push();" must be // atomic. long time = CurrentTimeUTC.getInMillis(); removeExpiredSessions(time); deleteSessionByUserID(userID); Session session = new Session(userID, time); mSessionsByID.push(session.getID(), session); mSessionsByUserID.put(session.getUserID(), session); setSessionCookie(session, context); return session; }