Java程序  |  542行  |  18.57 KB

/*
 * Copyright (C) 2009 Google Inc.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.polo.wire.json;

import com.google.polo.exception.BadSecretException;
import com.google.polo.exception.NoConfigurationException;
import com.google.polo.exception.PoloException;
import com.google.polo.exception.ProtocolErrorException;
import com.google.polo.json.JSONArray;
import com.google.polo.json.JSONException;
import com.google.polo.json.JSONObject;
import com.google.polo.pairing.message.ConfigurationAckMessage;
import com.google.polo.pairing.message.ConfigurationMessage;
import com.google.polo.pairing.message.EncodingOption;
import com.google.polo.pairing.message.EncodingOption.EncodingType;
import com.google.polo.pairing.message.OptionsMessage;
import com.google.polo.pairing.message.OptionsMessage.ProtocolRole;
import com.google.polo.pairing.message.PairingRequestAckMessage;
import com.google.polo.pairing.message.PairingRequestMessage;
import com.google.polo.pairing.message.PoloMessage;
import com.google.polo.pairing.message.PoloMessage.PoloMessageType;
import com.google.polo.pairing.message.SecretAckMessage;
import com.google.polo.pairing.message.SecretMessage;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

/**
 * A collection of methods to convert {@link PoloMessage}s to and from JSON
 * format.
 * <p>
 * Messages are based on the descriptions found in the file polo.proto.  This
 * mimics the field name and message compositions found in that file.
 */
public class JsonMessageBuilder {

  public static final int PROTOCOL_VERSION = 1;

  /*
   * Status types. These match the values defined by OuterMessage.MessageType
   * in polo.proto.
   */

  public static final int STATUS_OK = 200;
  public static final int STATUS_ERROR = 400;
  public static final int STATUS_BAD_CONFIGURATION = 401;
  public static final int STATUS_BAD_SECRET = 403;

  /*
   * Key names for JSON versions of messages.
   */

  // OuterMessage JSON key names
  private static final String OUTER_FIELD_PAYLOAD = "payload";
  private static final String OUTER_FIELD_TYPE = "type";
  private static final String OUTER_FIELD_STATUS = "status";
  private static final String OUTER_FIELD_PROTOCOL_VERSION = "protocol_version";

  // PairingRequestMessage JSON key names
  private static final String PAIRING_REQUEST_FIELD_SERVICE_NAME =
      "service_name";
  private static final String PAIRING_REQUEST_FIELD_CLIENT_NAME =
      "client_name";

  // PairingRequestAckMessage JSON key names
  private static final String PAIRING_REQUEST_ACK_FIELD_SERVER_NAME =
      "server_name";

  // OptionsMessage JSON key names
  private static final String OPTIONS_FIELD_PREFERRED_ROLE = "preferred_role";
  private static final String OPTIONS_FIELD_OUTPUT_ENCODINGS =
      "output_encodings";
  private static final String OPTIONS_FIELD_INPUT_ENCODINGS = "input_encodings";

  // ConfigurationMessage JSON key names
  private static final String CONFIG_FIELD_CLIENT_ROLE = "client_role";
  private static final String CONFIG_FIELD_ENCODING = "encoding";

  // EncodingOption JSON key names
  private static final String ENCODING_FIELD_TYPE = "type";
  private static final String ENCODING_FIELD_SYMBOL_LENGTH = "symbol_length";

  // SecretMessage JSON key names
  private static final String SECRET_FIELD_SECRET = "secret";

  // SecretAckMessage JSON key names
  private static final String SECRET_ACK_FIELD_SECRET = "secret";


  /**
   * Builds a {@link PoloMessage} from the JSON version of the outer message.
   *
   * @param outerMessage    a {@link JSONObject} corresponding to the
   *                        outermost wire message
   * @return                a new {@link PoloMessage}
   * @throws PoloException  on error parsing the {@link JSONObject}
   */
  public static PoloMessage outerJsonToPoloMessage(JSONObject outerMessage)
      throws PoloException {
    JSONObject payload;
    int status;
    PoloMessageType messageType;

    try {
      status = outerMessage.getInt(OUTER_FIELD_STATUS);
      if (status != STATUS_OK) {
        throw new ProtocolErrorException("Peer reported an error.");
      }
      payload = outerMessage.getJSONObject(OUTER_FIELD_PAYLOAD);
      int msgIntVal = outerMessage.getInt(OUTER_FIELD_TYPE);
      messageType = PoloMessageType.fromIntVal(msgIntVal);
    } catch (JSONException e) {
      throw new PoloException("Bad outer message.", e);
    }

    switch (messageType) {
      case PAIRING_REQUEST:
        return getPairingRequest(payload);
      case PAIRING_REQUEST_ACK:
        return getPairingRequestAck(payload);
      case OPTIONS:
        return getOptionsMessage(payload);
      case CONFIGURATION:
        return getConfigMessage(payload);
      case CONFIGURATION_ACK:
        return getConfigAckMessage(payload);
      case SECRET:
        return getSecretMessage(payload);
      case SECRET_ACK:
        return getSecretAckMessage(payload);
      default:
        return null;
    }
  }

