/** This method creates a detail packed exception to pass up */
 private AWSException createException(String errorResponse, String msgPrefix)
     throws JAXBException {
   String errorMsg;
   String requestId;
   List<AWSError> errors = null;
   ByteArrayInputStream bais = new ByteArrayInputStream(errorResponse.getBytes());
   if (errorResponse.indexOf("<ErrorResponse") > -1) {
     try {
       // this comes form the SQS2 schema, and is the standard new response
       ErrorResponse resp = JAXBuddy.deserializeXMLStream(ErrorResponse.class, bais);
       List<Error> errs = resp.getErrors();
       errorMsg = "(" + errs.get(0).getCode() + ") " + errs.get(0).getMessage();
       requestId = resp.getRequestID();
       errors = new ArrayList<AWSError>();
       for (Error e : errs) {
         errors.add(
             new AWSError(
                 AWSError.ErrorType.getTypeFromString(e.getType()), e.getCode(), e.getMessage()));
       }
     } catch (UnmarshalException ex) {
       // this comes form the DevpayLS schema, duplicated because of the different namespace
       bais = new ByteArrayInputStream(errorResponse.getBytes());
       com.xerox.amazonws.typica.jaxb.ErrorResponse resp =
           JAXBuddy.deserializeXMLStream(com.xerox.amazonws.typica.jaxb.ErrorResponse.class, bais);
       List<com.xerox.amazonws.typica.jaxb.Error> errs = resp.getErrors();
       errorMsg = "(" + errs.get(0).getCode() + ") " + errs.get(0).getMessage();
       requestId = resp.getRequestID();
       errors = new ArrayList<AWSError>();
       for (com.xerox.amazonws.typica.jaxb.Error e : errs) {
         errors.add(
             new AWSError(
                 AWSError.ErrorType.getTypeFromString(e.getType()), e.getCode(), e.getMessage()));
       }
     }
   } else {
     Response resp = JAXBuddy.deserializeXMLStream(Response.class, bais);
     String errorCode = resp.getErrors().getError().getCode();
     errorMsg = resp.getErrors().getError().getMessage();
     requestId = resp.getRequestID();
     if (errorCode != null && !errorCode.trim().equals("")) {
       errors = new ArrayList<AWSError>();
       errors.add(new AWSError(AWSError.ErrorType.SENDER, errorCode, errorMsg));
     }
   }
   return new AWSException(msgPrefix + errorMsg, requestId, errors);
 }
  /**
   * Make a http request and process the response. This method also performs automatic retries.
   *
   * @param method The HTTP method to use (GET, POST, DELETE, etc)
   * @param action the name of the action for this query request
   * @param params map of request params
   * @param respType the class that represents the desired/expected return type
   */
  protected <T> T makeRequest(
      HttpMethodBase method, String action, Map<String, String> params, Class<T> respType)
      throws HttpException, IOException, JAXBException, AWSException {

    // add auth params, and protocol specific headers
    Map<String, String> qParams = new HashMap<String, String>(params);
    qParams.put("Action", action);
    qParams.put("AWSAccessKeyId", getAwsAccessKeyId());
    qParams.put("SignatureVersion", "" + getSignatureVersion());
    qParams.put("Timestamp", httpDate());
    if (getSignatureVersion() == 2) {
      qParams.put("SignatureMethod", getAlgorithm());
    }
    if (headers != null) {
      for (Iterator<String> i = headers.keySet().iterator(); i.hasNext(); ) {
        String key = i.next();
        for (Iterator<String> j = headers.get(key).iterator(); j.hasNext(); ) {
          qParams.put(key, j.next());
        }
      }
    }
    // sort params by key
    ArrayList<String> keys = new ArrayList<String>(qParams.keySet());
    if (getSignatureVersion() == 2) {
      Collections.sort(keys);
    } else {
      Collator stringCollator = Collator.getInstance();
      stringCollator.setStrength(Collator.PRIMARY);
      Collections.sort(keys, stringCollator);
    }

    // build param string
    StringBuilder resource = new StringBuilder();
    if (getSignatureVersion() == 0) { // ensure Action, Timestamp come first!
      resource.append(qParams.get("Action"));
      resource.append(qParams.get("Timestamp"));
    } else if (getSignatureVersion() == 2) {
      resource.append(method.getName());
      resource.append("\n");
      resource.append(getServer().toLowerCase());
      resource.append("\n/");
      String reqURL = makeURL("").toString();
      // see if there is something after the host:port/ in the URL
      if (reqURL.lastIndexOf('/') < (reqURL.length() - 1)) {
        // if so, put that here in the string to sign
        resource.append(reqURL.substring(reqURL.lastIndexOf('/') + 1));
      }
      resource.append("\n");
      boolean first = true;
      for (String key : keys) {
        if (!first) {
          resource.append("&");
        } else {
          first = false;
        }
        resource.append(key);
        resource.append("=");
        resource.append(SignerEncoder.encode(qParams.get(key)));
        //				System.err.println("encoded params "+key+" :"+(urlencode(qParams.get(key))));
      }
    } else {
      for (String key : keys) {
        resource.append(key);
        resource.append(qParams.get(key));
      }
    }
    //		System.err.println("String to sign :"+resource.toString());

    // calculate signature
    String encoded = encode(getSecretAccessKey(), resource.toString(), true);

    // build param string, encoding values and adding request signature
    resource = new StringBuilder();
    for (String key : keys) {
      resource.append("&");
      resource.append(key);
      resource.append("=");
      resource.append(urlencode(qParams.get(key)));
    }
    resource.setCharAt(0, '?'); // set first param delimeter
    resource.append("&Signature=");
    resource.append(encoded);

    // finally, build request object
    URL url = makeURL(resource.toString());
    method.setURI(new URI(url.toString(), true));
    method.setRequestHeader(new Header("User-Agent", userAgent));
    if (getSignatureVersion() == 0) {
      method.setRequestHeader(
          new Header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"));
    }
    Object response = null;
    boolean done = false;
    int retries = 0;
    boolean doRetry = false;
    AWSException error = null;
    do {
      int responseCode = 600; // default to high value, so we don't think it is valid
      try {
        responseCode = getHttpClient().executeMethod(method);
      } catch (SocketException ex) {
        // these can generally be retried. Treat it like a 500 error
        doRetry = true;
        error = new AWSException(ex.getMessage(), ex);
      }
      // 100's are these are handled by httpclient
      if (responseCode < 300) {
        // 200's : parse normal response into requested object
        if (respType != null) {
          InputStream iStr = method.getResponseBodyAsStream();
          response = JAXBuddy.deserializeXMLStream(respType, iStr);
        }
        done = true;
      } else if (responseCode < 400) {
        // 300's : what to do?
        throw new HttpException("redirect error : " + responseCode);
      } else if (responseCode < 500) {
        // 400's : parse client error message
        String body = getStringFromStream(method.getResponseBodyAsStream());
        throw createException(body, "Client error : ");
      } else if (responseCode < 600) {
        // 500's : retry...
        doRetry = true;
        String body = getStringFromStream(method.getResponseBodyAsStream());
        error = createException(body, "");
      }
      if (doRetry) {
        retries++;
        if (retries > maxRetries) {
          throw new HttpException("Number of retries exceeded : " + action, error);
        }
        doRetry = false;
        try {
          Thread.sleep((int) Math.pow(2.0, retries) * 1000);
        } catch (InterruptedException ex) {
        }
      }
    } while (!done);
    return (T) response;
  }