content: /*
   * CDDL HEADER START
   *
   * The contents of this file are subject to the terms of the Common Development
   * and Distribution License (the "License").
   * You may not use this file except in compliance with the License.
   *
   * You can obtain a copy of the license at
   * src/com/vodafone360/people/VODAFONE.LICENSE.txt or
   * http://github.com/360/360-Engine-for-Android
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *
   * When distributing Covered Code, include this CDDL HEADER in each file and
   * include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
   * If applicable, add the following below this CDDL HEADER, with the fields
   * enclosed by brackets "[]" replaced with your own identifying information:
   * Portions Copyright [yyyy] [name of copyright owner]
   *
   * CDDL HEADER END
   *
   * Copyright 2010 Vodafone Sales & Services Ltd.  All rights reserved.
   * Use is subject to license terms.
   */
  
  package com.vodafone360.people.service.io;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.Vector;
  
  import com.vodafone360.people.Settings;
  import com.vodafone360.people.SettingsManager;
  import com.vodafone360.people.datatypes.AuthSessionHolder;
  import com.vodafone360.people.engine.EngineManager.EngineId;
  import com.vodafone360.people.engine.login.LoginEngine;
  import com.vodafone360.people.service.io.rpg.RpgMessage;
  import com.vodafone360.people.service.io.rpg.RpgMessageTypes;
  import com.vodafone360.people.service.transport.http.HttpConnectionThread;
  import com.vodafone360.people.service.utils.AuthUtils;
  import com.vodafone360.people.service.utils.hessian.HessianEncoder;
  
  /**
   * Container class for Requests issued from client to People server via the
   * transport layer. A request consists of a payload, an identifier for the
   * engine responsible for handling this request, a request type and a request id
   * which is generated on creation of the request.
   */
  public class Request {
  
      /**
       * Request types, these are based on the content requested from, or
       * delivered to, the server, and whether the returned response contains a
       * type identifier or not.
       */
      public enum Type {
          COMMON, // Strongly typed
          ADD_CONTACT,
          TEXT_RESPONSE_ONLY,
          CONTACT_CHANGES_OR_UPDATES,
          CONTACT_DELETE,
          CONTACT_DETAIL_DELETE,
          FRIENDSHIP_REQUEST,
          USER_INVITATION,
          SIGN_UP,
          SIGN_IN,
          RETRIEVE_PUBLIC_KEY,
          EXPECTING_STATUS_ONLY,
          GROUP_LIST,
          STATUS_LIST,
          STATUS,
          CONTACT_GROUP_RELATION_LIST,
          CONTACT_GROUP_RELATIONS,
          DELETE_CONTACT_GROUP_RELATIONS,
          ITEM_LIST_OF_LONGS,
          PRESENCE_LIST,
          AVAILABILITY,
          CREATE_CONVERSATION,
          SEND_CHAT_MESSAGE,
          PUSH_MSG,
          EXTERNAL_RPG_RESPONSE,
          GET_MY_IDENTITIES,
          GET_AVAILABLE_IDENTITIES,
          DELETE_IDENTITY
          // response to external RPG request
      }
  
      /*
       * List of parameters which will be used to generate the AUTH parameter.
       */
      private Hashtable<String, Object> mParameters = new Hashtable<String, Object>();
  
      /**
       * Name of method on the backend. E.g. identities/getavailableidentities.
       */
      private String mApiMethodName;
  
      /** RPG message payload (usually Hessian encoded message body). */
      // private byte[] mPayload;
      /** RPG Message type - as defined above. */
      public Type mType;
  
      /** Handle of Engine associated with this RPG message. */
      public EngineId mEngineId = EngineId.UNDEFINED; // to be used to map request
                                                      // to appropriate engine
  
      /** Whether we use RPG for this message. */
      // public boolean mUseRpg = true;
      /** active flag - set to true once we have actually issued a request */
      private boolean mIsActive = false;
  
      /**
       * The timeout set for the request. -1 if not set.
       */
      private long mTimeout = -1;
  
      /**
       * The expiry date calculated when the request gets executed.
       */
      private long mExpiryDate = -1;
  
      /** true if the request has expired */
      public boolean expired;
  
      /**
       * The timestamp when the request's auth was calculated.
       */
      private long mAuthTimestamp;
  
      /**
       * The timestamp when this request was created.
       */
      private long mCreationTimestamp;
  
      /**
       * <p>
       * Represents the authentication type that needs to be taken into account
       * when executing this specific request. There are 3 types of authentication
       * types:
       * </p>
       * <ul>
       * <li>USE_API: needs the API. These requests are usually requests like
       * getSessionByCredentials that use application authentication.</li>
       * <li>USE_RPG: these requests should use the RPG and some of them MUST use
       * the RPG. Usually, all requests after the auth requests should use the
       * RPG.</li>
       * <li>USE_BOTH: some requests like the requests for getting terms and
       * conditions or privacy statements need to be able to use the RPG or API at
       * any time of the application lifecycle.</li>
       * </ul>
       */
      private byte mAuthenticationType;
  
      public static final byte USE_API = 1, USE_RPG = 2, USE_BOTH = 3;
  
      /**
       * If true, this method is a fire and forget method and will not expect any
       * responses to come in. This fact can be used for removing requests from
       * the queue as soon as they have been sent.
       */
      private boolean mIsFireAndForget;
  
      /** RPG message request id. */
      private int mRequestId;
  
      /**
       * Constructor used for constructing internal (RPG/API) requests.
       * 
       * @param apiMethodName The method name of the call, e.g.
       *            "identities/getavailableidentities".
       * @param type RPG message type.
       * @param engId The engine ID. Will be used for routing the response to this
       *            request back to the engine that can process it.
       * @param needsUserAuthentication If true we need to authenticate this
       *            request by providing the session in the auth. This requires
       *            the user to be logged in.
       * @param isFireAndForget True if the request is a fire and forget request.
       * @param timeout the timeout in milliseconds before the request throws a
       *            timeout exception
       */
      public Request(String apiMethodName, Type type, EngineId engineId, boolean isFireAndForget,
              long timeout) {
          mType = type; // TODO find out a type yourself?
          mEngineId = engineId;
          mApiMethodName = apiMethodName;
          mIsFireAndForget = isFireAndForget;
          mCreationTimestamp = System.currentTimeMillis();
          mTimeout = timeout;
  
          if ((type == Type.RETRIEVE_PUBLIC_KEY) || (type == Type.SIGN_UP) || (type == Type.STATUS)
                  || (type == Type.SIGN_IN)) {
              // we need to register, sign in, get t&c's etc. so the request needs
              // to happen without
              // user auth
              mAuthenticationType = USE_API;
          } else if (type == Type.TEXT_RESPONSE_ONLY) { // t&c or privacy
              mAuthenticationType = USE_BOTH;
          } else { // all other requests should use the RPG by default
              mAuthenticationType = USE_RPG;
          }
      }
  
      /**
       * Constructor used for constructing an external request used for fetching
       * e.g. images.
       * 
       * @param externalUrl The external URL of the object to fetch.
       * @param urlParams THe parameters to add to the URL of this request.
       * @param engineId The ID of the engine that will be called back once the
       *            response for this request comes in.
       */
      public Request(String externalUrl, String urlParams, EngineId engineId) {
          mType = Type.EXTERNAL_RPG_RESPONSE;
          mEngineId = engineId;
          mApiMethodName = "";
          mIsFireAndForget = false;
          mCreationTimestamp = System.currentTimeMillis();
  
          mParameters = new Hashtable<String, Object>();
          mParameters.put("method", "GET");
          mParameters.put("url", externalUrl + urlParams);
  
          mAuthenticationType = USE_RPG;
      }
  
      /**
       * Is request active - i.e has it been issued
       * 
       * @return true if request is active
       */
      public boolean isActive() {
          return mIsActive;
      }
  
      /**
       * <p>
       * Sets whether this request is active or not. An active request is a
       * request that is currently being sent to the server and awaiting a
       * response.
       * </p>
       * <p>
       * The reason is that an active request should not be sent twice.
       * </p>
       * 
       * @param isActive True if the request is active, false otherwise.
       */
      public void setActive(boolean isActive) {
          mIsActive = isActive;
      }
  
      /**
       * Returns a description of the contents of this object
       * 
       * @return The description
       */
      @Override
      public String toString() {
          StringBuilder sb = new StringBuilder("Request [mEngineId=");
          sb.append(mEngineId); 
          sb.append(", mIsActive="); sb.append(mIsActive); 
          sb.append(", mTimeout="); sb.append(mTimeout); 
          sb.append(", mReqId="); sb.append(mRequestId); 
          sb.append(", mType="); sb.append(mType);
          sb.append(", mNeedsUserAuthentication="); sb.append(mAuthenticationType); 
          sb.append(", mExpired="); sb.append(expired);
          sb.append(", mDate="); sb.append(mExpiryDate); sb.append("]\n");
          sb.append(mParameters);
          
          return sb.toString();
      }
  
      /**
       * Adds element to list of params
       * 
       * @param name key name for object to be added to parameter list
       * @param nv object which will be added
       */
      public void addData(String name, Hashtable<String, ?> nv) {
          mParameters.put(name, nv);
      }
  
      /**
       * Adds element to list of params
       * 
       * @param name key name for object to be added to parameter list
       * @param value object which will be added
       */
      public void addData(String name, Vector<Object> value) {
          mParameters.put(name, value);
      }
  
      /**
       * Adds element to list of params
       * 
       * @param name key name for object to be added to parameter list
       * @param value object which will be added
       */
      public void addData(String name, List<String> value) {
          mParameters.put(name, value);
      }
  
      /**
       * Adds byte array to parameter list
       * 
       * @param varName name
       * @param value byte[] array to be added
       */
      public void addData(String varName, byte[] value) {
          mParameters.put(varName, value);
      }
  
      /**
       * Create new NameValue and adds it to list of params
       * 
       * @param varName name
       * @param value string value
       */
      public void addData(String varName, String value) {
          mParameters.put(varName, value);
      }
  
      /**
       * Create new NameValue and adds it to list of params
       * 
       * @param varName name
       * @param value Long value
       */
      public void addData(String varName, Long value) {
          mParameters.put(varName, value);
      }
  
      /**
       * Create new NameValue and adds it to list of params
       * 
       * @param varName name
       * @param value Integer value
       */
      public void addData(String varName, Integer value) {
          mParameters.put(varName, value);
      }
  
      /**
       * Create new NameValue and adds it to list of params
       * 
       * @param varName name
       * @param value Boolean value
       */
      public void addData(String varName, Boolean value) {
          mParameters.put(varName, value);
      }
  
      /**
       * Returns a Hashtable containing current request parameter list.
       * 
       * @return params The parameters that were added to this backend request.
       */
      /*
       * public Hashtable<String, Object> getParameters() { return mParameters; }
       */
  
      /**
       * Returns the authentication type of this request. This can be one of the
       * following: USE_API, which must be used for authentication requests that
       * need application authentication, USE_RPG, useful for requests against the
       * API that need user authentication and want to profit from the mobile
       * enhancements of the RPG, or USE_BOTH for requests that need to be able to
       * be used on the API or RPG. These requests are for example the Terms and
       * Conditions requests which need to be accessible from anywhere in the
       * client.
       * 
       * @return True if this method requires user authentication or a valid
       *         session to be more precise. False is returned if the method only
       *         needs application authentication.
       */
      public byte getAuthenticationType() {
          return mAuthenticationType;
      }
  
      /**
       * Gets the request ID of this request.
       * 
       * @return The unique request ID for this request.
       */
      public int getRequestId() {
          return mRequestId;
      }
  
      /**
       * Sets the request ID for this request.
       * 
       * @param requestId The request ID to set.
       */
      public void setRequestId(int requestId) {
          mRequestId = requestId;
      }
  
      /**
       * Gets the time stamp when this request's hash was calculated.
       * 
       * @return The time stamp representing the creation date of this request.
       */
      public long getAuthTimestamp() {
          return mAuthTimestamp;
      }
  
      /**
       * Gets the time stamp when this request was created.
       * 
       * @return The time stamp representing the creation date of this request.
       */
      public long getCreationTimestamp() {
          return mCreationTimestamp;
      }
  
      /**
       * Gets the set timeout.
       * 
       * @return the timeout in milliseconds, -1 if not set.
       */
      public long getTimeout() {
          return mTimeout;
      }
  
      /**
       * Gets the calculated expiry date.
       * 
       * @return the expiry date in milliseconds, -1 if not set.
       */
      public long getExpiryDate() {
          return mExpiryDate;
      }
  
      /**
       * Overwrites the timestamp if we need to wait one more second due to an
       * issue on the backend.
       * 
       * @param timestamp The timestamp in milliseconds(!) to overwrite with.
       */
      /*
      public void overwriteTimetampBecauseOfBadSessionErrorOnBackend(long timestamp) {
          mAuthTimestamp = timestamp;
  
          if (null != mParameters) {
              if (null != mParameters.get("timestamp")) {
                  String ts = "" + (timestamp / 1000);
                  mParameters.put("timestamp", ts);
              }
          }
      }
  */
      
      /**
       * Returns the API call this request will use.
       * 
       * @return The API method name this request will call.
       */
      public String getApiMethodName() {
          return mApiMethodName;
      }
  
      /**
       * Returns true if the method is a fire and forget method. Theses methods
       * can be removed from the request queue as soon as they have been sent out.
       * 
       * @return True if the request is fire and forget, false otherwise.
       */
      public boolean isFireAndForget() {
          return mIsFireAndForget;
      }
  
      /**
       * Serializes the request's data structure to the passed output stream
       * enabling the connection to easily prepare one or multiple) requests.
       * 
       * @param os The output stream to serialise this request to.
       * @param writeRpgHeader If true the RPG header is written.
       */
      public void writeToOutputStream(OutputStream os, boolean writeRpgHeader) {
          if (null == os) {
              return;
          }
  
          byte[] body;
          calculateAuth();
          try {
              body = makeBody();
              if (!writeRpgHeader) {
                  os.write(body); // writing to the api directly
                  return;
              }
          } catch (IOException ioe) {
              HttpConnectionThread.logE("Request.writeToOutputStream()",
                      "Failed writing standard API request: " + mRequestId, ioe);
              return;
          }
  
          int requestType = 0;
          if (mType == Request.Type.PRESENCE_LIST) {
              requestType = RpgMessageTypes.RPG_GET_PRESENCE;
          } else if (mType == Request.Type.AVAILABILITY) {
              requestType = RpgMessageTypes.RPG_SET_AVAILABILITY;
          } else if (mType == Request.Type.CREATE_CONVERSATION) {
              requestType = RpgMessageTypes.RPG_CREATE_CONV;
          } else if (mType == Request.Type.SEND_CHAT_MESSAGE) {
              requestType = RpgMessageTypes.RPG_SEND_IM;
          } else if (mType == Request.Type.EXTERNAL_RPG_RESPONSE) {
              requestType = RpgMessageTypes.RPG_EXT_REQ;
          } else {
              requestType = RpgMessageTypes.RPG_INT_REQ;
          }
  
          byte[] message = RpgMessage.createRpgMessage(body, requestType, mRequestId);
          
  
          try {
              os.write(message);
          } catch (IOException ioe) {
              HttpConnectionThread.logE("Request.writeToOutputStream()",
                      "Failed writing RPG request: " + mRequestId, ioe);
          }
      }
  
      /**
       * Creates the body of the request using the parameter list.
       * 
       * @return payload The hessian encoded payload of this request body.
       * @throws IOException Thrown if anything goes wrong using the hessian
       *             encoder.
       */
      private byte[] makeBody() throws IOException {
          // XXX this whole method needs to go or at least be changed into a
          // bytearray outputstream
  
          byte[] payload = HessianEncoder.createHessianByteArray(mApiMethodName, mParameters);
          if (payload == null) {
              return null;
          }
          payload[1] = (byte)1; // TODO we need to change this if we want to use a
                                // baos
          payload[2] = (byte)0;
  
          return payload;
      }
  
      /**
       * Gets the auth of this request. Prior to the the
       * writeToOutputStream()-method must have been called.
       * 
       * @return The auth of this request or null if it was not calculated before.
       */
      public String getAuth() {
          return (String)mParameters.get("auth");
      }
  
      /**
       * Calculate the Auth value for this Requester. TODO: Throttle by
       * timestamp/function to prevent automatic backend log out
       */
      private void calculateAuth() {
          String ts = null;
          if (null != mParameters) {
              ts = (String)mParameters.get("timestamp");
          }
  
          if (null == ts) {
          	mAuthTimestamp = System.currentTimeMillis();
              ts = "" + (mAuthTimestamp / 1000);
          }
  
          AuthSessionHolder session = LoginEngine.getSession();
          if (session != null) {
              addData("auth", SettingsManager.getProperty(Settings.APP_KEY_ID) + "::"
                      + session.sessionID + "::" + ts);
          } else {
              addData("auth", SettingsManager.getProperty(Settings.APP_KEY_ID) + "::" + ts);
          }
  
          addData("auth", AuthUtils.calculateAuth(mApiMethodName, mParameters, ts, session));
          /**
           * if (mNeedsUserAuthentication) { addData("auth",
           * AuthUtils.calculateAuth(mApiMethodName, mParameters, ts, session)); }
           * else { // create the final auth without the session addData("auth",
           * AuthUtils.calculateAuth(mApiMethodName, mParameters, ts, null)); }
           */
      }
  
      /**
       * Calculates the expiry date based on the timeout. TODO: should have
       * instead an execute() method to call when performing the request. it would
       * set the request to active, calculates the expiry date, etc...
       */
      public void calculateExpiryDate() {
          if (mTimeout > 0) {
              mExpiryDate = System.currentTimeMillis() + mTimeout;
          }
      }
  }
  