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

Extensions Samples

The code samples below provide examples of common extension functions using the AdWords API. Client Library.

Associate a Google My Business feed to that of a customer

// 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.extensions;

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

import adwords.axis.auth.GetRefreshToken;
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.ConstantOperand;
import com.google.api.ads.adwords.axis.v201809.cm.ConstantOperandConstantType;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerFeed;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerFeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerFeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Feed;
import com.google.api.ads.adwords.axis.v201809.cm.FeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.FeedOrigin;
import com.google.api.ads.adwords.axis.v201809.cm.FeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.FeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Function;
import com.google.api.ads.adwords.axis.v201809.cm.FunctionArgumentOperand;
import com.google.api.ads.adwords.axis.v201809.cm.FunctionOperator;
import com.google.api.ads.adwords.axis.v201809.cm.OAuthInfo;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.PlacesLocationFeedData;
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 javax.annotation.Nullable;

/**
 * This example adds a feed that syncs feed items from a Google
 * My Business (GMB) account and associates the feed with a customer.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from
 * the "ads.properties" file. See README for more info.
 */
public class AddGoogleMyBusinessLocationExtensions {

  /**
   * The placeholder type for location extensions.
   *
   * <p>See the Placeholder reference page for a list of all the placeholder types and fields.
   * https://developers.google.com/adwords/api/docs/appendix/placeholders
   */
  private static final int PLACEHOLDER_LOCATION = 7;

  /**
   * The maximum number of CustomerFeed ADD operation attempts to make before throwing
   * an exception.
   */
  private static final int MAX_CUSTOMER_FEED_ADD_ATTEMPTS = 10;

