/**
   * Returns a session from the session store, returning null if there's no cached session.
   *
   * @param key the session id
   * @param now the time in milliseconds. now == 0 implies that we're just checking for the
   *     existence of such a session in the cache and do not intend actually to load it if it is
   *     not.
   * @return the cached session.
   */
  public SessionArrayValue getSession(Env env, String key, long now) {
    SessionArrayValue session;
    boolean isNew = false;
    boolean killSession = false;

    if (_sessions == null) return null;

    // Check the cache first
    session = _sessions.get(key);

    if (session != null && !session.getId().equals(key))
      throw new IllegalStateException(key + " != " + session.getId());

    if (session != null) {
      if (session.inUse()) {
        System.out.println("USE:" + isNew);
        return (SessionArrayValue) session.copy(env);
      }
    }

    if (session == null) return null;

    if (isNew) {
      isNew = !load(env, session, now);
      System.out.println("LOAD:" + isNew);
    } else if (!getSaveOnlyOnShutdown() && !session.load()) {
      // if the load failed, then the session died out from underneath
      session.reset(now);
      isNew = true;
    }

    if (!isNew) session.setAccess(now);

    return (SessionArrayValue) session.copy(env);
  }
  /**
   * Creates a session. It's already been established that the key does not currently have a
   * session.
   */
  protected SessionArrayValue create(Env env, String key, long now) {
    SessionArrayValue session = createSessionValue(key, now, _sessionTimeout);

    load(env, session, now);

    // If another thread has created and stored a new session,
    // putIfNew will return the old session
    session = _sessions.putIfNew(key, session);

    if (!key.equals(session.getId()))
      throw new IllegalStateException(key + " != " + session.getId());

    return (SessionArrayValue) session.copy(env);
  }
  /**
   * Loads the session from the backing store.
   *
   * @param session the session to load.
   * @param now current time in milliseconds. now == 0 implies that we're just checking for the
   *     existence of such a session in the cache and do not intend actually to load it if it is
   *     not.
   */
  protected boolean load(Env env, SessionArrayValue session, long now) {
    try {
      if (session.inUse()) {
        return true;
      } else if (now <= 0) {
        return false;
      }

      if (_persistentStore != null) {
        String encoded = (String) _persistentStore.get(session.getId());

        if (encoded != null) {
          session.decode(env, new StringBuilderValue(encoded));
        }
      }

      if (session.load()) {
        session.setAccess(now);
        return true;
      } else {
        session.reset(now);
      }
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
      session.reset(now);
    }

    return false;
  }
  /** Timeout for reaping old sessions. */
  public void handleAlarm(Alarm alarm) {
    try {
      _sessionList.clear();

      int liveSessions = 0;

      if (_isClosed) return;

      long now = Alarm.getCurrentTime();

      synchronized (_sessions) {
        _sessionIter = _sessions.values(_sessionIter);

        while (_sessionIter.hasNext()) {
          SessionArrayValue session = _sessionIter.next();

          long maxIdleTime = session.getMaxInactiveInterval();

          if (session.inUse()) liveSessions++;
          else if (session.getAccessTime() + maxIdleTime < now) _sessionList.add(session);
          else liveSessions++;
        }
      }

      synchronized (_statisticsLock) {
        _sessionTimeoutCount += _sessionList.size();
      }

      for (int i = 0; i < _sessionList.size(); i++) {
        SessionArrayValue session = _sessionList.get(i);

        try {
          long maxIdleTime = session.getMaxInactiveInterval();
          _sessions.remove(session.getId());

          session.invalidate();
        } catch (Throwable e) {
          log.log(Level.FINER, e.toString(), e);
        }
      }
    } finally {
      if (!_isClosed) alarm.queue(60000);
    }
  }
  public void saveSession(Env env, SessionArrayValue session) {
    SessionArrayValue copy = (SessionArrayValue) session.copy(env);

    _sessions.put(session.getId(), copy);

    session.finish();

    if (_persistentStore != null) {
      _persistentStore.put(session.getId(), copy.encode(env));
    }
  }
  /** Cleans up the sessions when the Application shuts down gracefully. */
  public void close() {
    synchronized (this) {
      if (_isClosed) return;
      _isClosed = true;
    }

    if (_sessions == null) return;

    // _alarm.dequeue();

    _sessionList.clear();

    ArrayList<SessionArrayValue> list = new ArrayList<SessionArrayValue>();

    boolean isError = false;

    synchronized (_sessions) {
      _sessionIter = _sessions.values(_sessionIter);

      while (_sessionIter.hasNext()) {
        SessionArrayValue session = _sessionIter.next();

        if (session.isValid()) list.add(session);
      }
    }

    for (int i = list.size() - 1; i >= 0; i--) {
      SessionArrayValue session = list.get(i);

      try {
        if (session.isValid()) {
          synchronized (session) {
            if (!session.isEmpty()) session.storeOnShutdown();
          }
        }

        _sessions.remove(session.getId());
      } catch (Exception e) {
        if (!isError) log.log(Level.WARNING, "Can't store session: " + e, e);
        isError = true;
      }
    }
  }
  /** Saves the session. */
  public void store(OutputStream out, Object obj) throws IOException {
    SessionArrayValue session = (SessionArrayValue) obj;

    session.store(Env.getInstance(), out);
  }
  /** Notification from the cluster. */
  public void notifyRemove(String id) {
    SessionArrayValue session = _sessions.remove(id);

    if (session != null) session.invalidate();
  }
  /** Checks if the session is empty. */
  public boolean isEmpty(Object obj) {
    SessionArrayValue session = (SessionArrayValue) obj;

    return session.isEmpty();
  }
  /**
   * Loads the session.
   *
   * @param in the input stream containing the serialized session
   * @param obj the session object to be deserialized
   */
  public void load(ObjectInputStream in, Object obj) throws IOException {
    SessionArrayValue session = (SessionArrayValue) obj;

    session.load(null, in);
  }