public ClusterMessage requestCompleted(String sessionId) {
    if (!getDistributable()) {
      log.warn(
          "Received requestCompleted message, although this context["
              + getName()
              + "] is not distributable. Ignoring message");
      return null;
    }
    try {
      if (invalidatedSessions.get(sessionId) != null) {
        synchronized (invalidatedSessions) {
          invalidatedSessions.remove(sessionId);
          SessionMessage msg =
              new SessionMessageImpl(
                  name, SessionMessage.EVT_SESSION_EXPIRED, null, sessionId, sessionId);
          return msg;
        }
      } else {
        ReplicatedSession session = (ReplicatedSession) findSession(sessionId);
        if (session != null) {
          // return immediately if the session is not dirty
          if (useDirtyFlag && (!session.isDirty())) {
            // but before we return doing nothing,
            // see if we should send
            // an updated last access message so that
            // sessions across cluster dont expire
            long interval = session.getMaxInactiveInterval();
            long lastaccdist = System.currentTimeMillis() - session.getLastAccessWasDistributed();
            if (((interval * 1000) / lastaccdist) < 3) {
              SessionMessage accmsg =
                  new SessionMessageImpl(
                      name, SessionMessage.EVT_SESSION_ACCESSED, null, sessionId, sessionId);
              session.setLastAccessWasDistributed(System.currentTimeMillis());
              return accmsg;
            }
            return null;
          }

          session.setIsDirty(false);
          if (log.isDebugEnabled()) {
            try {
              log.debug("Sending session to cluster=" + session);
            } catch (Exception ignore) {
            }
          }
          SessionMessage msg =
              new SessionMessageImpl(
                  name,
                  SessionMessage.EVT_SESSION_CREATED,
                  writeSession(session),
                  session.getIdInternal(),
                  session.getIdInternal());
          return msg;
        } // end if
      } // end if
    } catch (Exception x) {
      log.error("Unable to replicate session", x);
    }
    return null;
  }
  /**
   * Creates a HTTP session. Most of the code in here is copied from the StandardManager. This is
   * not pretty, yeah I know, but it was necessary since the StandardManager had hard coded the
   * session instantiation to the a StandardSession, when we actually want to instantiate a
   * ReplicatedSession<br>
   * If the call comes from the Tomcat servlet engine, a SessionMessage goes out to the other nodes
   * in the cluster that this session has been created.
   *
   * @param notify - if set to true the other nodes in the cluster will be notified. This flag is
   *     needed so that we can create a session before we deserialize a replicated one
   * @see ReplicatedSession
   */
  protected Session createSession(String sessionId, boolean notify, boolean setId) {

    // inherited from the basic manager
    if ((getMaxActiveSessions() >= 0) && (sessions.size() >= getMaxActiveSessions())) {
      throw new TooManyActiveSessionsException(
          sm.getString("standardManager.createSession.ise"), getMaxActiveSessions());
    }

    Session session = new ReplicatedSession(this);

    // Initialize the properties of the new session and return it
    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(this.maxInactiveInterval);
    if (sessionId == null) sessionId = generateSessionId();
    if (setId) session.setId(sessionId);
    if (notify && (cluster != null)) {
      ((ReplicatedSession) session).setIsDirty(true);
    }
    return (session);
  } // createSession