/// <summary>
    /// Runs the code example.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    /// <param name="adGroupId">Id of the ad group to which keywords are added.
    /// </param>
    public void Run(AdWordsUser user, long adGroupId) {
      // Get the AdGroupCriterionService.
      AdGroupCriterionService adGroupCriterionService =
          (AdGroupCriterionService) user.GetService(
              AdWordsService.v201406.AdGroupCriterionService);

      List<AdGroupCriterionOperation> operations = new List<AdGroupCriterionOperation>();

      for (int i = 0; i < NUM_ITEMS; i++) {
        // Create the keyword.
        Keyword keyword = new Keyword();
        keyword.text = "mars cruise";
        keyword.matchType = KeywordMatchType.BROAD;

        // Create the biddable ad group criterion.
        BiddableAdGroupCriterion keywordCriterion = new BiddableAdGroupCriterion();
        keywordCriterion.adGroupId = adGroupId;
        keywordCriterion.criterion = keyword;

        // Optional: Set the user status.
        keywordCriterion.userStatus = UserStatus.PAUSED;

        // Optional: Set the keyword destination url.
        keywordCriterion.destinationUrl = "http://example.com/mars/cruise/" + i;

        // Create the operations.
        AdGroupCriterionOperation operation = new AdGroupCriterionOperation();
        operation.@operator = Operator.ADD;
        operation.operand = keywordCriterion;

        operations.Add(operation);
      }
      try {
        // Create the keywords.
        AdGroupCriterionReturnValue retVal = adGroupCriterionService.mutate(operations.ToArray());

        // Display the results.
        if (retVal != null && retVal.value != null) {
          foreach (AdGroupCriterion adGroupCriterion in retVal.value) {
            // If you are adding multiple type of criteria, then you may need to
            // check for
            //
            // if (adGroupCriterion is Keyword) { ... }
            //
            // to identify the criterion type.
            Console.WriteLine("Keyword with ad group id = '{0}', keyword id = '{1}', text = " +
                "'{2}' and match type = '{3}' was created.", adGroupCriterion.adGroupId,
                adGroupCriterion.criterion.id, (adGroupCriterion.criterion as Keyword).text,
                (adGroupCriterion.criterion as Keyword).matchType);
          }
        } else {
          Console.WriteLine("No keywords were added.");
        }
      } catch (Exception ex) {
        throw new System.ApplicationException("Failed to create keywords.", ex);
      }
    }
    /// <summary>
    /// Adds a set of keywords to a shared set.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    /// <param name="sharedSetId">The shared set id.</param>
    /// <param name="keywordTexts">The keywords to be added to the shared set.</param>
    /// <returns>The newly added set of shared criteria.</returns>
    public SharedCriterion[] AddKeywordsToSharedSet(AdWordsUser user, long sharedSetId,
        string[] keywordTexts) {
      // Get the SharedCriterionService.
      SharedCriterionService sharedSetService = (SharedCriterionService)
          user.GetService(AdWordsService.v201406.SharedCriterionService);

      List<SharedCriterionOperation> operations = new List<SharedCriterionOperation>();
      foreach (string keywordText in keywordTexts) {
        Keyword keyword = new Keyword();
        keyword.text = keywordText;
        keyword.matchType = KeywordMatchType.BROAD;

        SharedCriterion sharedCriterion = new SharedCriterion();
        sharedCriterion.criterion = keyword;
        sharedCriterion.negative = true;
        sharedCriterion.sharedSetId = sharedSetId;
        SharedCriterionOperation operation = new SharedCriterionOperation();
        operation.@operator = Operator.ADD;
        operation.operand = sharedCriterion;
        operations.Add(operation);
      }

      SharedCriterionReturnValue retval = sharedSetService.mutate(operations.ToArray());
      return retval.value;
    }
    /// <summary>
    /// Creates a keyword for running further tests.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    /// <param name="adGroupId">The adgroup id for which the keyword is
    /// created.</param>
    /// <returns>The keyword id.</returns>
    public long CreateKeyword(AdWordsUser user, long adGroupId) {
      AdGroupCriterionService adGroupCriterionService =
         (AdGroupCriterionService) user.GetService(AdWordsService.v201406.AdGroupCriterionService);

      AdGroupCriterionOperation operation = new AdGroupCriterionOperation();
      operation.@operator = Operator.ADD;
      operation.operand = new BiddableAdGroupCriterion();
      operation.operand.adGroupId = adGroupId;

      Keyword keyword = new Keyword();
      keyword.matchType = KeywordMatchType.BROAD;
      keyword.text = "mars cruise";

      operation.operand.criterion = keyword;
      AdGroupCriterionReturnValue retVal =
          adGroupCriterionService.mutate(new AdGroupCriterionOperation[] {operation});
      return retVal.value[0].criterion.id;
    }
    /// <summary>
    /// Runs the code example.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    /// <param name="adGroupId">Id of the ad group to which keywords are added.
    /// </param>
    public void Run(AdWordsUser user, long adGroupId) {
      // Get the AdGroupCriterionService.
      AdGroupCriterionService adGroupCriterionService =
          (AdGroupCriterionService) user.GetService(AdWordsService.v201406.AdGroupCriterionService);

      // Set partial failure mode for the service.
      adGroupCriterionService.RequestHeader.partialFailure = true;

      List<AdGroupCriterionOperation> operations = new List<AdGroupCriterionOperation>();

      // Create the keywords.
      string[] keywords = new String[] {"mars cruise", "inv@lid cruise", "venus cruise",
          "b(a)d keyword cruise"};

      foreach (String keywordText in keywords) {
        Keyword keyword = new Keyword();
        keyword.text = keywordText;
        keyword.matchType = KeywordMatchType.BROAD;

        // Create biddable ad group criterion.
        BiddableAdGroupCriterion keywordBiddableAdGroupCriterion = new BiddableAdGroupCriterion();
        keywordBiddableAdGroupCriterion.adGroupId = adGroupId;
        keywordBiddableAdGroupCriterion.criterion = keyword;

        // Create the operation.
        AdGroupCriterionOperation keywordAdGroupCriterionOperation =
            new AdGroupCriterionOperation();
        keywordAdGroupCriterionOperation.operand = keywordBiddableAdGroupCriterion;
        keywordAdGroupCriterionOperation.@operator = Operator.ADD;
        operations.Add(keywordAdGroupCriterionOperation);
      }

      try {
        // Create the keywords.
        AdGroupCriterionReturnValue result = adGroupCriterionService.mutate(operations.ToArray());

        // Display the results.
        if (result != null && result.value != null) {
          foreach (AdGroupCriterion adGroupCriterionResult in result.value) {
            if (adGroupCriterionResult.criterion != null) {
              Console.WriteLine("Keyword with ad group id '{0}', criterion id '{1}', and " +
                  "text '{2}' was added.\n", adGroupCriterionResult.adGroupId,
                  adGroupCriterionResult.criterion.id,
                  ((Keyword) adGroupCriterionResult.criterion).text);
            }
          }
        } else {
          Console.WriteLine("No keywords were added.");
        }

        // Display the partial failure errors.
        if (result != null && result.partialFailureErrors != null) {
          foreach (ApiError apiError in result.partialFailureErrors) {
            int operationIndex = ErrorUtilities.GetOperationIndex(apiError.fieldPath);
            if (operationIndex != -1) {
              AdGroupCriterion adGroupCriterion = operations[operationIndex].operand;
              Console.WriteLine("Keyword with ad group id '{0}' and text '{1}' "
                  + "triggered a failure for the following reason: '{2}'.\n",
                  adGroupCriterion.adGroupId, ((Keyword) adGroupCriterion.criterion).text,
                  apiError.errorString);
            } else {
              Console.WriteLine("A failure for the following reason: '{0}' has occurred.\n",
                  apiError.errorString);
            }
          }
        }
      } catch (Exception ex) {
        throw new System.ApplicationException("Failed to add keywords in partial failure mode.",
            ex);
      }
    }
    /// <summary>
    /// Runs the code example.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    /// <param name="adGroupId">Id of the ad groups to which keywords are
    /// added.</param>
    public void Run(AdWordsUser user, long adGroupId) {
      // Get the MutateJobService.
      MutateJobService mutateJobService = (MutateJobService) user.GetService(
          AdWordsService.v201406.MutateJobService);

      const int RETRY_INTERVAL = 30;
      const int RETRIES_COUNT = 30;
      const int KEYWORD_NUMBER = 100;
      const string INDEX_REGEX = "operations\\[(\\d+)\\].operand";

      List<Operation> operations = new List<Operation>();

      // Create AdGroupCriterionOperation to add keywords.
      for (int i = 0; i < KEYWORD_NUMBER; i++) {
        Keyword keyword = new Keyword();
        keyword.text = string.Format("mars cruise {0}", i);
        keyword.matchType = KeywordMatchType.BROAD;

        BiddableAdGroupCriterion criterion = new BiddableAdGroupCriterion();
        criterion.adGroupId = adGroupId;
        criterion.criterion = keyword;

        AdGroupCriterionOperation adGroupCriterionOperation = new AdGroupCriterionOperation();
        adGroupCriterionOperation.@operator = Operator.ADD;
        adGroupCriterionOperation.operand = criterion;

        operations.Add(adGroupCriterionOperation);
      }

      BulkMutateJobPolicy policy = new BulkMutateJobPolicy();
      // You can specify up to 3 job IDs that must successfully complete before
      // this job can be processed.
      policy.prerequisiteJobIds = new long[] {};

      SimpleMutateJob job = mutateJobService.mutate(operations.ToArray(), policy);

      // Wait for the job to complete.
      bool completed = false;
      int retryCount = 0;
      Console.WriteLine("Retrieving job status...");

      while (completed == false && retryCount < RETRIES_COUNT) {
        BulkMutateJobSelector selector = new BulkMutateJobSelector();
        selector.jobIds = new long[] {job.id};

        try {
          Job[] allJobs = mutateJobService.get(selector);
          if (allJobs != null && allJobs.Length > 0) {
            job = (SimpleMutateJob) allJobs[0];
            if (job.status == BasicJobStatus.COMPLETED || job.status == BasicJobStatus.FAILED) {
              completed = true;
              break;
            } else {
              Console.WriteLine("{0}: Current status is {1}, waiting {2} seconds to retry...",
                  retryCount, job.status, RETRY_INTERVAL);
              Thread.Sleep(RETRY_INTERVAL * 1000);
              retryCount++;
            }
          }
        } catch (Exception ex) {
          throw new System.ApplicationException("Failed to fetch simple mutate job with " +
              "id = {0}.", ex);
        }
      }

      if (job.status == BasicJobStatus.COMPLETED) {
        // Handle cases where the job completed.

        // Create the job selector.
        BulkMutateJobSelector selector = new BulkMutateJobSelector();
        selector.jobIds = new long[] {job.id};

        // Get the job results.
        JobResult jobResult = mutateJobService.getResult(selector);
        if (jobResult != null) {
          SimpleMutateResult results = (SimpleMutateResult) jobResult.Item;
          if (results != null) {
            // Display the results.
            if (results.results != null) {
              for (int i = 0; i < results.results.Length; i++) {
                Operand operand = results.results[i];
                Console.WriteLine("Operation {0} - {1}", i, (operand.Item is PlaceHolder) ?
                    "FAILED" : "SUCCEEDED");
              }
            }

            // Display the errors.
            if (results.errors != null) {
              foreach (ApiError apiError in results.errors) {
                Match match = Regex.Match(apiError.fieldPath, INDEX_REGEX, RegexOptions.IgnoreCase);
                string index = (match.Success)? match.Groups[1].Value : "???";
                Console.WriteLine("Operation index {0} failed due to reason: '{1}', " +
                    "trigger: '{2}'", index, apiError.errorString, apiError.trigger);
              }
            }
          }
        }
        Console.WriteLine("Job completed successfully!");
      } else if (job.status == BasicJobStatus.FAILED) {
        // Handle the cases where job failed.
        Console.WriteLine("Job failed with reason: " + job.failureReason);
      } else if (job.status == BasicJobStatus.PROCESSING || job.status == BasicJobStatus.PENDING) {
        // Handle the cases where job didn't complete after wait period.
        Console.WriteLine("Job did not complete in {0} secconds.", RETRY_INTERVAL * RETRIES_COUNT);
      }
    }
    /// <summary>
    /// Runs the code example.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    /// <param name="campaignId">Id of the campaign to which targeting criteria
    /// are added.</param>
    public void Run(AdWordsUser user, long campaignId) {
      // Get the CampaignCriterionService.
      CampaignCriterionService campaignCriterionService =
          (CampaignCriterionService) user.GetService(
              AdWordsService.v201406.CampaignCriterionService);

      // Create language criteria.
      // See http://code.google.com/apis/adwords/docs/appendix/languagecodes.html
      // for a detailed list of language codes.
      Language language1 = new Language();
      language1.id = 1002; // French
      CampaignCriterion languageCriterion1 = new CampaignCriterion();
      languageCriterion1.campaignId = campaignId;
      languageCriterion1.criterion = language1;

      Language language2 = new Language();
      language2.id = 1005; // Japanese
      CampaignCriterion languageCriterion2 = new CampaignCriterion();
      languageCriterion2.campaignId = campaignId;
      languageCriterion2.criterion = language2;

      // Target Tier 3 income group near Miami, Florida.
      LocationGroups incomeLocationGroups = new LocationGroups();

      IncomeOperand incomeOperand = new IncomeOperand();
      // Tiers are numbered 1-10, and represent 10% segments of earners.
      // For example, TIER_1 is the top 10%, TIER_2 is the 80-90%, etc.
      // Tiers 6 through 10 are grouped into TIER_6_TO_10.
      incomeOperand.tier = IncomeTier.TIER_3;

      GeoTargetOperand geoTargetOperand1 = new GeoTargetOperand();
      geoTargetOperand1.locations = new long[] {1015116}; // Miami, FL.

      incomeLocationGroups.matchingFunction = new Function();
      incomeLocationGroups.matchingFunction.lhsOperand =
          new FunctionArgumentOperand[] {incomeOperand};
      incomeLocationGroups.matchingFunction.@operator = FunctionOperator.AND;
      incomeLocationGroups.matchingFunction.rhsOperand =
          new FunctionArgumentOperand[] {geoTargetOperand1};

      CampaignCriterion locationGroupCriterion1 = new CampaignCriterion();
      locationGroupCriterion1.campaignId = campaignId;
      locationGroupCriterion1.criterion = incomeLocationGroups;

      // Target places of interest near Downtown Miami, Florida.
      LocationGroups interestLocationGroups = new LocationGroups();

      PlacesOfInterestOperand placesOfInterestOperand = new PlacesOfInterestOperand();
      placesOfInterestOperand.category = PlacesOfInterestOperandCategory.DOWNTOWN;

      GeoTargetOperand geoTargetOperand2 = new GeoTargetOperand();
      geoTargetOperand2.locations = new long[] { 1015116 }; // Miami, FL.

      interestLocationGroups.matchingFunction = new Function();
      interestLocationGroups.matchingFunction.lhsOperand =
          new FunctionArgumentOperand[] {placesOfInterestOperand};
      interestLocationGroups.matchingFunction.@operator = FunctionOperator.AND;
      interestLocationGroups.matchingFunction.rhsOperand =
          new FunctionArgumentOperand[] {geoTargetOperand2};

      CampaignCriterion locationGroupCriterion2 = new CampaignCriterion();
      locationGroupCriterion2.campaignId = campaignId;
      locationGroupCriterion2.criterion = interestLocationGroups;

      // Distance targeting. Area of 10 miles around targets above.
      ConstantOperand radius = new ConstantOperand();
      radius.type = ConstantOperandConstantType.DOUBLE;
      radius.unit = ConstantOperandUnit.MILES;
      radius.doubleValue = 10.0;
      LocationExtensionOperand distance = new LocationExtensionOperand();
      distance.radius = radius;

      LocationGroups radiusLocationGroups = new LocationGroups();
      radiusLocationGroups.matchingFunction = new Function();
      radiusLocationGroups.matchingFunction.@operator = FunctionOperator.IDENTITY;
      radiusLocationGroups.matchingFunction.lhsOperand = new FunctionArgumentOperand[] { distance };

      CampaignCriterion locationGroupCriterion3 = new CampaignCriterion();
      locationGroupCriterion3.campaignId = campaignId;
      locationGroupCriterion3.criterion = radiusLocationGroups;

      // Create location criteria.
      // See http://code.google.com/apis/adwords/docs/appendix/countrycodes.html
      // for a detailed list of country codes.
      Location location1 = new Location();
      location1.id = 2840; // USA
      CampaignCriterion locationCriterion1 = new CampaignCriterion();
      locationCriterion1.campaignId = campaignId;
      locationCriterion1.criterion = location1;

      Location location2 = new Location();
      location2.id = 2392; // Japan
      CampaignCriterion locationCriterion2 = new CampaignCriterion();
      locationCriterion2.campaignId = campaignId;
      locationCriterion2.criterion = location2;

      // Add a negative campaign keyword.
      NegativeCampaignCriterion negativeCriterion = new NegativeCampaignCriterion();
      negativeCriterion.campaignId = campaignId;

      Keyword keyword = new Keyword();
      keyword.matchType = KeywordMatchType.BROAD;
      keyword.text = "jupiter cruise";

      negativeCriterion.criterion = keyword;

      CampaignCriterion[] criteria = new CampaignCriterion[] {languageCriterion1,
          languageCriterion2, locationCriterion1, locationCriterion2, negativeCriterion,
          locationGroupCriterion1, locationGroupCriterion2, locationGroupCriterion3};

      List<CampaignCriterionOperation> operations = new List<CampaignCriterionOperation>();

      foreach (CampaignCriterion criterion in criteria) {
        CampaignCriterionOperation operation = new CampaignCriterionOperation();
        operation.@operator = Operator.ADD;
        operation.operand = criterion;
        operations.Add(operation);
      }

      try {
        // Set the campaign targets.
        CampaignCriterionReturnValue retVal = campaignCriterionService.mutate(operations.ToArray());

        if (retVal != null && retVal.value != null) {
          // Display campaign targets.
          foreach (CampaignCriterion criterion in retVal.value) {
            Console.WriteLine("Campaign criteria of type '{0}' was set to campaign with" +
                " id = '{1}'.", criterion.criterion.CriterionType, criterion.campaignId);
          }
        }
      } catch (Exception ex) {
        throw new System.ApplicationException("Failed to set Campaign criteria.", ex);
      }
    }
    /// <summary>
    /// Runs the code example.
    /// </summary>
    /// <param name="user">The AdWords user.</param>
    public void Run(AdWordsUser user) {
      // Get the TrafficEstimatorService.
      TrafficEstimatorService trafficEstimatorService = (TrafficEstimatorService) user.GetService(
          AdWordsService.v201406.TrafficEstimatorService);

      // Create keywords. Up to 2000 keywords can be passed in a single request.
      Keyword keyword1 = new Keyword();
      keyword1.text = "mars cruise";
      keyword1.matchType = KeywordMatchType.BROAD;

      Keyword keyword2 = new Keyword();
      keyword2.text = "cheap cruise";
      keyword2.matchType = KeywordMatchType.PHRASE;

      Keyword keyword3 = new Keyword();
      keyword3.text = "cruise";
      keyword3.matchType = KeywordMatchType.EXACT;

      Keyword[] keywords = new Keyword[] {keyword1, keyword2, keyword3};

      // Create a keyword estimate request for each keyword.
      List<KeywordEstimateRequest> keywordEstimateRequests = new List<KeywordEstimateRequest>();

      foreach (Keyword keyword in keywords) {
        KeywordEstimateRequest keywordEstimateRequest = new KeywordEstimateRequest();
        keywordEstimateRequest.keyword = keyword;
        keywordEstimateRequests.Add(keywordEstimateRequest);
      }

      // Create negative keywords.
      Keyword negativeKeyword1 = new Keyword();
      negativeKeyword1.text = "moon walk";
      negativeKeyword1.matchType = KeywordMatchType.BROAD;

      KeywordEstimateRequest negativeKeywordEstimateRequest = new KeywordEstimateRequest();
      negativeKeywordEstimateRequest.keyword = negativeKeyword1;
      negativeKeywordEstimateRequest.isNegative = true;
      keywordEstimateRequests.Add(negativeKeywordEstimateRequest);

      // Create ad group estimate requests.
      AdGroupEstimateRequest adGroupEstimateRequest = new AdGroupEstimateRequest();
      adGroupEstimateRequest.keywordEstimateRequests = keywordEstimateRequests.ToArray();
      adGroupEstimateRequest.maxCpc = new Money();
      adGroupEstimateRequest.maxCpc.microAmount = 1000000;

      // Create campaign estimate requests.
      CampaignEstimateRequest campaignEstimateRequest = new CampaignEstimateRequest();
      campaignEstimateRequest.adGroupEstimateRequests = new AdGroupEstimateRequest[] {
          adGroupEstimateRequest};

      // See http://code.google.com/apis/adwords/docs/appendix/countrycodes.html
      // for a detailed list of country codes.
      Location countryCriterion = new Location();
      countryCriterion.id = 2840; //US

      // See http://code.google.com/apis/adwords/docs/appendix/languagecodes.html
      // for a detailed list of language codes.
      Language languageCriterion = new Language();
      languageCriterion.id = 1000; //en

      campaignEstimateRequest.criteria = new Criterion[] {countryCriterion, languageCriterion};

      // Create the selector.
      TrafficEstimatorSelector selector = new TrafficEstimatorSelector();
      selector.campaignEstimateRequests = new CampaignEstimateRequest[] {campaignEstimateRequest};

      try {
        // Get traffic estimates.
        TrafficEstimatorResult result = trafficEstimatorService.get(selector);

        // Display traffic estimates.
        if (result != null && result.campaignEstimates != null &&
            result.campaignEstimates.Length > 0) {
          CampaignEstimate campaignEstimate = result.campaignEstimates[0];
          if (campaignEstimate.adGroupEstimates != null &&
              campaignEstimate.adGroupEstimates.Length > 0) {
            AdGroupEstimate adGroupEstimate = campaignEstimate.adGroupEstimates[0];

            if (adGroupEstimate.keywordEstimates != null) {
              for (int i = 0; i < adGroupEstimate.keywordEstimates.Length; i++) {
                Keyword keyword = keywordEstimateRequests[i].keyword;
                KeywordEstimate keywordEstimate = adGroupEstimate.keywordEstimates[i];

                if (keywordEstimateRequests[i].isNegative) {
                  continue;
                }

                // Find the mean of the min and max values.
                long meanAverageCpc = (keywordEstimate.min.averageCpc.microAmount
                    + keywordEstimate.max.averageCpc.microAmount) / 2;
                double meanAveragePosition = (keywordEstimate.min.averagePosition
                    + keywordEstimate.max.averagePosition) / 2;
                float meanClicks = (keywordEstimate.min.clicksPerDay
                   + keywordEstimate.max.clicksPerDay) / 2;
                long meanTotalCost = (keywordEstimate.min.totalCost.microAmount
                   + keywordEstimate.max.totalCost.microAmount) / 2;

               Console.WriteLine("Results for the keyword with text = '{0}' and match type = " +
                    "'{1}':", keyword.text, keyword.matchType);
               Console.WriteLine("  Estimated average CPC: {0}", meanAverageCpc);
               Console.WriteLine("  Estimated ad position: {0:0.00}", meanAveragePosition);
               Console.WriteLine("  Estimated daily clicks: {0}", meanClicks);
               Console.WriteLine("  Estimated daily cost: {0}", meanTotalCost);
              }
            }
          }
        } else {
          Console.WriteLine("No traffic estimates were returned.\n");
        }
      } catch (Exception ex) {
        throw new System.ApplicationException("Failed to retrieve traffic estimates.", ex);
      }
    }
      /// <summary>
      /// Main method for the thread.
      /// </summary>
      /// <param name="obj">The thread parameter.</param>
      public void Run(Object obj) {
        // Create the operations.
        List<AdGroupCriterionOperation> operations = new List<AdGroupCriterionOperation>();

        for (int j = 0; j < NUM_KEYWORDS; j++) {
          // Create the keyword.
          Keyword keyword = new Keyword();
          keyword.text = "mars cruise thread " + threadIndex.ToString() + " seed " + j.ToString();
          keyword.matchType = KeywordMatchType.BROAD;

          // Create the biddable ad group criterion.
          AdGroupCriterion keywordCriterion = new BiddableAdGroupCriterion();
          keywordCriterion.adGroupId = adGroupId;
          keywordCriterion.criterion = keyword;

          // Create the operations.
          AdGroupCriterionOperation keywordOperation = new AdGroupCriterionOperation();
          keywordOperation.@operator = Operator.ADD;
          keywordOperation.operand = keywordCriterion;

          operations.Add(keywordOperation);
        }

        // Get the AdGroupCriterionService. This should be done within the
        // thread, since a service can only handle one outgoing HTTP request
        // at a time.
        AdGroupCriterionService service = (AdGroupCriterionService) user.GetService(
            AdWordsService.v201406.AdGroupCriterionService);
        service.RequestHeader.validateOnly = true;
        int retryCount = 0;
        const int NUM_RETRIES = 3;
        try {
          while (retryCount < NUM_RETRIES) {
            try {
              // Validate the keywords.
              AdGroupCriterionReturnValue retval = service.mutate(operations.ToArray());
              break;
            } catch (AdWordsApiException ex) {
              // Handle API errors.
              ApiException innerException = ex.ApiException as ApiException;
              if (innerException == null) {
                throw new Exception("Failed to retrieve ApiError. See inner exception for more " +
                    "details.", ex);
              }
              foreach (ApiError apiError in innerException.errors) {
                if (!(apiError is RateExceededError)) {
                  // Rethrow any errors other than RateExceededError.
                  throw;
                }
                // Handle rate exceeded errors.
                RateExceededError rateExceededError = (RateExceededError) apiError;
                Console.WriteLine("Got Rate exceeded error - rate name = '{0}', scope = '{1}', " +
                    "retry After {2} seconds.", rateExceededError.rateScope,
                    rateExceededError.rateName, rateExceededError.retryAfterSeconds);
                Thread.Sleep(rateExceededError.retryAfterSeconds * 1000);
                retryCount = retryCount + 1;
              }
            } finally {
              if (retryCount == NUM_RETRIES) {
                throw new Exception(String.Format("Could not recover after making {0} attempts.",
                    retryCount));
              }
            }
          }
        } catch (Exception ex) {
          throw new System.ApplicationException("Failed to validate keywords.", ex);
        }
      }