package com.microsoft.bingads.v10.bulk.entities; import com.microsoft.bingads.v10.bulk.entities.BulkTargetIdentifier; import com.microsoft.bingads.v10.campaignmanagement.LocationTarget; import com.microsoft.bingads.v10.campaignmanagement.Target; import com.microsoft.bingads.InternalException; import com.microsoft.bingads.v10.internal.bulk.BulkObjectWriter; import com.microsoft.bingads.v10.internal.bulk.BulkStreamReader; import com.microsoft.bingads.v10.internal.bulk.TryResult; import com.microsoft.bingads.v10.internal.bulk.entities.MultiRecordBulkEntity; import com.microsoft.bingads.internal.functionalinterfaces.Predicate; import static com.microsoft.bingads.internal.utilities.Comparer.compareNullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; /** * This abstract base class provides properties that are shared by all bulk target classes, * for example {@link BulkAdGroupDayTimeTarget}. * * @param <TIdentifier> the common target identifier accross all sub types for a given target * @param <TAgeBid> an age target bid * @param <TAge> an age target * @param <TDayTimeBid> a day and time target bid * @param <TDayTime> a day and time target * @param <TDeviceOsBid> a device OS target bid * @param <TDeviceOs> a device OS target * @param <TGenderBid> a gender target bid * @param <TGender> a gender target * @param <TLocationBid> a location target bid * @param <TLocation> a location target * @param <TNegativeLocationBid> a negative location bid * @param <TNegativeLocation> a negative location * @param <TRadiusTargetBid> a radius target bid * @param <TRadius> a radius target */ abstract class BulkTarget< TIdentifier extends BulkTargetIdentifier, TAgeBid extends BulkAgeTargetBid, TAge extends BulkAgeTarget<TAgeBid>, TDayTimeBid extends BulkDayTimeTargetBid, TDayTime extends BulkDayTimeTarget<TDayTimeBid>, TDeviceOsBid extends BulkDeviceOsTargetBid, TDeviceOs extends BulkDeviceOsTarget<TDeviceOsBid>, TGenderBid extends BulkGenderTargetBid, TGender extends BulkGenderTarget<TGenderBid>, TLocationBid extends BulkLocationTargetBid, TLocation extends BulkLocationTarget<TLocationBid>, TNegativeLocationBid extends BulkNegativeLocationTargetBid, TNegativeLocation extends BulkNegativeLocationTarget<TNegativeLocationBid>, TRadiusTargetBid extends BulkRadiusTargetBid, TRadius extends BulkRadiusTarget<TRadiusTargetBid>> extends MultiRecordBulkEntity { private Status status; private Long entityId; private String entityName; private String parentEntityName; private Target target; private TAge ageTarget; private Class<TAgeBid> classOfTAgeBid; private TDayTime dayTimeTarget; private Class<TDayTimeBid> classOfTDayTimeBid; private TDeviceOs deviceOsTarget; private Class<TDeviceOsBid> classOfTDeviceOsBid; private TGender genderTarget; private Class<TGenderBid> classOfTGenderBid; private TLocation locationTarget; private Class<TLocationBid> classOfTLocationBid; private TNegativeLocation negativeLocationTarget; private Class<TNegativeLocationBid> classOfTNegativeLocationBid; private TRadius radiusTarget; private Class<TRadiusTargetBid> classOfTRadiusBid; private TIdentifier originalIdentifier; private Class<TIdentifier> classOfTIdentifier; private final List<BulkTargetBid> bids = new ArrayList<BulkTargetBid>(); private final List<TIdentifier> deleteAllRows = new ArrayList<TIdentifier>(); protected BulkTarget(BulkTargetBid bid, TAge age, Class<TAgeBid> classOfTAgeBid, TDayTime dayTime, Class<TDayTimeBid> classOfTDayTimeBid, TDeviceOs deviceOs, Class<TDeviceOsBid> classOfTDeviceOsBid, TGender gender, Class<TGenderBid> classOfTGenderBid, TLocation location, Class<TLocationBid> classOfTLocationBid, TNegativeLocation negativeLocation, Class<TNegativeLocationBid> classOfTNegativeLocationBid, TRadius radius, Class<TRadiusTargetBid> classOfTRadiusBid, Class<TIdentifier> classOfTIdentifier) { this((TIdentifier) bid.getIdentifier(), age, classOfTAgeBid, dayTime, classOfTDayTimeBid, deviceOs, classOfTDeviceOsBid, gender, classOfTGenderBid, location, classOfTLocationBid, negativeLocation, classOfTNegativeLocationBid, radius, classOfTRadiusBid, classOfTIdentifier); bids.add(bid); } protected BulkTarget(TIdentifier identifier, TAge age, Class<TAgeBid> classOfTAgeBid, TDayTime dayTime, Class<TDayTimeBid> classOfTDayTimeBid, TDeviceOs deviceOs, Class<TDeviceOsBid> classOfTDeviceOsBid, TGender gender, Class<TGenderBid> classOfTGenderBid, TLocation location, Class<TLocationBid> classOfTLocationBid, TNegativeLocation negativeLocation, Class<TNegativeLocationBid> classOfTNegativeLocationBid, TRadius radiusTarget, Class<TRadiusTargetBid> classOfTRadiusBid, Class<TIdentifier> classOfTIdentifier) { this( age, classOfTAgeBid, dayTime, classOfTDayTimeBid, deviceOs, classOfTDeviceOsBid, gender, classOfTGenderBid, location, classOfTLocationBid, negativeLocation, classOfTNegativeLocationBid, radiusTarget, classOfTRadiusBid ); setTarget(new Target()); getTarget().setId(identifier.getTargetId()); setEntityId(identifier.getEntityId()); setEntityName(identifier.getEntityName()); setParentEntityName(identifier.getParentEntityName()); if (identifier.isDeleteRow()) { deleteAllRows.add(identifier); } originalIdentifier = identifier; this.classOfTIdentifier = classOfTIdentifier; } protected BulkTarget( TAge age, Class<TAgeBid> classOfTAgeBid, TDayTime dayTime, Class<TDayTimeBid> classOfTDayTimeBid, TDeviceOs deviceOs, Class<TDeviceOsBid> classOfTDeviceOsBid, TGender gender, Class<TGenderBid> classOfTGenderBid, TLocation location, Class<TLocationBid> classOfTLocationBid, TNegativeLocation negativeLocation, Class<TNegativeLocationBid> classOfTNegativeLocationBid, TRadius radiusTarget, Class<TRadiusTargetBid> classOfTRadiusBid ) { setAgeTarget(age); setDayTime(dayTime); setDeviceOs(deviceOs); setGender(gender); setLocationTarget(location); setNegativeLocationTarget(negativeLocation); setRadiusTarget(radiusTarget); this.classOfTAgeBid = classOfTAgeBid; this.classOfTDayTimeBid = classOfTDayTimeBid; this.classOfTDeviceOsBid = classOfTDeviceOsBid; this.classOfTGenderBid = classOfTGenderBid; this.classOfTLocationBid = classOfTLocationBid; this.classOfTNegativeLocationBid = classOfTNegativeLocationBid; this.classOfTRadiusBid = classOfTRadiusBid; } @Override public void writeToStream(BulkObjectWriter rowWriter, boolean excludeReadonlyData) throws IOException { if (getStatus() != Status.DELETED) { validatePropertyNotNull(getTarget(), "Target"); } if (getTarget() != null) { if (getTarget().getAge() == null && getTarget().getLocation() == null && getTarget().getGender() == null && getTarget().getDayTime() == null && getTarget().getDeviceOS() == null) { throw new IllegalStateException("At least one sub target must not be null."); } } if (getTarget() != null) { getAgeTarget().setAgeTarget(getTarget().getAge()); getDayTimeTarget().setDayTimeTarget(getTarget().getDayTime()); getDeviceOsTarget().setDeviceOsTarget(getTarget().getDeviceOS()); getGenderTarget().setGenderTarget(getTarget().getGender()); getLocationTarget().setLocation(getTarget().getLocation()); getNegativeLocationTarget().setLocation(getTarget().getLocation()); getRadiusTarget().setLocation(getTarget().getLocation()); } setDefaultIdentifier(getAgeTarget(), classOfTAgeBid); setDefaultIdentifier(getDayTimeTarget(), classOfTDayTimeBid); setDefaultIdentifier(getDeviceOsTarget(), classOfTDeviceOsBid); setDefaultIdentifier(getGenderTarget(), classOfTGenderBid); setDefaultIdentifier(getLocationTarget(), classOfTLocationBid); setDefaultIdentifier(getNegativeLocationTarget(), classOfTNegativeLocationBid); setDefaultIdentifier(getRadiusTarget(), classOfTRadiusBid); getAgeTarget().isBeingWrittenAsPartOfParentTarget = true; getDayTimeTarget().isBeingWrittenAsPartOfParentTarget = true; getDeviceOsTarget().isBeingWrittenAsPartOfParentTarget = true; getGenderTarget().isBeingWrittenAsPartOfParentTarget = true; getLocationTarget().isBeingWrittenAsPartOfParentTarget = true; getNegativeLocationTarget().isBeingWrittenAsPartOfParentTarget = true; getRadiusTarget().isBeingWrittenAsPartOfParentTarget = true; for (BulkEntity childEntity : getSubTargets()) { childEntity.writeToStream(rowWriter, excludeReadonlyData); } } @Override public void readRelatedDataFromStream(BulkStreamReader reader) { if (classOfTIdentifier == null) { throw new InternalException(new IllegalStateException("Can't read a target that has been initialized manually")); } boolean hasMoreRows = true; while (hasMoreRows) { TryResult<BulkTargetBid> nextBulkTargetBidResult = reader.tryRead(new Predicate<BulkTargetBid>() { @Override public boolean test(BulkTargetBid x) { return originalIdentifier.equals(x.getIdentifier()); } }, BulkTargetBid.class); if (nextBulkTargetBidResult.isSuccessful()) { BulkTargetBid nextBid = nextBulkTargetBidResult.getResult(); bids.add(nextBid); // Delta download sends delete-all rows first, which don't have targetId. Have to look at all rows and set first non-null Id. if (getTarget().getId() == null && nextBid.getTargetId() != null) { getTarget().setId(nextBid.getTargetId()); } continue; } TryResult<TIdentifier> identifierResult = reader.tryRead(new Predicate<TIdentifier>() { @Override public boolean test(TIdentifier x) { return compareNullable(x.getEntityId(), getEntityId()) && x.isDeleteRow(); } }, classOfTIdentifier); if (identifierResult.isSuccessful()) { deleteAllRows.add(identifierResult.getResult()); continue; } hasMoreRows = false; } setStatus(bids.size() > 0 ? Status.ACTIVE : Status.DELETED); LocationTarget location = new LocationTarget(); getLocationTarget().setLocation(location); getNegativeLocationTarget().setLocation(location); getRadiusTarget().setLocation(location); HashMap<Class, List<BulkTargetBid>> bidGroups = groupBidsByType(); populateChildTargetBids(getAgeTarget(), bidGroups, classOfTAgeBid); populateChildTargetBids(getDayTimeTarget(), bidGroups, classOfTDayTimeBid); populateChildTargetBids(getDeviceOsTarget(), bidGroups, classOfTDeviceOsBid); populateChildTargetBids(getGenderTarget(), bidGroups, classOfTGenderBid); populateChildTargetBids(getLocationTarget(), bidGroups, classOfTLocationBid); populateChildTargetBids(getNegativeLocationTarget(), bidGroups, classOfTNegativeLocationBid); populateChildTargetBids(getRadiusTarget(), bidGroups, classOfTRadiusBid); HashMap<Class, List<TIdentifier>> deleteAllGroups = groupDeleteAllRowsByType(); populateChildTargetIdentifiers(getAgeTarget(), deleteAllGroups, classOfTAgeBid); populateChildTargetIdentifiers(getDayTimeTarget(), deleteAllGroups, classOfTDayTimeBid); populateChildTargetIdentifiers(getDeviceOsTarget(), deleteAllGroups, classOfTDeviceOsBid); populateChildTargetIdentifiers(getGenderTarget(), deleteAllGroups, classOfTGenderBid); populateChildTargetIdentifiers(getLocationTarget(), deleteAllGroups, classOfTLocationBid); populateChildTargetIdentifiers(getNegativeLocationTarget(), deleteAllGroups, classOfTNegativeLocationBid); populateChildTargetIdentifiers(getRadiusTarget(), deleteAllGroups, classOfTRadiusBid); if (location.getCityTarget() != null || location.getMetroAreaTarget() != null || location.getStateTarget() != null || location.getCountryTarget() != null || location.getPostalCodeTarget() != null || location.getRadiusTarget() != null) { getTarget().setLocation(location); } getTarget().setAge(getAgeTarget().getAgeTarget()); getTarget().setDayTime(getDayTimeTarget().getDayTimeTarget()); getTarget().setDeviceOS(getDeviceOsTarget().getDeviceOsTarget()); getTarget().setGender(getGenderTarget().getGenderTarget()); } private HashMap<Class, List<TIdentifier>> groupDeleteAllRowsByType() { HashMap<Class, List<TIdentifier>> deleteAllGroups = new HashMap<Class, List<TIdentifier>>(); for (TIdentifier identifier : deleteAllRows) { List<TIdentifier> identifiersForBidType = deleteAllGroups.get(identifier.getTargetBidType()); if (identifiersForBidType == null) { identifiersForBidType = new ArrayList<TIdentifier>(); deleteAllGroups.put(identifier.getTargetBidType(), identifiersForBidType); } identifiersForBidType.add(identifier); } return deleteAllGroups; } private HashMap<Class, List<BulkTargetBid>> groupBidsByType() { HashMap<Class, List<BulkTargetBid>> bidGroups = new HashMap<Class, List<BulkTargetBid>>(); for (BulkTargetBid bid : bids) { List<BulkTargetBid> bidsForType = bidGroups.get(bid.getClass()); if (bidsForType == null) { bidsForType = new ArrayList<BulkTargetBid>(); bidGroups.put(bid.getClass(), bidsForType); } bidsForType.add(bid); } return bidGroups; } private <T extends BulkTargetBid> void populateChildTargetBids(BulkSubTarget<T> target, HashMap<Class, List<BulkTargetBid>> groups, Class<T> classOfT) { if (!groups.containsKey(classOfT)) { target.setStatus(Status.DELETED); return; } List<BulkTargetBid> bidsForTarget = groups.get(classOfT); List<T> bidsForTargetAsT = new ArrayList<T>(); for (BulkTargetBid bid : bidsForTarget) { bidsForTargetAsT.add((T) bid); } target.setBids(bidsForTargetAsT); } private <T extends BulkTargetBid> void populateChildTargetIdentifiers(BulkSubTarget<T> target, HashMap<Class, List<TIdentifier>> groups, Class<T> classOfT) { // no delete all row for this target bid type if (!groups.containsKey(classOfT)) { setDefaultIdentifier(target, classOfT); return; } List<TIdentifier> identifiers = groups.get(classOfT); // should have only one delete row at most for (TIdentifier identifier : identifiers) { target.setIdentifier(identifier); } } /** * Gets the list of sub targets that the target contains can include * LocationTarget, AgeTarget, GenderTarget, DayTimeTarget, DeviceOsTarget, NegativeLocationTarget, and RadiusTarget. */ private List<BulkEntity> getSubTargets() { return Collections.unmodifiableList( Arrays.asList( (BulkEntity) getAgeTarget(), (BulkEntity) getDayTimeTarget(), (BulkEntity) getDeviceOsTarget(), (BulkEntity) getGenderTarget(), (BulkEntity) getLocationTarget(), (BulkEntity) getNegativeLocationTarget(), (BulkEntity) getRadiusTarget() )); } @Override public List<? extends BulkEntity> getChildEntities() { return getSubTargets(); } @Override public boolean allChildrenPresent() { for (BulkEntity child : getSubTargets()) { if (!((MultiRecordBulkEntity) child).allChildrenPresent()) { return false; } } return true; } abstract TIdentifier createIdentifier(Class bidType); private <TBid extends BulkTargetBid> void setDefaultIdentifier(BulkSubTarget<TBid> target, Class<TBid> classOfTBid) { TIdentifier identifier = createIdentifier(classOfTBid); identifier.setEntityId(getEntityId()); if (getTarget() != null) { identifier.setTargetId(getTarget().getId()); } identifier.setEntityName(getEntityName()); identifier.setParentEntityName(getParentEntityName()); if (getStatus() == Status.DELETED) { target.setStatus(Status.DELETED); } target.setIdentifier(identifier); } /** * Gets the status of the target. * * <p> * The value is Active if the target is available in the customer's shared library. * The value is Deleted if the target is deleted from the customer's shared library, * or should be deleted in a subsequent upload operation. * Corresponds to the 'Status' field in the bulk file. * </p> */ public Status getStatus() { return status; } /** * Sets the status of the target. * * <p> * The value is Active if the target is available in the customer's shared library. * The value is Deleted if the target is deleted from the customer's shared library, * or should be deleted in a subsequent upload operation. * Corresponds to the 'Status' field in the bulk file. * </p> */ public void setStatus(Status status) { this.status = status; } /** * Reserved for internal use. */ protected Long getEntityId() { return entityId; } /** * Reserved for internal use. */ protected void setEntityId(Long entityId) { this.entityId = entityId; } /** * Reserved for internal use. */ protected String getEntityName() { return entityName; } /** * Reserved for internal use. */ protected void setEntityName(String entityName) { this.entityName = entityName; } /** * Reserved for internal use. */ protected String getParentEntityName() { return parentEntityName; } /** * Reserved for internal use. */ protected void setParentEntityName(String parentEntityName) { this.parentEntityName = parentEntityName; } /** * Gets the associated target. */ public Target getTarget() { return target; } /** * Sets the associated target. */ public void setTarget(Target target) { this.target = target; } /** * Gets the {@link BulkAgeTarget} contains multiple {@link BulkAgeTargetBid}. */ public TAge getAgeTarget() { return ageTarget; } /** * Sets the {@link BulkAgeTarget} contains multiple {@link BulkAgeTargetBid}. */ public void setAgeTarget(TAge ageTarget) { this.ageTarget = ageTarget; } /** * Gets the {@link BulkDayTimeTarget} contains multiple {@link BulkDayTimeTargetBid}. */ public TDayTime getDayTimeTarget() { return dayTimeTarget; } /** * Sets the {@link BulkDayTimeTarget} contains multiple {@link BulkDayTimeTargetBid}. */ public void setDayTime(TDayTime dayTimeTarget) { this.dayTimeTarget = dayTimeTarget; } /** * Gets the {@link BulkDeviceOsTarget} contains multiple {@link BulkDeviceOsTargetBid}. */ public TDeviceOs getDeviceOsTarget() { return deviceOsTarget; } /** * Sets the {@link BulkDeviceOsTarget} contains multiple {@link BulkDeviceOsTargetBid}. */ public void setDeviceOs(TDeviceOs deviceOsTarget) { this.deviceOsTarget = deviceOsTarget; } /** * Gets the {@link BulkGenderTarget} contains multiple {@link BulkGenderTargetBid}. */ public TGender getGenderTarget() { return genderTarget; } /** * Sets the {@link BulkGenderTarget} contains multiple {@link BulkGenderTargetBid}. */ public void setGender(TGender genderTarget) { this.genderTarget = genderTarget; } /** * Gets the {@link BulkLocationTarget} contains multiple {@link BulkLocationTargetBid}. */ public TLocation getLocationTarget() { return locationTarget; } /** * Sets the {@link BulkLocationTarget} contains multiple {@link BulkLocationTargetBid}. */ public void setLocationTarget(TLocation locationTarget) { this.locationTarget = locationTarget; } /** * Gets the {@link BulkNegativeLocationTarget} contains multiple {@link BulkNegativeLocationTargetBid}. */ public TNegativeLocation getNegativeLocationTarget() { return negativeLocationTarget; } /** * Sets the {@link BulkNegativeLocationTarget} contains multiple {@link BulkNegativeLocationTargetBid}. */ public void setNegativeLocationTarget(TNegativeLocation negativeLocationTarget) { this.negativeLocationTarget = negativeLocationTarget; } /** * Gets the {@link BulkRadiusTarget} contains multiple {@link BulkRadiusTargetBid}. */ public TRadius getRadiusTarget() { return radiusTarget; } /** * Sets the {@link BulkRadiusTarget} contains multiple {@link BulkRadiusTargetBid}. */ // TODO fix the typo public void setRadiusTarget(TRadius radiusTarvet) { this.radiusTarget = radiusTarvet; } }