/**
   * 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;
  }