Skip to content
Draft
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
8 changes: 8 additions & 0 deletions extra/modules/optable-targeting/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@

<name>optable-targeting</name>
<description>Optable targeting module</description>

<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package org.prebid.server.hooks.modules.optable.targeting.config;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
import org.prebid.server.bidder.BidderCatalog;
import org.prebid.server.cache.PbcStorageService;
import org.prebid.server.hooks.execution.model.ExecutionPlan;
import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties;
import org.prebid.server.hooks.modules.optable.targeting.v1.OptableBidderRequestHook;
import org.prebid.server.hooks.modules.optable.targeting.v1.OptableRawAuctionRequestHook;
import org.prebid.server.hooks.modules.optable.targeting.v1.OptableTargetingAuctionResponseHook;
import org.prebid.server.hooks.modules.optable.targeting.v1.OptableTargetingModule;
import org.prebid.server.hooks.modules.optable.targeting.v1.OptableTargetingProcessedAuctionRequestHook;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.AliasesResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidderEnrichmentSampler;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.Cache;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.CompositeHookExecutionPlan;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.ConfigResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.IdsMapper;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.NetworkCall;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.OptableTargeting;
import org.prebid.server.hooks.modules.optable.targeting.v1.net.APIClientImpl;
import org.prebid.server.hooks.modules.optable.targeting.v1.net.CachedAPIClient;
Expand Down Expand Up @@ -46,12 +55,12 @@ APIClientImpl apiClient(HttpClient httpClient,
@Value("${logging.sampling-rate:0.01}")
double logSamplingRate,
OptableTargetingProperties optableTargetingProperties,
JacksonMapper jacksonMapperr) {
JacksonMapper jacksonMapper) {

return new APIClientImpl(
optableTargetingProperties.getApiEndpoint(),
httpClient,
jacksonMapperr,
jacksonMapper,
logSamplingRate);
}

Expand Down Expand Up @@ -86,19 +95,38 @@ ConfigResolver configResolver(JsonMerger jsonMerger, OptableTargetingProperties
return new ConfigResolver(ObjectMapperProvider.mapper(), jsonMerger, globalProperties);
}

@Bean
NetworkCall networkCall(OptableTargeting optableTargeting, UserFpdActivityMask userFpdActivityMask) {
return new NetworkCall(optableTargeting, userFpdActivityMask);
}