  //
  // Methods to convert JSON messages to PoloMessage instances
  //

  /**
   * Generates a new {@link PairingRequestMessage} from a JSON payload.
   *
   * @param  body           the JSON payload
   * @return                the new message
   * @throws PoloException  on error parsing the {@link JSONObject}
   */
  static PairingRequestMessage getPairingRequest(JSONObject body)
      throws PoloException {
    try {
      String serviceName = body.getString(PAIRING_REQUEST_FIELD_SERVICE_NAME);
      String clientName = null;
      if (body.has(PAIRING_REQUEST_FIELD_CLIENT_NAME)) {
        clientName = body.getString(PAIRING_REQUEST_FIELD_CLIENT_NAME);
      }
      return new PairingRequestMessage(serviceName, clientName);
    } catch (JSONException e) {
      throw new PoloException("Malformed message.", e);
    }
  }

  /**
   * Generates a new {@link PairingRequestAckMessage} from a JSON payload.
   *
   * @param  body           the JSON payload
   * @return                the new message
   */
  static PairingRequestAckMessage getPairingRequestAck(JSONObject body)
      throws PoloException {
    try {
      String serverName = null;
      if (body.has(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME)) {
        serverName = body.getString(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME);
      }
      return new PairingRequestAckMessage(serverName);
    } catch (JSONException e) {
      throw new PoloException("Malformed message.", e);
    }
  }

  /**
   * Generates a new {@link OptionsMessage} from a JSON payload.
   *
   * @param  body           the JSON payload
   * @return                the new message
   * @throws PoloException  on error parsing the {@link JSONObject}
   */
  static OptionsMessage getOptionsMessage(JSONObject body)
      throws PoloException {
    OptionsMessage options = new OptionsMessage();
    try {
      // Input encodings
      JSONArray inEncodings = new JSONArray();
      try {
        if (body.has(OPTIONS_FIELD_INPUT_ENCODINGS)) {
          inEncodings = body.getJSONArray(OPTIONS_FIELD_INPUT_ENCODINGS);
        }
      } catch (JSONException e) {
        throw new PoloException("Bad input encodings", e);
      }

      for (int i = 0; i < inEncodings.length(); i++) {
        JSONObject enc = inEncodings.getJSONObject(i);
        options.addInputEncoding(getEncodingOption(enc));
      }

      // Output encodings
      JSONArray outEncodings = new JSONArray();
      try {
        if (body.has(OPTIONS_FIELD_OUTPUT_ENCODINGS)) {
          outEncodings = body.getJSONArray(OPTIONS_FIELD_OUTPUT_ENCODINGS);
        }
      } catch (JSONException e) {
        throw new PoloException("Bad output encodings", e);
      }

      for (int i = 0; i < outEncodings.length(); i++) {
        JSONObject enc = outEncodings.getJSONObject(i);
        options.addOutputEncoding(getEncodingOption(enc));
      }

      // Role
      ProtocolRole role = ProtocolRole.fromIntVal(
          body.getInt(OPTIONS_FIELD_PREFERRED_ROLE));
      options.setProtocolRolePreference(role);
    } catch (JSONException e) {
      throw new PoloException("Malformed message.", e);
    }

    return options;
  }

  /**
   * Generates a new {@link ConfigurationMessage} from a JSON payload.
   *
   * @param  body           the JSON payload
   * @return                the new message
   * @throws PoloException  on error parsing the {@link JSONObject}
   */
  static ConfigurationMessage getConfigMessage(JSONObject body)
      throws PoloException {
    try {
      EncodingOption encoding = getEncodingOption(
          body.getJSONObject(CONFIG_FIELD_ENCODING));
      ProtocolRole role = ProtocolRole.fromIntVal(
          body.getInt(CONFIG_FIELD_CLIENT_ROLE));
      return new ConfigurationMessage(encoding, role);
    } catch (JSONException e) {
      throw new PoloException("Malformed message.", e);
    }
  }

  /**
   * Generates a new {@link ConfigurationAckMessage} from a JSON payload.
   *
   * @param  body           the JSON payload
   * @return                the new message
   */
  static ConfigurationAckMessage getConfigAckMessage(JSONObject body) {
    return new ConfigurationAckMessage();
  }