  private static class AddGoogleMyBusinessLocationExtensionsParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.GMB_EMAIL_ADDRESS, required = true,
        description = "The email address of either an owner or a manager of the GMB account.")
    private String gmbEmailAddress;

    @Parameter(names = ArgumentNames.GMB_ACCESS_TOKEN, required = true,
        description = "If the gmbEmailAddress is the same user you used to generate your AdWords"
            + " API refresh token, leave this unchanged. Otherwise, to obtain an access token for"
            + " your GMB account, run the GetRefreshToken example. Make sure you are logged in as"
            + " the same user as gmbEmailAddress above when you follow the link provided by the"
            + " example, then call Credential.getAccessToken() on the generated Credential object"
            + " and pass the value here.")
    private String gmbAccessToken;

    @Parameter(names = ArgumentNames.BUSINESS_ACCOUNT_IDENTIFIER,
        description = "If the gmbEmailAddress is for a GMB manager instead of the GMB account"
            + " owner, then set businessAccountIdentifier to the +Page ID of a location for which"
            + " the manager has access. See the location extensions guide at"
            + " https:developers.google.com/adwords/api/docs/guides/feed-services-locations for"
            + " details.")
    private String businessAccountIdentifier;
  }

  public static void main(String[] args) {
    AdWordsSession session;
    Credential oAuth2Credential;
    try {
      // Generate a refreshable OAuth2 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();

    AddGoogleMyBusinessLocationExtensionsParams params =
        new AddGoogleMyBusinessLocationExtensionsParams();
    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.gmbEmailAddress = "INSERT_GMB_EMAIL_ADDRESS_HERE";
      params.gmbAccessToken = oAuth2Credential.getAccessToken();
      params.businessAccountIdentifier = "INSERT_BUSINESS_ACCOUNT_IDENTIFIER_HERE";
    }

    try {
      runExample(adWordsServices, session, params.gmbEmailAddress, params.gmbAccessToken,
          params.businessAccountIdentifier);
    } 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);
    } catch (InterruptedException ie) {
      System.err.printf("Thread was interrupted: %s%n", ie);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param gmbEmailAddress the email address of the owner or manager of the GMB account.
   * @param gmbAccessToken the OAuth2 access token for GMB.
   * @param businessAccountIdentifier optional identifier of the Google My Business account. This is
   *     required when the {@code gmbEmailAddress} is a GMB manager.
   * @throws ApiException if the API request failed with one or more service errors.
   * @throws RemoteException if the API request failed due to other errors.
   * @throws InterruptedException if the thread was interrupted while sleeping between retries.
   */
  private static void runExample(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      String gmbEmailAddress,
      String gmbAccessToken,
      @Nullable String businessAccountIdentifier)
      throws RemoteException, InterruptedException {

    FeedServiceInterface feedService = adWordsServices.get(session, FeedServiceInterface.class);

    // Create a feed that will sync to the Google My Business account specified
    // by gmbEmailAddress. Do not add FeedAttributes to this object,
    // as AdWords will add them automatically because this will be a
    // system generated feed.
    Feed gmbFeed = new Feed();
    gmbFeed.setName("Google My Business feed #" + System.currentTimeMillis());

    PlacesLocationFeedData feedData = new PlacesLocationFeedData();
    feedData.setEmailAddress(gmbEmailAddress);
    feedData.setBusinessAccountIdentifier(businessAccountIdentifier);
    
    // Optional: specify labels to filter Google My Business listings. If
    // specified, only listings that have any of the labels set are
    // synchronized into FeedItems.
    feedData.setLabelFilters(new String[] {"Stores in New York City"});

    OAuthInfo oAuthInfo = new OAuthInfo();
    oAuthInfo.setHttpMethod("GET");
    oAuthInfo.setHttpRequestUrl(GetRefreshToken.ADWORDS_API_SCOPE);
    oAuthInfo.setHttpAuthorizationHeader(String.format("Bearer %s", gmbAccessToken));
    
    feedData.setOAuthInfo(oAuthInfo);

    gmbFeed.setSystemFeedGenerationData(feedData);
    
    // Since this feed's feed items will be managed by AdWords,
    // you must set its origin to ADWORDS.
    gmbFeed.setOrigin(FeedOrigin.ADWORDS);

    // Create an operation to add the feed.
    FeedOperation feedOperation = new FeedOperation();
    feedOperation.setOperand(gmbFeed);
    feedOperation.setOperator(Operator.ADD);

    // Add the feed. Since it is a system generated feed, AdWords will automatically:
    // 1. Set up the FeedAttributes on the feed.
    // 2. Set up a FeedMapping that associates the FeedAttributes of the feed
    // with the placeholder fields of the LOCATION placeholder type.
    FeedReturnValue addFeedResult = feedService.mutate(new FeedOperation[] {feedOperation});
    Feed addedFeed = addFeedResult.getValue(0);
    System.out.printf("Added GMB feed with ID %d%n", addedFeed.getId());

    // Add a CustomerFeed that associates the feed with this customer for
    // the LOCATION placeholder type.
    CustomerFeed customerFeed = new CustomerFeed();
    customerFeed.setFeedId(addedFeed.getId());
    customerFeed.setPlaceholderTypes(new int[] {PLACEHOLDER_LOCATION});

    // Create a matching function that will always evaluate to true.
    Function customerMatchingFunction = new Function();
    ConstantOperand constOperand = new ConstantOperand();
    constOperand.setType(ConstantOperandConstantType.BOOLEAN);
    constOperand.setBooleanValue(true);
    customerMatchingFunction.setLhsOperand(new FunctionArgumentOperand[] {constOperand});
    customerMatchingFunction.setOperator(FunctionOperator.IDENTITY);
    customerFeed.setMatchingFunction(customerMatchingFunction);

    // Create an operation to add the customer feed.
    CustomerFeedOperation customerFeedOperation = new CustomerFeedOperation();
    customerFeedOperation.setOperand(customerFeed);
    customerFeedOperation.setOperator(Operator.ADD);

    CustomerFeedServiceInterface customerFeedService =
        adWordsServices.get(session, CustomerFeedServiceInterface.class);

    // After the completion of the Feed ADD operation above the added feed will not be available
    // for usage in a CustomerFeed until the sync between the AdWords and GMB accounts
    // completes. The loop below will retry adding the CustomerFeed up to ten times with an
    // exponential back-off policy.
    CustomerFeed addedCustomerFeed = null;
    int numberOfAttempts = 0;
    do {
      numberOfAttempts++;
      try {
        CustomerFeedReturnValue customerFeedResult =
            customerFeedService.mutate(new CustomerFeedOperation[] {customerFeedOperation});
        addedCustomerFeed = customerFeedResult.getValue(0);
        System.out.printf("Attempt #%d to add the CustomerFeed was successful%n", numberOfAttempts);
      } catch (Exception e) {
        // Wait using exponential backoff policy
        long sleepSeconds = (long) Math.scalb(5, numberOfAttempts);
        System.out.printf("Attempt #%d to add the CustomerFeed was not successful. "
            + "Waiting %d seconds before trying again.%n", numberOfAttempts, sleepSeconds);
        Thread.sleep(sleepSeconds * 1000);
      }
    } while (numberOfAttempts < MAX_CUSTOMER_FEED_ADD_ATTEMPTS && addedCustomerFeed == null);

    if (addedCustomerFeed == null) {
      throw new RuntimeException("Could not create the CustomerFeed after "
          + MAX_CUSTOMER_FEED_ADD_ATTEMPTS + " attempts. Please retry "
          + "the CustomerFeed ADD operation later.");
    }

    System.out.printf("Added CustomerFeed for feed ID %d and placeholder type %d%n",
        addedCustomerFeed.getFeedId(), addedCustomerFeed.getPlaceholderTypes()[0]);

    // OPTIONAL: Create a CampaignFeed to specify which FeedItems to use at the Campaign
    // level. This will be similar to the CampaignFeed in the AddSiteLinks example, except
    // you can also filter based on the business name and category of each FeedItem
    // by using a FeedAttributeOperand in your matching function.

    // OPTIONAL: Create an AdGroupFeed for even more fine grained control over
    // which feed items are used at the AdGroup level.
  }
}

Associate a price extension to an account

// 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.extensions;

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

import adwords.axis.v201809.basicoperations.AddCampaigns;
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.CustomerExtensionSetting;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerExtensionSettingOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerExtensionSettingReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CustomerExtensionSettingServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.DayOfWeek;
import com.google.api.ads.adwords.axis.v201809.cm.ExtensionFeedItem;
import com.google.api.ads.adwords.axis.v201809.cm.ExtensionSetting;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemCampaignTargeting;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemSchedule;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemScheduling;
import com.google.api.ads.adwords.axis.v201809.cm.FeedType;
import com.google.api.ads.adwords.axis.v201809.cm.MinuteOfHour;
import com.google.api.ads.adwords.axis.v201809.cm.Money;
import com.google.api.ads.adwords.axis.v201809.cm.MoneyWithCurrency;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.PriceExtensionPriceQualifier;
import com.google.api.ads.adwords.axis.v201809.cm.PriceExtensionPriceUnit;
import com.google.api.ads.adwords.axis.v201809.cm.PriceExtensionType;
import com.google.api.ads.adwords.axis.v201809.cm.PriceFeedItem;
import com.google.api.ads.adwords.axis.v201809.cm.PriceTableRow;
import com.google.api.ads.adwords.axis.v201809.cm.UrlList;
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.ArrayList;
import java.util.List;

