Remarketing Samples

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

Create a remarketing user list (audience)

// 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.v201806.remarketing;

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.v201806.SelectorBuilder;
import com.google.api.ads.adwords.axis.v201806.cm.AdWordsConversionTracker;
import com.google.api.ads.adwords.axis.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTracker;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerPage;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerServiceInterface;
import com.google.api.ads.adwords.axis.v201806.cm.Operator;
import com.google.api.ads.adwords.axis.v201806.cm.Selector;
import com.google.api.ads.adwords.axis.v201806.rm.AdwordsUserListServiceInterface;
import com.google.api.ads.adwords.axis.v201806.rm.BasicUserList;
import com.google.api.ads.adwords.axis.v201806.rm.UserList;
import com.google.api.ads.adwords.axis.v201806.rm.UserListConversionType;
import com.google.api.ads.adwords.axis.v201806.rm.UserListMembershipStatus;
import com.google.api.ads.adwords.axis.v201806.rm.UserListOperation;
import com.google.api.ads.adwords.axis.v201806.rm.UserListReturnValue;
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.v201806.cm.AdwordsUserListField;
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 java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This example adds a remarketing user list (a.k.a. audience).
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddAudience {

  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 UserListService.
    AdwordsUserListServiceInterface userListService =
        adWordsServices.get(session, AdwordsUserListServiceInterface.class);

    // Get the ConversionTrackerService.
    ConversionTrackerServiceInterface conversionTrackerService =
        adWordsServices.get(session, ConversionTrackerServiceInterface.class);

    // Create conversion type (tag).
    UserListConversionType conversionType = new UserListConversionType();
    conversionType.setName("Mars cruise customers #" + System.currentTimeMillis());

    // Create remarketing user list.
    BasicUserList userList = new BasicUserList();
    userList.setName("Mars cruise customers #" + System.currentTimeMillis());
    userList.setDescription("A list of mars cruise customers in the last year");
    userList.setMembershipLifeSpan(365L);
    userList.setConversionTypes(new UserListConversionType[] {conversionType});

    // You can optionally provide these field(s).
    userList.setStatus(UserListMembershipStatus.OPEN);

    // Create operations.
    UserListOperation operation = new UserListOperation();
    operation.setOperand(userList);
    operation.setOperator(Operator.ADD);

    UserListOperation[] operations = new UserListOperation[] {operation};

    // Add user list.
    UserListReturnValue result = userListService.mutate(operations);

    // Display results.
    // Capture the ID(s) of the conversion.
    List<String> conversionIds = new ArrayList<String>();
    for (UserList userListResult : result.getValue()) {
      if (userListResult instanceof BasicUserList) {
        BasicUserList remarketingUserList = (BasicUserList) userListResult;
        for (UserListConversionType userListConversionType : remarketingUserList
            .getConversionTypes()) {
          conversionIds.add(userListConversionType.getId().toString());
        }
      }
    }

    // Create predicate and selector.
    Selector selector = new SelectorBuilder()
        .fields("Id", "GoogleGlobalSiteTag", "GoogleEventSnippet")
        .in(AdwordsUserListField.Id, conversionIds.toArray(new String[0]))
        .build();

    // Get all conversion trackers.
    Map<Long, AdWordsConversionTracker> conversionTrackers =
        new HashMap<Long, AdWordsConversionTracker>();
    ConversionTrackerPage page = conversionTrackerService.get(selector);
    if (page != null && page.getEntries() != null) {
      for (ConversionTracker conversionTracker : page.getEntries()) {
        conversionTrackers.put(conversionTracker.getId(),
            (AdWordsConversionTracker) conversionTracker);
      }
    }

    // Display user lists.
    for (UserList userListResult : result.getValue()) {
      System.out.printf("User list with name '%s' and ID %d was added.%n",
          userListResult.getName(), userListResult.getId());

      // Display user list associated conversion code snippets.
      if (userListResult instanceof BasicUserList) {
        BasicUserList remarketingUserList = (BasicUserList) userListResult;
        for (UserListConversionType userListConversionType : remarketingUserList
            .getConversionTypes()) {
          ConversionTracker conversionTracker =
              conversionTrackers.get(userListConversionType.getId());
          System.out.printf(
              "Google global site tag:%n%s%n%n", conversionTracker.getGoogleGlobalSiteTag());
          System.out.printf(
              "Google event snippet:%n%s%n%n", conversionTracker.getGoogleEventSnippet());
        }
      }
    }
  }
}

Create an AdWords conversion tracker and add to it upload conversions

// 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.v201806.remarketing;

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.v201806.cm.AdWordsConversionTracker;
import com.google.api.ads.adwords.axis.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTracker;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerCategory;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerOperation;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerReturnValue;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerServiceInterface;
import com.google.api.ads.adwords.axis.v201806.cm.ConversionTrackerStatus;
import com.google.api.ads.adwords.axis.v201806.cm.Operator;
import com.google.api.ads.adwords.axis.v201806.cm.UploadConversion;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
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 java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;