  /**
   * Generates a new {@link SecretMessage} from a JSON payload.
   *
   * @param  body           the JSON payload
   * @return                the new message
   * @throws PoloException  on error parsing the {@link JSONObject}
   */
  static SecretMessage getSecretMessage(JSONObject body) throws PoloException {
    try {
      byte[] secretBytes = Base64.decode(
          body.getString(SECRET_FIELD_SECRET).getBytes());
      return new SecretMessage(secretBytes);
    } catch (JSONException e) {
      throw new PoloException("Malformed message.", e);
    }
  }

  /**
   * Generates a new {@link SecretAckMessage} from a JSON payload.
   *
   * @param body  the JSON payload
   * @return      the new message
   * @throws PoloException  on error parsing the {@link JSONObject}
   */
  static SecretAckMessage getSecretAckMessage(JSONObject body)
      throws PoloException {
    try {
      byte[] secretBytes = Base64.decode(
          body.getString(SECRET_ACK_FIELD_SECRET).getBytes());
      return new SecretAckMessage(secretBytes);
    } catch (JSONException e) {
      throw new PoloException("Malformed message.", e);
    }
  }

  /**
   * Generates a new {@link EncodingOption} from a JSON sub-dictionary.
   *
   * @param  option         the JSON sub-dictionary describing the option
   * @return                the new {@link EncodingOption}
   * @throws JSONException  on error parsing the {@link JSONObject}
   */
  static EncodingOption getEncodingOption(JSONObject option)
      throws JSONException {
    int length = option.getInt(ENCODING_FIELD_SYMBOL_LENGTH);
    int intType = option.getInt(ENCODING_FIELD_TYPE);
    EncodingType type = EncodingType.fromIntVal(intType);
    return new EncodingOption(type, length);
  }

  /**
   * Converts a {@link PoloMessage} to a {@link JSONObject}
   *
   * @param message         the message to convert
   * @return                the same message, as translated to JSON
   * @throws PoloException  if the message could not be generated
   */
  public static JSONObject poloMessageToJson(PoloMessage message)
      throws PoloException {
    try {
      if (message instanceof PairingRequestMessage) {
        return toJson((PairingRequestMessage) message);
      } else if (message instanceof PairingRequestAckMessage) {
        return toJson((PairingRequestAckMessage) message);
      } else if (message instanceof OptionsMessage) {
        return toJson((OptionsMessage) message);
      } else if (message instanceof ConfigurationMessage) {
        return toJson((ConfigurationMessage) message);
      } else if (message instanceof ConfigurationAckMessage) {
        return toJson((ConfigurationAckMessage) message);
      } else if (message instanceof SecretMessage) {
        return toJson((SecretMessage) message);
      } else if (message instanceof SecretAckMessage) {
        return toJson((SecretAckMessage) message);
      }
    } catch (JSONException e) {
      throw new PoloException("Error generating message.", e);
    }
    throw new PoloException("Unknown PoloMessage type.");
  }

  /**
   * Generates a JSONObject corresponding to a full wire message (wrapped in
   * an outer message) for the given payload.
   *
   * @param message         the payload to wrap
   * @return                a {@link JSONObject} corresponding to the complete
   *                        wire message
   * @throws PoloException  on error building the {@link JSONObject}
   */
  public static JSONObject getOuterJson(PoloMessage message)
      throws PoloException {
    JSONObject out = new JSONObject();
    int msgType = message.getType().getAsInt();
    JSONObject innerJson = poloMessageToJson(message);

    try {
      out.put(OUTER_FIELD_PROTOCOL_VERSION, PROTOCOL_VERSION);
      out.put(OUTER_FIELD_STATUS, STATUS_OK);
      out.put(OUTER_FIELD_TYPE, msgType);
      out.put(OUTER_FIELD_PAYLOAD, innerJson);
    } catch (JSONException e) {
      throw new PoloException("Error serializing outer message", e);
    }
    return out;
  }

  /**
   * Generates a {@link JSONObject} corresponding to a wire message with an
   * error code in the status field.  The error code is determined by the type
   * of the exception.
   *
   * @param exception       the {@link Exception} to use to determine the error
   *                        code
   * @return                a {@link JSONObject} corresponding to the complete
   *                        wire message
   * @throws PoloException  on error building the {@link JSONObject}
   */
  public static JSONObject getErrorJson(Exception exception)
      throws PoloException {
    JSONObject out = new JSONObject();

    int errorStatus = STATUS_ERROR;

    if (exception instanceof NoConfigurationException) {
      errorStatus = STATUS_BAD_CONFIGURATION;
    } else if (exception instanceof BadSecretException) {
      errorStatus = STATUS_BAD_SECRET;
    }

    try {
      out.put(OUTER_FIELD_PROTOCOL_VERSION, PROTOCOL_VERSION);
      out.put(OUTER_FIELD_STATUS, errorStatus);
    } catch (JSONException e) {
      throw new PoloException("Error serializing outer message", e);
    }
    return out;

  }

