The AdWords API will sunset on April 27, 2022. Migrate to the Google Ads API to take advantage of the latest Google Ads features.

Shopping Campaign Samples

The code samples below provide examples for managing Shopping campaigns using the AdWords API. Client Library.

Build a product partition tree for an ad group

// Copyright 2017 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.v201809.shoppingcampaigns;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.beust.jcommander.Parameter;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201809.shopping.ProductDimensions;
import com.google.api.ads.adwords.axis.utils.v201809.shopping.ProductPartitionNode;
import com.google.api.ads.adwords.axis.utils.v201809.shopping.ProductPartitionTree;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.ProductCanonicalConditionCondition;
import com.google.api.ads.adwords.axis.v201809.cm.ProductDimensionType;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.utils.examples.ArgumentNames;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.ads.common.lib.utils.examples.CodeSampleParams;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;
import java.util.List;

/**
 * This example clears out any existing ProductPartition criteria on the ad group and rebuilds the
 * tree to contain:
 *
 * <pre>
 *ROOT
 *ProductCanonicalCondition NEW $0.20
 *ProductCanonicalCondition USED $0.10
 *ProductCanonicalCondition null (everything else)
 *  ProductBrand CoolBrand $0.90
 *  ProductBrand CheapBrand $0.01
 *  ProductBrand null (everything else)
 *    ProductType Level1 Luggage & Bags $0.75
 *    ProductType Level1 null (everything else) $0.11
 *</pre>
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddProductPartitionTree {
  private static class AddProductPartitionTreeParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.AD_GROUP_ID, required = true)
    private Long adGroupId;
  }

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    
    AddProductPartitionTreeParams params = new AddProductPartitionTreeParams();
    if (!params.parseArguments(args)) {
      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.adGroupId = Long.parseLong("INSERT_AD_GROUP_ID_HERE");
    }

    try {
      runExample(adWordsServices, session, params.adGroupId);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param adGroupId the ID of the ad group.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(AdWordsServicesInterface adWordsServices, AdWordsSession session,
      Long adGroupId) throws RemoteException {
    // Build a new ProductPartitionTree using the ad group's current set of criteria.
    ProductPartitionTree partitionTree =
        ProductPartitionTree.createAdGroupTree(adWordsServices, session, adGroupId);

    System.out.printf("Original tree:%n%s%n", partitionTree);

    // Clear out any existing criteria.
    ProductPartitionNode rootNode = partitionTree.getRoot().removeAllChildren();

    // Make the root node a subdivision.
    rootNode = rootNode.asSubdivision();

    // Add a unit node for condition = NEW.
    rootNode.addChild(
        ProductDimensions.createCanonicalCondition(ProductCanonicalConditionCondition.NEW))
        .asBiddableUnit().setBid(200000L);

    // Add a unit node for condition = USED.
    rootNode.addChild(
        ProductDimensions.createCanonicalCondition(ProductCanonicalConditionCondition.USED))
        .asBiddableUnit().setBid(100000L);

    // Add a subdivision node for condition = null (everything else).
    ProductPartitionNode otherConditionNode =
        rootNode.addChild(ProductDimensions.createCanonicalCondition(null)).asSubdivision();

    // Add a unit node under condition = null for brand = "CoolBrand".
    otherConditionNode.addChild(ProductDimensions.createBrand("CoolBrand")).asBiddableUnit()
        .setBid(900000L);

    // Add a unit node under condition = null for brand = "CheapBrand".
    otherConditionNode.addChild(ProductDimensions.createBrand("CheapBrand")).asBiddableUnit()
        .setBid(10000L);

    // Add a subdivision node under condition = null for brand = null (everything else).
    ProductPartitionNode otherBrandNode =
        otherConditionNode.addChild(ProductDimensions.createBrand(null)).asSubdivision();

    // Add unit nodes under condition = null/brand = null.
    // The value for each bidding category is a fixed ID for a specific
    // category. You can retrieve IDs for categories from the ConstantDataService.
    // See the 'GetProductCategoryTaxonomy' example for more details.

    // Add a unit node under condition = null/brand = null for product type
    // level 1 = 'Luggage & Bags'.
    otherBrandNode.addChild(ProductDimensions.createBiddingCategory(
        ProductDimensionType.BIDDING_CATEGORY_L1, -5914235892932915235L)).asBiddableUnit()
        .setBid(750000L);

    // Add a unit node under condition = null/brand = null for product type
    // level 1 = null (everything else).
    otherBrandNode.addChild(
        ProductDimensions.createBiddingCategory(ProductDimensionType.BIDDING_CATEGORY_L1, null))
        .asBiddableUnit().setBid(110000L);

    // Get the ad group criterion service.
    AdGroupCriterionServiceInterface adGroupCriterionService =
        adWordsServices.get(session, AdGroupCriterionServiceInterface.class);

    // Make the mutate request, using the operations returned by the ProductPartitionTree.
    List<AdGroupCriterionOperation> mutateOperations = partitionTree.getMutateOperations();

    if (mutateOperations.isEmpty()) {
      System.out.println("Skipping the mutate call because the original tree and the current tree "
          + "are logically identical.");
    } else {
      adGroupCriterionService.mutate(mutateOperations.toArray(new AdGroupCriterionOperation[0]));
    }

    // The request was successful, so create a new ProductPartitionTree based on the updated state
    // of the ad group.
    partitionTree = ProductPartitionTree.createAdGroupTree(adWordsServices, session, adGroupId);

    // Show the tree
    System.out.printf("Updated tree:%n%s%n", partitionTree);
  }
}

Set a product scope for a campaign

// Copyright 2017 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.v201809.shoppingcampaigns;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.beust.jcommander.Parameter;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignCriterion;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignCriterionOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignCriterionReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignCriterionServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.ProductBiddingCategory;
import com.google.api.ads.adwords.axis.v201809.cm.ProductBrand;
import com.google.api.ads.adwords.axis.v201809.cm.ProductCanonicalCondition;
import com.google.api.ads.adwords.axis.v201809.cm.ProductCanonicalConditionCondition;
import com.google.api.ads.adwords.axis.v201809.cm.ProductCustomAttribute;
import com.google.api.ads.adwords.axis.v201809.cm.ProductDimension;
import com.google.api.ads.adwords.axis.v201809.cm.ProductDimensionType;
import com.google.api.ads.adwords.axis.v201809.cm.ProductOfferId;
import com.google.api.ads.adwords.axis.v201809.cm.ProductScope;
import com.google.api.ads.adwords.axis.v201809.cm.ProductType;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.utils.examples.ArgumentNames;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.ads.common.lib.utils.examples.CodeSampleParams;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;

/**
 * This example restricts the products that will be included in the
 * campaign by setting a ProductScope.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddProductScope {
  private static class AddProductScopeParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.CAMPAIGN_ID, required = true)
    private Long campaignId;
  }

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    
    AddProductScopeParams params = new AddProductScopeParams();
    if (!params.parseArguments(args)) {
      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.campaignId = Long.parseLong("INSERT_CAMPAIGN_ID_HERE");
    }

    try {
      runExample(adWordsServices, session, params.campaignId);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param campaignId the ID of the shopping campaign.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices, AdWordsSession session, Long campaignId)
      throws RemoteException {
    // Get the campaign criterion service.
    CampaignCriterionServiceInterface campaignCriterionService = adWordsServices.get(session,
        CampaignCriterionServiceInterface.class);
    
    ProductScope productScope = new ProductScope();

    // This set of dimensions is for demonstration purposes only. It would be
    // extremely unlikely that you want to include so many dimensions in your
    // product scope.
    ProductBrand productBrand = new ProductBrand();
    productBrand.setValue("Nexus");

    ProductCanonicalCondition productCanonicalCondition = new ProductCanonicalCondition();
    productCanonicalCondition.setCondition(ProductCanonicalConditionCondition.NEW);

    ProductCustomAttribute productCustomAttribute = new ProductCustomAttribute();
    productCustomAttribute.setType(ProductDimensionType.CUSTOM_ATTRIBUTE_0);
    productCustomAttribute.setValue("my attribute value");

    ProductOfferId productOfferId = new ProductOfferId();
    productOfferId.setValue("book1");

    ProductType productTypeLevel1Media = new ProductType();
    productTypeLevel1Media.setType(ProductDimensionType.PRODUCT_TYPE_L1);
    productTypeLevel1Media.setValue("Media");

    ProductType productTypeLevel2Books = new ProductType();
    productTypeLevel2Books.setType(ProductDimensionType.PRODUCT_TYPE_L2);
    productTypeLevel2Books.setValue("Books");

    // The value for the bidding category is a fixed ID for the 'Luggage & Bags'
    // category. You can retrieve IDs for categories from the ConstantDataService.
    // See the 'GetProductCategoryTaxonomy' example for more details.
    ProductBiddingCategory productBiddingCategory = new ProductBiddingCategory();
    productBiddingCategory.setType(ProductDimensionType.BIDDING_CATEGORY_L1);
    productBiddingCategory.setValue(-5914235892932915235L);
    
    productScope.setDimensions(new ProductDimension[]{ productBrand, productCanonicalCondition,
        productCustomAttribute, productOfferId, productTypeLevel1Media, productTypeLevel2Books,
        productBiddingCategory});

    CampaignCriterion campaignCriterion = new CampaignCriterion();
    campaignCriterion.setCampaignId(campaignId);
    campaignCriterion.setCriterion(productScope);
    
    // Create operation.
    CampaignCriterionOperation criterionOperation = new CampaignCriterionOperation();
    criterionOperation.setOperand(campaignCriterion);
    criterionOperation.setOperator(Operator.ADD);
    
    // Make the mutate request.
    CampaignCriterionReturnValue result =
        campaignCriterionService.mutate(new CampaignCriterionOperation[] {criterionOperation});

    // Display the result.
    System.out.printf("Created a ProductScope criterion with ID %d.%n",
        result.getValue(0).getCriterion().getId());
  }
}

Add a Shopping campaign

// Copyright 2017 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.v201809.shoppingcampaigns;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.beust.jcommander.Parameter;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201809.shopping.ProductPartitionTree;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroup;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAd;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAdOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAdReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAdServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterion;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.AdvertisingChannelType;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.BiddingStrategyConfiguration;
import com.google.api.ads.adwords.axis.v201809.cm.BiddingStrategyType;
import com.google.api.ads.adwords.axis.v201809.cm.Budget;
import com.google.api.ads.adwords.axis.v201809.cm.Campaign;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignStatus;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.ProductAd;
import com.google.api.ads.adwords.axis.v201809.cm.Setting;
import com.google.api.ads.adwords.axis.v201809.cm.ShoppingSetting;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.utils.examples.ArgumentNames;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.ads.common.lib.utils.examples.CodeSampleParams;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;
import java.util.List;

/**
 * This example adds a shopping campaign.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddShoppingCampaign {
  private static class AddShoppingCampaignParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.BUDGET_ID, required = true)
    private Long budgetId;

    @Parameter(names = ArgumentNames.MERCHANT_ID, required = true)
    private Long merchantId;

    @Parameter(names = ArgumentNames.CREATE_DEFAULT_PARTITION, required = true, arity = 1,
        description = "If set to true, a default partition will be created. If running the"
            + " AddProductPartitionTree.java example right after this example, make sure this stays"
            + " set to false.")
    private boolean createDefaultPartition;
  }

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    AddShoppingCampaignParams params = new AddShoppingCampaignParams();
    if (!params.parseArguments(args)) {
      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.budgetId = Long.parseLong("INSERT_BUDGET_ID_HERE");
      params.merchantId = Long.parseLong("INSERT_MERCHANT_ID_HERE");
      params.createDefaultPartition = false;
    }

    try {
      runExample(adWordsServices, session, params.budgetId, params.merchantId,
          params.createDefaultPartition);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param budgetId the budget ID to use for the new campaign.
   * @param merchantId the Merchant Center ID for the new campaign.
   * @param createDefaultPartition if true, a default product partition for all products will be
   *     created.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      Long budgetId,
      Long merchantId,
      boolean createDefaultPartition)
      throws RemoteException {
    // Get the CampaignService
    CampaignServiceInterface campaignService =
        adWordsServices.get(session, CampaignServiceInterface.class);

    // Create campaign.
    Campaign campaign = new Campaign();
    campaign.setName("Shopping campaign #" + System.currentTimeMillis());
    // The advertisingChannelType is what makes this a Shopping campaign
    campaign.setAdvertisingChannelType(AdvertisingChannelType.SHOPPING);

    // Recommendation: Set the campaign to PAUSED when creating it to prevent
    // the ads from immediately serving. Set to ENABLED once you've added
    // targeting and the ads are ready to serve.
    campaign.setStatus(CampaignStatus.PAUSED);

    // Set shared budget (required).
    Budget budget = new Budget();
    budget.setBudgetId(budgetId);
    campaign.setBudget(budget);

    // Set bidding strategy (required).
    BiddingStrategyConfiguration biddingStrategyConfiguration = new BiddingStrategyConfiguration();
    biddingStrategyConfiguration.setBiddingStrategyType(BiddingStrategyType.MANUAL_CPC);
    campaign.setBiddingStrategyConfiguration(biddingStrategyConfiguration);

    // All Shopping campaigns need a ShoppingSetting.
    ShoppingSetting shoppingSetting = new ShoppingSetting();
    shoppingSetting.setSalesCountry("US");
    shoppingSetting.setCampaignPriority(0);
    shoppingSetting.setMerchantId(merchantId);
    
    // Set to 'true' to enable Local Inventory Ads in your campaign.
    shoppingSetting.setEnableLocal(true);
    
    campaign.setSettings(new Setting[] {shoppingSetting});

    // Create operation.
    CampaignOperation campaignOperation = new CampaignOperation();
    campaignOperation.setOperand(campaign);
    campaignOperation.setOperator(Operator.ADD);

    // Make the mutate request.
    CampaignReturnValue campaignAddResult =
        campaignService.mutate(new CampaignOperation[] {campaignOperation});

    // Display result.
    campaign = campaignAddResult.getValue(0);

    System.out.printf("Campaign with name '%s' and ID %d was added.%n", campaign.getName(),
        campaign.getId());

    // Get the AdGroupService.
    AdGroupServiceInterface adGroupService =
        adWordsServices.get(session, AdGroupServiceInterface.class);

    // Create ad group.
    AdGroup adGroup = new AdGroup();
    adGroup.setCampaignId(campaign.getId());
    adGroup.setName("Ad Group #" + System.currentTimeMillis());

    // Create operation.
    AdGroupOperation adGroupOperation = new AdGroupOperation();
    adGroupOperation.setOperand(adGroup);
    adGroupOperation.setOperator(Operator.ADD);

    // Make the mutate request.
    AdGroupReturnValue adGroupAddResult =
        adGroupService.mutate(new AdGroupOperation[] {adGroupOperation});

    // Display result.
    adGroup = adGroupAddResult.getValue(0);
    System.out.printf("Ad group with name '%s' and ID %d was added.%n", adGroup.getName(),
        adGroup.getId());

    // Create product ad.
    AdGroupAdServiceInterface adGroupAdService =
        adWordsServices.get(session, AdGroupAdServiceInterface.class);
    ProductAd productAd = new ProductAd();

    // Create ad group ad.
    AdGroupAd adGroupAd = new AdGroupAd();
    adGroupAd.setAdGroupId(adGroup.getId());
    adGroupAd.setAd(productAd);

    // Create operation.
    AdGroupAdOperation adGroupAdOperation = new AdGroupAdOperation();
    adGroupAdOperation.setOperand(adGroupAd);
    adGroupAdOperation.setOperator(Operator.ADD);

    // Make the mutate request.
    AdGroupAdReturnValue adGroupAdAddResult =
        adGroupAdService.mutate(new AdGroupAdOperation[] {adGroupAdOperation});

    // Display result.
    adGroupAd = adGroupAdAddResult.getValue(0);
    
    System.out.printf("Product ad with ID %d was added.%n", adGroupAd.getAd().getId());

    if (createDefaultPartition) {
      // Create an ad group criterion for 'All products' using the ProductPartitionTree utility.
      ProductPartitionTree productPartitionTree =
          ProductPartitionTree.createAdGroupTree(adWordsServices, session, adGroup.getId());
      productPartitionTree
          .getRoot()
          .asBiddableUnit()
          .setBid(500000L);
      List<AdGroupCriterionOperation> mutateOperations = productPartitionTree.getMutateOperations();

      // Make the mutate request.
      AdGroupCriterionServiceInterface adGroupCriterionService =
          adWordsServices.get(session, AdGroupCriterionServiceInterface.class);
      AdGroupCriterionReturnValue adGroupCriterionResult =
          adGroupCriterionService.mutate(
              mutateOperations.toArray(new AdGroupCriterionOperation[0]));

      // Display result.
      for (AdGroupCriterion adGroupCriterion : adGroupCriterionResult.getValue()) {
        System.out.printf(
            "Ad group criterion with ID %d in ad group with ID %d was added.%n",
            adGroupCriterion.getCriterion().getId(), adGroupCriterion.getAdGroupId());
      }
    }
  }
}

Add a Smart Shopping campaign

// Copyright 2018 Google LLC
//
// 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
//
// https://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.v201809.shoppingcampaigns;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.beust.jcommander.Parameter;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201809.shopping.ProductPartitionTree;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroup;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAd;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAdOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAdReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupAdServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterion;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupCriterionServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupOperation;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.AdGroupType;
import com.google.api.ads.adwords.axis.v201809.cm.AdvertisingChannelSubType;
import com.google.api.ads.adwords.axis.v201809.cm.AdvertisingChannelType;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.BiddingStrategyConfiguration;
import com.google.api.ads.adwords.axis.v201809.cm.BiddingStrategyType;
import com.google.api.ads.adwords.axis.v201809.cm.Budget;
import com.google.api.ads.adwords.axis.v201809.cm.BudgetBudgetDeliveryMethod;
import com.google.api.ads.adwords.axis.v201809.cm.BudgetOperation;
import com.google.api.ads.adwords.axis.v201809.cm.BudgetServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Campaign;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignStatus;
import com.google.api.ads.adwords.axis.v201809.cm.GoalOptimizedShoppingAd;
import com.google.api.ads.adwords.axis.v201809.cm.Money;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.Setting;
import com.google.api.ads.adwords.axis.v201809.cm.ShoppingSetting;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.utils.examples.ArgumentNames;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.ads.common.lib.utils.examples.CodeSampleParams;
import com.google.api.client.auth.oauth2.Credential;
import java.rmi.RemoteException;
import java.util.List;

/**
 * This example adds a Smart Shopping campaign with an ad group and ad group ad.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the "ads.properties" file.
 * See README for more info.
 */