/**
 * This example adds an AdWords conversion and an upload conversion tracker.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddConversionTrackers {

  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 ConversionTrackerService.
    ConversionTrackerServiceInterface service =
        adWordsServices.get(session, ConversionTrackerServiceInterface.class);

    List<ConversionTracker> conversionTrackers = new ArrayList<>();

    // Create an AdWords conversion tracker.
    AdWordsConversionTracker adWordsConversionTracker = new AdWordsConversionTracker();
    adWordsConversionTracker.setName("Earth to Mars Cruises Conversion # "
        + System.currentTimeMillis());
    adWordsConversionTracker.setCategory(ConversionTrackerCategory.DEFAULT);

    // You can optionally provide these field(s).
    adWordsConversionTracker.setStatus(ConversionTrackerStatus.ENABLED);
    adWordsConversionTracker.setViewthroughLookbackWindow(15);
    adWordsConversionTracker.setDefaultRevenueValue(1d);
    adWordsConversionTracker.setAlwaysUseDefaultRevenueValue(Boolean.TRUE);
    conversionTrackers.add(adWordsConversionTracker);

    // Create an upload conversion for offline conversion imports.
    UploadConversion uploadConversion = new UploadConversion();
    // Set an appropriate category. This field is optional, and will be set to
    // DEFAULT if not mentioned.
    uploadConversion.setCategory(ConversionTrackerCategory.LEAD);
    uploadConversion.setName("Upload Conversion #" + System.currentTimeMillis());
    uploadConversion.setViewthroughLookbackWindow(30);
    uploadConversion.setCtcLookbackWindow(90);

    // Optional: Set the default currency code to use for conversions
    // that do not specify a conversion currency. This must be an ISO 4217
    // 3-character currency code such as "EUR" or "USD".
    // If this field is not set on this UploadConversion, AdWords will use
    // the account's currency.
    uploadConversion.setDefaultRevenueCurrencyCode("EUR");

    // Optional: Set the default revenue value to use for conversions
    // that do not specify a conversion value. Note that this value
    // should NOT be in micros.
    uploadConversion.setDefaultRevenueValue(2.50);
    // Optional: To upload fractional conversion credits, mark the upload conversion
    // as externally attributed. See
    // https://developers.google.com/adwords/api/docs/guides/conversion-tracking#importing_externally_attributed_conversions
    // to learn more about importing externally attributed conversions.

    // uploadConversion.setIsExternallyAttributed(true);

    conversionTrackers.add(uploadConversion);

    // Create operations.
    List<ConversionTrackerOperation> operations = new ArrayList<>();
    for (ConversionTracker conversionTracker : conversionTrackers) {
      ConversionTrackerOperation operation = new ConversionTrackerOperation();
      operation.setOperator(Operator.ADD);
      operation.setOperand(conversionTracker);
      operations.add(operation);
    }

    // Add the conversions.
    ConversionTrackerReturnValue result =
        service.mutate(operations.toArray(new ConversionTrackerOperation[operations.size()]));

    // Display conversion.
    for (ConversionTracker conversionTracker : result.getValue()) {
      System.out.printf(
          "Conversion with ID %d, name '%s', status '%s', " + "category '%s' was added.%n",
          conversionTracker.getId(),
          conversionTracker.getName(),
          conversionTracker.getStatus(),
          conversionTracker.getCategory());
      if (conversionTracker instanceof AdWordsConversionTracker) {
        System.out.printf(
            "Google global site tag:%n%s%n%n", conversionTracker.getGoogleGlobalSiteTag());
        System.out.printf(
            "Google event snippet:%n%s%n%n", conversionTracker.getGoogleEventSnippet());
      }
    }
  }
}

Create and populate a user list

// 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.v201806.remarketing;

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.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.Operator;
import com.google.api.ads.adwords.axis.v201806.rm.AddressInfo;
import com.google.api.ads.adwords.axis.v201806.rm.AdwordsUserListServiceInterface;
import com.google.api.ads.adwords.axis.v201806.rm.CrmBasedUserList;
import com.google.api.ads.adwords.axis.v201806.rm.CustomerMatchUploadKeyType;
import com.google.api.ads.adwords.axis.v201806.rm.Member;
import com.google.api.ads.adwords.axis.v201806.rm.MutateMembersOperand;
import com.google.api.ads.adwords.axis.v201806.rm.MutateMembersOperation;
import com.google.api.ads.adwords.axis.v201806.rm.MutateMembersReturnValue;
import com.google.api.ads.adwords.axis.v201806.rm.UserList;
import com.google.api.ads.adwords.axis.v201806.rm.UserListOperation;
import com.google.api.ads.adwords.axis.v201806.rm.UserListReturnValue;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
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.collect.ImmutableList;
import java.io.UnsupportedEncodingException;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

/**
 * This example adds a user list (a.k.a. audience) and uploads members to populate the list.
 *
 * <p>
 * <em>Note:</em> It may take up to several hours for the list to be populated with members.
 * Email addresses must be associated with a Google account.
 * For privacy purposes, the user list size will show as zero until the list has at least 1,000
 * members. After that, the size will be rounded to the two most significant digits.
 * </p>
 *
 * <p>
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 * </p>
 */
public class AddCrmBasedUserList {

  private static final ImmutableList<String> EMAILS =
      ImmutableList.of("client1@example.com", "client2@example.com", " Client3@example.com ");
  private static final MessageDigest digest = getSHA256MessageDigest();

  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);
    } catch (UnsupportedEncodingException ue) {
      System.err.printf("Example failed due to encoding exception: %s%n", ue);
    }
  }

  /**
   * 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.
   * @throws UnsupportedEncodingException if encoding the hashed email failed.
   */
  public static void runExample(AdWordsServicesInterface adWordsServices, AdWordsSession session)
      throws RemoteException, UnsupportedEncodingException {
    // Get the UserListService.
    AdwordsUserListServiceInterface userListService =
        adWordsServices.get(session, AdwordsUserListServiceInterface.class);

    // Create a user list.
    CrmBasedUserList userList = new CrmBasedUserList();
    userList.setName("Customer relationship management list #" + System.currentTimeMillis());
    userList.setDescription("A list of customers that originated from email addresses");

    // CRM-based user lists can use a membershipLifeSpan of 10000 to indicate unlimited; otherwise
    // normal values apply.
    userList.setMembershipLifeSpan(30L);
    userList.setUploadKeyType(CustomerMatchUploadKeyType.CONTACT_INFO);

    // Create operation.
    UserListOperation operation = new UserListOperation();
    operation.setOperand(userList);
    operation.setOperator(Operator.ADD);

    // Add user list.
    UserListReturnValue result = userListService.mutate(new UserListOperation[] {operation});

    // Display user list.
    UserList userListAdded = result.getValue(0);
    System.out.printf(
        "User list with name '%s' and ID %d was added.%n",
        userListAdded.getName(), userListAdded.getId());

    // Get user list ID.
    Long userListId = userListAdded.getId();

    // Create operation to add members to the user list based on email addresses.
    MutateMembersOperation mutateMembersOperation = new MutateMembersOperation();
    MutateMembersOperand operand = new MutateMembersOperand();
    operand.setUserListId(userListId);

    // Hash normalized email addresses based on SHA-256 hashing algorithm.
    List<Member> members = new ArrayList<>(EMAILS.size());
    for (String email : EMAILS) {
      String normalizedEmail = toNormalizedString(email);
      Member member = new Member();
      member.setHashedEmail(toSHA256String(normalizedEmail));
      members.add(member);
    }

    String firstName = "John";
    String lastName = "Doe";
    String countryCode = "US";
    String zipCode = "10011";

    AddressInfo addressInfo = new AddressInfo();
    // First and last name must be normalized and hashed.
    addressInfo.setHashedFirstName(toSHA256String(toNormalizedString(firstName)));
    addressInfo.setHashedLastName(toSHA256String(toNormalizedString(lastName)));
    // Country code and zip code are sent in plaintext.
    addressInfo.setCountryCode(countryCode);
    addressInfo.setZipCode(zipCode);

    Member memberByAddress = new Member();
    memberByAddress.setAddressInfo(addressInfo);
    members.add(memberByAddress);

    operand.setMembersList(members.toArray(new Member[members.size()]));
    mutateMembersOperation.setOperand(operand);
    mutateMembersOperation.setOperator(Operator.ADD);

    // Add members to the user list based on email addresses.
    MutateMembersReturnValue mutateMembersResult =
        userListService.mutateMembers(new MutateMembersOperation[] {mutateMembersOperation});

    // Display results.
    // Reminder: it may take several hours for the list to be populated with members.
    for (UserList userListResult : mutateMembersResult.getUserLists()) {
      System.out.printf(
          "%d email addresses were uploaded to user list with name '%s' and ID %d "
              + "and are scheduled for review.%n",
          EMAILS.size(), userListResult.getName(), userListResult.getId());
    }
  }

  /**
   * Hash a string using SHA-256 hashing algorithm.
   *
   * @param str the string to hash.
   * @return the SHA-256 hash string representation.
   * @throws UnsupportedEncodingException If UTF-8 charset is not supported.
   */
  private static String toSHA256String(String str) throws UnsupportedEncodingException {
    byte[] hash = digest.digest(str.getBytes("UTF-8"));
    StringBuilder result = new StringBuilder();
    for (byte b : hash) {
      result.append(String.format("%02x", b));
    }

    return result.toString();
  }

  /**
   * Removes leading and trailing whitespace and converts all characters to lower case.
   *
   * @param value the string to normalize.
   * @return a normalized copy of the string.
   */
  private static String toNormalizedString(String value) {
    return value.trim().toLowerCase();
  }

  /** Returns SHA-256 hashing algorithm. */
  private static MessageDigest getSHA256MessageDigest() {
    try {
      return MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("Missing SHA-256 algorithm implementation.", e);
    }
  }
}

