Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable targeting prefix #2769

Merged
merged 3 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Targeting {
Boolean preferDeals
Boolean alwaysIncludeDeals
Boolean includeFormat
String prefix

static Targeting createWithAllValuesSetTo(Boolean value) {
new Targeting().tap {
Expand Down
Loading
Loading