/**
   * Handles a successful response from a service call by unmarshalling the results using the
   * specified response handler.
   *
   * @param <T> The type of object expected in the response.
   * @param request The original request that generated the response being handled.
   * @param responseHandler The response unmarshaller used to interpret the contents of the
   *     response.
   * @param method The HTTP method that was invoked, and contains the contents of the response.
   * @param executionContext Extra state information about the request currently being executed.
   * @return The contents of the response, unmarshalled using the specified response handler.
   * @throws IOException If any problems were encountered reading the response contents from the
   *     HTTP method object.
   */
  private <T> T handleResponse(
      Request<?> request,
      HttpResponseHandler<AmazonWebServiceResponse<T>> responseHandler,
      HttpRequestBase method,
      org.apache.http.HttpResponse apacheHttpResponse,
      ExecutionContext executionContext)
      throws IOException {

    HttpResponse httpResponse = createResponse(method, request, apacheHttpResponse);
    if (responseHandler.needsConnectionLeftOpen() && method instanceof HttpEntityEnclosingRequest) {
      HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) method;
      httpResponse.setContent(new HttpMethodReleaseInputStream(httpEntityEnclosingRequest));
    }

    try {
      CountingInputStream countingInputStream = null;
      if (System.getProperty(PROFILING_SYSTEM_PROPERTY) != null) {
        countingInputStream = new CountingInputStream(httpResponse.getContent());
        httpResponse.setContent(countingInputStream);
      }

      long startTime = System.currentTimeMillis();
      AmazonWebServiceResponse<? extends T> awsResponse = responseHandler.handle(httpResponse);
      long endTime = System.currentTimeMillis();

      if (System.getProperty(PROFILING_SYSTEM_PROPERTY) != null) {
        if (executionContext.getTimingInfo() != null) {
          TimingInfo timingInfo = executionContext.getTimingInfo();
          TimingInfo responseProcessingTiming = new TimingInfo(startTime, endTime);
          timingInfo.addSubMeasurement(
              RESPONSE_PROCESSING_SUBMEASUREMENT, responseProcessingTiming);

          if (countingInputStream != null) {
            responseProcessingTiming.addCounter(
                BYTES_PROCESSED_COUNTER, countingInputStream.getByteCount());
          }
        }
      }

      if (awsResponse == null) throw new RuntimeException("Unable to unmarshall response metadata");

      responseMetadataCache.add(request.getOriginalRequest(), awsResponse.getResponseMetadata());

      if (requestLog.isDebugEnabled()) {
        requestLog.debug(
            "Received successful response: "
                + apacheHttpResponse.getStatusLine().getStatusCode()
                + ", AWS Request ID: "
                + awsResponse.getRequestId());
      }

      return awsResponse.getResult();
    } catch (Exception e) {
      String errorMessage = "Unable to unmarshall response (" + e.getMessage() + ")";
      log.warn(errorMessage, e);
      throw new AmazonClientException(errorMessage, e);
    }
  }
  /**
   * Executes the request and returns the result.
   *
   * @param request The AmazonWebServices request to send to the remote server
   * @param responseHandler A response handler to accept a successful response from the remote
   *     server
   * @param errorResponseHandler A response handler to accept an unsuccessful response from the
   *     remote server
   * @param executionContext Additional information about the context of this web service call
   */
  public <T> T execute(
      Request<?> request,
      HttpResponseHandler<AmazonWebServiceResponse<T>> responseHandler,
      HttpResponseHandler<AmazonServiceException> errorResponseHandler,
      ExecutionContext executionContext)
      throws AmazonClientException, AmazonServiceException {
    long startTime = System.currentTimeMillis();

    if (executionContext == null)
      throw new AmazonClientException(
          "Internal SDK Error: No execution context parameter specified.");
    List<RequestHandler> requestHandlers = executionContext.getRequestHandlers();
    if (requestHandlers == null) requestHandlers = new ArrayList<RequestHandler>();

    // Apply any additional service specific request handlers that need to be run
    for (RequestHandler requestHandler : requestHandlers) {
      requestHandler.beforeRequest(request);
    }

    try {
      TimingInfo timingInfo = new TimingInfo(startTime);
      executionContext.setTimingInfo(timingInfo);
      T t = executeHelper(request, responseHandler, errorResponseHandler, executionContext);
      timingInfo.setEndTime(System.currentTimeMillis());

      for (RequestHandler handler : requestHandlers) {
        try {
          handler.afterResponse(request, t, timingInfo);
        } catch (ClassCastException cce) {
        }
      }
      return t;
    } catch (AmazonClientException e) {
      for (RequestHandler handler : requestHandlers) {
        handler.afterError(request, e);
      }
      throw e;
    }
  }