// Copyright 2016 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 com.google.api.ads.adwords.axis.utils.v201609.shopping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.google.api.ads.adwords.axis.factory.AdWordsServices;
import com.google.api.ads.adwords.axis.utils.AxisSerializer;
import com.google.api.ads.adwords.axis.v201609.cm.AdGroupCriterion;
import com.google.api.ads.adwords.axis.v201609.cm.AdGroupCriterionOperation;
import com.google.api.ads.adwords.axis.v201609.cm.AdGroupCriterionPage;
import com.google.api.ads.adwords.axis.v201609.cm.BidSource;
import com.google.api.ads.adwords.axis.v201609.cm.BiddableAdGroupCriterion;
import com.google.api.ads.adwords.axis.v201609.cm.BiddingStrategyConfiguration;
import com.google.api.ads.adwords.axis.v201609.cm.Bids;
import com.google.api.ads.adwords.axis.v201609.cm.CpcBid;
import com.google.api.ads.adwords.axis.v201609.cm.Money;
import com.google.api.ads.adwords.axis.v201609.cm.NegativeAdGroupCriterion;
import com.google.api.ads.adwords.axis.v201609.cm.Operator;
import com.google.api.ads.adwords.axis.v201609.cm.ProductBrand;
import com.google.api.ads.adwords.axis.v201609.cm.ProductDimension;
import com.google.api.ads.adwords.axis.v201609.cm.ProductPartition;
import com.google.api.ads.adwords.axis.v201609.cm.ProductPartitionType;
import com.google.api.ads.adwords.axis.v201609.cm.UserStatus;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.utils.AdWordsInternals;
import com.google.api.ads.common.lib.testing.MockHttpIntegrationTest;
import com.google.api.ads.common.lib.utils.AdsUtility;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.Credential;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.axis.encoding.SerializationContext;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.xml.sax.Attributes;
/**
* Tests for {@link ProductPartitionTree}.
*/
@RunWith(JUnit4.class)
public class ProductPartitionTreeTest extends MockHttpIntegrationTest {
private final BiddingStrategyConfiguration biddingStrategyConfig =
new BiddingStrategyConfiguration();
private boolean isUtilityRegistryExpected = true;
@Before
public void setUp() {
AdWordsInternals.getInstance().getAdsUtilityRegistry().removeUtilities(
AdWordsInternals.getInstance().getAdsUtilityRegistry().getRegisteredUtilities());
}
@After
public void tearDown() {
if (isUtilityRegistryExpected) {
Set<AdsUtility> expectedUtilities = Sets.<AdsUtility>newTreeSet();
expectedUtilities.add(AdsUtility.PRODUCT_PARTITION_TREE);
assertEquals(
expectedUtilities,
AdWordsInternals.getInstance().getAdsUtilityRegistry().getRegisteredUtilities());
}
}
/**
* Tests creating an empty ad group tree. In this case, all operations generated should be ADD
* operations.
*/
@Test
public void testCreateEmptyTree() {
ProductPartitionTree tree = ProductPartitionTree.createAdGroupTree(-1L, biddingStrategyConfig,
Collections.<AdGroupCriterion>emptyList());
assertNotNull("Even an empty tree should automatically have a root node", tree.getRoot());
assertTrue("The root node for an empty tree should have a negative (temporary) ID",
tree.getRoot().getProductPartitionId().longValue() < 0L);
assertTrue("The root node for an empty tree should be a UNIT", tree.getRoot().isUnit());
List<AdGroupCriterionOperation> mutateOperations = tree.getMutateOperations();
assertEquals("Number of operations is incorrect", 1, mutateOperations.size());
AdGroupCriterionOperation operation = mutateOperations.iterator().next();
assertEquals("Should have a single operation to ADD the root node", Operator.ADD,
operation.getOperator());
BiddableAdGroupCriterion adGroupCriterion = (BiddableAdGroupCriterion) operation.getOperand();
assertNull("Product dimension of operation's operand should be null",
((ProductPartition) adGroupCriterion.getCriterion()).getCaseValue());
assertTrue("Partition ID of the operand should be negative",
adGroupCriterion.getCriterion().getId().longValue() < 0L);
}
/**
* Tests creating a tree that in its <em>final</em> state is just an empty tree.
*/
@Test
public void testCreateUltimatelyEmptyTree() {
ProductPartitionTree tree = ProductPartitionTree.createAdGroupTree(-1L, biddingStrategyConfig,
Collections.<AdGroupCriterion>emptyList());
ProductPartitionNode rootNode = tree.getRoot().asSubdivision();
ProductPartitionNode brand1 =
rootNode.addChild(ProductDimensions.createBrand("google")).asSubdivision();
brand1.addChild(ProductDimensions.createOfferId("A")).asBiddableUnit().setBid(1000000L);
brand1.addChild(ProductDimensions.createOfferId(null)).asExcludedUnit();
ProductPartitionNode brand2 =
rootNode.addChild(ProductDimensions.createBrand(null)).asExcludedUnit();
// Now remove the two child nodes under the root and set the root back to a UNIT. This should
// result in operations that simply create the root node.
rootNode.removeChild(brand1.getDimension());
rootNode.removeChild(brand2.getDimension());
rootNode = rootNode.asBiddableUnit();
List<AdGroupCriterionOperation> mutateOperations = tree.getMutateOperations();
assertEquals("Number of operations is incorrect", 1, mutateOperations.size());
AdGroupCriterionOperation operation = mutateOperations.iterator().next();
assertEquals("Should have a single operation to ADD the root node", Operator.ADD,
operation.getOperator());
BiddableAdGroupCriterion adGroupCriterion = (BiddableAdGroupCriterion) operation.getOperand();
assertNull("Product dimension of operation's operand should be null",
((ProductPartition) adGroupCriterion.getCriterion()).getCaseValue());
assertTrue("Partition ID of the operand should be negative",
adGroupCriterion.getCriterion().getId().longValue() < 0L);
}
/**
* Tests mutating an existing tree with multiple nodes.
*/
@Test
public void testMutateMultiNodeTree() {
List<AdGroupCriterion> adGroupCriteria = Lists.newArrayList();
List<CriterionDescriptor> descriptors = Lists.newArrayList();
descriptors.add(new CriterionDescriptor(false, false, null, null, 1L, null));
ProductBrand brandGoogle = ProductDimensions.createBrand("google");
descriptors.add(new CriterionDescriptor(false, false, brandGoogle, null, 2L, 1L));
descriptors.add(new CriterionDescriptor(true,
false,
ProductDimensions.createOfferId("A"),
1000000L,
3L,
2L));
Long offerBOriginalPartitionId = 4L;
descriptors.add(new CriterionDescriptor(true,
true,
ProductDimensions.createOfferId("B"),
null,
offerBOriginalPartitionId,
2L));
Long brandOtherOriginalPartitionId = 5L;
descriptors.add(new CriterionDescriptor(true,
true,
ProductDimensions.createBrand(null),
null,
brandOtherOriginalPartitionId,
1L));
ProductBrand brandMotorola = ProductDimensions.createBrand("motorola");
Long brandMotorolaOriginalPartitionId = 6L;
descriptors.add(new CriterionDescriptor(true,
true,
brandMotorola,
null,
brandMotorolaOriginalPartitionId,
1L));
for (CriterionDescriptor descriptor : descriptors) {
adGroupCriteria.add(descriptor.createCriterion());
}
Map<Long, Map<Long, CriterionDescriptor>> descriptorMap = buildDescriptorMap(descriptors);
ProductPartitionTree tree =
ProductPartitionTree.createAdGroupTree(-1L, biddingStrategyConfig, adGroupCriteria);
assertEquals("ad group ID is incorrect", -1L, tree.getAdGroupId().longValue());
Queue<ProductPartitionNode> nodes = Lists.newLinkedList();
nodes.add(tree.getRoot());
int nodesFound = 0;
while (!nodes.isEmpty()) {
ProductPartitionNode node = nodes.remove();
Long parentId = node.getParent() == null ? null : node.getParent().getProductPartitionId();
CriterionDescriptor expectedDescriptor =
descriptorMap.get(parentId).get(node.getProductPartitionId());
CriterionDescriptor actualDescriptor = new CriterionDescriptor(node);
expectedDescriptor.assertDescriptorEquals(actualDescriptor);
// Add children to process.
Iterables.addAll(nodes, node.getChildren());
nodesFound++;
}
assertEquals("Tree does not contain the expected # of nodes", adGroupCriteria.size(),
nodesFound);
// Change the bids on leaf nodes.
ProductPartitionNode brandGoogleNode = tree.getRoot().getChild(brandGoogle);
ProductPartitionNode offerANode =
brandGoogleNode.getChild(ProductDimensions.createOfferId("A"));
// This should produce 1 SET operation.
offerANode.setBid(offerANode.getBid() * 10);
// This should produce 1 REMOVE operation + 1 ADD operation.
ProductPartitionNode offerBNode =
brandGoogleNode.getChild(ProductDimensions.createOfferId("B"));
offerBNode.asBiddableUnit().setBid(5000000L);
// This should produce 1 REMOVE operation + 1 ADD operation.
ProductPartitionNode brandOtherNode =
tree.getRoot().getChild(ProductDimensions.createBrand(null));
brandOtherNode = brandOtherNode.asBiddableUnit();
// Add an offer C node. This should produce 1 ADD operation.
ProductPartitionNode offerCNode = brandGoogleNode.addChild(ProductDimensions.createOfferId("C"))
.asBiddableUnit().setBid(1500000L);
// Remove the brand Motorola node. This should produce 1 REMOVE operation.
tree.getRoot().removeChild(brandMotorola);
// Get the mutate operations generated by the modifications made to the tree.
List<AdGroupCriterionOperation> mutateOperations = tree.getMutateOperations();
assertEquals(7, mutateOperations.size());
// Put the mutate operations in a map keyed by partition ID.
Map<Long, CriterionDescriptor> opsDescriptorMap = Maps.newHashMap();
int i = 0;
for (AdGroupCriterionOperation mutateOperation : mutateOperations) {
CriterionDescriptor descriptor = new CriterionDescriptor(mutateOperation.getOperand(), i++);
opsDescriptorMap.put(descriptor.partitionId, descriptor);
}
// Check the node that simply had a bid update.
int setOpNumber = opsDescriptorMap.get(offerANode.getProductPartitionId()).operationNumber;
assertEquals("Offer A node with a bid update should have a SET operation", Operator.SET,
mutateOperations.get(setOpNumber).getOperator());
// Check the offer B node that went from excluded to biddable.
int addOfferBOpNumber =
opsDescriptorMap.get(offerBNode.getProductPartitionId()).operationNumber;
assertEquals("Offer B node with a biddable change should have an add operation for the new ID",
Operator.ADD, mutateOperations.get(addOfferBOpNumber).getOperator());
int removeOfferBOpNumber = opsDescriptorMap.get(offerBOriginalPartitionId).operationNumber;
assertEquals(
"Offer B node with a biddable change should have a remove operation for the original ID",
Operator.REMOVE, mutateOperations.get(removeOfferBOpNumber).getOperator());
// Check the offer C node that was added.
int addOfferCOpNumber =
opsDescriptorMap.get(offerCNode.getProductPartitionId()).operationNumber;
assertEquals("New offer C node should have an add operation for the new ID", Operator.ADD,
mutateOperations.get(addOfferCOpNumber).getOperator());
// Check the brand null node that went from excluded to biddable.
int addBrandOtherOpNumber =
opsDescriptorMap.get(brandOtherNode.getProductPartitionId()).operationNumber;
assertEquals(
"Brand null node with a biddable change should have an add operation for the new ID",
Operator.ADD, mutateOperations.get(addBrandOtherOpNumber).getOperator());
int brandOtherOpNumber = opsDescriptorMap.get(offerBOriginalPartitionId).operationNumber;
assertEquals(
"Brand null node with a biddable change should have a remove operation for the original ID",
Operator.REMOVE, mutateOperations.get(brandOtherOpNumber).getOperator());
// Check the brand Motorola node that was removed.
int brandMotorolaOpNumber =
opsDescriptorMap.get(brandMotorolaOriginalPartitionId).operationNumber;
assertEquals("Removed node should have a remove operation", Operator.REMOVE,
mutateOperations.get(brandMotorolaOpNumber).getOperator());
}
/**
* Tests creating an empty tree and then adding several levels of nodes.
*/
@Test
public void testCreateMultiNodeTreeFromScratch() {
ProductPartitionTree tree = ProductPartitionTree.createAdGroupTree(-1L, biddingStrategyConfig,
Collections.<AdGroupCriterion>emptyList());
ProductPartitionNode rootNode = tree.getRoot().asSubdivision();
ProductPartitionNode brand1 =
rootNode.addChild(ProductDimensions.createBrand("google")).asSubdivision();
ProductPartitionNode brand1Offer1 =
brand1.addChild(ProductDimensions.createOfferId("A")).asBiddableUnit().setBid(1000000L);
ProductPartitionNode brand1Offer2 =
brand1.addChild(ProductDimensions.createOfferId(null)).asExcludedUnit();
ProductPartitionNode brand2 =
rootNode.addChild(ProductDimensions.createBrand(null)).asExcludedUnit();
int expectedOpCount = 5;
List<AdGroupCriterionOperation> mutateOperations = tree.getMutateOperations();
assertEquals("Number of operations is incorrect", expectedOpCount, mutateOperations.size());
List<CriterionDescriptor> nodeDescriptors = Lists.newArrayList();
for (ProductPartitionNode node :
Arrays.asList(rootNode, brand1, brand1Offer1, brand1Offer2, brand2)) {
nodeDescriptors.add(new CriterionDescriptor(node));
}
int opNum = 0;
List<CriterionDescriptor> opDescriptors = Lists.newArrayList();
Map<Long, CriterionDescriptor> opDescriptorsById = Maps.newHashMap();
for (AdGroupCriterionOperation op : mutateOperations) {
CriterionDescriptor opDescriptor = new CriterionDescriptor(op.getOperand(), opNum++);
opDescriptors.add(opDescriptor);
opDescriptorsById.put(opDescriptor.partitionId, opDescriptor);
}
Map<Long, Map<Long, CriterionDescriptor>> opDescriptorMap = buildDescriptorMap(opDescriptors);
for (CriterionDescriptor nodeDescriptor : nodeDescriptors) {
CriterionDescriptor opDescriptor =
opDescriptorMap.get(nodeDescriptor.parentPartitionId).get(nodeDescriptor.partitionId);
nodeDescriptor.assertDescriptorEquals(opDescriptor);
AdGroupCriterionOperation op = mutateOperations.get(opDescriptor.operationNumber);
assertEquals("operator is incorrect", Operator.ADD, op.getOperator());
if (nodeDescriptor.parentPartitionId != null) {
CriterionDescriptor parentOpDescriptor =
opDescriptorsById.get(nodeDescriptor.parentPartitionId);
assertNotNull("no operation found for parent", parentOpDescriptor);
assertThat("operation # for parent is > operation # for child",
opDescriptor.operationNumber, Matchers.greaterThan(parentOpDescriptor.operationNumber));
}
}
assertThat("Tree toString does not contain the root's detailed toString", tree.toString(),
Matchers.containsString(tree.getRoot().toDetailedString()));
assertThat("Tree toString does not contain the ad group ID", tree.toString(),
Matchers.containsString(tree.getAdGroupId().toString()));
}
/**
* Tests that the factory method ignores removed criteria.
*/
@Test
public void testRemovedCriteriaIgnored() {
isUtilityRegistryExpected = false;
CriterionDescriptor rootDescriptor =
new CriterionDescriptor(true, false, null, 1000000L, 1L, null);
List<AdGroupCriterion> criteria = Lists.newArrayList();
criteria.add(rootDescriptor.createCriterion());
// Create a criteria for a child node and set its UserStatus to REMOVED.
ProductBrand brandGoogle = ProductDimensions.createBrand("google");
CriterionDescriptor removedDescriptor =
new CriterionDescriptor(true, false, brandGoogle, null, 2L, 1L);
AdGroupCriterion removedCriterion = removedDescriptor.createCriterion();
((BiddableAdGroupCriterion) removedCriterion).setUserStatus(UserStatus.REMOVED);
criteria.add(removedCriterion);
ProductPartitionTree tree =
ProductPartitionTree.createAdGroupTree(-1L, biddingStrategyConfig, criteria);
assertFalse("Brand = google criteria had status removed, but it is in the tree",
tree.getRoot().hasChild(brandGoogle));
}
/**
* Tests that the factory method that retrieves the tree using API services builds
* the correct tree and passes the correct paging arguments.
*/
@Test
public void testCreateTreeUsingService() throws Exception {
isUtilityRegistryExpected = false;
AdWordsServices adWordsServices = new AdWordsServices();
AdWordsSession session =
new AdWordsSession.Builder()
.withClientCustomerId("123-456-7890")
.withOAuth2Credential(new Credential(BearerToken.authorizationHeaderAccessMethod()))
.withDeveloperToken("devtoken")
.withUserAgent("test")
// Use the test server's endpoint
.withEndpoint(testHttpServer.getServerUrl())
.build();
// Extract the API version from this test's package.
List<String> packageComponents =
Lists.newArrayList(Splitter.on('.').split(getClass().getPackage().getName()));
final String apiVersion = packageComponents.get(packageComponents.size() - 2);
final int pageSize = 100;
final int numberOfCriteria = (pageSize * 5) + 1;
// Construct a list of CriterionDescriptors that will build a tree of the form:
// root
// OfferId = null EXCLUDED
// OfferId = 1 BIDDABLE
// OfferId = 2 BIDDABLE
// ...
// OfferId = numberOfCriteria BIDDABLE
List<CriterionDescriptor> descriptors = Lists.newArrayList();
long partitionId = 1L;
final long rootPartitionId = partitionId;
descriptors.add(new CriterionDescriptor(false, false, null, null, partitionId++, null));
descriptors.add(new CriterionDescriptor(
true, true, ProductDimensions.createOfferId(null), null, partitionId++, rootPartitionId));
for (int i = 1; i <= (numberOfCriteria - 2); i++) {
descriptors.add(
new CriterionDescriptor(true, false, ProductDimensions.createOfferId(Integer.toString(i)),
10000000L, partitionId++, rootPartitionId));
}
// Split the descriptor list into batches of size pageSize.
List<List<CriterionDescriptor>> descriptorBatches = Lists.partition(descriptors, pageSize);
List<String> responseBodies = Lists.newArrayList();
for (List<CriterionDescriptor> descriptorBatch : descriptorBatches) {
// For this batch of descriptors, manually construct the AdGroupCriterionPage
// to return. This is required because AdWordsServices is a final class, so this test
// cannot mock its behavior.
AdGroupCriterionPage mockPage = new AdGroupCriterionPage();
mockPage.setTotalNumEntries(numberOfCriteria);
mockPage.setEntries(new AdGroupCriterion[descriptorBatch.size()]);
int i = 0;
for (CriterionDescriptor descriptor : descriptorBatch) {
mockPage.setEntries(i++, descriptor.createCriterion());
}
// Serialize the page.
StringWriter writer = new StringWriter();
SerializationContext serializationContext = new SerializationContext(writer) {
/**
* Override the serialize method called by the Axis serializer and force it to
* pass {@code includeNull = false}.
*/
@SuppressWarnings("rawtypes")
@Override
public void serialize(QName elemQName, Attributes attributes, Object value, QName xmlType,
Class javaType) throws IOException {
super.serialize(elemQName, attributes, value, xmlType, javaType, false, null);
}
};
serializationContext.setSendDecl(false);
new AxisSerializer().serialize(mockPage, serializationContext);
// Wrap the serialized page in a SOAP envelope.
StringBuilder response = new StringBuilder();
response.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
+ "<soap:Header/><soap:Body>");
response.append(String.format(
"<getResponse xmlns=\"https://adwords.google.com/api/adwords/cm/%s\">", apiVersion));
// Replace the element name AdGroupCriterionPage with the expected name rval in the
// serialized page.
response.append(writer.toString().replaceAll("AdGroupCriterionPage", "rval"));
response.append("</getResponse></soap:Body></soap:Envelope>");
responseBodies.add(response.toString());
}
// Set the test server to return the response bodies constructed above.
testHttpServer.setMockResponseBodies(responseBodies);
// Build the tree.
ProductPartitionTree tree = ProductPartitionTree.createAdGroupTree(
adWordsServices, session, 9999L/* dummy ad group ID */);
// First, confirm that the paging elements were correct in each request's selector.
int requestNumber = 0;
for (String requestBody : testHttpServer.getAllRequestBodies()) {
int expectedOffset = requestNumber * pageSize;
assertThat("numberResults paging element is missing or incorrect in request", requestBody,
Matchers.containsString("numberResults>" + pageSize + "</"));
if (requestNumber == 0) {
assertThat("startIndex paging element unexpectedly found in the first request", requestBody,
Matchers.not(Matchers.containsString("startIndex>")));
} else {
assertThat("startIndex paging element is missing or incorrect in request", requestBody,
Matchers.containsString("startIndex>" + expectedOffset + "</"));
}
requestNumber++;
}
// Confirm that the tree returned by the factory method matches the expected tree.
descriptors.get(0).assertDescriptorEquals(new CriterionDescriptor(tree.getRoot()));
// Get a map of all of the child descriptors for the root node.
Map<Long, CriterionDescriptor> descriptorMap =
buildDescriptorMap(descriptors).get(rootPartitionId);
// Confirm each ProductPartitionNode under the root node has a matching entry in the descriptor
// map.
int childrenFound = 0;
for (ProductPartitionNode childNode : tree.getRoot().getChildren()) {
CriterionDescriptor nodeDescriptor = new CriterionDescriptor(childNode);
nodeDescriptor.assertDescriptorEquals(descriptorMap.get(nodeDescriptor.partitionId));
childrenFound++;
}
assertEquals("Did not find an entry in the response for every expected child node",
descriptorMap.size(), childrenFound);
}
/**
* Returns a map from parentPartitionId to map of partitionId to CriterionDescriptor.
*/
private Map<Long, Map<Long, CriterionDescriptor>> buildDescriptorMap(
Iterable<CriterionDescriptor> descriptors) {
Map<Long, Map<Long, CriterionDescriptor>> descriptorMap = Maps.newHashMap();
for (CriterionDescriptor descriptor : descriptors) {
Map<Long, CriterionDescriptor> mapForParent = descriptorMap.get(descriptor.parentPartitionId);
if (mapForParent == null) {
mapForParent = Maps.newHashMap();
descriptorMap.put(descriptor.parentPartitionId, mapForParent);
}
CriterionDescriptor existingDescriptor = mapForParent.put(descriptor.partitionId, descriptor);
assertNull("Multiple descriptors found for parent ID " + descriptor.parentPartitionId
+ " and partition ID " + descriptor.partitionId, existingDescriptor);
}
return descriptorMap;
}
/**
* Helper class that describes an {@link AdGroupCriterion}.
*/
private static class CriterionDescriptor {
private final boolean isUnit;
private final ProductDimension dimension;
private final boolean isExcluded;
private final Long bid;
private final Long partitionId;
private final Long parentPartitionId;
private final Integer operationNumber;
/**
* Creates a new instance based on explicitly provided attribute values.
*/
CriterionDescriptor(boolean isUnit,
boolean isExcluded,
ProductDimension dimension,
Long bid,
Long partitionId,
Long parentPartitionId) {
// Add a few sanity checks to catch coding errors in tests as early as possible.
if (isExcluded) {
Preconditions.checkArgument(isUnit, "Cannot exclude a non-unit");
Preconditions.checkArgument(bid == null,
"Cannot specify a non-null bid for an excluded criterion");
}
Preconditions.checkNotNull(partitionId, "Null partition ID");
if (dimension == null) {
Preconditions.checkArgument(parentPartitionId == null,
"Dimension is null (root node) but parent ID specified");
} else {
Preconditions.checkNotNull(parentPartitionId,
"Dimension is not null but parent ID is null");
}
this.isUnit = isUnit;
this.dimension = dimension;
this.isExcluded = isExcluded;
this.bid = bid;
this.partitionId = partitionId;
this.parentPartitionId = parentPartitionId;
this.operationNumber = null;
}
/**
* Creates a new instance based on a ProductPartitionNode.
*/
CriterionDescriptor(ProductPartitionNode node) {
Preconditions.checkNotNull(node, "node is null");
this.isUnit = node.isUnit();
this.dimension = node.getDimension();
this.isExcluded = node.isExcludedUnit();
this.bid = node.getBid();
this.partitionId = node.getProductPartitionId();
if (node.getParent() != null) {
this.parentPartitionId = node.getParent().getProductPartitionId();
} else {
this.parentPartitionId = null;
}
this.operationNumber = null;
}
CriterionDescriptor(AdGroupCriterion adGroupCriterion, int operationNumber) {
Preconditions.checkNotNull(adGroupCriterion, "Null ad group criterion");
ProductPartition partition = (ProductPartition) adGroupCriterion.getCriterion();
this.isUnit = ProductPartitionType.UNIT.equals(partition.getPartitionType());
this.dimension = partition.getCaseValue();
this.partitionId = partition.getId();
this.parentPartitionId = partition.getParentCriterionId();
if (adGroupCriterion instanceof BiddableAdGroupCriterion) {
BiddableAdGroupCriterion biddableCriterion = (BiddableAdGroupCriterion) adGroupCriterion;
this.isExcluded = false;
BiddingStrategyConfiguration biddingConfig =
biddableCriterion.getBiddingStrategyConfiguration();
Long bidAmount = null;
if (biddingConfig != null) {
Bids[] bids = biddingConfig.getBids();
if (bids != null) {
for (Bids bid : bids) {
CpcBid cpcBid = (CpcBid) bid;
if (BidSource.CRITERION.equals(cpcBid.getCpcBidSource())) {
bidAmount = cpcBid.getBid().getMicroAmount();
break;
}
}
}
}
this.bid = bidAmount;
} else {
this.isExcluded = true;
this.bid = null;
}
this.operationNumber = operationNumber;
}
/**
* Asserts that this object matches {@code other} on each attribute.
*/
void assertDescriptorEquals(CriterionDescriptor other) {
assertNotNull("Null descriptor compared to: " + this, other);
assertEquals("dimension does not match", 0,
new ProductDimensionComparator().compare(this.dimension, other.dimension));
assertEquals("bid is incorrect", this.bid, other.bid);
assertEquals("isUnit is incorrect", this.isUnit, other.isUnit);
assertEquals("isExcludedUnit is incorrect", this.isExcluded, other.isExcluded);
}
/**
* Returns a new AdGroupCriterion based on this descriptor.
*/
AdGroupCriterion createCriterion() {
AdGroupCriterion adGroupCriterion;
ProductPartition partition = new ProductPartition();
partition.setId(partitionId);
partition.setParentCriterionId(parentPartitionId);
partition.setCaseValue(dimension);
partition.setPartitionType(
isUnit ? ProductPartitionType.UNIT : ProductPartitionType.SUBDIVISION);
if (isExcluded) {
NegativeAdGroupCriterion negative = new NegativeAdGroupCriterion();
adGroupCriterion = negative;
} else {
BiddableAdGroupCriterion biddable = new BiddableAdGroupCriterion();
biddable.setUserStatus(UserStatus.ENABLED);
BiddingStrategyConfiguration biddingConfig = new BiddingStrategyConfiguration();
if (isUnit && bid != null) {
CpcBid cpcBid = new CpcBid();
Money bidMoney = new Money();
bidMoney.setMicroAmount(bid);
cpcBid.setBid(bidMoney);
cpcBid.setCpcBidSource(BidSource.CRITERION);
biddingConfig.setBids(new Bids[] {cpcBid});
}
biddable.setBiddingStrategyConfiguration(biddingConfig);
adGroupCriterion = biddable;
}
adGroupCriterion.setCriterion(partition);
return adGroupCriterion;
}
}
}