/**
 * This example adds a price extension and associates it with an account. Campaign targeting is
 * also set using the specified campaign ID.
 * 
 * <p>To get campaigns, run {@link AddCampaigns}.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddPrices {

  private static class AddPricesParams 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();

    AddPricesParams params = new AddPricesParams();
    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 campaign where price feed items will be added.
   * @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 ApiException, RemoteException {
    // Get the CustomerExtensionSettingService.
    CustomerExtensionSettingServiceInterface customerExtensionSettingService =
        adWordsServices.get(session, CustomerExtensionSettingServiceInterface.class);

    // Create the price extension feed item.
    PriceFeedItem priceFeedItem = new PriceFeedItem();
    priceFeedItem.setPriceExtensionType(PriceExtensionType.SERVICES);

    // Price qualifier is optional.
    priceFeedItem.setPriceQualifier(PriceExtensionPriceQualifier.FROM);
    priceFeedItem.setTrackingUrlTemplate("http://tracker.example.com/?u={lpurl}");
    priceFeedItem.setLanguage("en");
    FeedItemCampaignTargeting campaignTargeting = new FeedItemCampaignTargeting();
    campaignTargeting.setTargetingCampaignId(campaignId);
    priceFeedItem.setCampaignTargeting(campaignTargeting);
    priceFeedItem.setScheduling(
        new FeedItemScheduling(
            new FeedItemSchedule[] {
              new FeedItemSchedule(DayOfWeek.SUNDAY, 10, MinuteOfHour.ZERO, 18, MinuteOfHour.ZERO),
              new FeedItemSchedule(DayOfWeek.SATURDAY, 10, MinuteOfHour.ZERO, 22, MinuteOfHour.ZERO)
            }));

    // To create a price extension, at least three table rows are needed.
    List<PriceTableRow> priceTableRows = new ArrayList<>();
    String currencyCode = "USD";
    priceTableRows.add(
        createPriceTableRow(
            "Scrubs",
            "Body Scrub, Salt Scrub",
            "http://www.example.com/scrubs",
            "http://m.example.com/scrubs",
            60000000,
            currencyCode,
            PriceExtensionPriceUnit.PER_HOUR));
    priceTableRows.add(
        createPriceTableRow(
            "Hair Cuts",
            "Once a month",
            "http://www.example.com/haircuts",
            "http://m.example.com/haircuts",
            75000000,
            currencyCode,
            PriceExtensionPriceUnit.PER_MONTH));
    priceTableRows.add(
        createPriceTableRow(
            "Skin Care Package",
            "Four times a month",
            "http://www.example.com/skincarepackage",
            null,
            250000000,
            currencyCode,
            PriceExtensionPriceUnit.PER_MONTH));
    priceFeedItem.setTableRows(priceTableRows.toArray(new PriceTableRow[priceTableRows.size()]));

    // Create your campaign extension settings. This associates the sitelinks
    // to your campaign.
    CustomerExtensionSetting customerExtensionSetting = new CustomerExtensionSetting();
    customerExtensionSetting.setExtensionType(FeedType.PRICE);
    ExtensionSetting extensionSetting = new ExtensionSetting();
    extensionSetting.setExtensions(new ExtensionFeedItem[] {priceFeedItem});
    customerExtensionSetting.setExtensionSetting(extensionSetting);
    
    CustomerExtensionSettingOperation operation = new CustomerExtensionSettingOperation();
    operation.setOperand(customerExtensionSetting);
    operation.setOperator(Operator.ADD);

    // Add the extensions.
    CustomerExtensionSettingReturnValue returnValue =
        customerExtensionSettingService.mutate(new CustomerExtensionSettingOperation[] {operation});
    if (returnValue.getValue() != null && returnValue.getValue().length > 0) {
      CustomerExtensionSetting newExtensionSetting = returnValue.getValue(0);
      System.out.printf(
          "Extension setting with type '%s' was added.%n",
          newExtensionSetting.getExtensionType().getValue());
    } else {
      System.out.println("No extension settings were created.");
    }
  }

  /**
   * Creates a new {@link PriceTableRow} with the specified attributes.
   *
   * @param header the header for the row
   * @param description the description for the row
   * @param finalUrl the final URL for the row
   * @param finalMobileUrl the final mobile URL for the row, or null if this field should not be set
   * @param priceInMicros the price for the row, in micros
   * @param currencyCode the currency for the row
   * @param priceUnit the price unit for the row
   * @return a new {@link PriceTableRow}
   */
  private static PriceTableRow createPriceTableRow(
      String header,
      String description,
      String finalUrl,
      String finalMobileUrl,
      long priceInMicros,
      String currencyCode,
      PriceExtensionPriceUnit priceUnit) {
    PriceTableRow priceTableRow = new PriceTableRow();
    priceTableRow.setHeader(header);
    priceTableRow.setDescription(description);

    UrlList finalUrls = new UrlList();
    finalUrls.setUrls(new String[] {finalUrl});
    priceTableRow.setFinalUrls(finalUrls);

    if (finalMobileUrl != null) {
      UrlList finalMobileUrls = new UrlList();
      finalMobileUrls.setUrls(new String[] {finalMobileUrl});
      priceTableRow.setFinalMobileUrls(finalMobileUrls);
    }

    MoneyWithCurrency price = new MoneyWithCurrency();
    Money priceMoney = new Money();
    price.setCurrencyCode(currencyCode);
    priceMoney.setMicroAmount(priceInMicros);
    price.setMoney(priceMoney);
    priceTableRow.setPrice(price);
    priceTableRow.setPriceUnit(priceUnit);

    return priceTableRow;
  }

}

