Skip to content

Commit

Permalink
Configurable targeting prefix
Browse files Browse the repository at this point in the history
  • Loading branch information
VeryExtraordinaryUsername committed Nov 28, 2023
1 parent aafdaea commit 06020dd
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 55 deletions.
29 changes: 26 additions & 3 deletions src/main/java/org/prebid/server/auction/BidResponseCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import org.prebid.server.settings.model.AccountAuctionConfig;
import org.prebid.server.settings.model.AccountAuctionEventConfig;
import org.prebid.server.settings.model.AccountEventsConfig;
import org.prebid.server.settings.model.AccountTargetingConfig;
import org.prebid.server.settings.model.VideoStoredDataResult;
import org.prebid.server.util.LineItemUtil;
import org.prebid.server.util.StreamUtil;
Expand Down Expand Up @@ -1658,18 +1659,20 @@ private TargetingKeywordsCreator createKeywordsCreator(ExtRequestTargeting targe
JsonNode priceGranularity,
BidRequest bidRequest,
Account account) {

final int resolvedTruncateAttrChars = resolveTruncateAttrChars(targeting, account);
final String resolveKeyPrefix = resolveKeyPrefix(bidRequest, account, resolvedTruncateAttrChars);
return TargetingKeywordsCreator.create(
parsePriceGranularity(priceGranularity),
BooleanUtils.toBoolean(targeting.getIncludewinners()),
BooleanUtils.toBoolean(targeting.getIncludebidderkeys()),
BooleanUtils.toBoolean(targeting.getAlwaysincludedeals()),
BooleanUtils.isTrue(targeting.getIncludeformat()),
isApp,
resolveTruncateAttrChars(targeting, account),
resolvedTruncateAttrChars,
cacheHost,
cachePath,
TargetingKeywordsResolver.create(bidRequest, mapper));
TargetingKeywordsResolver.create(bidRequest, mapper),
resolveKeyPrefix);
}

/**
Expand All @@ -1686,6 +1689,26 @@ private int resolveTruncateAttrChars(ExtRequestTargeting targeting, Account acco
truncateAttrChars);
}

/**
* Returns targeting key prefix.
* Default prefix for targeting keys used in cases,
* when correspond value is missing in account auction configuration or bid request ext,
* or may compose keys longer than 'settings.targeting.truncate-attr-chars' value.
*/
private static String resolveKeyPrefix(BidRequest bidRequest, Account account, int truncateAttrChars) {
final String prefix = Optional.of(bidRequest)
.map(BidRequest::getExt)
.map(ExtRequest::getPrebid)
.map(ExtRequestPrebid::getTargeting)
.map(ExtRequestTargeting::getPrefix)
.orElse(Optional.ofNullable(account)
.map(Account::getAuction)
.map(AccountAuctionConfig::getTargeting)
.map(AccountTargetingConfig::getPrefix)
.orElse(null));
return StringUtils.isNotEmpty(prefix) && prefix.length() + 11 < truncateAttrChars ? prefix : "hb";
}

