// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package adwords.axis.v201609.campaignmanagement;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201609.SelectorBuilder;
import com.google.api.ads.adwords.axis.utils.v201609.batchjob.BatchJobHelper;
import com.google.api.ads.adwords.axis.utils.v201609.batchjob.BatchJobMutateResponse;
import com.google.api.ads.adwords.axis.utils.v201609.batchjob.MutateResult;
import com.google.api.ads.adwords.axis.v201609.cm.AdGroupCriterionOperation;
import com.google.api.ads.adwords.axis.v201609.cm.ApiException;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJob;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJobError;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJobErrorReason;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJobOperation;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJobProcessingError;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJobServiceInterface;
import com.google.api.ads.adwords.axis.v201609.cm.BatchJobStatus;
import com.google.api.ads.adwords.axis.v201609.cm.BiddableAdGroupCriterion;
import com.google.api.ads.adwords.axis.v201609.cm.Keyword;
import com.google.api.ads.adwords.axis.v201609.cm.KeywordMatchType;
import com.google.api.ads.adwords.axis.v201609.cm.Operator;
import com.google.api.ads.adwords.axis.v201609.cm.Selector;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.selectorfields.v201609.cm.BatchJobField;
import com.google.api.ads.adwords.lib.utils.BatchJobUploadResponse;
import com.google.api.ads.adwords.lib.utils.BatchJobUploadStatus;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.client.auth.oauth2.Credential;
import com.google.common.collect.Sets;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
/**
* This code sample illustrates how to perform asynchronous requests using BatchJobService
* and incremental uploads of operations. It also demonstrates how to cancel a running
* batch job.
*
* <p>Credentials and properties in {@code fromFile()} are pulled from the
* "ads.properties" file. See README for more info.
*/
public class AddKeywordsUsingIncrementalBatchJob {
private static final long NUMBER_OF_KEYWORDS_TO_ADD = 100;
private static final int KEYWORDS_PER_UPLOAD = 10;
private static final int MAX_POLL_ATTEMPTS = 5;
private static final Set<BatchJobStatus> PENDING_STATUSES =
Sets.newHashSet(
BatchJobStatus.ACTIVE, BatchJobStatus.AWAITING_FILE, BatchJobStatus.CANCELING);
public static void main(String[] args) throws Exception {
// Generate a refreshable OAuth2 credential.
Credential oAuth2Credential = new OfflineCredentials.Builder()
.forApi(Api.ADWORDS)
.fromFile()
.build()
.generateCredential();
// Construct an AdWordsSession.
AdWordsSession session = new AdWordsSession.Builder()
.fromFile()
.withOAuth2Credential(oAuth2Credential)
.build();
long adGroupId = Long.parseLong("INSERT_AD_GROUP_ID_HERE");
AdWordsServices adWordsServices = new AdWordsServices();
runExample(adWordsServices, session, adGroupId);
}
public static void runExample(
AdWordsServices adWordsServices, AdWordsSession session, Long adGroupId) throws Exception {
// Get the BatchJobService.
BatchJobServiceInterface batchJobService =
adWordsServices.get(session, BatchJobServiceInterface.class);
BatchJobOperation addOp = new BatchJobOperation();
addOp.setOperator(Operator.ADD);
addOp.setOperand(new BatchJob());
BatchJob batchJob = batchJobService.mutate(new BatchJobOperation[] {addOp}).getValue(0);
System.out.printf("Created BatchJob with ID %d, status '%s' and upload URL %s.%n",
batchJob.getId(), batchJob.getStatus(), batchJob.getUploadUrl().getUrl());
// Create a BatchJobHelper for uploading operations.
BatchJobHelper batchJobHelper = new BatchJobHelper(session);
BatchJobUploadStatus batchJobUploadStatus = new BatchJobUploadStatus(0,
URI.create(batchJob.getUploadUrl().getUrl()));
List<AdGroupCriterionOperation> operations = new ArrayList<AdGroupCriterionOperation>();
// Create AdGroupCriterionOperations to add keywords, and upload every 10 operations
// incrementally.
for (int i = 0; i < NUMBER_OF_KEYWORDS_TO_ADD; i++) {
// Create Keyword.
String text = String.format("mars%d", i);
// Make 10% of keywords invalid to demonstrate error handling.
if (i % 10 == 0) {
text = text + "!!!";
}
Keyword keyword = new Keyword();
keyword.setText(text);
keyword.setMatchType(KeywordMatchType.BROAD);
// Create BiddableAdGroupCriterion.
BiddableAdGroupCriterion bagc = new BiddableAdGroupCriterion();
bagc.setAdGroupId(adGroupId);
bagc.setCriterion(keyword);
// Create AdGroupCriterionOperation.
AdGroupCriterionOperation agco = new AdGroupCriterionOperation();
agco.setOperand(bagc);
agco.setOperator(Operator.ADD);
// Add to the list of operations.
operations.add(agco);
// If the current list of operations has reached KEYWORDS_PER_UPLOAD or this is the last
// operation, upload the current list of operations.
boolean isLastOperation = i == NUMBER_OF_KEYWORDS_TO_ADD - 1;
if (operations.size() == KEYWORDS_PER_UPLOAD || isLastOperation) {
BatchJobUploadResponse uploadResponse =
batchJobHelper.uploadIncrementalBatchJobOperations(
operations, isLastOperation, batchJobUploadStatus);
System.out.printf("Uploaded %d operations for batch job with ID %d.%n",
operations.size(), batchJob.getId());
// Set the batch job upload status and clear the operations list in preparation for the
// next upload.
batchJobUploadStatus = uploadResponse.getBatchJobUploadStatus();
operations.clear();
}
}
// Poll for completion of the batch job using an exponential back off.
int pollAttempts = 0;
boolean isPending = true;
boolean wasCancelRequested = false;
Selector selector =
new SelectorBuilder()
.fields(
BatchJobField.Id,
BatchJobField.Status,
BatchJobField.DownloadUrl,
BatchJobField.ProcessingErrors,
BatchJobField.ProgressStats)
.equalsId(batchJob.getId())
.build();
do {
long sleepSeconds = (long) Math.scalb(30, pollAttempts);
System.out.printf("Sleeping %d seconds...%n", sleepSeconds);
Thread.sleep(sleepSeconds * 1000);
batchJob = batchJobService.get(selector).getEntries(0);
System.out.printf(
"Batch job ID %d has status '%s'.%n", batchJob.getId(), batchJob.getStatus());
pollAttempts++;
isPending = PENDING_STATUSES.contains(batchJob.getStatus());
// Optional: Cancel the job if it has not completed after polling MAX_POLL_ATTEMPTS
// times.
if (isPending && !wasCancelRequested && pollAttempts == MAX_POLL_ATTEMPTS) {
batchJob.setStatus(BatchJobStatus.CANCELING);
BatchJobOperation batchJobSetOperation = new BatchJobOperation();
batchJobSetOperation.setOperand(batchJob);
batchJobSetOperation.setOperator(Operator.SET);
// Only request cancellation once per job.
wasCancelRequested = true;
try {
batchJob =
batchJobService.mutate(new BatchJobOperation[] {batchJobSetOperation}).getValue(0);
System.out.printf("Requested cancellation of batch job with ID %d.%n", batchJob.getId());
} catch (ApiException e) {
if (e.getErrors() != null
&& e.getErrors().length > 0
&& e.getErrors(0) instanceof BatchJobError) {
BatchJobError batchJobError = (BatchJobError) e.getErrors(0);
if (BatchJobErrorReason.INVALID_STATE_CHANGE.equals(batchJobError.getReason())) {
System.out.printf(
"Attempt to cancel batch job with ID %d was rejected because the job already "
+ "completed or was canceled.",
batchJob.getId());
continue;
}
}
throw e;
} finally {
// Reset the poll attempt counter to wait for cancellation.
pollAttempts = 0;
}
}
} while (isPending && pollAttempts < MAX_POLL_ATTEMPTS);
if (isPending) {
throw new TimeoutException(
"Job is still in pending state after polling " + MAX_POLL_ATTEMPTS + " times.");
}
if (batchJob.getProcessingErrors() != null) {
int errorIndex = 0;
for (BatchJobProcessingError processingError : batchJob.getProcessingErrors()) {
System.out.printf(
" Processing error [%d]: errorType=%s, trigger=%s, errorString=%s, fieldPath=%s"
+ ", reason=%s%n",
errorIndex++,
processingError.getApiErrorType(),
processingError.getTrigger(),
processingError.getErrorString(),
processingError.getFieldPath(),
processingError.getReason());
}
} else {
System.out.println("No processing errors found.");
}
if (batchJob.getDownloadUrl() != null && batchJob.getDownloadUrl().getUrl() != null) {
BatchJobMutateResponse mutateResponse =
batchJobHelper.downloadBatchJobMutateResponse(batchJob.getDownloadUrl().getUrl());
System.out.printf("Downloaded results from %s:%n", batchJob.getDownloadUrl().getUrl());
for (MutateResult mutateResult : mutateResponse.getMutateResults()) {
String outcome = mutateResult.getErrorList() == null ? "SUCCESS" : "FAILURE";
System.out.printf(" Operation [%d] - %s%n", mutateResult.getIndex(), outcome);
}
} else {
System.out.println("No results available for download.");
}
}
}