|
24 | 24 | import com.google.ads.googleads.v5.common.UserData; |
25 | 25 | import com.google.ads.googleads.v5.common.UserIdentifier; |
26 | 26 | import com.google.ads.googleads.v5.enums.CustomerMatchUploadKeyTypeEnum.CustomerMatchUploadKeyType; |
| 27 | +import com.google.ads.googleads.v5.enums.OfflineUserDataJobStatusEnum.OfflineUserDataJobStatus; |
27 | 28 | import com.google.ads.googleads.v5.enums.OfflineUserDataJobTypeEnum.OfflineUserDataJobType; |
28 | 29 | import com.google.ads.googleads.v5.errors.GoogleAdsError; |
29 | 30 | import com.google.ads.googleads.v5.errors.GoogleAdsException; |
|
41 | 42 | import com.google.ads.googleads.v5.services.SearchGoogleAdsStreamResponse; |
42 | 43 | import com.google.ads.googleads.v5.services.UserListOperation; |
43 | 44 | import com.google.ads.googleads.v5.services.UserListServiceClient; |
44 | | -import com.google.api.gax.longrunning.OperationFuture; |
45 | 45 | import com.google.api.gax.rpc.ServerStream; |
46 | 46 | import com.google.common.collect.ImmutableList; |
47 | 47 | import com.google.protobuf.BoolValue; |
48 | | -import com.google.protobuf.Empty; |
49 | 48 | import com.google.protobuf.Int64Value; |
50 | 49 | import com.google.protobuf.StringValue; |
51 | 50 | import java.io.FileNotFoundException; |
|
56 | 55 | import java.util.ArrayList; |
57 | 56 | import java.util.Arrays; |
58 | 57 | import java.util.List; |
59 | | -import java.util.concurrent.ExecutionException; |
60 | | -import java.util.concurrent.TimeUnit; |
61 | | -import java.util.concurrent.TimeoutException; |
62 | 58 |
|
63 | 59 | /** |
64 | 60 | * Creates a user list (a.k.a. audience) and uploads members to populate the list. |
65 | 61 | * |
66 | 62 | * <p><em>Notes:</em> |
67 | 63 | * |
68 | 64 | * <ul> |
69 | | - * <li>This feature is only available to whitelisted accounts. See |
| 65 | + * <li>This feature is only available to allowlisted accounts. See |
70 | 66 | * https://support.google.com/adspolicy/answer/6299717 for more details. |
71 | 67 | * <li>It may take up to several hours for the list to be populated with members. |
72 | 68 | * <li>Email addresses must be associated with a Google account. |
|
76 | 72 | */ |
77 | 73 | public class AddCustomerMatchUserList { |
78 | 74 |
|
79 | | - private static final long MAX_TOTAL_POLL_INTERVAL_SECONDS = 60L; |
80 | | - |
81 | 75 | private static class AddCustomerMatchUserListParams extends CodeSampleParams { |
82 | 76 |
|
83 | 77 | @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true) |
84 | 78 | private Long customerId; |
85 | 79 | } |
86 | 80 |
|
87 | 81 | public static void main(String[] args) |
88 | | - throws InterruptedException, ExecutionException, TimeoutException, |
89 | | - UnsupportedEncodingException { |
| 82 | + throws UnsupportedEncodingException { |
90 | 83 | AddCustomerMatchUserListParams params = new AddCustomerMatchUserListParams(); |
91 | 84 | if (!params.parseArguments(args)) { |
92 | 85 |
|
@@ -133,16 +126,12 @@ public static void main(String[] args) |
133 | 126 | * @throws GoogleAdsException if an API request failed with one or more service errors. |
134 | 127 | */ |
135 | 128 | private void runExample(GoogleAdsClient googleAdsClient, long customerId) |
136 | | - throws InterruptedException, ExecutionException, TimeoutException, |
137 | | - UnsupportedEncodingException { |
| 129 | + throws UnsupportedEncodingException { |
138 | 130 | // Creates a Customer Match user list. |
139 | 131 | String userListResourceName = createCustomerMatchUserList(googleAdsClient, customerId); |
140 | 132 |
|
141 | 133 | // Adds members to the user list. |
142 | 134 | addUsersToCustomerMatchUserList(googleAdsClient, customerId, userListResourceName); |
143 | | - |
144 | | - // Prints information about the user list. |
145 | | - printCustomerMatchUserListInfo(googleAdsClient, customerId, userListResourceName); |
146 | 135 | } |
147 | 136 |
|
148 | 137 | /** |
@@ -193,13 +182,12 @@ private String createCustomerMatchUserList(GoogleAdsClient googleAdsClient, long |
193 | 182 | * |
194 | 183 | * @param googleAdsClient the Google Ads API client. |
195 | 184 | * @param customerId the client customer ID. |
196 | | - * @param userListResourceName the resource name of the Customer Match user list to add members |
| 185 | + * @param userListResourceName the resource name of the Customer Match user list to add members. |
197 | 186 | * to. |
198 | 187 | */ |
199 | 188 | private void addUsersToCustomerMatchUserList( |
200 | 189 | GoogleAdsClient googleAdsClient, long customerId, String userListResourceName) |
201 | | - throws InterruptedException, ExecutionException, TimeoutException, |
202 | | - UnsupportedEncodingException { |
| 190 | + throws UnsupportedEncodingException { |
203 | 191 | try (OfflineUserDataJobServiceClient offlineUserDataJobServiceClient = |
204 | 192 | googleAdsClient.getLatestVersion().createOfflineUserDataJobServiceClient()) { |
205 | 193 | // Creates a new offline user data job. |
@@ -245,21 +233,14 @@ private void addUsersToCustomerMatchUserList( |
245 | 233 | System.out.printf( |
246 | 234 | "Successfully added %d operations to the offline user data job.%n", |
247 | 235 | userDataJobOperations.size()); |
248 | | - } |
249 | | - |
250 | | - // Issues an asynchronous request to run the offline user data job for executing all added |
251 | | - // operations. |
252 | | - OperationFuture<Empty, Empty> runFuture = |
253 | | - offlineUserDataJobServiceClient.runOfflineUserDataJobAsync( |
254 | | - offlineUserDataJobResourceName); |
255 | | - System.out.println("Asynchronous request to execute the added operations started."); |
256 | | - System.out.println("Waiting until operation completes."); |
257 | 236 |
|
258 | | - // The polling future implements a default back-off policy for retrying. |
259 | | - runFuture.getPollingFuture().get(MAX_TOTAL_POLL_INTERVAL_SECONDS, TimeUnit.SECONDS); |
260 | | - System.out.printf( |
261 | | - "Offline user data job with resource name '%s' has finished.%n", |
262 | | - offlineUserDataJobResourceName); |
| 237 | + // Offline user data jobs may take up to 24 hours to complete, so instead of waiting for the |
| 238 | + // job to complete, retrieves and displays the job status once. If the job is completed |
| 239 | + // successfully, prints information about the user list. Otherwise, prints the query to use |
| 240 | + // to check the job again later. |
| 241 | + checkJobStatus( |
| 242 | + googleAdsClient, customerId, offlineUserDataJobResourceName, userListResourceName); |
| 243 | + } |
263 | 244 | } |
264 | 245 | } |
265 | 246 |
|
@@ -310,6 +291,85 @@ private List<OfflineUserDataJobOperation> buildOfflineUserDataJobOperations() |
310 | 291 | return operations; |
311 | 292 | } |
312 | 293 |
|
| 294 | + /** |
| 295 | + * Returns the result of normalizing and then hashing the string using the provided digest. |
| 296 | + * Private customer data must be hashed during upload, as described at |
| 297 | + * https://support.google.com/google-ads/answer/7474263. |
| 298 | + * |
| 299 | + * @param digest the digest to use to hash the normalized string. |
| 300 | + * @param s the string to normalize and hash. |
| 301 | + */ |
| 302 | + private String normalizeAndHash(MessageDigest digest, String s) |
| 303 | + throws UnsupportedEncodingException { |
| 304 | + // Normalizes by removing leading and trailing whitespace and converting all characters to |
| 305 | + // lower case. |
| 306 | + String normalized = s.trim().toLowerCase(); |
| 307 | + // Hashes the normalized string using the hashing algorithm. |
| 308 | + byte[] hash = digest.digest(normalized.getBytes("UTF-8")); |
| 309 | + StringBuilder result = new StringBuilder(); |
| 310 | + for (byte b : hash) { |
| 311 | + result.append(String.format("%02x", b)); |
| 312 | + } |
| 313 | + |
| 314 | + return result.toString(); |
| 315 | + } |
| 316 | + |
| 317 | + /** |
| 318 | + * Retrieves, checks, and prints the status of the offline user data job. |
| 319 | + * |
| 320 | + * @param googleAdsClient the Google Ads API client. |
| 321 | + * @param customerId the client customer ID. |
| 322 | + * @param offlineUserDataJobResourceName the resource name of the OfflineUserDataJob to get the |
| 323 | + * status for. |
| 324 | + * @param userListResourceName the resource name of the Customer Match user list. |
| 325 | + */ |
| 326 | + private void checkJobStatus( |
| 327 | + GoogleAdsClient googleAdsClient, |
| 328 | + long customerId, |
| 329 | + String offlineUserDataJobResourceName, |
| 330 | + String userListResourceName) { |
| 331 | + try (GoogleAdsServiceClient googleAdsServiceClient = |
| 332 | + googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { |
| 333 | + String query = |
| 334 | + String.format( |
| 335 | + "SELECT offline_user_data_job.resource_name, " |
| 336 | + + "offline_user_data_job.id, " |
| 337 | + + "offline_user_data_job.status, " |
| 338 | + + "offline_user_data_job.type, " |
| 339 | + + "offline_user_data_job.failure_reason " |
| 340 | + + "FROM offline_user_data_job " |
| 341 | + + "WHERE offline_user_data_job.resource_name = '%s'", |
| 342 | + offlineUserDataJobResourceName); |
| 343 | + // Issues the query and gets the GoogleAdsRow containing the job from the response. |
| 344 | + GoogleAdsRow googleAdsRow = |
| 345 | + googleAdsServiceClient |
| 346 | + .search(Long.toString(customerId), query) |
| 347 | + .iterateAll() |
| 348 | + .iterator() |
| 349 | + .next(); |
| 350 | + OfflineUserDataJob offlineUserDataJob = googleAdsRow.getOfflineUserDataJob(); |
| 351 | + System.out.printf( |
| 352 | + "Offline user data job ID %d with type '%s' has status: %s%n", |
| 353 | + offlineUserDataJob.getId().getValue(), |
| 354 | + offlineUserDataJob.getType(), |
| 355 | + offlineUserDataJob.getStatus()); |
| 356 | + OfflineUserDataJobStatus jobStatus = offlineUserDataJob.getStatus(); |
| 357 | + if (OfflineUserDataJobStatus.SUCCESS == jobStatus) { |
| 358 | + // Prints information about the user list. |
| 359 | + printCustomerMatchUserListInfo(googleAdsClient, customerId, userListResourceName); |
| 360 | + } else if (OfflineUserDataJobStatus.FAILED == jobStatus) { |
| 361 | + System.out.printf(" Failure reason: %s%n", offlineUserDataJob.getFailureReason()); |
| 362 | + } else if (OfflineUserDataJobStatus.PENDING == jobStatus |
| 363 | + || OfflineUserDataJobStatus.RUNNING == jobStatus) { |
| 364 | + System.out.println(); |
| 365 | + System.out.printf( |
| 366 | + "To check the status of the job periodically, use the following GAQL query with" |
| 367 | + + " GoogleAdsService.search:%n%s%n", |
| 368 | + query); |
| 369 | + } |
| 370 | + } |
| 371 | + } |
| 372 | + |
313 | 373 | /** |
314 | 374 | * Prints information about the Customer Match user list. |
315 | 375 | * |
@@ -348,31 +408,6 @@ private void printCustomerMatchUserListInfo( |
348 | 408 | userList.getResourceName(), |
349 | 409 | userList.getSizeForDisplay().getValue(), |
350 | 410 | userList.getSizeForSearch().getValue()); |
351 | | - System.out.println( |
352 | | - "Reminder: It may take several hours for the user list to be populated with the users."); |
353 | 411 | } |
354 | 412 | } |
355 | | - |
356 | | - /** |
357 | | - * Returns the result of normalizing and then hashing the string using the provided digest. |
358 | | - * Private customer data must be hashed during upload, as described at |
359 | | - * https://support.google.com/google-ads/answer/7474263. |
360 | | - * |
361 | | - * @param digest the digest to use to hash the normalized string. |
362 | | - * @param s the string to normalize and hash. |
363 | | - */ |
364 | | - private String normalizeAndHash(MessageDigest digest, String s) |
365 | | - throws UnsupportedEncodingException { |
366 | | - // Normalizes by removing leading and trailing whitespace and converting all characters to |
367 | | - // lower case. |
368 | | - String normalized = s.trim().toLowerCase(); |
369 | | - // Hashes the normalized string using the hashing algorithm. |
370 | | - byte[] hash = digest.digest(normalized.getBytes("UTF-8")); |
371 | | - StringBuilder result = new StringBuilder(); |
372 | | - for (byte b : hash) { |
373 | | - result.append(String.format("%02x", b)); |
374 | | - } |
375 | | - |
376 | | - return result.toString(); |
377 | | - } |
378 | 413 | } |
0 commit comments