public class AddSmartShoppingAd {
  private static class AddSmartShoppingAdParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.MERCHANT_ID, required = true)
    private Long merchantId;

    @Parameter(
        names = ArgumentNames.CREATE_DEFAULT_PARTITION,
        required = true,
        arity = 1,
        description =
            "If set to true, a default partition will be created. If running the"
                + " AddProductPartitionTree.java example right after this example, make sure this"
                + " stays set to false.")
    private boolean createDefaultPartition;
  }

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    AddSmartShoppingAdParams params = new AddSmartShoppingAdParams();
    if (!params.parseArguments(args)) {
      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.merchantId = Long.parseLong("INSERT_MERCHANT_ID_HERE");
      params.createDefaultPartition = false;
    }

    try {
      runExample(adWordsServices, session, params.merchantId, params.createDefaultPartition);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString.
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf("Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param merchantId the Merchant Center ID for the new campaign.
   * @param createDefaultPartition if true, a default product partition for all products will be
   *     created.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      long merchantId,
      boolean createDefaultPartition)
      throws RemoteException {
    Budget budget = createBudget(adWordsServices, session);
    Campaign campaign =
        createSmartShoppingCampaign(adWordsServices, session, budget.getBudgetId(), merchantId);
    AdGroup adGroup = createSmartShoppingAdGroup(adWordsServices, session, campaign.getId());
    createSmartShoppingAd(adWordsServices, session, adGroup.getId());
    if (createDefaultPartition) {
      createDefaultPartition(adWordsServices, session, adGroup.getId());
    }
  }

  /**
   * Creates a non-shared budget for a Smart Shopping campaign. Smart Shopping campaigns support
   * only non-shared budgets.
   */
  private static Budget createBudget(
      AdWordsServicesInterface adWordsServices, AdWordsSession session) throws RemoteException {
    BudgetServiceInterface budgetService =
        adWordsServices.get(session, BudgetServiceInterface.class);

    // Create a budget.
    Budget budget = new Budget();
    budget.setName("Interplanetary Cruise #" + System.currentTimeMillis());
    Money budgetAmount = new Money();
    // This budget equals 50.00 units of your account's currency, e.g.,
    // 50 USD if your currency is USD.
    budgetAmount.setMicroAmount(50_000_000L);
    budget.setAmount(budgetAmount);
    budget.setDeliveryMethod(BudgetBudgetDeliveryMethod.STANDARD);
    // Non-shared budgets are required for Smart Shopping campaigns.
    budget.setIsExplicitlyShared(false);

    // Create operation.
    BudgetOperation budgetOperation = new BudgetOperation();
    budgetOperation.setOperand(budget);
    budgetOperation.setOperator(Operator.ADD);

    // Add the budget.
    Budget newBudget = budgetService.mutate(new BudgetOperation[] {budgetOperation}).getValue(0);
    System.out.printf(
        "Budget with name '%s' and ID %d was added.%n",
        newBudget.getName(), newBudget.getBudgetId());
    return newBudget;
  }

  /** Creates a Smart Shopping campaign. */
  private static Campaign createSmartShoppingCampaign(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      Long budgetId,
      long merchantId)
      throws RemoteException {
    CampaignServiceInterface campaignService =
        adWordsServices.get(session, CampaignServiceInterface.class);
    // Create a campaign with required and optional settings.
    Campaign campaign = new Campaign();
    campaign.setName("Smart Shopping campaign #" + System.currentTimeMillis());
    // The advertisingChannelType is what makes this a Shopping campaign.
    campaign.setAdvertisingChannelType(AdvertisingChannelType.SHOPPING);
    // Sets the advertisingChannelSubType to SHOPPING_GOAL_OPTIMIZED_ADS to
    // make this a Smart Shopping campaign.
    campaign.setAdvertisingChannelSubType(AdvertisingChannelSubType.SHOPPING_GOAL_OPTIMIZED_ADS);
    // Recommendation: Set the campaign to PAUSED when creating it to stop
    // the ads from immediately serving. Set to ENABLED once you've added
    // targeting and the ads are ready to serve.
    campaign.setStatus(CampaignStatus.PAUSED);

    // Set a budget.
    Budget budget = new Budget();
    budget.setBudgetId(budgetId);
    campaign.setBudget(budget);

    // Set bidding strategy. Only MAXIMIZE_CONVERSION_VALUE is supported.
    BiddingStrategyConfiguration biddingStrategyConfiguration = new BiddingStrategyConfiguration();
    biddingStrategyConfiguration.setBiddingStrategyType(
        BiddingStrategyType.MAXIMIZE_CONVERSION_VALUE);
    campaign.setBiddingStrategyConfiguration(biddingStrategyConfiguration);

    // All Shopping campaigns need a ShoppingSetting.
    ShoppingSetting shoppingSetting = new ShoppingSetting();
    shoppingSetting.setSalesCountry("US");
    shoppingSetting.setMerchantId(merchantId);
    campaign.setSettings(new Setting[] {shoppingSetting});

    // Create operation.
    CampaignOperation campaignOperation = new CampaignOperation();
    campaignOperation.setOperand(campaign);
    campaignOperation.setOperator(Operator.ADD);

    // Make the mutate request.
    CampaignReturnValue campaignAddResult =
        campaignService.mutate(new CampaignOperation[] {campaignOperation});

    // Display result.
    campaign = campaignAddResult.getValue(0);

    System.out.printf(
        "Smart Shopping campaign with name '%s' and ID %d was added.%n",
        campaign.getName(), campaign.getId());
    return campaign;
  }

  /**
   * Creates a Smart Shopping ad group by setting the ad group type to SHOPPING_GOAL_OPTIMIZED_ADS.
   */
  private static AdGroup createSmartShoppingAdGroup(
      AdWordsServicesInterface adWordsServices, AdWordsSession session, long campaignId)
      throws RemoteException {
    // Get the AdGroupService.
    AdGroupServiceInterface adGroupService =
        adWordsServices.get(session, AdGroupServiceInterface.class);

    // Create ad group.
    AdGroup adGroup = new AdGroup();
    adGroup.setCampaignId(campaignId);
    adGroup.setName("Smart Shopping ad group #" + System.currentTimeMillis());

    // Set the ad group type to SHOPPING_GOAL_OPTIMIZED_ADS.
    adGroup.setAdGroupType(AdGroupType.SHOPPING_GOAL_OPTIMIZED_ADS);

    // Create operation.
    AdGroupOperation adGroupOperation = new AdGroupOperation();
    adGroupOperation.setOperand(adGroup);
    adGroupOperation.setOperator(Operator.ADD);

    // Make the mutate request.
    AdGroupReturnValue adGroupAddResult =
        adGroupService.mutate(new AdGroupOperation[] {adGroupOperation});

    // Display result.
    adGroup = adGroupAddResult.getValue(0);
    System.out.printf(
        "Smart Shopping ad group with name '%s' and ID %d was added.%n",
        adGroup.getName(), adGroup.getId());

    return adGroup;
  }

  /** Creates a Smart Shopping ad. */
  private static void createSmartShoppingAd(
      AdWordsServicesInterface adWordsServices, AdWordsSession session, long adGroupId)
      throws RemoteException {
    AdGroupAdServiceInterface adGroupAdService =
        adWordsServices.get(session, AdGroupAdServiceInterface.class);

    // Create a Smart Shopping ad (Goal-optimized Shopping ad).
    GoalOptimizedShoppingAd smartShoppingAd = new GoalOptimizedShoppingAd();

    // Create ad group ad.
    AdGroupAd adGroupAd = new AdGroupAd();
    adGroupAd.setAdGroupId(adGroupId);
    adGroupAd.setAd(smartShoppingAd);

    // Create operation.
    AdGroupAdOperation adGroupAdOperation = new AdGroupAdOperation();
    adGroupAdOperation.setOperand(adGroupAd);
    adGroupAdOperation.setOperator(Operator.ADD);

    // Make the mutate request.
    AdGroupAdReturnValue adGroupAdAddResult =
        adGroupAdService.mutate(new AdGroupAdOperation[] {adGroupAdOperation});

    // Display result.
    adGroupAd = adGroupAdAddResult.getValue(0);

    System.out.printf("Smart Shopping ad with ID %d was added.%n", adGroupAd.getAd().getId());
  }

  /** Creates a default product partition as an ad group criterion. */
  private static void createDefaultPartition(
      AdWordsServicesInterface adWordsServices, AdWordsSession session, long adGroupId)
      throws RemoteException {
    // Create an ad group criterion for 'All products' using the ProductPartitionTree utility.
    ProductPartitionTree productPartitionTree =
        ProductPartitionTree.createAdGroupTree(adWordsServices, session, adGroupId);
    List<AdGroupCriterionOperation> mutateOperations = productPartitionTree.getMutateOperations();

    // Make the mutate request.
    AdGroupCriterionServiceInterface adGroupCriterionService =
        adWordsServices.get(session, AdGroupCriterionServiceInterface.class);
    AdGroupCriterionReturnValue adGroupCriterionResult =
        adGroupCriterionService.mutate(mutateOperations.toArray(new AdGroupCriterionOperation[0]));

    // Display result.
    for (AdGroupCriterion adGroupCriterion : adGroupCriterionResult.getValue()) {
      System.out.printf(
          "Ad group criterion with ID %d in ad group with ID %d was added.%n",
          adGroupCriterion.getCriterion().getId(), adGroupCriterion.getAdGroupId());
    }
  }
}

