Skip to content

Commit bb75ca9

Browse files
authored
Add GetAccountHierarchy example (#269)
* Add GetAccountHierarchy example * Address comments and make managerId required * Make managerId optional and add optional loginCustomerId * Address comments * Address fiboknacky comments * Address comments * Handle unhandled exception * Remove createGoogleAdsClient method * Address comments * Docs fix * Address comment * Address nbirnie comments * Change managerAccountsToSearch to LinkedList * LinkedList to queue
1 parent 2ac2674 commit bb75ca9

2 files changed

Lines changed: 342 additions & 0 deletions

File tree

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.ads.googleads.examples.accountmanagement;
16+
17+
import com.beust.jcommander.Parameter;
18+
import com.google.ads.googleads.examples.utils.ArgumentNames;
19+
import com.google.ads.googleads.examples.utils.CodeSampleParams;
20+
import com.google.ads.googleads.lib.GoogleAdsClient;
21+
import com.google.ads.googleads.v3.errors.GoogleAdsError;
22+
import com.google.ads.googleads.v3.errors.GoogleAdsException;
23+
import com.google.ads.googleads.v3.resources.CustomerClient;
24+
import com.google.ads.googleads.v3.resources.CustomerName;
25+
import com.google.ads.googleads.v3.services.CustomerServiceClient;
26+
import com.google.ads.googleads.v3.services.GoogleAdsRow;
27+
import com.google.ads.googleads.v3.services.GoogleAdsServiceClient;
28+
import com.google.ads.googleads.v3.services.GoogleAdsServiceClient.SearchPagedResponse;
29+
import com.google.ads.googleads.v3.services.ListAccessibleCustomersRequest;
30+
import com.google.ads.googleads.v3.services.ListAccessibleCustomersResponse;
31+
import com.google.ads.googleads.v3.services.SearchGoogleAdsRequest;
32+
import com.google.common.base.Strings;
33+
import com.google.common.collect.ArrayListMultimap;
34+
import com.google.common.collect.Multimap;
35+
import java.io.FileNotFoundException;
36+
import java.io.IOException;
37+
import java.util.ArrayList;
38+
import java.util.HashMap;
39+
import java.util.LinkedList;
40+
import java.util.List;
41+
import java.util.Map;
42+
import java.util.Queue;
43+
44+
/**
45+
* Gets the account hierarchy of the specified manager account and login customer ID. If you don't
46+
* specify manager ID and login customer ID, the example will instead print the hierarchies of all
47+
* accessible customer accounts for your authenticated Google account. Note that if the list of
48+
* accessible customers for your authenticated Google account includes accounts within the same
49+
* hierarchy, this example will retrieve and print the overlapping portions of the hierarchy for
50+
* each accessible customer.
51+
*/
52+
public class GetAccountHierarchy {
53+
54+
private static class GetAccountHierarchyParams extends CodeSampleParams {
55+
56+
@Parameter(names = ArgumentNames.MANAGER_ID)
57+
private Long managerId;
58+
59+
@Parameter(names = ArgumentNames.LOGIN_CUSTOMER_ID)
60+
private Long loginCustomerId;
61+
}
62+
63+
public static void main(String[] args) {
64+
GetAccountHierarchyParams params = new GetAccountHierarchyParams();
65+
if (!params.parseArguments(args)) {
66+
67+
// Optional: You may pass the managerId on the command line or specify a managerId here. If
68+
// neither are set, a null value will be passed to the run example method, and the example
69+
// will print the hierarchies of all accessible customer IDs.
70+
// params.managerId = Long.parseLong("INSERT_MANAGER_ID_HERE");
71+
72+
// Optional: You may pass the loginCustomerId on the command line or specify a loginCustomerId
73+
// here if and only if the managerId is set. If the loginCustomerId is set neither on the
74+
// command line nor below, a null value will be passed to the run example method, and the
75+
// example will use each respective accessible customer ID as the loginCustomerId.
76+
// params.managerId = Long.parseLong("INSERT_LOGIN_CUSTOMER_ID_HERE");
77+
}
78+
79+
if (params.managerId != null && params.loginCustomerId == null) {
80+
System.err.println("loginCustomerId must be provided if managerId is provided.");
81+
return;
82+
} else if (params.managerId == null && params.loginCustomerId != null) {
83+
System.err.println("loginCustomerId may not be provided if managerId is not provided.");
84+
return;
85+
}
86+
87+
GoogleAdsClient googleAdsClient;
88+
try {
89+
googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
90+
} catch (FileNotFoundException fnfe) {
91+
System.err.printf(
92+
"Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
93+
return;
94+
} catch (IOException ioe) {
95+
System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
96+
return;
97+
}
98+
99+
try {
100+
new GetAccountHierarchy()
101+
.runExample(googleAdsClient, params.managerId, params.loginCustomerId);
102+
} catch (GoogleAdsException gae) {
103+
// GoogleAdsException is the base class for most exceptions thrown by an API request.
104+
// Instances of this exception have a message and a GoogleAdsFailure that contains a
105+
// collection of GoogleAdsErrors that indicate the underlying causes of the
106+
// GoogleAdsException.
107+
System.err.printf(
108+
"Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
109+
gae.getRequestId());
110+
int i = 0;
111+
for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
112+
System.err.printf(" Error %d: %s%n", i++, googleAdsError);
113+
}
114+
} catch (IOException ioe) {
115+
System.err.printf("Request failed. Exception: %s%n", ioe);
116+
}
117+
}
118+
119+
/**
120+
* Runs the example.
121+
*
122+
* @param googleAdsClient the Google Ads API client.
123+
* @param managerId the root customer ID from which to begin the search.
124+
* @param loginCustomerId the loginCustomerId used to create the GoogleAdsClient.
125+
* @throws IOException if a Google Ads Client is not successfully created.
126+
*/
127+
private void runExample(GoogleAdsClient googleAdsClient, Long managerId, Long loginCustomerId)
128+
throws IOException {
129+
List<Long> seedCustomerIds = new ArrayList<>();
130+
if (managerId == null) {
131+
// Gets the account hierarchies for all accessible customers.
132+
seedCustomerIds = getAccessibleCustomers(googleAdsClient);
133+
} else {
134+
// Only gets the hierarchy for the provided manager ID if provided.
135+
seedCustomerIds.add(managerId);
136+
}
137+
138+
Map<CustomerClient, Multimap<Long, CustomerClient>> allHierarchies = new HashMap<>();
139+
List<Long> accountsWithNoInfo = new ArrayList<>();
140+
// Constructs a map of account hierarchies.
141+
for (Long seedCustomerId : seedCustomerIds) {
142+
Map<CustomerClient, Multimap<Long, CustomerClient>> customerClientToHierarchy =
143+
createCustomerClientToHierarchy(loginCustomerId, seedCustomerId);
144+
145+
if (customerClientToHierarchy == null) {
146+
accountsWithNoInfo.add(seedCustomerId);
147+
} else {
148+
allHierarchies.putAll(customerClientToHierarchy);
149+
}
150+
}
151+
152+
// Prints the IDs of any accounts that did not produce hierarchy information.
153+
if (!accountsWithNoInfo.isEmpty()) {
154+
System.out.println(
155+
"Unable to retrieve information for the following accounts which are likely either test "
156+
+ "accounts or accounts with setup issues. Please check the logs for details.");
157+
for (long accountId : accountsWithNoInfo) {
158+
System.out.println(accountId);
159+
}
160+
System.out.println();
161+
}
162+
163+
int depth = 0;
164+
// Prints the hierarchy information for all accounts for which there is hierarchy information
165+
// available.
166+
for (CustomerClient rootCustomerClient : allHierarchies.keySet()) {
167+
System.out.printf("Hierarchy of customer ID %d:%n", rootCustomerClient.getId().getValue());
168+
printAccountHierarchy(rootCustomerClient, allHierarchies.get(rootCustomerClient), depth);
169+
System.out.println();
170+
}
171+
}
172+
173+
/**
174+
* Creates a map between a CustomerClient and each of its managers' mappings.
175+
*
176+
* @param loginCustomerId the loginCustomerId used to create the GoogleAdsClient.
177+
* @param seedCustomerId the ID of the customer at the root of the tree.
178+
* @return a map between a CustomerClient and each of its managers' mappings if the account
179+
* hierarchy can be retrieved. If the account hierarchy cannot be retrieved, returns null.
180+
* @throws IOException if a Google Ads Client is not successfully created.
181+
*/
182+
private Map<CustomerClient, Multimap<Long, CustomerClient>> createCustomerClientToHierarchy(
183+
Long loginCustomerId, long seedCustomerId) throws IOException {
184+
Queue<Long> managerAccountsToSearch = new LinkedList<>();
185+
CustomerClient rootCustomerClient = null;
186+
187+
// Creates a GoogleAdsClient with the specified loginCustomerId. See
188+
// https://developers.google.com/google-ads/api/docs/concepts/call-structure#cid for more
189+
// information.
190+
GoogleAdsClient googleAdsClient =
191+
GoogleAdsClient.newBuilder()
192+
.fromPropertiesFile()
193+
.setLoginCustomerId(loginCustomerId == null ? seedCustomerId : loginCustomerId)
194+
.build();
195+
196+
// Creates the Google Ads Service client.
197+
try (GoogleAdsServiceClient googleAdsServiceClient =
198+
googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) {
199+
// Creates a query that retrieves all child accounts of the manager specified in search
200+
// calls below.
201+
String query =
202+
"SELECT customer_client.client_customer, customer_client.level, "
203+
+ "customer_client.manager, customer_client.descriptive_name, "
204+
+ "customer_client.currency_code, customer_client.time_zone, "
205+
+ "customer_client.id "
206+
+ "FROM customer_client "
207+
+ "WHERE customer_client.level <= 1";
208+
209+
// Adds the seed customer ID to the list of IDs to be processed.
210+
managerAccountsToSearch.add(seedCustomerId);
211+
// Performs a breadth-first search algorithm to build a mapping of managers to their
212+
// child accounts.
213+
Multimap<Long, CustomerClient> customerIdsToChildAccounts = ArrayListMultimap.create();
214+
while (!managerAccountsToSearch.isEmpty()) {
215+
long customerIdToSearchFrom = managerAccountsToSearch.poll();
216+
SearchPagedResponse response;
217+
try {
218+
// Issues a search request.
219+
response =
220+
googleAdsServiceClient.search(
221+
SearchGoogleAdsRequest.newBuilder()
222+
.setQuery(query)
223+
.setCustomerId(Long.toString(customerIdToSearchFrom))
224+
.build());
225+
226+
// Iterates over all rows in all pages to get all customer clients under the specified
227+
// customer's hierarchy.
228+
for (GoogleAdsRow googleAdsRow : response.iterateAll()) {
229+
CustomerClient customerClient = googleAdsRow.getCustomerClient();
230+
231+
// Gets the CustomerClient object for the root customer in the tree.
232+
if (customerClient.getId().getValue() == seedCustomerId) {
233+
rootCustomerClient = customerClient;
234+
}
235+
236+
// The steps below map parent and children accounts. Continue here so that managers
237+
// accounts exclude themselves from the list of their children accounts.
238+
if (customerClient.getId().getValue() == customerIdToSearchFrom) {
239+
continue;
240+
}
241+
242+
// For all level-1 (direct child) accounts that are manager accounts, the above
243+
// query will be run against them to create a map of managers to their
244+
// child accounts for printing the hierarchy afterwards.
245+
customerIdsToChildAccounts.put(customerIdToSearchFrom, customerClient);
246+
// Checks if the child account is a manager itself so that it can later be processed
247+
// and added to the map if it hasn't been already.
248+
if (customerClient.getManager().getValue()) {
249+
// A customer can be managed by multiple managers, so to prevent visiting the same
250+
// customer multiple times, we need to check if it's already in the map.
251+
boolean alreadyVisited =
252+
customerIdsToChildAccounts.containsKey(customerClient.getId().getValue());
253+
if (!alreadyVisited && customerClient.getLevel().getValue() == 1) {
254+
managerAccountsToSearch.add(customerClient.getId().getValue());
255+
}
256+
}
257+
}
258+
} catch (GoogleAdsException gae) {
259+
System.out.printf(
260+
"Unable to retrieve hierarchy for customer ID %d: %s%n",
261+
customerIdToSearchFrom, gae.getGoogleAdsFailure().getErrors(0).getMessage());
262+
}
263+
}
264+
265+
// The rootCustomerClient will be null if the account hierarchy was unable to be retrieved
266+
// (e.g. the account is a test account or a client account with an incomplete billing setup.
267+
// This method returns null in these cases to add the seedCustomerId to the list of
268+
// customer IDs for which the account hierarchy could not be retrieved.
269+
if (rootCustomerClient == null) {
270+
return null;
271+
}
272+
273+
Map<CustomerClient, Multimap<Long, CustomerClient>> customerClientToHierarchy =
274+
new HashMap<>();
275+
customerClientToHierarchy.put(rootCustomerClient, customerIdsToChildAccounts);
276+
return customerClientToHierarchy;
277+
}
278+
}
279+
280+
/**
281+
* Retrieves a list of accessible customers with the provided set up credentials.
282+
*
283+
* @param googleAdsClient the Google Ads API client.
284+
* @return a list of customer IDs.
285+
*/
286+
private List<Long> getAccessibleCustomers(GoogleAdsClient googleAdsClient) {
287+
List<Long> seedCustomerIds = new ArrayList<>();
288+
// Issues a request for listing all accessible customers by this authenticated Google account.
289+
try (CustomerServiceClient customerServiceClient =
290+
googleAdsClient.getLatestVersion().createCustomerServiceClient()) {
291+
ListAccessibleCustomersResponse accessibleCustomers =
292+
customerServiceClient.listAccessibleCustomers(
293+
ListAccessibleCustomersRequest.newBuilder().build());
294+
System.out.println(
295+
"No manager customer ID is specified. The example will print the "
296+
+ "hierarchies of all accessible customer IDs:");
297+
298+
for (String customerResourceName : accessibleCustomers.getResourceNamesList()) {
299+
Long customer = Long.parseLong(CustomerName.parse(customerResourceName).getCustomer());
300+
System.out.println(customer);
301+
seedCustomerIds.add(customer);
302+
}
303+
}
304+
return seedCustomerIds;
305+
}
306+
307+
/**
308+
* Prints the specified account's hierarchy using recursion.
309+
*
310+
* @param customerClient the customer client whose info will be printed and its child accounts
311+
* will be processed if it's a manager.
312+
* @param customerIdsToChildAccounts a map containing the account hierarchy information.
313+
* @param depth the current depth we are printing from in the account hierarchy.
314+
*/
315+
private void printAccountHierarchy(
316+
CustomerClient customerClient,
317+
Multimap<Long, CustomerClient> customerIdsToChildAccounts,
318+
int depth) {
319+
String leadingSpace = " ";
320+
if (depth == 0) {
321+
System.out.println("Customer ID (Descriptive Name, Currency Code, Time Zone");
322+
leadingSpace = "";
323+
} else {
324+
System.out.println("|");
325+
}
326+
System.out.print(Strings.repeat("-", depth * 2));
327+
long customerId = customerClient.getId().getValue();
328+
System.out.printf(
329+
leadingSpace + "%d ('%s', '%s', '%s')%n",
330+
customerId,
331+
customerClient.getDescriptiveName().getValue(),
332+
customerClient.getCurrencyCode().getValue(),
333+
customerClient.getTimeZone().getValue());
334+
335+
// Recursively calls this function for all child accounts of customerClient if the current
336+
// customer is a manager account.
337+
for (CustomerClient childCustomer : customerIdsToChildAccounts.get(customerId)) {
338+
printAccountHierarchy(childCustomer, customerIdsToChildAccounts, depth + 1);
339+
}
340+
}
341+
}

google-ads-examples/src/main/java/com/google/ads/googleads/examples/utils/ArgumentNames.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public final class ArgumentNames {
4242
public static final String KEYWORD_TEXT = "--keywordText";
4343
public static final String LANGUAGE_ID = "--languageId";
4444
public static final String LOCATION_ID = "--locationId";
45+
public static final String LOGIN_CUSTOMER_ID = "--loginCustomerId";
4546
public static final String RECOMMENDATION_ID = "--recommendationId";
4647
public static final String RESTATEMENT_VALUE = "--restatementValue";
4748
public static final String HOTEL_CENTER_ACCOUNT_ID = "--hotelCenterAccountId";

0 commit comments

Comments
 (0)