private static Integer truncateAttrCharsOrNull(Integer value) {
return value != null && value >= 0 && value <= 255 ? value : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,54 +31,54 @@ public class TargetingKeywordsCreator {
* Exists to support the Prebid Universal Creative. If it exists, the only legal value is mobile-app.
* It will exist only if the incoming bidRequest defiend request.app instead of request.site.
*/
private static final String HB_ENV_KEY = "hb_env";
private static final String ENV_KEY = "_env";
/**
* Used as a value for HB_ENV_KEY.
* Used as a value for ENV_KEY.
*/
private static final String HB_ENV_APP_VALUE = "mobile-app";
private static final String ENV_APP_VALUE = "mobile-app";
/**
* Name of the Bidder. For example, "appnexus" or "rubicon".
*/
private static final String HB_BIDDER_KEY = "hb_bidder";
private static final String BIDDER_KEY = "_bidder";
/**
* Respects rounded CPM value.
*/
private static final String HB_PB_KEY = "hb_pb";
private static final String PB_KEY = "_pb";
/**
* Describes the size in format: [Width]x[Height].
*/
private static final String HB_SIZE_KEY = "hb_size";
private static final String SIZE_KEY = "_size";
/**
* Stores the UUID which can be used to fetch the bid data from prebid cache.
* Callers should *never* assume that this exists, since the call to the cache may always fail.
*/
private static final String HB_CACHE_ID_KEY = "hb_cache_id";
private static final String CACHE_ID_KEY = "_cache_id";
/**
* Stores the UUID which can be used to fetch the video XML data from prebid cache.
* Callers should *never* assume that this exists, since the call to the cache may always fail.
*/
private static final String HB_VAST_ID_KEY = "hb_uuid";
private static final String VAST_ID_KEY = "_uuid";
/**
* Stores the deal ID for the given bid.
*/
private static final String HB_DEAL_KEY = "hb_deal";
private static final String DEAL_KEY = "_deal";
/**
* Stores protocol, host and port for cache service endpoint.
*/
private static final String HB_CACHE_HOST_KEY = "hb_cache_host";
private static final String CACHE_HOST_KEY = "_cache_host";
/**
* Stores http path for cache service endpoint.
*/
private static final String HB_CACHE_PATH_KEY = "hb_cache_path";
private static final String CACHE_PATH_KEY = "_cache_path";
/**
* Stores category duration for video bids
*/
private static final String HB_CATEGORY_DURATION_KEY = "hb_pb_cat_dur";
private static final String CATEGORY_DURATION_KEY = "_pb_cat_dur";

/**
* Stores bid's format. For example "video" or "banner".
*/
private static final String HB_FORMAT_KEY = "hb_format";
private static final String FORMAT_KEY = "_format";

private static final String DEFAULT_CPM = "0.0";

Expand All @@ -92,6 +92,7 @@ public class TargetingKeywordsCreator {
private final String cacheHost;
private final String cachePath;
private final TargetingKeywordsResolver resolver;
private final String keyPrefix;

private TargetingKeywordsCreator(PriceGranularity priceGranularity,
boolean includeWinners,
Expand All @@ -102,7 +103,8 @@ private TargetingKeywordsCreator(PriceGranularity priceGranularity,
int truncateAttrChars,
String cacheHost,
String cachePath,
TargetingKeywordsResolver resolver) {
TargetingKeywordsResolver resolver,
String keyPrefix) {

this.priceGranularity = priceGranularity;
this.includeWinners = includeWinners;
Expand All @@ -114,6 +116,7 @@ private TargetingKeywordsCreator(PriceGranularity priceGranularity,
this.cacheHost = cacheHost;
this.cachePath = cachePath;
this.resolver = resolver;
this.keyPrefix = keyPrefix;
}

/**
Expand All @@ -128,8 +131,8 @@ public static TargetingKeywordsCreator create(ExtPriceGranularity extPriceGranul
int truncateAttrChars,
String cacheHost,
String cachePath,
TargetingKeywordsResolver resolver) {

TargetingKeywordsResolver resolver,
String keyPrefix) {
return new TargetingKeywordsCreator(
PriceGranularity.createFromExtPriceGranularity(extPriceGranularity),
includeWinners,
Expand All @@ -140,7 +143,8 @@ public static TargetingKeywordsCreator create(ExtPriceGranularity extPriceGranul
truncateAttrChars,
cacheHost,
cachePath,
resolver);
resolver,
keyPrefix);
}

/**
Expand Down Expand Up @@ -199,38 +203,38 @@ private Map<String, String> makeFor(String bidder,
Collections.emptySet());

final String roundedCpm = isPriceGranularityValid() ? CpmRange.fromCpm(price, priceGranularity) : DEFAULT_CPM;
keywordMap.put(HB_PB_KEY, roundedCpm);
keywordMap.put(this.keyPrefix + PB_KEY, roundedCpm);

keywordMap.put(HB_BIDDER_KEY, bidder);
keywordMap.put(this.keyPrefix + BIDDER_KEY, bidder);

final String hbSize = sizeFrom(width, height);
if (hbSize != null) {
keywordMap.put(HB_SIZE_KEY, hbSize);
keywordMap.put(this.keyPrefix + SIZE_KEY, hbSize);
}
if (StringUtils.isNotBlank(cacheId)) {
keywordMap.put(HB_CACHE_ID_KEY, cacheId);
keywordMap.put(this.keyPrefix + CACHE_ID_KEY, cacheId);
}
if (StringUtils.isNotBlank(vastCacheId)) {
keywordMap.put(HB_VAST_ID_KEY, vastCacheId);
keywordMap.put(this.keyPrefix + VAST_ID_KEY, vastCacheId);
}
if ((StringUtils.isNotBlank(vastCacheId) || StringUtils.isNotBlank(cacheId))
&& cacheHost != null && cachePath != null) {
keywordMap.put(HB_CACHE_HOST_KEY, cacheHost);
keywordMap.put(HB_CACHE_PATH_KEY, cachePath);
keywordMap.put(this.keyPrefix + CACHE_HOST_KEY, cacheHost);
keywordMap.put(this.keyPrefix + CACHE_PATH_KEY, cachePath);
}
if (StringUtils.isNotBlank(format) && includeFormat) {
keywordMap.put(HB_FORMAT_KEY, format);
keywordMap.put(this.keyPrefix + FORMAT_KEY, format);
}

// get Line Item by dealId
if (StringUtils.isNotBlank(dealId)) {
keywordMap.put(HB_DEAL_KEY, dealId);
keywordMap.put(this.keyPrefix + DEAL_KEY, dealId);
}
if (isApp) {
keywordMap.put(HB_ENV_KEY, HB_ENV_APP_VALUE);
keywordMap.put(this.keyPrefix + ENV_KEY, ENV_APP_VALUE);
}
if (StringUtils.isNotBlank(categoryDuration)) {
keywordMap.put(HB_CATEGORY_DURATION_KEY, categoryDuration);
keywordMap.put(this.keyPrefix + CATEGORY_DURATION_KEY, categoryDuration);
}

return keywordMap.asMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private List<ExtAdPod> adPodsWithTargetingFrom(List<Bid> bids) {
final List<ExtAdPod> adPods = new ArrayList<>();
for (Bid bid : bids) {
final Map<String, String> targeting = targeting(bid);
if (findByPrefix(targeting, "hb_uuid") == null) {
if (findByPrefix(targeting, "_uuid") == null) {
continue;
}
final String impId = bid.getImpid();
Expand All @@ -99,9 +99,9 @@ private List<ExtAdPod> adPodsWithTargetingFrom(List<Bid> bids) {
final Integer podId = Integer.parseInt(podIdString);

final ExtResponseVideoTargeting videoTargeting = ExtResponseVideoTargeting.of(
findByPrefix(targeting, "hb_pb"),
findByPrefix(targeting, "hb_pb_cat_dur"),
findByPrefix(targeting, "hb_uuid"));
findByPrefix(targeting, "_pb"),
findByPrefix(targeting, "_pb_cat_dur"),
findByPrefix(targeting, "_uuid"));

ExtAdPod adPod = adPods.stream()
.filter(extAdPod -> extAdPod.getPodid().equals(podId))
Expand Down Expand Up @@ -136,7 +136,7 @@ private Map<String, String> targeting(Bid bid) {

private static String findByPrefix(Map<String, String> keyToValue, String prefix) {
return keyToValue.entrySet().stream()
.filter(keyAndValue -> keyAndValue.getKey().startsWith(prefix))
.filter(keyAndValue -> keyAndValue.getKey().contains(prefix))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ private Map<String, String> targetingFrom(Bid bid, String bidder) {
// go in the AMP response
final Map<String, String> targeting = extBidPrebid != null ? extBidPrebid.getTargeting() : null;
if (targeting != null && targeting.keySet().stream()
.anyMatch(key -> key != null && key.startsWith("hb_cache_id"))) {
.anyMatch(key -> key != null && key.contains("_cache_id"))) {

return enrichWithCustomTargeting(targeting, bidExt, bidder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,9 @@ public class ExtRequestTargeting {

@JsonAlias("alwaysIncludeDeals")
Boolean alwaysincludedeals;

/**
* Defines the contract for bidrequest.ext.prebid.targeting.prefix
*/
String prefix;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ public class AccountTargetingConfig {

@JsonProperty("alwaysincludedeals")
Boolean alwaysIncludeDeals;

@JsonProperty("prefix")
String prefix;
}
16 changes: 16 additions & 0 deletions src/main/java/org/prebid/server/validation/RequestValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,22 @@ private void validateTargeting(ExtRequestTargeting extRequestTargeting) throws V
throw new ValidationException("ext.prebid.targeting: At least one of includewinners or includebidderkeys"
+ " must be enabled to enable targeting support");
}

validateTargetingPrefix(extRequestTargeting);
}

private void validateTargetingPrefix(ExtRequestTargeting extRequestTargeting) throws ValidationException {
final Integer truncateattrchars = extRequestTargeting.getTruncateattrchars();
final int prefixLength = extRequestTargeting.getPrefix() != null
? extRequestTargeting.getPrefix().length()
: 0;
final boolean prefixLengthInvalid = truncateattrchars != null
&& prefixLength > 0
&& prefixLength + 11 > truncateattrchars; // 11 - length of the longest targeting keyword without prefix
if (prefixLengthInvalid) {
throw new ValidationException("ext.prebid.targeting: decrease prefix length or increase truncateattrchars"
+ " by " + (prefixLength + 11 - truncateattrchars) + " characters");
}
}

/**
Expand Down
Loading

0 comments on commit 06020dd

Please sign in to comment.