/** Send prepare message to the server and check that a valid acknowledged message is received. */
  private void doHandshake(String contextService) throws ReplicatorException {
    try {
      // Send prepare message.
      toServer.print(messageGenerator.prepare());
      toServer.flush();

      // Receive & check acknowledged message.
      String header = fromServer.readLine();
      if (logger.isDebugEnabled()) logger.debug("Received header: " + header);

      JSONObject obj = (JSONObject) parser.parse(header);
      long payloadLen = (Long) obj.get("payload");
      if (logger.isDebugEnabled()) logger.debug("Payload length: " + payloadLen);

      String payload = NetworkClientFilter.Protocol.readPayload(fromServer, (int) payloadLen);
      if (logger.isDebugEnabled()) logger.debug("Received payload: " + payload);

      String type = (String) obj.get("type");
      String service = (String) obj.get("service");
      long returnCode = (Long) obj.get("return");

      validateMessage(Protocol.TYPE_ACKNOWLEDGED, type, returnCode, service, payload);

      logger.info("Server: " + payload);
    } catch (ParseException e) {
      throw new ReplicatorException(
          "Server returned an invalid message during prepare-acknowledged message handshake: " + e,
          e);
    } catch (IOException e) {
      throw new ReplicatorException("prepare-acknowledged message handshake failed: " + e, e);
    }
  }
  /**
   * Sends release message to the server. Confirms the success. Failure are logged, but otherwise
   * ignored.
   */
  private void sendRelease() {
    try {
      // Send release message.
      toServer.print(messageGenerator.release());
      toServer.flush();

      // Receive & check acknowledged message.
      String header = fromServer.readLine();
      if (header != null) {
        if (logger.isDebugEnabled()) logger.debug("Received header: " + header);

        JSONObject obj = (JSONObject) parser.parse(header);
        long payloadLen = (Long) obj.get("payload");
        if (logger.isDebugEnabled()) logger.debug("Payload length: " + payloadLen);

        String payload = NetworkClientFilter.Protocol.readPayload(fromServer, (int) payloadLen);
        if (logger.isDebugEnabled()) logger.debug("Received payload: " + payload);

        String type = (String) obj.get("type");
        long returnCode = (Long) obj.get("return");

        if (type.equals(Protocol.TYPE_ACKNOWLEDGED)) {
          if (returnCode == 0) {
            logger.info("Server acknowledged filter release: " + payload);
          } else {
            logger.warn(
                "Server returned a non-zero code ("
                    + returnCode
                    + ") in response to release message: "
                    + payload);
          }
        } else {
          logger.warn(
              "Server should have returned message of type \""
                  + Protocol.TYPE_ACKNOWLEDGED
                  + "\", but returned \""
                  + type
                  + "\" instead. Full message: "
                  + header
                  + payload);
        }
      } else {
        logger.warn("Server didn't send response to a release request");
      }
    } catch (ParseException e) {
      logger.warn(
          "Error parsing message received back from the filtering server after release message (ignoring): "
              + e);
    } catch (IOException e) {
      logger.warn("Sending of release message to the filtering server failed (ignoring): " + e);
    } catch (ReplicatorException e) {
      logger.warn("Sending of release message to the filtering server failed (ignoring): " + e);
    }
  }
  /** Sends column/key value to the server and receives filtered result. */
  private Object sendToFilter(
      String transformation,
      long seqno,
      int row,
      String schema,
      String table,
      String column,
      Object value)
      throws ReplicatorException {
    try {
      // Convert various data types to string for transfer.
      String str = valueToString(value);

      // Send filter message.
      String send = messageGenerator.filter(transformation, seqno, row, schema, table, column, str);
      toServer.print(send);
      toServer.flush();

      // Receive & check filtered message.
      String header = fromServer.readLine();
      if (logger.isDebugEnabled()) logger.debug("Received header: " + header);

      if (header == null)
        throw new ReplicatorException("Server didn't send response to a filter request: " + send);

      JSONObject obj = (JSONObject) parser.parse(header);
      long payloadLen = (Long) obj.get("payload");
      if (logger.isDebugEnabled()) logger.debug("Payload length: " + payloadLen);

      String payload = NetworkClientFilter.Protocol.readPayload(fromServer, (int) payloadLen);
      if (logger.isDebugEnabled()) logger.debug("Received payload: " + payload);

      String type = (String) obj.get("type");
      long newSeqno = (Long) obj.get("seqno");
      long newRow = (Long) obj.get("row");
      String newSchema = (String) obj.get("schema");
      String newTable = (String) obj.get("table");
      long returnCode = (Long) obj.get("return");
      String service = (String) obj.get("service");

      // Validate that returned information matches what we requested.
      validateMessage(Protocol.TYPE_FILTERED, type, returnCode, service, payload);
      if (newSeqno != seqno)
        throw new ReplicatorException(
            "Expected to receive seqno "
                + seqno
                + ", but server sent "
                + newSeqno
                + " instead: "
                + header
                + payload);
      if (newRow != row)
        throw new ReplicatorException(
            "Expected to receive row "
                + row
                + ", but server sent "
                + newRow
                + " instead: "
                + header
                + payload);
      if (!newSchema.equals(schema))
        throw new ReplicatorException(
            "Expected to receive schema "
                + schema
                + ", but server sent "
                + newSchema
                + " instead: "
                + header
                + payload);
      if (!newTable.equals(table))
        throw new ReplicatorException(
            "Expected to receive table "
                + table
                + ", but server sent "
                + newTable
                + " instead: "
                + header
                + payload);

      // Convert result back to correct data type.
      return stringToValue(value, payload);
    } catch (ParseException e) {
      throw new ReplicatorException(
          "Server returned an invalid message during prepare-acknowledged message handshake: " + e,
          e);
    } catch (IOException e) {
      throw new ReplicatorException("prepare-acknowledged message handshake failed: " + e, e);
    }
  }