Create rule-based user lists

// 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.v201806.remarketing;

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.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.Operator;
import com.google.api.ads.adwords.axis.v201806.rm.AdwordsUserListServiceInterface;
import com.google.api.ads.adwords.axis.v201806.rm.CombinedRuleUserList;
import com.google.api.ads.adwords.axis.v201806.rm.CombinedRuleUserListRuleOperator;
import com.google.api.ads.adwords.axis.v201806.rm.DateKey;
import com.google.api.ads.adwords.axis.v201806.rm.DateRuleItem;
import com.google.api.ads.adwords.axis.v201806.rm.DateRuleItemDateOperator;
import com.google.api.ads.adwords.axis.v201806.rm.DateSpecificRuleUserList;
import com.google.api.ads.adwords.axis.v201806.rm.ExpressionRuleUserList;
import com.google.api.ads.adwords.axis.v201806.rm.NumberKey;
import com.google.api.ads.adwords.axis.v201806.rm.NumberRuleItem;
import com.google.api.ads.adwords.axis.v201806.rm.NumberRuleItemNumberOperator;
import com.google.api.ads.adwords.axis.v201806.rm.Rule;
import com.google.api.ads.adwords.axis.v201806.rm.RuleBasedUserListPrepopulationStatus;
import com.google.api.ads.adwords.axis.v201806.rm.RuleItem;
import com.google.api.ads.adwords.axis.v201806.rm.RuleItemGroup;
import com.google.api.ads.adwords.axis.v201806.rm.StringKey;
import com.google.api.ads.adwords.axis.v201806.rm.StringRuleItem;
import com.google.api.ads.adwords.axis.v201806.rm.StringRuleItemStringOperator;
import com.google.api.ads.adwords.axis.v201806.rm.UserList;
import com.google.api.ads.adwords.axis.v201806.rm.UserListOperation;
import com.google.api.ads.adwords.axis.v201806.rm.UserListReturnValue;
import com.google.api.ads.adwords.axis.v201806.rm.UserListRuleTypeEnumsEnum;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.factory.AdWordsServicesInterface;
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 java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;

/**
 * This example adds two rule-based remarketing user lists: one with no site visit date
 * restrictions, and another that will only include users who visit your site in the
 * next six months.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 */
public class AddRuleBasedUserLists {

  private static final String DATE_FORMAT_STRING = "yyyyMMdd";

  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 AdwordsUserListService.
    AdwordsUserListServiceInterface userListService =
        adWordsServices.get(session, AdwordsUserListServiceInterface.class);

    // First rule item group - users who visited the checkout page and had more than one item
    // in their shopping cart.
    StringKey pageTypeKey = new StringKey("ecomm_pagetype");

    StringRuleItem checkoutStringRuleItem = new StringRuleItem();
    checkoutStringRuleItem.setKey(pageTypeKey);
    checkoutStringRuleItem.setOp(StringRuleItemStringOperator.EQUALS);
    checkoutStringRuleItem.setValue("checkout");

    RuleItem checkoutRuleItem = new RuleItem();
    checkoutRuleItem.setStringRuleItem(checkoutStringRuleItem);

    NumberKey cartSizeKey = new NumberKey("cartsize");

    NumberRuleItem cartSizeNumberRuleItem = new NumberRuleItem();
    cartSizeNumberRuleItem.setKey(cartSizeKey);
    cartSizeNumberRuleItem.setOp(NumberRuleItemNumberOperator.GREATER_THAN);
    cartSizeNumberRuleItem.setValue(1.0);

    RuleItem cartSizeRuleItem = new RuleItem();
    cartSizeRuleItem.setNumberRuleItem(cartSizeNumberRuleItem);

    // Combine the two rule items into a RuleItemGroup so AdWords will AND their rules
    // together.
    RuleItemGroup checkoutMultipleItemGroup = new RuleItemGroup();
    checkoutMultipleItemGroup.setItems(new RuleItem[] {checkoutRuleItem, cartSizeRuleItem});

    // Second rule item group - users who checked out within the next 3 months.
    DateKey checkoutDateKey = new DateKey("checkoutdate");

    DateRuleItem startDateDateRuleItem = new DateRuleItem();
    startDateDateRuleItem.setKey(checkoutDateKey);
    startDateDateRuleItem.setOp(DateRuleItemDateOperator.AFTER);
    startDateDateRuleItem.setValue(new DateTime().toString(DATE_FORMAT_STRING));

    RuleItem startDateRuleItem = new RuleItem();
    startDateRuleItem.setDateRuleItem(startDateDateRuleItem);

    DateRuleItem endDateDateRuleItem = new DateRuleItem();
    endDateDateRuleItem.setKey(checkoutDateKey);
    endDateDateRuleItem.setOp(DateRuleItemDateOperator.BEFORE);
    endDateDateRuleItem.setValue(new DateTime().plusMonths(3).toString(DATE_FORMAT_STRING));

    RuleItem endDateRuleItem = new RuleItem();
    endDateRuleItem.setDateRuleItem(endDateDateRuleItem);