  /**
   * Translates a {@link PairingRequestMessage} to a {@link JSONObject}.
   *
   * @throws JSONException  on error generating the {@link JSONObject}
   */
  static JSONObject toJson(PairingRequestMessage message) throws JSONException {
    JSONObject jsonObj = new JSONObject();
    jsonObj.put(PAIRING_REQUEST_FIELD_SERVICE_NAME, message.getServiceName());
    if (message.hasClientName()) {
      jsonObj.put(PAIRING_REQUEST_FIELD_CLIENT_NAME, message.getClientName());
    }
    return jsonObj;
  }

  /**
   * Translates a {@link PairingRequestAckMessage} to a {@link JSONObject}.
   * @throws JSONException
   */
  static JSONObject toJson(PairingRequestAckMessage message)
      throws JSONException {
    JSONObject jsonObj = new JSONObject();
    if (message.hasServerName()) {
      jsonObj.put(PAIRING_REQUEST_ACK_FIELD_SERVER_NAME,
          message.getServerName());
    }
    return jsonObj;
  }

  /**
   * Translates a {@link OptionsMessage} to a {@link JSONObject}.
   *
   * @throws JSONException  on error generating the {@link JSONObject}
   */
  static JSONObject toJson(OptionsMessage message) throws JSONException {
    JSONObject jsonObj = new JSONObject();

    JSONArray inEncsArray = new JSONArray();
    for (EncodingOption encoding : message.getInputEncodingSet()) {
      inEncsArray.put(toJson(encoding));
    }
    jsonObj.put(OPTIONS_FIELD_INPUT_ENCODINGS, inEncsArray);

    JSONArray outEncsArray = new JSONArray();
    for (EncodingOption encoding : message.getOutputEncodingSet()) {
      outEncsArray.put(toJson(encoding));
    }
    jsonObj.put(OPTIONS_FIELD_OUTPUT_ENCODINGS, outEncsArray);

    int intRole = message.getProtocolRolePreference().getAsInt();
    jsonObj.put(OPTIONS_FIELD_PREFERRED_ROLE, intRole);
    return jsonObj;
  }

  /**
   * Translates a {@link ConfigurationMessage} to a {@link JSONObject}.
   *
   * @throws JSONException  on error generating the {@link JSONObject}
   */
  static JSONObject toJson(ConfigurationMessage message) throws JSONException {
    JSONObject jsonObj = new JSONObject();
    JSONObject encoding = toJson(message.getEncoding());
    jsonObj.put(CONFIG_FIELD_ENCODING, encoding);
    int intRole = message.getClientRole().getAsInt();
    jsonObj.put(CONFIG_FIELD_CLIENT_ROLE, intRole);
    return jsonObj;
  }

  /**
   * Translates a {@link ConfigurationAckMessage} to a {@link JSONObject}.
   */
  static JSONObject toJson(ConfigurationAckMessage message) {
    return new JSONObject();
  }

  /**
   * Translates a {@link SecretMessage} to a {@link JSONObject}.
   *
   * @throws JSONException  on error generating the {@link JSONObject}
   */
  static JSONObject toJson(SecretMessage message) throws JSONException {
    JSONObject jsonObj = new JSONObject();
    String bytesStr;
    String charsetName = Charset.defaultCharset().name();
    try {
      bytesStr = new String(Base64.encode(message.getSecret(), charsetName));
    } catch (UnsupportedEncodingException e) {
      // Should never happen.
      bytesStr = "";
    }
    jsonObj.put(SECRET_FIELD_SECRET, bytesStr);
    return jsonObj;
  }

  /**
   * Translates a {@link SecretAckMessage} to a {@link JSONObject}.
   *
   * @throws JSONException  on error generating the {@link JSONObject}
   */
  static JSONObject toJson(SecretAckMessage message) throws JSONException {
    JSONObject jsonObj = new JSONObject();
    String bytesStr;
    String charsetName = Charset.defaultCharset().name();
    try {
      bytesStr = new String(Base64.encode(message.getSecret(), charsetName));
    } catch (UnsupportedEncodingException e) {
      // Should never happen.
      bytesStr = "";
    }
    jsonObj.put(SECRET_ACK_FIELD_SECRET, bytesStr);
    return jsonObj;
  }

  /**
   * Translates a {@link EncodingOption} to a {@link JSONObject}.
   *
   * @throws JSONException  on error generating the {@link JSONObject}
   */
  static JSONObject toJson(EncodingOption encoding) throws JSONException {
    JSONObject result = new JSONObject();
    int intType = encoding.getType().getAsInt();
    result.put(ENCODING_FIELD_TYPE, intType);
    result.put(ENCODING_FIELD_SYMBOL_LENGTH, encoding.getSymbolLength());
    return result;
  }

}