|
| 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 | +} |
0 commit comments