    // Combine the date rule items into a RuleItemGroup.
    RuleItemGroup checkedOutNextThreeMonthsItemGroup = new RuleItemGroup();
    checkedOutNextThreeMonthsItemGroup.setItems(
        new RuleItem[] {startDateRuleItem, endDateRuleItem});

    // Combine the rule item groups into a Rule so AdWords knows how to apply the rules.
    Rule rule = new Rule();
    rule.setGroups(
        new RuleItemGroup[] {checkoutMultipleItemGroup, checkedOutNextThreeMonthsItemGroup});
    // ExpressionRuleUserLists can use either CNF or DNF for matching. CNF means 'at least one item
    // in each rule item group must match', and DNF means 'at least one entire rule item group must
    // match'. DateSpecificRuleUserList only supports DNF. You can also omit the rule type
    // altogether to default to DNF.
    rule.setRuleType(UserListRuleTypeEnumsEnum.DNF);

    // Third and fourth rule item groups.
    // Visitors of a page who visited another page.
    StringKey urlStringKey = new StringKey("url__");

    StringRuleItem site1StringRuleItem = new StringRuleItem();
    site1StringRuleItem.setKey(urlStringKey);
    site1StringRuleItem.setOp(StringRuleItemStringOperator.EQUALS);
    site1StringRuleItem.setValue("example.com/example1");
    RuleItem site1RuleItem = new RuleItem();
    site1RuleItem.setStringRuleItem(site1StringRuleItem);

    StringRuleItem site2StringRuleItem = new StringRuleItem();
    site2StringRuleItem.setKey(urlStringKey);
    site2StringRuleItem.setOp(StringRuleItemStringOperator.EQUALS);
    site2StringRuleItem.setValue("example.com/example2");
    RuleItem site2RuleItem = new RuleItem();
    site2RuleItem.setStringRuleItem(site2StringRuleItem);

    // Create two RuleItemGroups to show that a visitor browsed two sites.
    RuleItemGroup site1RuleItemGroup = new RuleItemGroup();
    site1RuleItemGroup.setItems(new RuleItem[]{site1RuleItem});
    RuleItemGroup site2RuleItemGroup = new RuleItemGroup();
    site2RuleItemGroup.setItems(new RuleItem[]{site2RuleItem});

    // Create two rules to show that a visitor browsed two sites.
    Rule userVisitedSite1Rule = new Rule();
    userVisitedSite1Rule.setGroups(new RuleItemGroup[]{site1RuleItemGroup});

    Rule userVisitedSite2Rule = new Rule();
    userVisitedSite2Rule.setGroups(new RuleItemGroup[]{site2RuleItemGroup});

    // Create the user list with no restrictions on site visit date.
    ExpressionRuleUserList expressionUserList = new ExpressionRuleUserList();
    String creationTimeString = new DateTime().toString("yyyyMMdd_HHmmss");
    expressionUserList.setName(
        "Expression based user list created at " + creationTimeString);
    expressionUserList.setDescription(
        "Users who checked out in three month window OR visited the checkout page "
        + "with more than one item in their cart");
    expressionUserList.setRule(rule);

    // Optional: Set the prepopulationStatus to REQUESTED to include past users in the user list.
    expressionUserList.setPrepopulationStatus(RuleBasedUserListPrepopulationStatus.REQUESTED);

    // Create the user list restricted to users who visit your site within the next six months.
    DateTime startDate = new DateTime();
    DateTime endDate = startDate.plusMonths(6);

    DateSpecificRuleUserList dateUserList = new DateSpecificRuleUserList();
    dateUserList.setName(
        "Date rule user list created at " + creationTimeString);
    dateUserList.setDescription(String.format("Users who visited the site between %s and %s and "
        + "checked out in three month window OR visited the checkout page "
        + "with more than one item in their cart", startDate.toString(DATE_FORMAT_STRING),
        endDate.toString(DATE_FORMAT_STRING)));
    dateUserList.setRule(rule);

    // Set the start and end dates of the user list.
    dateUserList.setStartDate(startDate.toString(DATE_FORMAT_STRING));
    dateUserList.setEndDate(endDate.toString(DATE_FORMAT_STRING));

    // Create the user list where "Visitors of a page who did visit another page".
    // To create a user list where "Visitors of a page who did not visit another
    // page", change the ruleOperator from AND to AND_NOT.
    CombinedRuleUserList combinedRuleUserList = new CombinedRuleUserList();
    combinedRuleUserList.setName("Combined rule user list created at " + creationTimeString);
    combinedRuleUserList.setDescription("Users who visited two sites.");
    combinedRuleUserList.setLeftOperand(userVisitedSite1Rule);
    combinedRuleUserList.setRightOperand(userVisitedSite2Rule);
    combinedRuleUserList.setRuleOperator(CombinedRuleUserListRuleOperator.AND);

    // Create operations to add the user lists.
    List<UserListOperation> operations = new ArrayList<>();
    for (UserList userList :
        new UserList[] {expressionUserList, dateUserList, combinedRuleUserList}) {
      UserListOperation operation = new UserListOperation();
      operation.setOperand(userList);
      operation.setOperator(Operator.ADD);
      operations.add(operation);
    }

    // Submit the operations.
    UserListReturnValue result =
        userListService.mutate(operations.toArray(new UserListOperation[operations.size()]));

    // Display the results.
    for (UserList userListResult : result.getValue()) {
      System.out.printf("User list added with ID %d, name '%s', status '%s', list type '%s',"
          + " accountUserListStatus '%s', description '%s'.%n",
          userListResult.getId(),
          userListResult.getName(),
          userListResult.getStatus().getValue(),
          userListResult.getListType() == null ? null : userListResult.getListType().getValue(),
          userListResult.getAccountUserListStatus().getValue(),
          userListResult.getDescription());
    }
  }
}

Import conversion adjustments for existing conversions

// 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.v201806.remarketing;

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.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.GclidOfflineConversionAdjustmentFeed;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionAdjustmentFeed;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionAdjustmentFeedOperation;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionAdjustmentFeedReturnValue;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionAdjustmentFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionAdjustmentType;
import com.google.api.ads.adwords.axis.v201806.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 java.rmi.RemoteException;
import javax.annotation.Nullable;

/**
 * This code example imports conversion adjustments for conversions that already exist. To set up a
 * conversion tracker, run the AddConversionTrackers.java example.
 */
public class UploadConversionAdjustment {

  private static class UploadOfflineConversionsParams extends CodeSampleParams {
    @Parameter(
        names = ArgumentNames.CONVERSION_NAME,
        required = true,
        description = "Name of the conversion tracker.")
    private String conversionName;