// 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.extensions;

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

import adwords.axis.v201809.basicoperations.AddCampaigns;
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.CampaignExtensionSetting;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignExtensionSettingOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignExtensionSettingReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignExtensionSettingServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.DayOfWeek;
import com.google.api.ads.adwords.axis.v201809.cm.ExtensionFeedItem;
import com.google.api.ads.adwords.axis.v201809.cm.ExtensionSetting;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemDevicePreference;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemGeoRestriction;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemSchedule;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemScheduling;
import com.google.api.ads.adwords.axis.v201809.cm.FeedType;
import com.google.api.ads.adwords.axis.v201809.cm.GeoRestriction;
import com.google.api.ads.adwords.axis.v201809.cm.Keyword;
import com.google.api.ads.adwords.axis.v201809.cm.KeywordMatchType;
import com.google.api.ads.adwords.axis.v201809.cm.Location;
import com.google.api.ads.adwords.axis.v201809.cm.MinuteOfHour;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
import com.google.api.ads.adwords.axis.v201809.cm.SitelinkFeedItem;
import com.google.api.ads.adwords.axis.v201809.cm.UrlList;
import com.google.api.ads.adwords.axis.v201809.mcm.Customer;
import com.google.api.ads.adwords.axis.v201809.mcm.CustomerServiceInterface;
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 org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

/**
 * This example adds sitelinks to a campaign. To create a campaign, run {@link AddCampaigns}.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddSiteLinks {

  private static class AddSiteLinksParams 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();

    AddSiteLinksParams params = new AddSiteLinksParams();
    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 campaign where sitelinks will be added.
   * @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 ApiException, RemoteException {
    // Get the CustomerService.
    CustomerServiceInterface customerService =
        adWordsServices.get(session, CustomerServiceInterface.class);

    // Find the matching customer and its time zone. The getCustomers method will return
    // a single Customer object corresponding to the session's clientCustomerId.
    Customer customer = customerService.getCustomers()[0];
    DateTimeZone customerTimeZone = DateTimeZone.forID(customer.getDateTimeZone());
    System.out.printf(
        "Found customer ID %d with time zone '%s'.%n",
        customer.getCustomerId(), customer.getDateTimeZone());

    // Get the CampaignExtensionSettingService.
    CampaignExtensionSettingServiceInterface campaignExtensionSettingService =
        adWordsServices.get(session, CampaignExtensionSettingServiceInterface.class);

    // Create the sitelinks.
    SitelinkFeedItem sitelink1 =
        createSiteLinkFeedItem("Store Hours", "http://www.example.com/storehours");

    // Show the Thanksgiving specials link only from 20 - 27 Nov.
    SitelinkFeedItem sitelink2 =
        createSiteLinkFeedItem("Thanksgiving Specials", "http://www.example.com/thanksgiving");

    // The time zone of the start and end date/times must match the time zone of the customer.
    DateTime startTime = new DateTime(DateTime.now().getYear(), 11, 20, 0, 0, 0, customerTimeZone);
    if (startTime.isBeforeNow()) {
      // Move the startTime to next year if the current date is past November 20th.
      startTime = startTime.plusYears(1);
    }
    sitelink2.setStartTime(startTime.toString("yyyyMMdd HHmmss ZZZ"));
    // Use the same year as startTime when creating endTime.
    DateTime endTime = new DateTime(startTime.getYear(), 11, 27, 23, 59, 59, customerTimeZone);
    sitelink2.setEndTime(endTime.toString("yyyyMMdd HHmmss ZZZ"));

    // Target this sitelink for United States only. See
    // https://developers.google.com/adwords/api/docs/appendix/geotargeting
    // for valid geolocation codes.
    Location unitedStates = new Location();
    unitedStates.setId(2840L);
    sitelink2.setGeoTargeting(unitedStates);

    // Restrict targeting only to people physically within the United States.
    // Otherwise, this could also show to people interested in the United States
    // but not physically located there.
    FeedItemGeoRestriction geoTargetingRestriction = new FeedItemGeoRestriction();
    geoTargetingRestriction.setGeoRestriction(GeoRestriction.LOCATION_OF_PRESENCE);
    sitelink2.setGeoTargetingRestriction(geoTargetingRestriction);

    // Show the wifi details primarily for high end mobile users.
    SitelinkFeedItem sitelink3 =
        createSiteLinkFeedItem("Wifi available", "http://www.example.com/mobile/wifi");
    // See https://developers.google.com/adwords/api/docs/appendix/platforms for device criteria
    // IDs.
    FeedItemDevicePreference devicePreference = new FeedItemDevicePreference(30001L);
    sitelink3.setDevicePreference(devicePreference);

    // Target this sitelink for the keyword "free wifi".
    Keyword wifiKeyword = new Keyword();
    wifiKeyword.setText("free wifi");
    wifiKeyword.setMatchType(KeywordMatchType.BROAD);
    sitelink3.setKeywordTargeting(wifiKeyword);

    // Show the happy hours link only during Mon - Fri 6PM to 9PM.
    SitelinkFeedItem sitelink4 =
        createSiteLinkFeedItem("Happy hours", "http://www.example.com/happyhours");
    sitelink4.setScheduling(new FeedItemScheduling(new FeedItemSchedule[] {
        new FeedItemSchedule(DayOfWeek.MONDAY, 18, MinuteOfHour.ZERO, 21, MinuteOfHour.ZERO),
        new FeedItemSchedule(DayOfWeek.TUESDAY, 18, MinuteOfHour.ZERO, 21, MinuteOfHour.ZERO),
        new FeedItemSchedule(DayOfWeek.WEDNESDAY, 18, MinuteOfHour.ZERO, 21, MinuteOfHour.ZERO),
        new FeedItemSchedule(DayOfWeek.THURSDAY, 18, MinuteOfHour.ZERO, 21, MinuteOfHour.ZERO),
        new FeedItemSchedule(DayOfWeek.FRIDAY, 18, MinuteOfHour.ZERO, 21, MinuteOfHour.ZERO)}));

    // Create your campaign extension settings. This associates the sitelinks
    // to your campaign.
    CampaignExtensionSetting campaignExtensionSetting = new CampaignExtensionSetting();
    campaignExtensionSetting.setCampaignId(campaignId);
    campaignExtensionSetting.setExtensionType(FeedType.SITELINK);
    ExtensionSetting extensionSetting = new ExtensionSetting();
    extensionSetting.setExtensions(
        new ExtensionFeedItem[] {sitelink1, sitelink2, sitelink3, sitelink4});
    campaignExtensionSetting.setExtensionSetting(extensionSetting);

    CampaignExtensionSettingOperation operation = new CampaignExtensionSettingOperation();
    operation.setOperand(campaignExtensionSetting);
    operation.setOperator(Operator.ADD);

    // Add the extensions.
    CampaignExtensionSettingReturnValue returnValue =
        campaignExtensionSettingService.mutate(new CampaignExtensionSettingOperation[] {operation});
    if (returnValue.getValue() != null && returnValue.getValue().length > 0) {
      CampaignExtensionSetting newExtensionSetting = returnValue.getValue(0);
      System.out.printf("Extension setting with type '%s' was added to campaign ID %d.%n",
          newExtensionSetting.getExtensionType().getValue(), newExtensionSetting.getCampaignId());
    } else {
      System.out.println("No extension settings were created.");
    }
  }

  /**
   * Creates a new {@link SitelinkFeedItem} with the specified attributes.
   *
   * @param sitelinkText the text for the sitelink
   * @param sitelinkUrl the URL for the sitelink
   * @return a new SitelinkFeedItem
   */
  private static SitelinkFeedItem createSiteLinkFeedItem(String sitelinkText, String sitelinkUrl) {
    SitelinkFeedItem sitelinkFeedItem = new SitelinkFeedItem();
    sitelinkFeedItem.setSitelinkText(sitelinkText);
    sitelinkFeedItem.setSitelinkFinalUrls(new UrlList(new String[] {sitelinkUrl}));
    return sitelinkFeedItem;
  }

}

