/**
   * Reinstantiates a serialized session from the data passed in. This will first call
   * createSession() so that we get a fresh instance with all the managers set and all the transient
   * fields validated. Then it calls Session.readObjectData(byte[]) to deserialize the object
   *
   * @param data - a byte array containing session data
   * @return a valid Session object, null if an error occurs
   */
  protected Session readSession(byte[] data, String sessionId) {
    try {
      ReplicationStream session_in = getReplicationStream(data);

      Session session = sessionId != null ? this.findSession(sessionId) : null;
      boolean isNew = (session == null);
      // clear the old values from the existing session
      if (session != null) {
        ReplicatedSession rs = (ReplicatedSession) session;
        rs.expire(false); // cleans up the previous values, since we are not doing removes
        session = null;
      } // end if

      if (session == null) {
        session = createSession(null, false, false);
        sessions.remove(session.getIdInternal());
      }

      boolean hasPrincipal = session_in.readBoolean();
      SerializablePrincipal p = null;
      if (hasPrincipal) p = (SerializablePrincipal) session_in.readObject();
      ((ReplicatedSession) session).readObjectData(session_in);
      if (hasPrincipal) session.setPrincipal(p.getPrincipal(getContainer().getRealm()));
      ((ReplicatedSession) session).setId(sessionId, isNew);
      ReplicatedSession rsession = (ReplicatedSession) session;
      rsession.setAccessCount(1);
      session.setManager(this);
      session.setValid(true);
      rsession.setLastAccessedTime(System.currentTimeMillis());
      rsession.setThisAccessedTime(System.currentTimeMillis());
      ((ReplicatedSession) session).setAccessCount(0);
      session.setNew(false);
      if (log.isTraceEnabled())
        log.trace(
            "Session loaded id="
                + sessionId
                + " actualId="
                + session.getId()
                + " exists="
                + this.sessions.containsKey(sessionId)
                + " valid="
                + rsession.isValid());
      return session;

    } catch (Exception x) {
      log.error("Failed to deserialize the session!", x);
    }
    return null;
  }
  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;
  }
  /**
   * Serialize a session into a byte array<br>
   * This method simple calls the writeObjectData method on the session and returns the byte data
   * from that call
   *
   * @param session - the session to be serialized
   * @return a byte array containing the session data, null if the serialization failed
   */
  protected byte[] writeSession(Session session) {
    try {
      java.io.ByteArrayOutputStream session_data = new java.io.ByteArrayOutputStream();
      java.io.ObjectOutputStream session_out = new java.io.ObjectOutputStream(session_data);
      session_out.flush();
      boolean hasPrincipal = session.getPrincipal() != null;
      session_out.writeBoolean(hasPrincipal);
      if (hasPrincipal) {
        session_out.writeObject(
            SerializablePrincipal.createPrincipal((GenericPrincipal) session.getPrincipal()));
      } // end if
      ((ReplicatedSession) session).writeObjectData(session_out);
      return session_data.toByteArray();

    } catch (Exception x) {
      log.error("Failed to serialize the 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
 /**
  * This method is called by the received thread when a SessionMessage has been received from one
  * of the other nodes in the cluster.
  *
  * @param msg - the message received
  * @param sender - the sender of the message, this is used if we receive a EVT_GET_ALL_SESSION
  *     message, so that we only reply to the requesting node
  */
 protected void messageReceived(SessionMessage msg, Member sender) {
   try {
     if (log.isInfoEnabled()) {
       log.debug("Received SessionMessage of type=" + msg.getEventTypeString());
       log.debug("Received SessionMessage sender=" + sender);
     }
     switch (msg.getEventType()) {
       case SessionMessage.EVT_GET_ALL_SESSIONS:
         {
           // get a list of all the session from this manager
           Object[] sessions = findSessions();
           java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
           java.io.ObjectOutputStream oout = new java.io.ObjectOutputStream(bout);
           oout.writeInt(sessions.length);
           for (int i = 0; i < sessions.length; i++) {
             ReplicatedSession ses = (ReplicatedSession) sessions[i];
             oout.writeUTF(ses.getIdInternal());
             byte[] data = writeSession(ses);
             oout.writeObject(data);
           } // for
           // don't send a message if we don't have to
           oout.flush();
           oout.close();
           byte[] data = bout.toByteArray();
           SessionMessage newmsg =
               new SessionMessageImpl(
                   name,
                   SessionMessage.EVT_ALL_SESSION_DATA,
                   data,
                   "SESSION-STATE",
                   "SESSION-STATE-" + getName());
           cluster.send(newmsg, sender);
           break;
         }
       case SessionMessage.EVT_ALL_SESSION_DATA:
         {
           java.io.ByteArrayInputStream bin = new java.io.ByteArrayInputStream(msg.getSession());
           java.io.ObjectInputStream oin = new java.io.ObjectInputStream(bin);
           int size = oin.readInt();
           for (int i = 0; i < size; i++) {
             String id = oin.readUTF();
             byte[] data = (byte[]) oin.readObject();
             Session session = readSession(data, id);
           } // for
           stateTransferred = true;
           break;
         }
       case SessionMessage.EVT_SESSION_CREATED:
         {
           Session session = this.readSession(msg.getSession(), msg.getSessionID());
           if (log.isDebugEnabled()) {
             log.debug("Received replicated session=" + session + " isValid=" + session.isValid());
           }
           break;
         }
       case SessionMessage.EVT_SESSION_EXPIRED:
         {
           Session session = findSession(msg.getSessionID());
           if (session != null) {
             session.expire();
             this.remove(session);
           } // end if
           break;
         }
       case SessionMessage.EVT_SESSION_ACCESSED:
         {
           Session session = findSession(msg.getSessionID());
           if (session != null) {
             session.access();
             session.endAccess();
           }
           break;
         }
       default:
         {
           // we didn't recognize the message type, do nothing
           break;
         }
     } // switch
   } catch (Exception x) {
     log.error("Unable to receive message through TCP channel", x);
   }
 }