    @Parameter(names = ArgumentNames.GCL_ID, required = true)
    private String gclId;

    @Parameter(names = ArgumentNames.ADJUSTMENT_TYPE, required = true)
    private String adjustmentType;

    @Parameter(
        names = ArgumentNames.CONVERSION_TIME,
        required = true,
        description = "Conversion time should be after the click time.")
    private String conversionTime;

    @Parameter(names = ArgumentNames.ADJUSTMENT_TIME, required = true)
    private String adjustmentTime;

    @Parameter(
        names = ArgumentNames.ADJUSTED_VALUE,
        description = "Adjusted value for restatements.")
    private Double adjustedValue = null;
  }

  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();

    UploadOfflineConversionsParams params = new UploadOfflineConversionsParams();
    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.conversionName = "INSERT_CONVERSION_NAME_HERE";
      params.gclId = "INSERT_GCL_ID_HERE";
      params.adjustmentType = "INSERT_ADJUSTMENT_TYPE_HERE";
      params.conversionTime = "INSERT_CONVERSION_TIME_HERE";
      params.adjustmentTime = "INSERT_ADJUSTMENT_TIME_HERE";
      // Optional: Adjusted value for adjustment type RESTATE.
      params.adjustedValue = Double.parseDouble("INSERT_ADJUSTED_VALUE_HERE");
    }

    OfflineConversionAdjustmentType adjustmentTypeEnum =
        OfflineConversionAdjustmentType.fromString(params.adjustmentType);

    try {
      runExample(
          adWordsServices,
          session,
          params.conversionName,
          params.gclId,
          adjustmentTypeEnum,
          params.conversionTime,
          params.adjustmentTime,
          params.adjustedValue);
    } 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 conversionName the name of the conversion tracker.
   * @param gclId the GCLID for the conversion.
   * @param adjustmentType the type of adjustment.
   * @param adjustmentTime the date and time of the adjustment.
   * @param conversionTime the date and time of the conversion.
   * @param adjustedValue the adjusted value.
   * @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,
      String conversionName,
      String gclId,
      OfflineConversionAdjustmentType adjustmentType,
      String conversionTime,
      String adjustmentTime,
      @Nullable Double adjustedValue)
      throws RemoteException {

    // Get the OfflineConversionAdjustmentFeedService.
    OfflineConversionAdjustmentFeedServiceInterface offlineConversionFeedService =
        adWordsServices.get(session, OfflineConversionAdjustmentFeedServiceInterface.class);

    // Associate conversion adjustments with the existing named conversion tracker. The GCLID should
    // have been uploaded before with a conversion.
    GclidOfflineConversionAdjustmentFeed feed = new GclidOfflineConversionAdjustmentFeed();
    feed.setConversionName(conversionName);
    feed.setAdjustmentType(adjustmentType);
    feed.setConversionTime(conversionTime);
    feed.setAdjustmentTime(adjustmentTime);
    feed.setAdjustedValue(adjustedValue);
    feed.setGoogleClickId(gclId);

    OfflineConversionAdjustmentFeedOperation offlineConversionOperation =
        new OfflineConversionAdjustmentFeedOperation();
    offlineConversionOperation.setOperator(Operator.ADD);
    offlineConversionOperation.setOperand(feed);

    OfflineConversionAdjustmentFeedReturnValue offlineConversionReturnValue =
        offlineConversionFeedService.mutate(
            new OfflineConversionAdjustmentFeedOperation[] {offlineConversionOperation});

    for (OfflineConversionAdjustmentFeed newFeed : offlineConversionReturnValue.getValue()) {
      System.out.printf(
          "Uploaded conversion adjusted value of %.4f for Google Click ID '%s'.%n",
          newFeed.getAdjustedValue(),
          ((GclidOfflineConversionAdjustmentFeed) newFeed).getGoogleClickId());
    }
  }
}

Import offline call conversions

// 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.v201806.remarketing;

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.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineCallConversionFeed;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineCallConversionFeedOperation;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineCallConversionFeedReturnValue;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineCallConversionFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201806.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.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 java.rmi.RemoteException;

/**
 * This example imports offline call conversion values for calls related to the ads in your account.
 *
 * <p>Credentials and properties in {@code fromFile()} are pulled from the "ads.properties" file.
 * See README for more info.
 */
public class UploadOfflineCallConversions {

  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();

    // Replace with valid values of your account.
    String callerId = "INSERT_CALLER_ID_HERE";

    // For times, use the format yyyyMMdd HHmmss tz. For more details on formats, see:
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
    // For time zones, see:
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
    String callStartTime = "INSERT_CALL_START_TIME_HERE";

    // Name of the conversion tracker to upload to.
    String conversionName = "INSERT_CONVERSION_NAME_HERE";

    String conversionTime = "INSERT_CONVERSION_TIME_HERE";
    Double conversionValue = Double.valueOf("INSERT_CONVERSION_VALUE_HERE");

    try {
      runExample(
          adWordsServices,
          session,
          callerId,
          callStartTime,
          conversionName,
          conversionTime,
          conversionValue);
    } 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 callerId the caller ID of the call.
   * @param callStartTime the call start time of the call.
   * @param conversionName the name of the conversion tracker.
   * @param conversionTime the date and time of the conversion.
   * @param conversionValue the value of the conversion.
   * @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,
      String callerId,
      String callStartTime,
      String conversionName,
      String conversionTime,
      double conversionValue)
      throws RemoteException {

    // Get the OfflineCallConversionFeedService.
    OfflineCallConversionFeedServiceInterface offlineCallConversionFeedService =
        adWordsServices.get(session, OfflineCallConversionFeedServiceInterface.class);

    // Associate offline call conversions with the existing named conversion tracker. If this
    // tracker was newly created, it may be a few hours before it can accept conversions.
    OfflineCallConversionFeed feed = new OfflineCallConversionFeed();
    feed.setCallerId(callerId);
    feed.setCallStartTime(callStartTime);
    feed.setConversionName(conversionName);
    feed.setConversionTime(conversionTime);
    feed.setConversionValue(conversionValue);

    OfflineCallConversionFeedOperation offlineCallConversionOperation =
        new OfflineCallConversionFeedOperation();
    offlineCallConversionOperation.setOperator(Operator.ADD);
    offlineCallConversionOperation.setOperand(feed);

    // This example uploads only one call conversion, but you can upload multiple call conversions
    // by passing additional operations.
    OfflineCallConversionFeedReturnValue offlineCallConversionReturnValue =
        offlineCallConversionFeedService.mutate(
            new OfflineCallConversionFeedOperation[] {offlineCallConversionOperation});

    // Display results.
    for (OfflineCallConversionFeed feedResult : offlineCallConversionReturnValue.getValue()) {
      System.out.printf(
          "Uploaded offline conversion value of %.4f for caller ID '%s'.%n",
          feedResult.getConversionValue(), feedResult.getCallerId());
    }
  }
}