// 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.extensions;

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

import adwords.axis.v201809.basicoperations.AddCampaigns;
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.AttributeFieldMapping;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignFeed;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignFeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignFeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.CampaignFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Feed;
import com.google.api.ads.adwords.axis.v201809.cm.FeedAttribute;
import com.google.api.ads.adwords.axis.v201809.cm.FeedAttributeType;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItem;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemAdGroupTarget;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemAttributeValue;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemCriterionTarget;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemGeoRestriction;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemOperation;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemTargetOperation;
import com.google.api.ads.adwords.axis.v201809.cm.FeedItemTargetServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.FeedMapping;
import com.google.api.ads.adwords.axis.v201809.cm.FeedMappingOperation;
import com.google.api.ads.adwords.axis.v201809.cm.FeedMappingReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.FeedMappingServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.FeedOperation;
import com.google.api.ads.adwords.axis.v201809.cm.FeedOrigin;
import com.google.api.ads.adwords.axis.v201809.cm.FeedReturnValue;
import com.google.api.ads.adwords.axis.v201809.cm.FeedServiceInterface;
import com.google.api.ads.adwords.axis.v201809.cm.Function;
import com.google.api.ads.adwords.axis.v201809.cm.GeoRestriction;
import com.google.api.ads.adwords.axis.v201809.cm.Location;
import com.google.api.ads.adwords.axis.v201809.cm.Operator;
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 com.google.common.base.Joiner;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;

