This interactive guide demonstrates how to create a standard or retail Performance Max campaign. Enter values in the form fields in the left-hand panel to customize the code. Required fields will be populated with placeholders. Optional fields will be displayed if values are provided. Your inputs will be saved as you proceed through this guide. The standard guide is also available for additional information.
// Copyright 2022 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 com.google.ads.googleads.examples.shoppingads; import com.beust.jcommander.Parameter; import com.google.ads.googleads.examples.utils.ArgumentNames; import com.google.ads.googleads.examples.utils.CodeSampleParams; import com.google.ads.googleads.lib.GoogleAdsClient; import com.google.ads.googleads.lib.utils.FieldMasks; import com.google.ads.googleads.v12.enums.ListingGroupFilterBiddingCategoryLevelEnum.ListingGroupFilterBiddingCategoryLevel; import com.google.ads.googleads.v12.enums.ListingGroupFilterCustomAttributeIndexEnum.ListingGroupFilterCustomAttributeIndex; import com.google.ads.googleads.v12.enums.ListingGroupFilterProductConditionEnum.ListingGroupFilterProductCondition; import com.google.ads.googleads.v12.enums.ListingGroupFilterProductChannelEnum.ListingGroupFilterProductChannel; import com.google.ads.googleads.v12.common.AdScheduleInfo; import com.google.ads.googleads.v12.common.AudienceInfo; import com.google.ads.googleads.v12.common.CallToActionAsset; import com.google.ads.googleads.v12.common.ImageAsset; import com.google.ads.googleads.v12.common.LanguageInfo; import com.google.ads.googleads.v12.common.LocationGroupInfo; import com.google.ads.googleads.v12.common.LocationInfo; import com.google.ads.googleads.v12.common.MaximizeConversionValue; import com.google.ads.googleads.v12.common.MaximizeConversions; import com.google.ads.googleads.v12.common.MediaBundleAsset; import com.google.ads.googleads.v12.common.TextAsset; import com.google.ads.googleads.v12.common.WebpageConditionInfo; import com.google.ads.googleads.v12.common.WebpageInfo; import com.google.ads.googleads.v12.common.YoutubeVideoAsset; import com.google.ads.googleads.v12.enums.AdvertisingChannelTypeEnum.AdvertisingChannelType; import com.google.ads.googleads.v12.enums.AssetFieldTypeEnum.AssetFieldType; import com.google.ads.googleads.v12.enums.AssetGroupStatusEnum.AssetGroupStatus; import com.google.ads.googleads.v12.enums.BudgetDeliveryMethodEnum.BudgetDeliveryMethod; import com.google.ads.googleads.v12.enums.CallToActionTypeEnum.CallToActionType; import com.google.ads.googleads.v12.enums.CampaignStatusEnum.CampaignStatus; import com.google.ads.googleads.v12.enums.ConversionActionCategoryEnum.ConversionActionCategory; import com.google.ads.googleads.v12.enums.ConversionOriginEnum.ConversionOrigin; import com.google.ads.googleads.v12.enums.DayOfWeekEnum.DayOfWeek; import com.google.ads.googleads.v12.enums.ListingGroupFilterProductTypeLevelEnum.ListingGroupFilterProductTypeLevel; import com.google.ads.googleads.v12.enums.ListingGroupFilterTypeEnum.ListingGroupFilterType; import com.google.ads.googleads.v12.enums.ListingGroupFilterVerticalEnum.ListingGroupFilterVertical; import com.google.ads.googleads.v12.enums.LocationGroupRadiusUnitsEnum.LocationGroupRadiusUnits; import com.google.ads.googleads.v12.enums.MinuteOfHourEnum.MinuteOfHour; import com.google.ads.googleads.v12.enums.WebpageConditionOperandEnum.WebpageConditionOperand; import com.google.ads.googleads.v12.enums.WebpageConditionOperatorEnum.WebpageConditionOperator; import com.google.ads.googleads.v12.errors.GoogleAdsError; import com.google.ads.googleads.v12.errors.GoogleAdsException; import com.google.ads.googleads.v12.resources.Asset; import com.google.ads.googleads.v12.resources.AssetGroup; import com.google.ads.googleads.v12.resources.AssetGroupAsset; import com.google.ads.googleads.v12.resources.AssetGroupListingGroupFilter; import com.google.ads.googleads.v12.resources.AssetGroupSignal; import com.google.ads.googleads.v12.resources.Campaign; import com.google.ads.googleads.v12.resources.Campaign.ShoppingSetting; import com.google.ads.googleads.v12.resources.CampaignBudget; import com.google.ads.googleads.v12.resources.CampaignConversionGoal; import com.google.ads.googleads.v12.resources.CampaignCriterion; import com.google.ads.googleads.v12.resources.CustomerConversionGoal; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductBiddingCategory; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductBrand; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductChannel; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductCondition; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductCustomAttribute; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductItemId; import com.google.ads.googleads.v12.resources.ListingGroupFilterDimension.ProductType; import com.google.ads.googleads.v12.services.AssetGroupAssetOperation; import com.google.ads.googleads.v12.services.AssetGroupListingGroupFilterOperation; import com.google.ads.googleads.v12.services.AssetGroupOperation; import com.google.ads.googleads.v12.services.AssetGroupSignalOperation; import com.google.ads.googleads.v12.services.AssetOperation; import com.google.ads.googleads.v12.services.AssetServiceClient; import com.google.ads.googleads.v12.services.CampaignBudgetOperation; import com.google.ads.googleads.v12.services.CampaignConversionGoalOperation; import com.google.ads.googleads.v12.services.CampaignConversionGoalServiceClient; import com.google.ads.googleads.v12.services.CampaignCriterionOperation; import com.google.ads.googleads.v12.services.CampaignCriterionServiceClient; import com.google.ads.googleads.v12.services.CampaignOperation; import com.google.ads.googleads.v12.services.GoogleAdsRow; import com.google.ads.googleads.v12.services.GoogleAdsServiceClient; import com.google.ads.googleads.v12.services.GoogleAdsServiceClient.SearchPagedResponse; import com.google.ads.googleads.v12.services.MutateAssetResult; import com.google.ads.googleads.v12.services.MutateAssetsResponse; import com.google.ads.googleads.v12.services.MutateCampaignConversionGoalResult; import com.google.ads.googleads.v12.services.MutateCampaignConversionGoalsResponse; import com.google.ads.googleads.v12.services.MutateCampaignCriteriaResponse; import com.google.ads.googleads.v12.services.MutateCampaignCriterionResult; import com.google.ads.googleads.v12.services.MutateGoogleAdsResponse; import com.google.ads.googleads.v12.services.MutateOperation; import com.google.ads.googleads.v12.services.MutateOperationResponse; import com.google.ads.googleads.v12.utils.ResourceNames; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors.FieldDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; public class BuildPerformanceRetailCampaign { // We specify temporary IDs that are specific to a single mutate request. Temporary IDs are always // negative and unique within one mutate request. // // <p>See https://developers.google.com/google-ads/api/docs/mutating/best-practices for further // details. // // <p>These temporary IDs are fixed because they are used in multiple places. private static final int BUDGET_TEMPORARY_ID = -1; private static final int PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID = -2; // There are also entities that will be created in the same request but do not // need to be fixed temporary IDs because they are referenced only once. private static long temporaryId = PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID - 1; private List<Long> tempAssetGroupIds = new ArrayList<>(); private static class BuildPerformanceRetailCampaignParams extends CodeSampleParams { @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true) private Long customerId; } public static void main(String[] args) throws IOException { BuildPerformanceRetailCampaignParams params = new BuildPerformanceRetailCampaignParams(); 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.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE"); } GoogleAdsClient googleAdsClient = null; try { googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build(); } catch (FileNotFoundException fnfe) { System.err.printf( "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe); System.exit(1); } catch (IOException ioe) { System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe); System.exit(1); } try { new BuildPerformanceRetailCampaign().runExample(googleAdsClient, params.customerId); } catch (GoogleAdsException gae) { // GoogleAdsException is the base class for most exceptions thrown by an API request. // Instances of this exception have a message and a GoogleAdsFailure that contains a // collection of GoogleAdsErrors that indicate the underlying causes of the // GoogleAdsException. System.err.printf( "Request ID %s failed due to GoogleAdsException. Underlying errors:%n", gae.getRequestId()); int i = 0; for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) { System.err.printf(" Error %d: %s%n", i++, googleAdsError); } System.exit(1); } } /** * Runs the example. * * @param googleAdsClient the Google Ads API client. * @param customerId the client customer ID. */ private void runExample(GoogleAdsClient googleAdsClient, long customerId) throws IOException { // Adds assets to the account library. createAssets(googleAdsClient, customerId); // Creates the PMax campaign. List<MutateOperation> mutateOperations = new ArrayList<>(); mutateOperations.add(createCampaignBudgetOperation(customerId)); mutateOperations.add(createPerformanceMaxCampaignOperation(customerId)); mutateOperations.addAll(createAssetGroupOperations(googleAdsClient, customerId)); // Only for use with PMax Retail campaigns. mutateOperations.addAll(createAssetGroupListingGroupFilterOperations(customerId)); String campaignResourceName; try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { MutateGoogleAdsResponse response = googleAdsServiceClient.mutate(Long.toString(customerId), mutateOperations); printResponseDetails(response); campaignResourceName = response.getMutateOperationResponsesList().stream() .filter(result -> result.hasCampaignResult()) .collect(Collectors.toList()) .get(0) .getCampaignResult() .getResourceName(); } // Customizes targeting and conversion goals on the PMax campaign. createCampaignCriteria(googleAdsClient, customerId, campaignResourceName); createConversionGoals(googleAdsClient, customerId, campaignResourceName); } /** Creates multiple assets. */ private void createAssets(GoogleAdsClient googleAdsClient, long customerId) throws IOException { createTextAssets(googleAdsClient, customerId); createImageAssets(googleAdsClient, customerId); } /** * Helper method to make an API request to add assets. This method will be * called by the methods that create each asset type. */ private List<String> addAssets( GoogleAdsClient googleAdsClient, long customerId, List<AssetOperation> operations) { try (AssetServiceClient assetServiceClient = googleAdsClient.getLatestVersion().createAssetServiceClient()) { MutateAssetsResponse response = assetServiceClient.mutateAssets(Long.toString(customerId), operations); List<String> resourceNames = new ArrayList<>(); for (MutateAssetResult result : response.getResultsList()) { System.out.printf("Created asset with resource name '%s'.%n", result.getResourceName()); resourceNames.add(result.getResourceName()); } return resourceNames; } } /** Creates multiple text assets. */ private List<String> createTextAssets(GoogleAdsClient googleAdsClient, long customerId) { List<AssetOperation> operations = new ArrayList<>(); operations.add(createTextAssetOperation("[INSERT_ASSET_NAME]", "[INSERT_ASSET_TEXT]")); return addAssets(googleAdsClient, customerId, operations); } /** Helper method to create an asset operation to create a text asset. */ private AssetOperation createTextAssetOperation(String name, String text) { Asset textAsset = Asset.newBuilder() .setName(name) .setTextAsset(TextAsset.newBuilder().setText(text).build()) .build(); return AssetOperation.newBuilder().setCreate(textAsset).build(); } /** Creates multiple image assets. */ private List<String> createImageAssets(GoogleAdsClient googleAdsClient, long customerId) throws IOException { List<AssetOperation> operations = new ArrayList<>(); operations.add( createImageAssetOperation("[INSERT_ASSET_NAME]", "[INSERT_IMAGE_URL]")); return addAssets(googleAdsClient, customerId, operations); } /** Helper method to create an asset operation to create an image asset. */ private AssetOperation createImageAssetOperation(String name, String imageUrl) throws IOException { byte[] assetBytes = ByteStreams.toByteArray(new URL(imageUrl).openStream()); Asset imageAsset = Asset.newBuilder() .setName(name) .setImageAsset(ImageAsset.newBuilder().setData(ByteString.copyFrom(assetBytes)).build()) .build(); return AssetOperation.newBuilder().setCreate(imageAsset).build(); } /** Creates multiple YouTube video assets. */ private List<String> createYouTubeVideoAssets(GoogleAdsClient googleAdsClient, long customerId) { List<AssetOperation> operations = new ArrayList<>(); return addAssets(googleAdsClient, customerId, operations); } /** Helper method to create an asset operation to create a YouTube video asset. */ private AssetOperation createVideoAssetOperation( String name, String videoTitle, String videoId) { Asset videoAsset = Asset.newBuilder() .setName(name) .setYoutubeVideoAsset( YoutubeVideoAsset.newBuilder() .setYoutubeVideoTitle(videoTitle) .setYoutubeVideoId(videoId) .build()) .build(); return AssetOperation.newBuilder().setCreate(videoAsset).build(); } /** Creates multiple call to action assets. */ private List<String> createCallToActionAssets(GoogleAdsClient googleAdsClient, long customerId) { List<AssetOperation> operations = new ArrayList<>(); return addAssets(googleAdsClient, customerId, operations); } /** Helper method to create an asset operation to create a call to action asset. */ private AssetOperation createCallToActionAssetOperation( String name, CallToActionType ctaType) { Asset ctaAsset = Asset.newBuilder() .setName(name) .setCallToActionAsset(CallToActionAsset.newBuilder().setCallToAction(ctaType).build()) .build(); return AssetOperation.newBuilder().setCreate(ctaAsset).build(); } /** Creates multiple media bundle assets. */ private List<String> createMediaBundleAssets(GoogleAdsClient googleAdsClient, long customerId) throws IOException { List<AssetOperation> operations = new ArrayList<>(); return addAssets(googleAdsClient, customerId, operations); } /** Helper method to create an asset operation to create a media bundle asset. */ private AssetOperation createMediaBundleAssetOperation(String name, String bundleUrl) throws IOException { // Reads the sample media bundle from the URL into a byte array. byte[] bundleData = ByteStreams.toByteArray(new URL(bundleUrl).openStream()); Asset mediaBundleAsset = Asset.newBuilder() .setName(name) .setMediaBundleAsset( MediaBundleAsset.newBuilder().setData(ByteString.copyFrom(bundleData)).build()) .build(); return AssetOperation.newBuilder().setCreate(mediaBundleAsset).build(); } /** Creates a MutateOperation that creates a new CampaignBudget. */ private MutateOperation createCampaignBudgetOperation(long customerId) { CampaignBudget campaignBudget = CampaignBudget.newBuilder() .setName("[INSERT_BUDGET_NAME]") // The budget period already defaults to DAILY. .setAmountMicros([INSERT_AMOUNT_MICROS]) // A Performance Max campaign must use a STANDARD delivery method. .setDeliveryMethod(BudgetDeliveryMethod.STANDARD) // A Performance Max campaign cannot use a shared campaign budget. .setExplicitlyShared(false) // Set a temporary ID in the budget's resource name, so it can be referenced // by the campaign in later steps. .setResourceName(ResourceNames.campaignBudget(customerId, BUDGET_TEMPORARY_ID)) .build(); return MutateOperation.newBuilder() .setCampaignBudgetOperation( CampaignBudgetOperation.newBuilder().setCreate(campaignBudget).build()) .build(); } /** Creates a MutateOperation that creates a new Performance Max campaign. */ private MutateOperation createPerformanceMaxCampaignOperation(long customerId) { Campaign performanceMaxCampaign = Campaign.newBuilder() .setName("[INSERT_CAMPAIGN_NAME]") .setStatus(CampaignStatus.[ENABLED|PAUSED|REMOVED]) // All Performance Max campaigns have an advertising_channel_type of // PERFORMANCE_MAX. The advertising_channel_sub_type should not be set. .setAdvertisingChannelType(AdvertisingChannelType.PERFORMANCE_MAX) // You must set a bidding strategy of either // MAXIMIZE_CONVERSIONS // .setMaximizeConversions(MaximizeConversions.newBuilder().build()) // or MAXIMIZE_CONVERSION_VALUE // .setMaximizeConversionValue(MaximizeConversionValue.newBuilder()) // Assigns the resource name with a temporary ID. .setResourceName( ResourceNames.campaign(customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID)) // Sets the budget using the given budget resource name. .setCampaignBudget(ResourceNames.campaignBudget(customerId, BUDGET_TEMPORARY_ID)) .build(); return MutateOperation.newBuilder() .setCampaignOperation( CampaignOperation.newBuilder().setCreate(performanceMaxCampaign).build()) .build(); } /** Creates a list of MutateOperations that create new asset groups. */ private List<MutateOperation> createAssetGroupOperations( GoogleAdsClient googleAdsClient, long customerId) { List<MutateOperation> operations = new ArrayList<>(); List<AssetGroupMetadata> assetGroupsMetadata = new ArrayList<>(); assetGroupsMetadata.add( new AssetGroupMetadata( customerId, "[INSERT_ASSET_GROUP_NAME]", ImmutableList.of("INSERT_FINAL_URLS"), ImmutableList.of("INSERT_FINAL_MOBILE_URLS"), AssetGroupStatus.[ENABLED|PAUSED|REMOVED], null)); for (AssetGroupMetadata assetGroupMetadata : assetGroupsMetadata) { long assetGroupTempId = assetGroupMetadata.tempAssetGroupId; String assetGroupResourceName = ResourceNames.assetGroup(customerId, assetGroupTempId); AssetGroupOperation assetGroupOperation = AssetGroupOperation.newBuilder().setCreate(assetGroupMetadata.assetGroup).build(); operations.add( MutateOperation.newBuilder().setAssetGroupOperation(assetGroupOperation).build()); operations.addAll( createAssetGroupAssetOperations(googleAdsClient, customerId, assetGroupResourceName)); if (assetGroupMetadata.audienceId != null) { operations.add( createAssetGroupSignalOperation( customerId, assetGroupResourceName, assetGroupMetadata.audienceId)); } } return operations; } /** * Helper class to help populate AssetGroupOperations and encapsulate information about each * AssetGroup that is not present in the AssetGroup object. */ private class AssetGroupMetadata { Long tempAssetGroupId; AssetGroup assetGroup; Long audienceId; public AssetGroupMetadata( long customerId, String name, List<String> finalUrls, List<String> finalMobileUrls, AssetGroupStatus status, Long audienceId) { String campaignResourceName = ResourceNames.campaign(customerId, PERFORMANCE_MAX_CAMPAIGN_TEMPORARY_ID); tempAssetGroupId = getNextTemporaryId(); tempAssetGroupIds.add(tempAssetGroupId); // Creates the AssetGroup. assetGroup = AssetGroup.newBuilder() .setResourceName(ResourceNames.assetGroup(customerId, tempAssetGroupId)) .setCampaign(campaignResourceName) .setName(name) .addAllFinalUrls(finalUrls) .addAllFinalMobileUrls(finalMobileUrls) .setStatus(status) .build(); if (audienceId != null) { this.audienceId = audienceId; } } } /** Creates a MutateOperation that creates an Asset Group signal. */ private MutateOperation createAssetGroupSignalOperation( long customerId, String assetGroupResourceName, long audienceId) { AssetGroupSignal assetGroupSignal = AssetGroupSignal.newBuilder() .setAssetGroup(assetGroupResourceName) .setAudience( AudienceInfo.newBuilder() .setAudience(ResourceNames.audience(customerId, audienceId))) .build(); // Adds an operation to the list to create the asset group signal. return MutateOperation.newBuilder() .setAssetGroupSignalOperation( AssetGroupSignalOperation.newBuilder().setCreate(assetGroupSignal)) .build(); } /** * Creates a list of MutateOperation that create new asset group listing group filters for each * asset group. */ private List<MutateOperation> createAssetGroupListingGroupFilterOperations( long customerId) { // This will be used to store a list of product partition trees for each AssetGroup. List<List<List<AssetGroupListingGroupFilter>>> assetGroupListingGroupFilters = new ArrayList<>(); List<List<AssetGroupListingGroupFilter>> treeLevels; List<AssetGroupListingGroupFilter> nodes; // Creates a product partition tree for a single AssetGroup. treeLevels = new ArrayList<>(); // Adds the tree for the given AssetGroup. // There aren't any nodes in the tree, so the code will automatically create a product // partition tree with a single node containing all products because PMax retail // campaigns must have a valid product partition tree to serve. assetGroupListingGroupFilters.add(treeLevels); List<MutateOperation> operations = new ArrayList<>(); // Creates operations to add the listing group filters, which combine to form a complete // product partition tree, for each AssetGroup. for (int i = 0; i < assetGroupListingGroupFilters.size(); i++) { long assetGroupId = tempAssetGroupIds.get(i); List<List<AssetGroupListingGroupFilter>> assetGroupTreeLevels = assetGroupListingGroupFilters.get(i); operations.addAll( createAssetGroupListingGroupFilters(customerId, assetGroupId, assetGroupTreeLevels)); } // Returns a list of operation to create the listing group filters. return operations; } /** * Creates a list of MutateOperation that create a new asset group listing group filters for a * single asset group. */ private List<MutateOperation> createAssetGroupListingGroupFilters( long customerId, long assetGroupId, List<List<AssetGroupListingGroupFilter>> treeLevels) { List<MutateOperation> operations = new ArrayList<>(); // Creates the root node. long tempFilterId = getNextTemporaryId(); ListingGroupFilterType rootFilterType; boolean rootHasChildren = false; for (List<AssetGroupListingGroupFilter> treeLevel : treeLevels) { if (!treeLevel.isEmpty()) { rootHasChildren = true; } } if (rootHasChildren) { rootFilterType = ListingGroupFilterType.SUBDIVISION; } else { rootFilterType = ListingGroupFilterType.UNIT_INCLUDED; } AssetGroupListingGroupFilter rootNode = createListingGroupFilter(null, rootFilterType); String rootResourceName = ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, tempFilterId); operations.add(createListingGroupFilterOperation(rootNode, rootResourceName, null)); Long otherNodeId = null; // Creates the partitions. for (List<AssetGroupListingGroupFilter> treeLevel : treeLevels) { long parentNodeId = tempFilterId; if (otherNodeId != null) { parentNodeId = otherNodeId; } for (AssetGroupListingGroupFilter node : treeLevel) { long nodeId = getNextTemporaryId(); String nodeResourceName = ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, nodeId); String parentResourceName = ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, parentNodeId); operations.add( createListingGroupFilterOperation(node, nodeResourceName, parentResourceName)); } // Creates the "Other" category node if the tree level has nodes. if (!treeLevel.isEmpty()) { otherNodeId = getNextTemporaryId(); String nodeResourceName = ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, otherNodeId); String parentResourceName = ResourceNames.assetGroupListingGroupFilter(customerId, assetGroupId, parentNodeId); ListingGroupFilterType otherFilterType; if (isOtherUnitNode(treeLevels, treeLevel)) { otherFilterType = ListingGroupFilterType.UNIT_EXCLUDED; } else { otherFilterType = ListingGroupFilterType.SUBDIVISION; } ListingGroupFilterDimension dimension = treeLevel.get(0).getCaseValue(); AssetGroupListingGroupFilter otherNode = createOtherNode(dimension, otherFilterType); operations.add( createListingGroupFilterOperation(otherNode, nodeResourceName, parentResourceName)); } } return operations; } /** * Checks if an "Other" node should be a unit or subdivision node. It should be a unit node if it * is the last level in the tree or if all subsequent tree levels are empty. */ private boolean isOtherUnitNode( List<List<AssetGroupListingGroupFilter>> treeLevels, List<AssetGroupListingGroupFilter> currentTreeLevel) { int currentTreeLevelIndex = treeLevels.indexOf(currentTreeLevel); if (currentTreeLevelIndex == treeLevels.size() - 1) { return true; } for (int i = currentTreeLevelIndex + 1; i < treeLevels.size(); i++) { if (!treeLevels.get(i).isEmpty()) { return false; } } return true; } /** * Creates a generic AssetGroupListingGroupFilter with a specified ListingGroupFilterDimension and * listingGroupFilterType. */ private AssetGroupListingGroupFilter createListingGroupFilter( ListingGroupFilterDimension dimension, ListingGroupFilterType listingGroupFilterType) { AssetGroupListingGroupFilter.Builder assetGroupListingGroupFilter = AssetGroupListingGroupFilter.newBuilder() .setType(listingGroupFilterType) .setVertical(ListingGroupFilterVertical.SHOPPING); if (dimension != null) { assetGroupListingGroupFilter.setCaseValue(dimension); } return assetGroupListingGroupFilter.build(); } /** Creates an "Other" node for a given ListingGroupFilterDimension. */ private AssetGroupListingGroupFilter createOtherNode( ListingGroupFilterDimension dimension, ListingGroupFilterType listingGroupFilterType) { if (dimension.hasProductBiddingCategory()) { ListingGroupFilterBiddingCategoryLevel level = dimension.getProductBiddingCategory().getLevel(); return biddingCategoryListingGroupFilter( null, level, listingGroupFilterType); } else if (dimension.hasProductBrand()) { return brandListingGroupFilter(null, listingGroupFilterType); } else if (dimension.hasProductChannel()) { return channelListingGroupFilter(null, listingGroupFilterType); } else if (dimension.hasProductCondition()) { return conditionListingGroupFilter(null, listingGroupFilterType); } else if (dimension.hasProductCustomAttribute()) { ListingGroupFilterCustomAttributeIndex index = dimension.getProductCustomAttribute().getIndex(); return customAttributeListingGroupFilter(null, index, listingGroupFilterType); } else if (dimension.hasProductItemId()) { return itemIdListingGroupFilter(null, listingGroupFilterType); } else if (dimension.hasProductType()) { ListingGroupFilterProductTypeLevel level = dimension.getProductType().getLevel(); return typeListingGroupFilter(null, level, listingGroupFilterType); } return createListingGroupFilter(null, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductBiddingCategory. */ private AssetGroupListingGroupFilter biddingCategoryListingGroupFilter( Integer biddingCategoryId, ListingGroupFilterBiddingCategoryLevel level, ListingGroupFilterType listingGroupFilterType) { ProductBiddingCategory.Builder biddingCategory = ProductBiddingCategory.newBuilder().setLevel(level); if (biddingCategoryId != null) { biddingCategory.setId(biddingCategoryId); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder() .setProductBiddingCategory(biddingCategory.build()) .build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductBrand. */ private AssetGroupListingGroupFilter brandListingGroupFilter( String brand, ListingGroupFilterType listingGroupFilterType) { ProductBrand.Builder productBrand = ProductBrand.newBuilder(); if (brand != null) { productBrand.setValue(brand); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder().setProductBrand(productBrand.build()).build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductChannel. */ private AssetGroupListingGroupFilter channelListingGroupFilter( ListingGroupFilterProductChannel channel, ListingGroupFilterType listingGroupFilterType) { ProductChannel.Builder productChanel = ProductChannel.newBuilder(); if (channel != null) { productChanel.setChannel(channel); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder().setProductChannel(productChanel.build()).build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductCondition. */ private AssetGroupListingGroupFilter conditionListingGroupFilter( ListingGroupFilterProductCondition productCondition, ListingGroupFilterType listingGroupFilterType) { ProductCondition.Builder condition = ProductCondition.newBuilder(); if (productCondition != null) { condition.setCondition(productCondition); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder().setProductCondition(condition.build()).build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductCustomAttribute. */ private AssetGroupListingGroupFilter customAttributeListingGroupFilter( String value, ListingGroupFilterCustomAttributeIndex index, ListingGroupFilterType listingGroupFilterType) { ProductCustomAttribute.Builder customAttribute = ProductCustomAttribute.newBuilder().setIndex(index); if (value != null) { customAttribute.setValue(value); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder() .setProductCustomAttribute(customAttribute.build()) .build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductItemId. */ private AssetGroupListingGroupFilter itemIdListingGroupFilter( String id, ListingGroupFilterType listingGroupFilterType) { ProductItemId.Builder itemId = ProductItemId.newBuilder(); if (id != null) { itemId.setValue(id); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder().setProductItemId(itemId.build()).build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a AssetGroupListingGroupFilter for a ProductType. */ private AssetGroupListingGroupFilter typeListingGroupFilter( String type, ListingGroupFilterProductTypeLevel level, ListingGroupFilterType listingGroupFilterType) { ProductType.Builder productType = ProductType.newBuilder().setLevel(level); if (type != null) { productType.setValue(type); } ListingGroupFilterDimension dimension = ListingGroupFilterDimension.newBuilder().setProductType(productType.build()).build(); return createListingGroupFilter(dimension, listingGroupFilterType); } /** Creates a MutateOperation for an AssetGroupListingGroupFilterOperation. */ private MutateOperation createListingGroupFilterOperation( AssetGroupListingGroupFilter assetGroupListingGroupFilter, String resourceName, String parentResourceName) { AssetGroupListingGroupFilter.Builder node = assetGroupListingGroupFilter.toBuilder().setResourceName(resourceName); if (parentResourceName != null) { node.setParentListingGroupFilter(parentResourceName); } return MutateOperation.newBuilder() .setAssetGroupListingGroupFilterOperation( AssetGroupListingGroupFilterOperation.newBuilder().setCreate(node.build())) .build(); } /** Creates a list of MutateOperations that creates new asset group assets. */ private List<MutateOperation> createAssetGroupAssetOperations( GoogleAdsClient googleAdsClient, long customerId, String assetGroupResourceName) { List<MutateOperation> mutateOperations = new ArrayList<>(); // The AssetFieldTypes below will be used to perform search requests in the // getAssetResourceNames method that retrieve the resources names of assets of each type. // This method assumes // 1. you have already added the minimum required assets to your Google Ads account // https://developers.google.com/google-ads/api/docs/performance-max/assets // 2. asset names begin with their AssetFieldTypes, such as "HEADLINE - Book travel" // // Your application will likely use a different mechanism for choosing which assets to // associate with a given asset group because the implementation is dependent on your // application flow and campaign goals. This example illustrates the concept of using // a search request to retrieve asset resource names to associate with an asset group. List<AssetFieldType> assetFieldTypes = ImmutableList.of( AssetFieldType.HEADLINE, AssetFieldType.DESCRIPTION, AssetFieldType.LONG_HEADLINE, AssetFieldType.BUSINESS_NAME, AssetFieldType.MARKETING_IMAGE, AssetFieldType.SQUARE_MARKETING_IMAGE, AssetFieldType.LOGO ); for (AssetFieldType assetFieldType : assetFieldTypes) { List<String> resourceNames = getAssetResourceNames(googleAdsClient, customerId, assetFieldType); // Alternatively, create new assets and use their resource names. // // For text assets: // resourceNames = createTextAssets() // // For image assets: // resourceNames = createImageAssets() // // Or, use a combination of newly created and existing assets. for (String resourceName : resourceNames) { mutateOperations.add( createAssetGroupAssetOperation(resourceName, assetGroupResourceName, assetFieldType)); } } return mutateOperations; } /** Retrieves a list of asset resource names from a search request. */ private List<String> getAssetResourceNames( GoogleAdsClient googleAdsClient, long customerId, AssetFieldType assetFieldType) { Map<AssetFieldType, String> assetTypes = new HashMap<AssetFieldType, String>() {{ put(AssetFieldType.HEADLINE, "TEXT"); put(AssetFieldType.DESCRIPTION, "TEXT"); put(AssetFieldType.LONG_HEADLINE, "TEXT"); put(AssetFieldType.BUSINESS_NAME, "TEXT"); put(AssetFieldType.MARKETING_IMAGE, "IMAGE"); put(AssetFieldType.SQUARE_MARKETING_IMAGE, "IMAGE"); put(AssetFieldType.LOGO, "IMAGE"); }}; Map<AssetFieldType, Integer> minRequired = new HashMap<AssetFieldType, Integer>() {{ put(AssetFieldType.HEADLINE, 3); put(AssetFieldType.DESCRIPTION, 1); put(AssetFieldType.LONG_HEADLINE, 1); put(AssetFieldType.BUSINESS_NAME, 1); put(AssetFieldType.MARKETING_IMAGE, 1); put(AssetFieldType.SQUARE_MARKETING_IMAGE,1); put(AssetFieldType.LOGO, 1); }}; Map<AssetFieldType, Integer> maxAllowed = new HashMap<AssetFieldType, Integer>() {{ put(AssetFieldType.HEADLINE, 5); put(AssetFieldType.DESCRIPTION, 5); put(AssetFieldType.LONG_HEADLINE, 5); put(AssetFieldType.BUSINESS_NAME, 1); put(AssetFieldType.MARKETING_IMAGE, 20); put(AssetFieldType.SQUARE_MARKETING_IMAGE, 20); put(AssetFieldType.LOGO, 5); }}; String assetType = assetTypes.get(assetFieldType); Integer min = minRequired.get(assetFieldType); Integer max = maxAllowed.get(assetFieldType); String query = String.format( "SELECT asset.resource_name, asset.name " + "FROM asset " + "WHERE asset.type = '%s' " + "AND asset.name LIKE '%s%%' " + "LIMIT %s", assetType, assetFieldType, max); List<String> resourceNames = new ArrayList<>(); try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { // The number of assets will be less than 50, so we use // GoogleAdsService.search instead of search_stream. SearchPagedResponse response = googleAdsServiceClient.search(Long.toString(customerId), query); for (GoogleAdsRow googleAdsRow : response.iterateAll()) { resourceNames.add(googleAdsRow.getAsset().getResourceName()); } } if (resourceNames.size() < min) { throw new IllegalStateException( String.format( "Minimum asset requirement not met for asset type %s", assetFieldType)); } return resourceNames; } /** Creates a MutateOperation that create a new asset group asset. */ private MutateOperation createAssetGroupAssetOperation( String assetResourceName, String assetGroupResourceName, AssetFieldType assetFieldType) { AssetGroupAsset assetGroupAsset = AssetGroupAsset.newBuilder() .setAsset(assetResourceName) .setFieldType(assetFieldType) .setAssetGroup(assetGroupResourceName) .build(); return MutateOperation.newBuilder() .setAssetGroupAssetOperation( AssetGroupAssetOperation.newBuilder().setCreate(assetGroupAsset).build()) .build(); } private void createCampaignCriteria( GoogleAdsClient googleAdsClient, long customerId, String campaignResourceName) { List<CampaignCriterion> campaignCriteria = new ArrayList<>(); campaignCriteria.addAll(createAdScheduleCriteria(campaignResourceName)); campaignCriteria.addAll(createLanguageCriteria(campaignResourceName)); campaignCriteria.addAll(createLocationCriteria(campaignResourceName)); campaignCriteria.addAll(createLocationGroupCriteria(campaignResourceName)); campaignCriteria.addAll(createWebpageCriteria(campaignResourceName)); List<CampaignCriterionOperation> operations = new ArrayList<>(); for (CampaignCriterion campaignCriterion : campaignCriteria) { operations.add(CampaignCriterionOperation.newBuilder().setCreate(campaignCriterion).build()); } try (CampaignCriterionServiceClient campaignCriterionServiceClient = googleAdsClient.getLatestVersion().createCampaignCriterionServiceClient()) { MutateCampaignCriteriaResponse response = campaignCriterionServiceClient.mutateCampaignCriteria( Long.toString(customerId), operations); for (MutateCampaignCriterionResult result : response.getResultsList()) { System.out.printf("Added campaign criterion '%s'.%n", result.getResourceName()); } } } /** Creates a list of new ad schedule campaign criterion objects. */ private List<CampaignCriterion> createAdScheduleCriteria( String campaignResourceName) { List<CampaignCriterion> campaignCriteria = new ArrayList<>(); return campaignCriteria; } /** Creates a list of language campaign criterion objects. */ private List<CampaignCriterion> createLanguageCriteria(String campaignResourceName) { List<CampaignCriterion> campaignCriteria = new ArrayList<>(); return campaignCriteria; } /** Creates a list of location campaign criterion objects. */ private List<CampaignCriterion> createLocationCriteria(String campaignResourceName) { List<CampaignCriterion> campaignCriteria = new ArrayList<>(); return campaignCriteria; } /** Creates a list of location campaign criterion objects. */ private List<CampaignCriterion> createLocationGroupCriteria( String campaignResourceName) { List<CampaignCriterion> campaignCriteria = new ArrayList<>(); return campaignCriteria; } /** Creates a list of webpage campaign criterion objects. */ private List<CampaignCriterion> createWebpageCriteria(String campaignResourceName) { List<CampaignCriterion> campaignCriteria = new ArrayList<>(); return campaignCriteria; } /** Retrieves the list of customer conversion goals. */ private static List<CustomerConversionGoal> getCustomerConversionGoals( GoogleAdsClient googleAdsClient, long customerId) { String query = "SELECT customer_conversion_goal.category, customer_conversion_goal.origin " + "FROM customer_conversion_goal"; List<CustomerConversionGoal> customerConversionGoals = new ArrayList<>(); try (GoogleAdsServiceClient googleAdsServiceClient = googleAdsClient.getLatestVersion().createGoogleAdsServiceClient()) { // The number of conversion goals is typically less than 50, so we use // GoogleAdsService.search instead of search_stream. SearchPagedResponse response = googleAdsServiceClient.search(Long.toString(customerId), query); for (GoogleAdsRow googleAdsRow : response.iterateAll()) { customerConversionGoals.add(googleAdsRow.getCustomerConversionGoal()); } } return customerConversionGoals; } /** Creates a list of MutateOperations that override customer conversion goals. */ private static void createConversionGoals( GoogleAdsClient googleAdsClient, long customerId, String campaignResourceName) { // To override the customer conversion goals, we will change the // biddability of each of the customer conversion goals so that only // the desired conversion goal is biddable in this campaign. List<String> campaignResourceNameTokens = Arrays.asList(campaignResourceName.split("/")); Long campaignId = Long.valueOf( campaignResourceNameTokens.get(campaignResourceNameTokens.size() - 1)); List<CampaignConversionGoal> desiredGoals = new ArrayList<>(); desiredGoals.add( CampaignConversionGoal.newBuilder() .setCategory(ConversionActionCategory.[INSERT_CONVERSION_ACTION_CATEGORY]) .setOrigin(ConversionOrigin.[INSERT_CONVERSION_ORIGIN]) .build()); List<CustomerConversionGoal> customerConversionGoals = getCustomerConversionGoals(googleAdsClient, customerId); List<CampaignConversionGoalOperation> operations = new ArrayList<>(); for (CustomerConversionGoal customerConversionGoal : customerConversionGoals) { ConversionActionCategory category = customerConversionGoal.getCategory(); ConversionOrigin origin = customerConversionGoal.getOrigin(); CampaignConversionGoal.Builder campaignConversionGoalBuilder = CampaignConversionGoal.newBuilder().setResourceName( ResourceNames.campaignConversionGoal(customerId, campaignId, category, origin)); boolean biddable = false; for (CampaignConversionGoal desiredGoal : desiredGoals) { if (category == desiredGoal.getCategory() && origin == desiredGoal.getOrigin()) { biddable = true; } } campaignConversionGoalBuilder.setBiddable(biddable); CampaignConversionGoal campaignConversionGoal = campaignConversionGoalBuilder.build(); CampaignConversionGoalOperation campaignConversionGoalOperation = CampaignConversionGoalOperation.newBuilder() .setUpdate(campaignConversionGoal) .setUpdateMask(FieldMasks.allSetFieldsOf(campaignConversionGoal)) .build(); operations.add(campaignConversionGoalOperation); } try (CampaignConversionGoalServiceClient campaignConversionGoalServiceClient = googleAdsClient.getLatestVersion().createCampaignConversionGoalServiceClient()) { MutateCampaignConversionGoalsResponse response = campaignConversionGoalServiceClient.mutateCampaignConversionGoals( Long.toString(customerId), operations); for (MutateCampaignConversionGoalResult result : response.getResultsList()) { System.out.printf( "Created campaign conversion goal with resource name '%s'.%n", result.getResourceName()); } } } /** * Prints the details of a MutateGoogleAdsResponse. * * <p>Parses the "response" oneof field name and uses it to extract the new entity's name and * resource name. */ private void printResponseDetails(MutateGoogleAdsResponse response) { // Parses the Mutate response to print details about the entities that were created by the // request. String suffix = "_result"; for (MutateOperationResponse result : response.getMutateOperationResponsesList()) { for (Entry<FieldDescriptor, Object> responseFields : result.getAllFields().entrySet()) { String fieldName = responseFields.getKey().getName(); String value = responseFields.getValue().toString().trim(); if (fieldName.endsWith(suffix)) { fieldName = fieldName.substring(0, fieldName.length() - suffix.length()); } System.out.printf("Created a(n) %s with %s.%n", fieldName, value); } } } /** Returns the next temporary ID and decreases it by one. */ private long getNextTemporaryId() { return temporaryId--; } }