Import offline click conversions

// 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.v201806.remarketing;

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.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionFeed;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionFeedOperation;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionFeedReturnValue;
import com.google.api.ads.adwords.axis.v201806.cm.OfflineConversionFeedServiceInterface;
import com.google.api.ads.adwords.axis.v201806.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 java.rmi.RemoteException;

/**
 * This code example imports offline conversion values for specific clicks to
 * your account. To get Google Click ID for a click, run
 * CLICK_PERFORMANCE_REPORT. To set up a conversion tracker, run the
 * AddConversionTrackers.java example.
 */
public class UploadOfflineConversions {

  private static class UploadOfflineConversionsParams extends CodeSampleParams {
    @Parameter(names = ArgumentNames.CONVERSION_NAME, required = true,
        description = "Name of the conversion tracker to upload to.")
    private String conversionName;

    @Parameter(names = ArgumentNames.GCL_ID, required = true,
        description = "Google Click ID should be newer than 30 days.")
    private String gclId;

    @Parameter(names = ArgumentNames.CONVERSION_TIME, required = true,
        description = "Conversion time should be after the click time.")
    private String conversionTime;

    @Parameter(names = ArgumentNames.CONVERSION_VALUE, required = true)
    private Double conversionValue;
  }

  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();



    UploadOfflineConversionsParams params = new UploadOfflineConversionsParams();
    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.conversionName = "INSERT_CONVERSION_NAME_HERE";
      params.gclId = "INSERT_GCL_ID_HERE";
      params.conversionTime = "INSERT_CONVERSION_TIME_HERE";
      params.conversionValue = Double.parseDouble("INSERT_CONVERSION_VALUE_HERE");
    }

    try {
      runExample(
          adWordsServices,
          session,
          params.conversionName,
          params.gclId,
          params.conversionTime,
          params.conversionValue);
    } 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 conversionName the name of the conversion tracker.
   * @param gClid the GCLID for the conversion.
   * @param conversionTime the date and time of the conversion.
   * @param conversionValue the value of the conversion.
   * @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,
      String conversionName, String gClid, String conversionTime,
      double conversionValue) throws RemoteException {

    // Get the OfflineConversionFeedService.
    OfflineConversionFeedServiceInterface offlineConversionFeedService =
        adWordsServices.get(session, OfflineConversionFeedServiceInterface.class);

    // Associate offline conversions with the existing named conversion tracker. If this tracker
    // was newly created, it may be a few hours before it can accept conversions.
    OfflineConversionFeed feed = new OfflineConversionFeed();
    feed.setConversionName(conversionName);
    feed.setConversionTime(conversionTime);
    feed.setConversionValue(conversionValue);
    feed.setGoogleClickId(gClid);

    OfflineConversionFeedOperation offlineConversionOperation =
        new OfflineConversionFeedOperation();
    offlineConversionOperation.setOperator(Operator.ADD);
    offlineConversionOperation.setOperand(feed);

    OfflineConversionFeedReturnValue offlineConversionReturnValue = offlineConversionFeedService
        .mutate(new OfflineConversionFeedOperation[] {offlineConversionOperation});

    OfflineConversionFeed newFeed = offlineConversionReturnValue.getValue(0);

    System.out.printf(
        "Uploaded offline conversion value of %.4f for Google Click ID '%s' to '%s'.%n",
        newFeed.getConversionValue(), newFeed.getGoogleClickId(), newFeed.getConversionName());
  }
}

Upload offline data for store sales transactions

// 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.v201806.remarketing;

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.v201806.cm.ApiError;
import com.google.api.ads.adwords.axis.v201806.cm.ApiException;
import com.google.api.ads.adwords.axis.v201806.cm.FieldPathElement;
import com.google.api.ads.adwords.axis.v201806.cm.Money;
import com.google.api.ads.adwords.axis.v201806.cm.Operator;
import com.google.api.ads.adwords.axis.v201806.rm.FirstPartyUploadMetadata;
import com.google.api.ads.adwords.axis.v201806.rm.MoneyWithCurrency;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineData;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineDataUpload;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineDataUploadOperation;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineDataUploadReturnValue;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineDataUploadServiceInterface;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineDataUploadType;
import com.google.api.ads.adwords.axis.v201806.rm.OfflineDataUploadUserIdentifierType;
import com.google.api.ads.adwords.axis.v201806.rm.StoreSalesTransaction;
import com.google.api.ads.adwords.axis.v201806.rm.StoreSalesUploadCommonMetadata;
import com.google.api.ads.adwords.axis.v201806.rm.ThirdPartyUploadMetadata;
import com.google.api.ads.adwords.axis.v201806.rm.UploadMetadata;
import com.google.api.ads.adwords.axis.v201806.rm.UserIdentifier;
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.collect.ImmutableSet;
import java.io.UnsupportedEncodingException;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.joda.time.DateTime;

/**
 * This code example shows how to upload offline data for store sales transactions.
 *
 * <p>
 * <p>Credentials and properties in {@code fromFile()} are pulled from the
 * "ads.properties" file. See README for more info.
 * </p>
 */
public class UploadOfflineData {

  /** User identifier types whose values must be hashed. */
  private static final ImmutableSet<OfflineDataUploadUserIdentifierType> HASHED_IDENTIFIER_TYPES =
      ImmutableSet.of(
          OfflineDataUploadUserIdentifierType.HASHED_EMAIL,
          OfflineDataUploadUserIdentifierType.HASHED_FIRST_NAME,
          OfflineDataUploadUserIdentifierType.HASHED_LAST_NAME,
          OfflineDataUploadUserIdentifierType.HASHED_PHONE);

  /** Digest to use for hashing values. */
  private static final MessageDigest digest = getSHA256MessageDigest();

  private static final class UploadOfflineDataParams extends CodeSampleParams {

    /**
     * Email addresses for the transactions. Exactly two addresses required. You can pass these on
     * the command line using either of the following options:
     *
     * <ol>
     *   <li>Separate parameter name/value pairs, e.g., {@code --emailAddress address1
     *       --emailAddress address2}
     *   <li>A single name/value pair where the value is comma-separated, e.g., {@code
     *       --emailAddress address1,address2}
     * </ol>
     */
    @Parameter(
      names = ArgumentNames.EMAIL_ADDRESS,
      description = "Email addresses for the transactions. Exactly two addresses required.",
      required = true
    )
    private List<String> emailAddresses;