Get the set of product bidding categories

// Copyright 2017 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.v201809.shoppingcampaigns;

import static com.google.api.ads.common.lib.utils.Builder.DEFAULT_CONFIGURATION_FILENAME;

import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.v201809.SelectorBuilder;
import com.google.api.ads.adwords.axis.v201809.cm.ApiError;
import com.google.api.ads.adwords.axis.v201809.cm.ApiException;
import com.google.api.ads.adwords.axis.v201809.cm.ConstantDataServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.ProductBiddingCategoryData;
import com.google.api.ads.adwords.axis.v201809.cm.Selector;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
import com.google.api.ads.adwords.lib.selectorfields.v201809.cm.ConstantDataField;
import com.google.api.ads.common.lib.auth.OfflineCredentials;
import com.google.api.ads.common.lib.auth.OfflineCredentials.Api;
import com.google.api.ads.common.lib.conf.ConfigurationLoadException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ValidationException;
import com.google.api.client.auth.oauth2.Credential;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * This example fetches the set of valid ProductBiddingCategories.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class GetProductCategoryTaxonomy {

  public static void main(String[] args) {
    AdWordsSession session;
    try {
      // Generate a refreshable OAuth2 credential.
      Credential oAuth2Credential =
          new OfflineCredentials.Builder()
              .forApi(Api.ADWORDS)
              .fromFile()
              .build()
              .generateCredential();

      // Construct an AdWordsSession.
      session =
          new AdWordsSession.Builder().fromFile().withOAuth2Credential(oAuth2Credential).build();
    } catch (ConfigurationLoadException cle) {
      System.err.printf(
          "Failed to load configuration from the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, cle);
      return;
    } catch (ValidationException ve) {
      System.err.printf(
          "Invalid configuration in the %s file. Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, ve);
      return;
    } catch (OAuthException oe) {
      System.err.printf(
          "Failed to create OAuth credentials. Check OAuth settings in the %s file. "
              + "Exception: %s%n",
          DEFAULT_CONFIGURATION_FILENAME, oe);
      return;
    }

    AdWordsServicesInterface adWordsServices = AdWordsServices.getInstance();

    try {
      runExample(adWordsServices, session);
    } catch (ApiException apiException) {
      // ApiException is the base class for most exceptions thrown by an API request. Instances
      // of this exception have a message and a collection of ApiErrors that indicate the
      // type and underlying cause of the exception. Every exception object in the adwords.axis
      // packages will return a meaningful value from toString
      //
      // ApiException extends RemoteException, so this catch block must appear before the
      // catch block for RemoteException.
      System.err.println("Request failed due to ApiException. Underlying ApiErrors:");
      if (apiException.getErrors() != null) {
        int i = 0;
        for (ApiError apiError : apiException.getErrors()) {
          System.err.printf("  Error %d: %s%n", i++, apiError);
        }
      }
    } catch (RemoteException re) {
      System.err.printf(
          "Request failed unexpectedly due to RemoteException: %s%n", re);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   */
  public static void runExample(AdWordsServicesInterface adWordsServices, AdWordsSession session)
      throws RemoteException {
    // Get the constant data service.
    ConstantDataServiceInterface constantDataService =
        adWordsServices.get(session, ConstantDataServiceInterface.class);
    
    Selector selector = new SelectorBuilder()
      .equals(ConstantDataField.Country, "US")
      .build();
    
    ProductBiddingCategoryData[] results =
        constantDataService.getProductBiddingCategoryData(selector);
    
    // List of top level category nodes.
    List<CategoryNode> rootCategories = new ArrayList<>();
    // Map of category ID to category node for all categories found in the results.
    Map<Long, CategoryNode> biddingCategories = Maps.newHashMap();
    
    for (ProductBiddingCategoryData productBiddingCategoryData : results) {
      Long id = productBiddingCategoryData.getDimensionValue().getValue();
      String name = productBiddingCategoryData.getDisplayValue(0).getValue();
      CategoryNode node = biddingCategories.get(id);
      if (node == null) {
        node = new CategoryNode(id, name);
        biddingCategories.put(id, node);
      } else if (node.name == null) {
        // Ensure that the name attribute for the node is set. Name will be null for nodes added
        // to biddingCategories as a result of being a parentNode below.
        node.name = name;
      }

      if (productBiddingCategoryData.getParentDimensionValue() != null
          && productBiddingCategoryData.getParentDimensionValue().getValue() != null) {
        Long parentId = productBiddingCategoryData.getParentDimensionValue().getValue();
        CategoryNode parentNode = biddingCategories.get(parentId);
        if (parentNode == null) {
          parentNode = new CategoryNode(parentId);
          biddingCategories.put(parentId, parentNode);
        }
        parentNode.children.add(node);
      } else {
        rootCategories.add(node);
      }
    }
    displayCategories(rootCategories, "");
  }

  /**
   * Recursively prints out each category node and its children.
   *
   * @param categories the categories to print.
   * @param prefix the string to print at the beginning of each line of output.
   */
  private static void displayCategories(List<CategoryNode> categories, String prefix) {
    for (CategoryNode category : categories) {
      System.out.printf("%s%s [%s]%n", prefix, category.name, category.id);
      displayCategories(category.children, String.format("%s%s > ", prefix, category.name));
    }
  }

  /**
   * Node that tracks a product bidding category's id, name, and child nodes.
   */
  private static class CategoryNode {
    final Long id;
    String name;
    final List<CategoryNode> children;

    /**
     * Constructor for categories first encountered as non-parent elements in the results.
     *
     * @param id the ID of the category
     * @param name the name of the category
     */
    CategoryNode(Long id, String name) {
      this.children = new ArrayList<>();
      this.id = Preconditions.checkNotNull(id);
      this.name = name;
    }

    /**
     * Constructor for categories first encountered as a parent category, in which case only the ID
     * is available.
     *
     * @param id the ID of the category
     */
    CategoryNode(Long id) {
      this(id, null);
    }
  }
}