@Bean
OptableTargetingModule optableTargetingModule(ConfigResolver configResolver,
OptableTargeting optableTargeting,
UserFpdActivityMask userFpdActivityMask,
NetworkCall networkCall,
JsonMerger jsonMerger,
BidderCatalog bidderCatalog,
JacksonMapper mapper,
@Value("${hooks.host-execution-plan:}")
String executionPlan,
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {

final CompositeHookExecutionPlan hooksExecutionPlan = CompositeHookExecutionPlan.of(
StringUtils.isNoneEmpty(executionPlan)
? mapper.decodeValue(executionPlan, ExecutionPlan.class)
: null);

return new OptableTargetingModule(List.of(
new OptableRawAuctionRequestHook(
configResolver,
networkCall,
BidderEnrichmentSampler.of(AliasesResolver.of(bidderCatalog)),
logSamplingRate),
new OptableTargetingProcessedAuctionRequestHook(
configResolver,
optableTargeting,
userFpdActivityMask,
networkCall,
hooksExecutionPlan,
logSamplingRate),
new OptableBidderRequestHook(),
new OptableTargetingAuctionResponseHook(
configResolver,
ObjectMapperProvider.mapper(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package org.prebid.server.hooks.modules.optable.targeting.model;

import io.vertx.core.Future;
import lombok.Data;
import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties;
import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.Audience;
import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.TargetingResult;
import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;

import java.util.List;
import java.util.Set;

@Data
public class ModuleContext {
Expand All @@ -19,8 +23,29 @@ public class ModuleContext {

private long optableTargetingExecutionTime;

private boolean isEarlyNetworkCallEnabled = false;

private Future<TargetingResult> optableTargetingCall;

private long callTargetingAPITimestamp;

private Set<String> biddersToEnrich;

private OptableTargetingProperties optableTargetingProperties;

private boolean shouldSkipEnrichment;

public static ModuleContext of(AuctionInvocationContext invocationContext) {
final ModuleContext moduleContext = (ModuleContext) invocationContext.moduleContext();
return moduleContext != null ? moduleContext : new ModuleContext();
}

public void failWithExecutionTime(long executionTime) {
setOptableTargetingExecutionTime(executionTime);
setEnrichRequestStatus(EnrichmentStatus.failure());
}

public boolean hasOptableTargetingProperties() {
return optableTargetingProperties != null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections.MapUtils;

import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -42,4 +43,17 @@ public final class OptableTargetingProperties {
Set<String> optableInserterEidsIgnore = Set.of();

CacheProperties cache = new CacheProperties();

Integer enrichmentPercentage = 100;

@JsonProperty("bidder-enrichment-percentages")
Map<String, Integer> bidderEnrichmentPercentages = Map.of();

Boolean enrichWeb = true;

Boolean enrichApp = true;

public boolean isPerBidderEnrichmentEnabled() {
return enrichmentPercentage != 100 || MapUtils.isNotEmpty(bidderEnrichmentPercentages);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.prebid.server.hooks.modules.optable.targeting.v1;

import io.vertx.core.Future;
import org.apache.commons.collections4.CollectionUtils;
import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
import org.prebid.server.hooks.modules.optable.targeting.model.EnrichmentStatus;
import org.prebid.server.hooks.modules.optable.targeting.model.ModuleContext;
import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties;
import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.TargetingResult;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidderRequestEnricher;
import org.prebid.server.hooks.v1.InvocationAction;
import org.prebid.server.hooks.v1.InvocationResult;
import org.prebid.server.hooks.v1.InvocationStatus;
import org.prebid.server.hooks.v1.PayloadUpdate;
import org.prebid.server.hooks.v1.bidder.BidderInvocationContext;
import org.prebid.server.hooks.v1.bidder.BidderRequestHook;
import org.prebid.server.hooks.v1.bidder.BidderRequestPayload;

import java.util.Set;

public class OptableBidderRequestHook implements BidderRequestHook {

public static final String CODE = "optable-targeting-bidder-request-hook";

@Override
public Future<InvocationResult<BidderRequestPayload>> call(BidderRequestPayload bidderRequestPayload,
BidderInvocationContext invocationContext) {

final ModuleContext moduleContext = ModuleContext.of(invocationContext);
final OptableTargetingProperties properties = moduleContext.getOptableTargetingProperties();
if (!properties.isPerBidderEnrichmentEnabled()) {
return noAction(moduleContext);
}

final Set<String> biddersToEnrich = moduleContext.getBiddersToEnrich();
if (CollectionUtils.isEmpty(biddersToEnrich)) {
return noAction(moduleContext);
}

return moduleContext.getOptableTargetingCall()
.compose(targetingResult ->
enrichedPayload(
targetingResult, moduleContext, moduleContext.getOptableTargetingProperties()))
.recover(throwable -> noAction(moduleContext));
}

private Future<InvocationResult<BidderRequestPayload>> enrichedPayload(TargetingResult targetingResult,
ModuleContext moduleContext,
OptableTargetingProperties properties) {

moduleContext.setTargeting(targetingResult.getAudience());
moduleContext.setEnrichRequestStatus(EnrichmentStatus.success());

return update(BidderRequestEnricher.of(targetingResult, properties), moduleContext);
}

private Future<InvocationResult<BidderRequestPayload>> noAction(ModuleContext moduleContext) {
return Future.succeededFuture(
InvocationResultImpl.<BidderRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.no_action)
.moduleContext(moduleContext)
.build());
}

private static Future<InvocationResult<BidderRequestPayload>> update(
PayloadUpdate<BidderRequestPayload> payloadUpdate,
ModuleContext moduleContext) {

return Future.succeededFuture(
InvocationResultImpl.<BidderRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.update)
.payloadUpdate(payloadUpdate)
.moduleContext(moduleContext)
.build());
}

@Override
public String code() {
return CODE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.prebid.server.hooks.modules.optable.targeting.v1;

import com.iab.openrtb.request.BidRequest;
import io.vertx.core.Future;
import org.apache.commons.collections4.CollectionUtils;
import org.prebid.server.hooks.execution.v1.InvocationResultImpl;
import org.prebid.server.hooks.modules.optable.targeting.model.ModuleContext;
import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties;
import org.prebid.server.hooks.modules.optable.targeting.model.openrtb.TargetingResult;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidRequestCleaner;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidderEnrichmentSampler;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.ConfigResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.NetworkCall;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.PropertiesValidator;
import org.prebid.server.hooks.v1.InvocationAction;
import org.prebid.server.hooks.v1.InvocationResult;
import org.prebid.server.hooks.v1.InvocationStatus;
import org.prebid.server.hooks.v1.PayloadUpdate;
import org.prebid.server.hooks.v1.auction.AuctionInvocationContext;
import org.prebid.server.hooks.v1.auction.AuctionRequestPayload;
import org.prebid.server.hooks.v1.auction.RawAuctionRequestHook;
import org.prebid.server.log.ConditionalLogger;
import org.prebid.server.log.LoggerFactory;

import java.util.Objects;
import java.util.Set;

public class OptableRawAuctionRequestHook implements RawAuctionRequestHook {

private static final ConditionalLogger conditionalLogger = new ConditionalLogger(
LoggerFactory.getLogger(OptableRawAuctionRequestHook.class));

private static final String CODE = "optable-targeting-raw-auction-request-hook";

private final ConfigResolver configResolver;
private final NetworkCall networkCall;
private final BidderEnrichmentSampler bidderEnrichmentSampler;
private final double logSamplingRate;

public OptableRawAuctionRequestHook(ConfigResolver configResolver,
NetworkCall networkCall,
BidderEnrichmentSampler bidderEnrichmentSampler,
double logSamplingRate) {

this.configResolver = Objects.requireNonNull(configResolver);
this.networkCall = Objects.requireNonNull(networkCall);
this.bidderEnrichmentSampler = Objects.requireNonNull(bidderEnrichmentSampler);
this.logSamplingRate = logSamplingRate;
}

@Override
public Future<InvocationResult<AuctionRequestPayload>> call(AuctionRequestPayload payload,
AuctionInvocationContext invocationContext) {

final OptableTargetingProperties properties = configResolver.resolve(invocationContext.accountConfig());
final ModuleContext moduleContext = new ModuleContext();
moduleContext.setEarlyNetworkCallEnabled(true);
moduleContext.setCallTargetingAPITimestamp(System.currentTimeMillis());
moduleContext.setOptableTargetingProperties(properties);

if (!PropertiesValidator.isValid(properties)) {
conditionalLogger.error(
"Account not properly configured: tenant and/or origin is missing.", logSamplingRate);

moduleContext.failWithExecutionTime(
System.currentTimeMillis() - moduleContext.getCallTargetingAPITimestamp());

return update(BidRequestCleaner.instance(), moduleContext);
}

final BidRequest bidRequest = invocationContext.auctionContext().getBidRequest();
if (!PropertiesValidator.isTrafficSourceValid(bidRequest, properties)) {
moduleContext.setShouldSkipEnrichment(true);
return update(BidRequestCleaner.instance(), moduleContext);
}

final Set<String> biddersToEnrich = bidderEnrichmentSampler.sample(bidRequest, properties);
if (CollectionUtils.isEmpty(biddersToEnrich)) {
return update(BidRequestCleaner.instance(), moduleContext);
}

moduleContext.setBiddersToEnrich(biddersToEnrich);
final Future<TargetingResult> optableTargetingCall = networkCall.makeRequest(
payload,
invocationContext,
properties);

moduleContext.setOptableTargetingCall(optableTargetingCall);

return updateModuleContext(moduleContext);
}

private static Future<InvocationResult<AuctionRequestPayload>> updateModuleContext(ModuleContext moduleContext) {

return Future.succeededFuture(
InvocationResultImpl.<AuctionRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.no_action)
.moduleContext(moduleContext)
.build());
}

public static <AuctionRequestPayload> Future<InvocationResult<AuctionRequestPayload>> update(
PayloadUpdate<AuctionRequestPayload> payloadUpdate,
ModuleContext moduleContext) {

return Future.succeededFuture(
InvocationResultImpl.<AuctionRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.update)
.payloadUpdate(payloadUpdate)
.moduleContext(moduleContext)
.build());
}

@Override
public String code() {
return CODE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public Future<InvocationResult<AuctionResponsePayload>> call(AuctionResponsePayl
final ModuleContext moduleContext = ModuleContext.of(invocationContext);
moduleContext.setAdserverTargetingEnabled(adserverTargeting);

if (!adserverTargeting) {
if (moduleContext.isShouldSkipEnrichment() || !adserverTargeting) {
return success(moduleContext);
}

Expand Down
Loading