    @Parameter(names = ArgumentNames.CONVERSION_NAME, required = true)
    private String conversionName;

    @Parameter(names = ArgumentNames.EXTERNAL_UPLOAD_ID, required = true)
    private Long externalUploadId;

    @Parameter(names = ArgumentNames.OFFLINE_DATA_UPLOAD_TYPE)
    private String offlineDataUploadType =
        OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY.getValue();

    @Parameter(
      names = ArgumentNames.UPLOAD_TIME,
      description = "Only required if uploading third party data."
    )
    private String advertiserUploadTime;

    @Parameter(
      names = ArgumentNames.BRIDGE_MAP_VERSION_ID,
      description = "Only required if uploading third party data."
    )
    private String bridgeMapVersionId;

    @Parameter(
      names = ArgumentNames.PARTNER_ID,
      description = "Only required if uploading third party data."
    )
    private Integer partnerId;
  }

  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();

    UploadOfflineDataParams params = new UploadOfflineDataParams();
    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.emailAddresses =
          Arrays.asList("INSERT_EMAIL_ADDRESS_1_HERE", "INSERT_EMAIL_ADDRESS_2_HERE");
      params.conversionName = "INSERT_CONVERSION_NAME_HERE";
      params.externalUploadId = Long.valueOf("INSERT_EXTERNAL_UPLOAD_ID_HERE");

      // Change the enum below to STORE_SALES_UPLOAD_THIRD_PARTY if uploading third party data.
      params.offlineDataUploadType =
          OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY.getValue();