/**
 * This example adds a sitelinks feed and associates it with a campaign. To create a campaign,
 * run {@link AddCampaigns}. To add sitelinks using the simpler ExtensionSetting services, see
 * {@link AddSiteLinks}.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddSiteLinksUsingFeeds {

  private static class AddSiteLinksUsingFeedsParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.CAMPAIGN_ID, required = true)
    private Long campaignId;

    @Parameter(names = ArgumentNames.FEED_NAME, required = true)
    private String feedName;

    @Parameter(names = ArgumentNames.AD_GROUP_ID)
    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();

    AddSiteLinksUsingFeedsParams params = new AddSiteLinksUsingFeedsParams();
    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");
      params.feedName = "INSERT_FEED_NAME_HERE";
      // Optional: Ad group to restrict targeting to.
      params.adGroupId = null;
    }

    try {
      runExample(adWordsServices, session, params.campaignId, params.feedName, 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 campaignId the ID of the campaign where sitelinks will be added.
   * @param feedName the name of the new sitelinks feed.
   * @param adGroupId the ID of the ad group to which additional targeting will be applied.
   * @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, String feedName, @Nullable Long adGroupId) throws RemoteException {
    SiteLinksDataHolder siteLinksData = new SiteLinksDataHolder();
    createSiteLinksFeed(adWordsServices, session, siteLinksData, feedName);
    createSiteLinksFeedItems(adWordsServices, session, siteLinksData);
    createSiteLinksFeedMapping(adWordsServices, session, siteLinksData);
    createSiteLinksCampaignFeed(adWordsServices, session, siteLinksData, campaignId);
    // Optional: Restrict the first feed item to only serve with ads for the specified ad group ID.
    if (adGroupId != null) {
      restrictFeedItemToAdGroup(adWordsServices, session, siteLinksData, adGroupId);
    }
  }

  private static void createSiteLinksFeed(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      SiteLinksDataHolder siteLinksData,
      String feedName)
      throws RemoteException {
    // Get the FeedService.
    FeedServiceInterface feedService = adWordsServices.get(session, FeedServiceInterface.class);

    // Create attributes.
    FeedAttribute textAttribute = new FeedAttribute();
    textAttribute.setType(FeedAttributeType.STRING);
    textAttribute.setName("Link Text");
    FeedAttribute finalUrlAttribute = new FeedAttribute();
    finalUrlAttribute.setType(FeedAttributeType.URL_LIST);
    finalUrlAttribute.setName("Link Final URLs");
    FeedAttribute line2Attribute = new FeedAttribute();
    line2Attribute.setType(FeedAttributeType.STRING);
    line2Attribute.setName("Line 2");
    FeedAttribute line3Attribute = new FeedAttribute();
    line3Attribute.setType(FeedAttributeType.STRING);
    line3Attribute.setName("Line 3");

    // Create the feed.
    Feed siteLinksFeed = new Feed();
    siteLinksFeed.setName(feedName);
    siteLinksFeed.setAttributes(
        new FeedAttribute[] {textAttribute, finalUrlAttribute, line2Attribute, line3Attribute});
    siteLinksFeed.setOrigin(FeedOrigin.USER);

    // Create operation.
    FeedOperation operation = new FeedOperation();
    operation.setOperand(siteLinksFeed);
    operation.setOperator(Operator.ADD);

    // Add the feed.
    FeedReturnValue result = feedService.mutate(new FeedOperation[] {operation});

    Feed savedFeed = result.getValue()[0];
    siteLinksData.siteLinksFeedId = savedFeed.getId();
    FeedAttribute[] savedAttributes = savedFeed.getAttributes();
    siteLinksData.linkTextFeedAttributeId = savedAttributes[0].getId();
    siteLinksData.linkFinalUrlFeedAttributeId = savedAttributes[1].getId();
    siteLinksData.line2FeedAttributeId = savedAttributes[2].getId();
    siteLinksData.line3FeedAttributeId = savedAttributes[3].getId();
    System.out.printf("Feed with name '%s' and ID %d with linkTextAttributeId %d"
        + " and linkFinalUrlAttributeId %d and line2AttributeId %d"
        + " and line3AttributeId %d was created.%n",
        savedFeed.getName(),
        savedFeed.getId(),
        savedAttributes[0].getId(),
        savedAttributes[1].getId(),
        savedAttributes[2].getId(),
        savedAttributes[3].getId());
  }

  private static void createSiteLinksFeedItems(AdWordsServicesInterface adWordsServices,
      AdWordsSession session, SiteLinksDataHolder siteLinksData) throws RemoteException {
    // Get the FeedItemService.
    FeedItemServiceInterface feedItemService =
        adWordsServices.get(session, FeedItemServiceInterface.class);

    // Create operations to add FeedItems.
    FeedItemOperation home = newSiteLinkFeedItemAddOperation(siteLinksData, "Home",
        "http://www.example.com", "Home line 2", "Home line 3");
    FeedItemOperation stores = newSiteLinkFeedItemAddOperation(siteLinksData, "Stores",
        "http://www.example.com/stores", "Stores line 2", "Stores line 3");
    FeedItemOperation onSale = newSiteLinkFeedItemAddOperation(siteLinksData, "On Sale",
        "http://www.example.com/sale", "On Sale line 2", "On Sale line 3");
    FeedItemOperation support = newSiteLinkFeedItemAddOperation(siteLinksData, "Support",
        "http://www.example.com/support", "Support line 2", "Support line 3");
    FeedItemOperation products = newSiteLinkFeedItemAddOperation(siteLinksData, "Products",
        "http://www.example.com/prods", "Products line 2", "Products line 3");
    // This site link is using geographical targeting to use LOCATION_OF_PRESENCE.
    FeedItemOperation aboutUs = newSiteLinkFeedItemAddOperation(siteLinksData, "About Us",
        "http://www.example.com/about", "About Us line 2", "About Us line 3", true);

    FeedItemOperation[] operations =
        new FeedItemOperation[] {home, stores, onSale, support, products, aboutUs};

    FeedItemReturnValue result = feedItemService.mutate(operations);
    for (FeedItem item : result.getValue()) {
      System.out.printf("FeedItem with feedItemId %d was added.%n", item.getFeedItemId());
      siteLinksData.siteLinkFeedItemIds.add(item.getFeedItemId());
    }

    // Target the "aboutUs" sitelink to geographically target California.
    // See https://developers.google.com/adwords/api/docs/appendix/geotargeting for
    // location criteria for supported locations.
    restrictFeedItemToGeoTarget(adWordsServices, session, result.getValue(5), 21137L);
  }


  // See the Placeholder reference page for a list of all the placeholder types and fields.
  // https://developers.google.com/adwords/api/docs/appendix/placeholders
  private static final int PLACEHOLDER_SITELINKS = 1;

  // See the Placeholder reference page for a list of all the placeholder types and fields.
  // https://developers.google.com/adwords/api/docs/appendix/placeholders
  private static final int PLACEHOLDER_FIELD_SITELINK_LINK_TEXT = 1;
  private static final int PLACEHOLDER_FIELD_SITELINK_FINAL_URL = 5;
  private static final int PLACEHOLDER_FIELD_LINE_2_TEXT = 3;
  private static final int PLACEHOLDER_FIELD_LINE_3_TEXT = 4;

  private static void createSiteLinksFeedMapping(AdWordsServicesInterface adWordsServices,
      AdWordsSession session, SiteLinksDataHolder siteLinksData) throws RemoteException {
    // Get the FeedItemService.
    FeedMappingServiceInterface feedMappingService =
        adWordsServices.get(session, FeedMappingServiceInterface.class);

    // Map the FeedAttributeIds to the fieldId constants.
    AttributeFieldMapping linkTextFieldMapping = new AttributeFieldMapping();
    linkTextFieldMapping.setFeedAttributeId(siteLinksData.linkTextFeedAttributeId);
    linkTextFieldMapping.setFieldId(PLACEHOLDER_FIELD_SITELINK_LINK_TEXT);
    AttributeFieldMapping linkFinalUrlFieldMapping = new AttributeFieldMapping();
    linkFinalUrlFieldMapping.setFeedAttributeId(siteLinksData.linkFinalUrlFeedAttributeId);
    linkFinalUrlFieldMapping.setFieldId(PLACEHOLDER_FIELD_SITELINK_FINAL_URL);
    AttributeFieldMapping line2FieldMapping = new AttributeFieldMapping();
    line2FieldMapping.setFeedAttributeId(siteLinksData.line2FeedAttributeId);
    line2FieldMapping.setFieldId(PLACEHOLDER_FIELD_LINE_2_TEXT);
    AttributeFieldMapping line3FieldMapping = new AttributeFieldMapping();
    line3FieldMapping.setFeedAttributeId(siteLinksData.line3FeedAttributeId);
    line3FieldMapping.setFieldId(PLACEHOLDER_FIELD_LINE_3_TEXT);


    // Create the FeedMapping and operation.
    FeedMapping feedMapping = new FeedMapping();
    feedMapping.setPlaceholderType(PLACEHOLDER_SITELINKS);
    feedMapping.setFeedId(siteLinksData.siteLinksFeedId);
    feedMapping.setAttributeFieldMappings(new AttributeFieldMapping[] {linkTextFieldMapping,
        linkFinalUrlFieldMapping, line2FieldMapping, line3FieldMapping});
    FeedMappingOperation operation = new FeedMappingOperation();
    operation.setOperand(feedMapping);
    operation.setOperator(Operator.ADD);

    // Save the field mapping.
    FeedMappingReturnValue result =
        feedMappingService.mutate(new FeedMappingOperation[] {operation});
    for (FeedMapping savedFeedMapping : result.getValue()) {
      System.out.printf(
          "Feed mapping with ID %d and placeholderType %d was saved for feed with ID %d.%n",
          savedFeedMapping.getFeedMappingId(), savedFeedMapping.getPlaceholderType(),
          savedFeedMapping.getFeedId());
    }
  }

  private static void createSiteLinksCampaignFeed(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      SiteLinksDataHolder siteLinksData,
      Long campaignId)
      throws RemoteException {
    // Get the CampaignFeedService.
    CampaignFeedServiceInterface campaignFeedService =
        adWordsServices.get(session, CampaignFeedServiceInterface.class);

    // Construct a matching function that associates the sitelink feed items to the campaign, and
    // sets the device preference to mobile. See the matching function guide at
    // https://developers.google.com/adwords/api/docs/guides/feed-matching-functions
    // for more details.
    String matchingFunctionString = String.format(
        "AND( IN(FEED_ITEM_ID, {%s}), EQUALS(CONTEXT.DEVICE, 'Mobile') )",
        Joiner.on(',').join(siteLinksData.siteLinkFeedItemIds));

    CampaignFeed campaignFeed = new CampaignFeed();
    campaignFeed.setFeedId(siteLinksData.siteLinksFeedId);
    campaignFeed.setCampaignId(campaignId);

    Function matchingFunction = new Function();
    matchingFunction.setFunctionString(matchingFunctionString);

    campaignFeed.setMatchingFunction(matchingFunction);
    // Specifying placeholder types on the CampaignFeed allows the same feed
    // to be used for different placeholders in different Campaigns.
    campaignFeed.setPlaceholderTypes(new int[] {PLACEHOLDER_SITELINKS});

    CampaignFeedOperation operation = new CampaignFeedOperation();
    operation.setOperand(campaignFeed);
    operation.setOperator(Operator.ADD);
    CampaignFeedReturnValue result =
        campaignFeedService.mutate(new CampaignFeedOperation[] {operation});
    for (CampaignFeed savedCampaignFeed : result.getValue()) {
      System.out.printf("Campaign with ID %d was associated with feed with ID %d.%n",
          savedCampaignFeed.getCampaignId(), savedCampaignFeed.getFeedId());
    }
  }

  private static FeedItemOperation newSiteLinkFeedItemAddOperation(
      SiteLinksDataHolder siteLinksData, String text, String finalUrl, String line2, String line3) {
    return newSiteLinkFeedItemAddOperation(siteLinksData, text, finalUrl, line2, line3, false);
  }

  private static FeedItemOperation newSiteLinkFeedItemAddOperation(
      SiteLinksDataHolder siteLinksData, String text, String finalUrl, String line2, String line3,
      boolean restrictToLop) {
    // Create the FeedItemAttributeValues for our text values.
    FeedItemAttributeValue linkTextAttributeValue = new FeedItemAttributeValue();
    linkTextAttributeValue.setFeedAttributeId(siteLinksData.linkTextFeedAttributeId);
    linkTextAttributeValue.setStringValue(text);
    FeedItemAttributeValue linkFinalUrlAttributeValue = new FeedItemAttributeValue();
    linkFinalUrlAttributeValue.setFeedAttributeId(siteLinksData.linkFinalUrlFeedAttributeId);
    linkFinalUrlAttributeValue.setStringValues(new String[] {finalUrl});
    FeedItemAttributeValue line2TextAttributeValue = new FeedItemAttributeValue();
    line2TextAttributeValue.setFeedAttributeId(siteLinksData.line2FeedAttributeId);
    line2TextAttributeValue.setStringValue(line2);
    FeedItemAttributeValue line3TextAttributeValue = new FeedItemAttributeValue();
    line3TextAttributeValue.setFeedAttributeId(siteLinksData.line3FeedAttributeId);
    line3TextAttributeValue.setStringValue(line3);

    // Create the feed item and operation.
    FeedItem item = new FeedItem();
    item.setFeedId(siteLinksData.siteLinksFeedId);
    item.setAttributeValues(new FeedItemAttributeValue[] {linkTextAttributeValue,
        linkFinalUrlAttributeValue, line2TextAttributeValue, line3TextAttributeValue});

    // OPTIONAL: Restrict targeting only to people physically within the location.
    if (restrictToLop) {
      FeedItemGeoRestriction geoTargetingRestriction = new FeedItemGeoRestriction();
      geoTargetingRestriction.setGeoRestriction(GeoRestriction.LOCATION_OF_PRESENCE);
      item.setGeoTargetingRestriction(geoTargetingRestriction);
    }

    // Optional: use item.setStartTime() and item.setEndTime() to specify the
    // time period for the feed to deliver.  The example below will make the feed
    // start now and stop in one month.
    // Make sure you specify the DateTime in the customer's time zone.  You can
    // retrieve this from customer.getDateTimeZone().
    //   item.setStartTime(new DateTime(customerTimeZone).toString("yyyyMMdd HHmmss"));
    //   item.setEndTime(new DateTime(customerTimeZone).plusMonths(1).toString("yyyyMMdd HHmmss"));

    // Optional: use item.setScheduling() to specify time and days of the week for feed to deliver.
    FeedItemOperation operation = new FeedItemOperation();
    operation.setOperand(item);
    operation.setOperator(Operator.ADD);
    return operation;
  }

  /**
   * Restricts the first feed item in {@code siteLinksData} to only serve with ads for the specified
   * ad group ID.
   */
  private static void restrictFeedItemToAdGroup(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      SiteLinksDataHolder siteLinksData,
      Long adGroupId)
      throws RemoteException {
    FeedItemTargetServiceInterface feedItemTargetService =
        adWordsServices.get(session, FeedItemTargetServiceInterface.class);
    FeedItemAdGroupTarget feedItemAdGroupTarget = new FeedItemAdGroupTarget();
    feedItemAdGroupTarget.setAdGroupId(adGroupId);
    feedItemAdGroupTarget.setFeedId(siteLinksData.siteLinksFeedId);
    feedItemAdGroupTarget.setFeedItemId(siteLinksData.siteLinkFeedItemIds.get(0));

    FeedItemTargetOperation operation = new FeedItemTargetOperation();
    operation.setOperand(feedItemAdGroupTarget);
    operation.setOperator(Operator.ADD);

    feedItemAdGroupTarget =
        (FeedItemAdGroupTarget)
            feedItemTargetService.mutate(new FeedItemTargetOperation[] {operation}).getValue(0);
    System.out.printf(
        "Feed item target for feed ID %d and feed item ID %d was created to restrict serving to "
            + "ad group ID %d'.%n",
        feedItemAdGroupTarget.getFeedId(),
        feedItemAdGroupTarget.getFeedItemId(),
        feedItemAdGroupTarget.getAdGroupId());
  }

  /**
   * Restricts the first feed item in {@code siteLinksData} to only serve with ads for the specified
   * location ID.
   */
  private static void restrictFeedItemToGeoTarget(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      FeedItem feedItem,
      Long locationId)
      throws RemoteException {
    FeedItemTargetServiceInterface feedItemTargetService =
        adWordsServices.get(session, FeedItemTargetServiceInterface.class);
    // Optional: Restrict the feed item to only serve with ads for the specified geo target.
    FeedItemCriterionTarget feedItemCriterionTarget = new FeedItemCriterionTarget();
    feedItemCriterionTarget.setFeedId(feedItem.getFeedId());
    feedItemCriterionTarget.setFeedItemId(feedItem.getFeedItemId());
    Location location = new Location();
    // The IDs can be found in the documentation or retrieved with the LocationCriterionService.
    location.setId(locationId);
    feedItemCriterionTarget.setCriterion(location);

    FeedItemTargetOperation operation = new FeedItemTargetOperation();
    operation.setOperand(feedItemCriterionTarget);
    operation.setOperator(Operator.ADD);

    feedItemCriterionTarget =
        (FeedItemCriterionTarget)
            feedItemTargetService.mutate(new FeedItemTargetOperation[] {operation}).getValue(0);
    System.out.printf(
        "Feed item target for feed ID %d and feed item ID %d was created to restrict serving to "
            + "location ID %d'.%n",
        feedItemCriterionTarget.getFeedId(),
        feedItemCriterionTarget.getFeedItemId(),
        feedItemCriterionTarget.getCriterion().getId());
  }

  private static class SiteLinksDataHolder {
    private Long siteLinksFeedId;
    private Long linkTextFeedAttributeId;
    private Long linkFinalUrlFeedAttributeId;
    private Long line2FeedAttributeId;
    private Long line3FeedAttributeId;
    private List<Long> siteLinkFeedItemIds = new ArrayList<Long>();
  }
}