/// <summary>
    /// Calls the token endpoint to obtain an access token.
    /// </summary>
    /// <param name="body">The request body.</param>
    /// <param name="errorMessage">The error message.</param>
    protected void CallTokenEndpoint(string body) {
      WebRequest request = HttpUtilities.BuildRequest(TOKEN_ENDPOINT, "POST", config);
      request.ContentType = "application/x-www-form-urlencoded";

      using (StreamWriter writer = new StreamWriter(request.GetRequestStream())) {
        writer.Write(body);
      }

      LogEntry logEntry = new LogEntry(config, new DefaultDateTimeProvider());
      logEntry.LogRequest(request, body, REQUEST_HEADERS_TO_MASK);

      WebResponse response = null;

      try {
        response = request.GetResponse();

        string contents = MediaUtilities.GetStreamContentsAsString(response.GetResponseStream());
        logEntry.LogResponse(response, false, contents, RESPONSE_FIELDS_TO_MASK,
            new JsonBodyFormatter());
        logEntry.Flush();

        Dictionary<string, string> values = ParseJsonObjectResponse(contents);
        if (values.ContainsKey("access_token")) {
          this.AccessToken = values["access_token"];
        }
        if (values.ContainsKey("refresh_token")) {
          this.RefreshToken = values["refresh_token"];
        }
        if (values.ContainsKey("token_type")) {
          this.tokenType = values["token_type"];
        }
        if (values.ContainsKey("expires_in")) {
          this.expiresIn = int.Parse(values["expires_in"]);
        }
        this.updatedOn = DateTime.Now;

        if (this.OnOAuthTokensObtained != null) {
          this.OnOAuthTokensObtained(this);
        }
      } catch (WebException e) {
        string contents = "";
        response = e.Response;

        try {
          contents = MediaUtilities.GetStreamContentsAsString(response.GetResponseStream());
          logEntry.LogResponse(response, true, contents, RESPONSE_FIELDS_TO_MASK,
              new JsonBodyFormatter());
        } catch {
          contents = e.Message;
          logEntry.LogResponse(response, true, contents);
        }

        logEntry.Flush();

        throw new ApplicationException(contents, e);
      } finally {
        if (response != null) {
          response.Close();
        }
      }
    }
        /// <summary>
        /// Downloads a report to stream.
        /// </summary>
        /// <param name="downloadUrl">The download url.</param>
        /// <param name="postBody">The POST body.</param>
        private ReportResponse DownloadReport(string downloadUrl, string postBody)
        {
            AdWordsErrorHandler errorHandler = new AdWordsErrorHandler(this.User as AdWordsUser);
              while (true) {
            WebResponse response = null;
            HttpWebRequest request = BuildRequest(downloadUrl, postBody);

            LogEntry logEntry = new LogEntry(User.Config, new DefaultDateTimeProvider());

            logEntry.LogRequest(request, postBody, HEADERS_TO_MASK);

            try {
              response = request.GetResponse();

              logEntry.LogResponse(response, false, "Response truncated.");
              logEntry.Flush();
              return new ReportResponse(response);
            } catch (WebException e) {
              Exception reportsException = null;

              string contents = HttpUtilities.GetErrorResponseBody(e);

              logEntry.LogResponse(e.Response, true, contents);
              logEntry.Flush();

              reportsException = ParseException(e, contents);

              if (AdWordsErrorHandler.IsOAuthTokenExpiredError(reportsException)) {
            reportsException = new AdWordsCredentialsExpiredException(
                request.Headers["Authorization"]);
              }
              if (errorHandler.ShouldRetry(reportsException)) {
            errorHandler.PrepareForRetry(reportsException);
              } else {
            throw reportsException;
              }
            }
              }
        }
    /// <summary>
    /// Performs the SOAP and HTTP logging.
    /// </summary>
    /// <param name="service">The SOAP service.</param>
    /// <param name="soapResponse">The SOAP response xml.</param>
    /// <param name="soapRequest">The SOAP request xml.</param>
    private void PerformLogging(AdsClient service, string soapRequest, string soapResponse) {
      if (service == null || service.User == null || soapRequest == null || soapResponse == null) {
        return;
      }

      bool isFailure = service.LastResponse != null && service.LastResponse is HttpWebResponse &&
          (service.LastResponse as HttpWebResponse).StatusCode ==
                HttpStatusCode.InternalServerError;

      LogEntry logEntry = new LogEntry(config, dateTimeProvider);
      logEntry.LogRequestDetails(service.LastRequest, soapRequest, GetFieldsToMask(),
          new SoapTraceFormatter());
      logEntry.LogResponseDetails(service.LastResponse, soapResponse, new HashSet<string>(),
          new DefaultBodyFormatter());
      logEntry.LogRequestSummary(service.LastRequest, GetSummaryRequestLogs(soapRequest));
      logEntry.LogResponseSummary(isFailure, GetSummaryResponseLogs(soapResponse));
      logEntry.Flush();

      ContextStore.AddKey("FormattedSoapLog", logEntry.DetailedLog);
      ContextStore.AddKey("FormattedRequestLog", logEntry.SummaryLog);
    }
    /// <summary>
    /// Revokes the refresh token.
    /// </summary>
    /// <exception cref="ArgumentNullException">Thrown if one of the following
    /// OAuth2 parameters are empty: RefreshToken.</exception>
    public void RevokeRefreshToken() {
      ValidateOAuth2Parameter("RefreshToken", RefreshToken);

      string url = string.Format("{0}?token={1}", REVOKE_ENDPOINT, RefreshToken);
      WebRequest request = HttpWebRequest.Create(url);
      request.Proxy = config.Proxy;

      LogEntry logEntry = new LogEntry(this.Config, new DefaultDateTimeProvider());
      logEntry.LogRequest(request, "", new HashSet<string>());

      WebResponse response = null;

      try {
        response = request.GetResponse();

        string contents = MediaUtilities.GetStreamContentsAsString(response.GetResponseStream());
        logEntry.LogResponse(response, false, contents);
        logEntry.Flush();
      } catch (WebException e) {
        string contents = "";
        response = e.Response;

        try {
          contents = MediaUtilities.GetStreamContentsAsString(response.GetResponseStream());
        } catch {
          contents = e.Message;
        }

        logEntry.LogResponse(response, true, contents);
        logEntry.Flush();

        throw new AdsOAuthException("Failed to revoke refresh token.\n" + contents, e);
      } finally {
        if (response != null) {
          response.Close();
        }
      }
    }
        /// <summary>
        /// Uploads a chunk of data for the batch job.
        /// </summary>
        /// <param name="url">The resumable upload URL.</param>
        /// <param name="postBody">The post body.</param>
        /// <param name="start">The start of range of bytes to be uploaded.</param>
        /// <param name="end">The end of range of bytes to be uploaded.</param>
        private void UploadChunk(string url, byte[] postBody, int start, int end)
        {
            BulkJobErrorHandler errorHandler = new BulkJobErrorHandler(user);

              while (true) {
            WebResponse response = null;
            LogEntry logEntry = new LogEntry(User.Config, new DefaultDateTimeProvider());

            int bytesToWrite = end - start + 1;
            HttpWebRequest request = (HttpWebRequest) HttpUtilities.BuildRangeRequest(
            url, bytesToWrite,
            string.Format("bytes {0}-{1}/{2}", start, end, postBody.Length), user.Config);

            request.ContentType = "application/xml";

            try {
              using (Stream requestStream = request.GetRequestStream()) {
            requestStream.Write(postBody, start, bytesToWrite);
              }

              logEntry.LogRequest(request, "Truncated", HEADERS_TO_MASK);

              response = request.GetResponse();

              logEntry.LogResponse(response, true, "");
              logEntry.Flush();
              return;
            } catch (WebException e) {
              response = e.Response;
              if (IsPartialUploadSuccessResponse(e)) {
            logEntry.LogResponse(e.Response, true, "");
            logEntry.Flush();
            return;
              } else {
            HandleCloudException(errorHandler, logEntry, e);
              }
            }
              }
        }
        /// <summary>
        /// Handles the exception from Google Cloud Storage servers when uploading
        /// operations.
        /// </summary>
        /// <param name="errorHandler">The error handler.</param>
        /// <param name="logEntry">The log entry.</param>
        /// <param name="e">The web exception that was thrown by the server.</param>
        /// <returns>True if this is a success, false if this was a server error.
        /// </returns>
        private void HandleCloudException(BulkJobErrorHandler errorHandler, LogEntry logEntry,
        WebException e)
        {
            Exception downloadException = null;

              using (WebResponse response = e.Response) {
            string contents = HttpUtilities.GetErrorResponseBody(e);

            logEntry.LogResponse(response, false, contents);
            logEntry.Flush();

            downloadException = ParseException(e, contents);

            if (errorHandler.ShouldRetry(downloadException)) {
              errorHandler.PrepareForRetry(downloadException);
            } else {
              throw downloadException;
            }
              }
        }
        /// <summary>
        /// Gets the upload progress.
        /// </summary>
        /// <param name="url">The resumable upload URL.</param>
        /// <param name="totalLength">The total length of upload.</param>
        /// <returns>The number of bytes uploaded so far.</returns>
        private int GetUploadProgress(string url, int totalLength)
        {
            int retval = 0;
              BulkJobErrorHandler errorHandler = new BulkJobErrorHandler(user);
              while (true) {
            WebResponse response = null;
            WebRequest request = HttpUtilities.BuildRangeRequest(url, 0,
            string.Format("bytes */{0}", totalLength), user.Config);

            LogEntry logEntry = new LogEntry(User.Config, new DefaultDateTimeProvider());
            logEntry.LogRequest(request, "Truncated", HEADERS_TO_MASK);

            try {
              response = request.GetResponse();
            } catch (WebException e) {
              if (IsPartialUploadSuccessResponse(e)) {
            retval = ExtractUpperRange(e.Response.Headers["Range"], retval);

            logEntry.LogResponse(e.Response, true, "");
            logEntry.Flush();
            break;
              } else {
            HandleCloudException(errorHandler, logEntry, e);
              }
            }
              }
              return retval;
        }
        /// <summary>
        /// Downloads the batch job results from a specified URL.
        /// </summary>
        /// <param name="url">The download URL from a batch job.</param>
        /// <returns>The results from the batch job.</returns>
        public BatchJobMutateResponse Download(string url)
        {
            BulkJobErrorHandler errorHandler = new BulkJobErrorHandler(user);

              while (true) {
            WebRequest request = HttpUtilities.BuildRequest(url, "GET", user.Config);

            WebResponse response = null;

            LogEntry logEntry = new LogEntry(User.Config, new DefaultDateTimeProvider());
            logEntry.LogRequest(request, "", HEADERS_TO_MASK);

            try {
              response = request.GetResponse();
              string contents = MediaUtilities.GetStreamContentsAsString(
              response.GetResponseStream());
              logEntry.LogResponse(response, false, contents);
              logEntry.Flush();

              return ParseResponse(contents);
            } catch (WebException e) {
              HandleCloudException(errorHandler, logEntry, e);
            }
              }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="TraceHelperTests"/> class.
 /// </summary>
 public TraceHelperTests() {
   logEntry = new LogEntry(CONFIG, DATE_PROVIDER);
 }