      // The three constants below are needed when uploading third party data.
      // You can safely ignore them if you are uploading first party data.
      // For times, use the format yyyyMMdd HHmmss tz. For more details on formats, see:
      // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
      // For time zones, see:
      // https://developers.google.com/adwords/api/docs/appendix/codes-formats#timezone-ids
      params.advertiserUploadTime = "INSERT_ADVERTISER_UPLOAD_TIME";
      params.bridgeMapVersionId = "INSERT_BRIDGE_MAP_VERSION_ID";
      params.partnerId = Integer.valueOf("INSERT_PARTNER_ID");
    }

    OfflineDataUploadType uploadTypeEnum =
        OfflineDataUploadType.fromString(params.offlineDataUploadType);

    // Set partial failure to true since this example demonstrates how to handle partial
    // failure errors.
    session.setPartialFailure(true);
    try {
      runExample(
          adWordsServices,
          session,
          params.conversionName,
          params.externalUploadId,
          params.emailAddresses,
          uploadTypeEnum,
          params.advertiserUploadTime,
          params.bridgeMapVersionId,
          params.partnerId);
    } 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 (UnsupportedEncodingException ue) {
      System.err.printf("Example failed due to encoding exception: %s%n", ue);
    }
  }

  /**
   * Runs the example.
   *
   * @param adWordsServices the services factory.
   * @param session the session.
   * @param conversionName the name of the conversion tracker.
   * @param externalUploadId the external upload ID.
   * @param emailAddresses the list of email addresses. Must contain exactly two entries for this
   *     example.
   * @param offlineDataUploadType the type of store sales upload metadata to upload.
   * @param advertiserUploadTime the date and time of the advertiser upload.
   * @param bridgeMapVersionId the bridge map version ID.
   * @param partnerId the partner ID.
   * @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 UnsupportedEncodingException if encoding the offline data values failed.
   */
  public static void runExample(
      AdWordsServicesInterface adWordsServices,
      AdWordsSession session,
      String conversionName,
      long externalUploadId,
      List<String> emailAddresses,
      OfflineDataUploadType offlineDataUploadType,
      String advertiserUploadTime,
      String bridgeMapVersionId,
      Integer partnerId)
      throws RemoteException, UnsupportedEncodingException {
    // This example requires exactly 2 email addresses.
    if (emailAddresses.size() != 2) {
      throw new IllegalArgumentException(
          String.format(
              "%d email addresses specified. Please specify exactly 2 email addresses.",
              emailAddresses.size()));
    }

    // Get the OfflineDataUploadService.
    OfflineDataUploadServiceInterface offlineDataUploadService =
        adWordsServices.get(session, OfflineDataUploadServiceInterface.class);

    List<OfflineData> offlineDataList = new ArrayList<>();

    // Create the first offline data for upload.
    // This transaction occurred 7 days ago with amount of 200 USD.
    DateTime transactionTime1 = DateTime.now().minusDays(7);
    long transactionAmount1 = 200_000_000L;
    List<UserIdentifier> userIdentifiers1 =
        Arrays.asList(
            createUserIdentifier(
                OfflineDataUploadUserIdentifierType.HASHED_EMAIL, emailAddresses.get(0)),
            createUserIdentifier(OfflineDataUploadUserIdentifierType.STATE, "New York"));

    offlineDataList.add(
        createOfflineData(
            transactionTime1, transactionAmount1, "USD", conversionName, userIdentifiers1));

    // Create the second offline data for upload.
    // This transaction occurred 14 days ago with amount of 450 EUR.
    DateTime transactionTime2 = DateTime.now().minusDays(14);
    long transactionAmount2 = 450_000_000L;

    List<UserIdentifier> userIdentifiers2 =
        Arrays.asList(
            createUserIdentifier(
                OfflineDataUploadUserIdentifierType.HASHED_EMAIL, emailAddresses.get(1)),
            createUserIdentifier(OfflineDataUploadUserIdentifierType.STATE, "California"));

    offlineDataList.add(
        createOfflineData(
            transactionTime2, transactionAmount2, "EUR", conversionName, userIdentifiers2));

    // Create offline data upload object.
    OfflineDataUpload offlineDataUpload = new OfflineDataUpload();
    offlineDataUpload.setExternalUploadId(externalUploadId);
    offlineDataUpload.setOfflineDataList(
        offlineDataList.toArray(new OfflineData[offlineDataList.size()]));
    offlineDataUpload.setUploadType(offlineDataUploadType);

    // Set the type and metadata of this upload.
    StoreSalesUploadCommonMetadata storeSalesUploadMetadata;
    if (OfflineDataUploadType.STORE_SALES_UPLOAD_FIRST_PARTY.equals(offlineDataUploadType)) {
      storeSalesUploadMetadata = new FirstPartyUploadMetadata();
    } else {
      ThirdPartyUploadMetadata thirdPartyUploadMetadata = new ThirdPartyUploadMetadata();
      thirdPartyUploadMetadata.setAdvertiserUploadTime(advertiserUploadTime);
      thirdPartyUploadMetadata.setValidTransactionRate(1.0);
      thirdPartyUploadMetadata.setPartnerMatchRate(1.0);
      thirdPartyUploadMetadata.setPartnerUploadRate(1.0);
      thirdPartyUploadMetadata.setBridgeMapVersionId(bridgeMapVersionId);
      thirdPartyUploadMetadata.setPartnerId(partnerId);
      storeSalesUploadMetadata = thirdPartyUploadMetadata;
    }
    storeSalesUploadMetadata.setLoyaltyRate(1.0);
    storeSalesUploadMetadata.setTransactionUploadRate(1.0);

    UploadMetadata uploadMetadata = new UploadMetadata();
    uploadMetadata.setStoreSalesUploadCommonMetadata(storeSalesUploadMetadata);
    offlineDataUpload.setUploadMetadata(uploadMetadata);

    // Create an offline data upload operation.
    List<OfflineDataUploadOperation> operations = new ArrayList<>();
    OfflineDataUploadOperation offlineDataUploadOperation = new OfflineDataUploadOperation();
    offlineDataUploadOperation.setOperator(Operator.ADD);
    offlineDataUploadOperation.setOperand(offlineDataUpload);
    operations.add(offlineDataUploadOperation);

    // Upload offline data on the server and print some information.
    OfflineDataUploadReturnValue returnValue =
        offlineDataUploadService.mutate(operations.toArray(new OfflineDataUploadOperation[0]));
    offlineDataUpload = returnValue.getValue(0);
    System.out.printf(
        "Uploaded offline data with external upload ID %d, and upload status %s.%n",
        offlineDataUpload.getExternalUploadId(), offlineDataUpload.getUploadStatus());

    // Print any partial failure errors from the response.
    if (returnValue.getPartialFailureErrors() != null) {
      for (ApiError apiError : returnValue.getPartialFailureErrors()) {
        // Get the index of the failed operation from the error's field path elements.
        Integer operationIndex = getFieldPathElementIndex(apiError, "operations");
        if (operationIndex != null) {
          OfflineDataUpload failedOfflineDataUpload = operations.get(operationIndex).getOperand();
          // Get the index of the entry in the offline data list from the error's field path
          // elements.
          Integer offlineDataListIndex = getFieldPathElementIndex(apiError, "offlineDataList");
          System.out.printf(
              "Offline data list entry %d in operation %d with external upload ID %d and type "
                  + "'%s' has triggered a failure for the following reason: '%s'.%n",
              offlineDataListIndex,
              operationIndex,
              failedOfflineDataUpload.getExternalUploadId(),
              failedOfflineDataUpload.getUploadType(),
              apiError.getErrorString());
        } else {
          System.out.printf(
              "A failure has occurred for the following reason: %s%n", apiError.getErrorString());
        }
      }
    }
  }

  /**
   * Returns the {@link FieldPathElement#getIndex()} for the specified {@code field} name, if
   * present in the error's field path elements.
   *
   * @param apiError the error to inspect.
   * @param field the name of the field to search for in the error's field path elements.
   * @return the index of the entry with the specified field, or {@code null} if no such entry
   *     exists or the entry has a null index.
   */
  private static Integer getFieldPathElementIndex(ApiError apiError, String field) {
    FieldPathElement[] fieldPathElements = apiError.getFieldPathElements();
    if (fieldPathElements == null) {
      return null;
    }
    for(int i = 0; i < fieldPathElements.length; i++) {
      FieldPathElement fieldPathElement = fieldPathElements[i];
      if (field.equals(fieldPathElement.getField())) {
        return fieldPathElement.getIndex();
      }
    }
    return null;
  }

  /** Returns a new offline data object with the specified values. */
  private static OfflineData createOfflineData(
      DateTime transactionTime,
      long transactionMicroAmount,
      String transactionCurrency,
      String conversionName,
      List<UserIdentifier> userIdentifiers) {
    StoreSalesTransaction storeSalesTransaction = new StoreSalesTransaction();

    // For times use the format yyyyMMdd HHmmss [tz].
    // For details, see
    // https://developers.google.com/adwords/api/docs/appendix/codes-formats#date-and-time-formats
    storeSalesTransaction.setTransactionTime(transactionTime.toString("yyyyMMdd HHmmss ZZZ"));
    storeSalesTransaction.setConversionName(conversionName);
    storeSalesTransaction.setUserIdentifiers(
        userIdentifiers.toArray(new UserIdentifier[userIdentifiers.size()]));

    Money money = new Money();
    money.setMicroAmount(transactionMicroAmount);

    MoneyWithCurrency moneyWithCurrency = new MoneyWithCurrency();
    moneyWithCurrency.setMoney(money);
    moneyWithCurrency.setCurrencyCode(transactionCurrency);
    storeSalesTransaction.setTransactionAmount(moneyWithCurrency);

    OfflineData offlineData = new OfflineData();
    offlineData.setStoreSalesTransaction(storeSalesTransaction);
    return offlineData;
  }

  /** Returns a new user identifier with the specified type and value. */
  private static UserIdentifier createUserIdentifier(
      OfflineDataUploadUserIdentifierType identifierType, String value)
      throws UnsupportedEncodingException {
    // If the user identifier type is a hashed type, also call hash function
    // on the value.
    if (HASHED_IDENTIFIER_TYPES.contains(identifierType)) {
      value = toSHA256String(value);
    }
    UserIdentifier userIdentifier = new UserIdentifier();
    userIdentifier.setUserIdentifierType(identifierType);
    userIdentifier.setValue(value);
    return userIdentifier;
  }

  /**
   * Hash a string using SHA-256 hashing algorithm.
   *
   * @param str the string to hash.
   * @return the SHA-256 hash string representation.
   * @throws UnsupportedEncodingException If UTF-8 charset is not supported.
   */
  private static String toSHA256String(String str) throws UnsupportedEncodingException {
    // Normalize the string before hashing.
    byte[] hash = digest.digest(toNormalizedString(str).getBytes("UTF-8"));
    StringBuilder result = new StringBuilder();
    for (byte b : hash) {
      result.append(String.format("%02x", b));
    }

    return result.toString();
  }

  /**
   * Removes leading and trailing whitespace and converts all characters to lower case.
   *
   * @param value the string to normalize.
   * @return a normalized copy of the string.
   */
  private static String toNormalizedString(String value) {
    return value.trim().toLowerCase();
  }

  /** Returns SHA-256 hashing algorithm. */
  private static MessageDigest getSHA256MessageDigest() {
    try {
      return MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException("Missing SHA-256 algorithm implementation.", e);
    }
  }
}

Send feedback about...

AdWords API
AdWords API
Need help? Visit our support page.