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
Expand Up @@ -4,12 +4,14 @@
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
import org.prebid.server.cache.PbcStorageService;
import org.prebid.server.hooks.modules.optable.targeting.model.config.OptableTargetingProperties;
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.Cache;
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 @@ -86,18 +88,25 @@ 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,
@Value("${logging.sampling-rate:0.01}") double logSamplingRate) {

return new OptableTargetingModule(List.of(
new OptableRawAuctionRequestHook(
configResolver,
networkCall,
logSamplingRate),
new OptableTargetingProcessedAuctionRequestHook(
configResolver,
optableTargeting,
userFpdActivityMask,
networkCall,
logSamplingRate),
new OptableTargetingAuctionResponseHook(
configResolver,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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.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;
Expand All @@ -19,8 +21,19 @@ public class ModuleContext {

private long optableTargetingExecutionTime;

private boolean isEarlyNetworkCallEnabled = false;

private Future<TargetingResult> optableTargetingCall;

private long callTargetingAPITimestamp;

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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.prebid.server.hooks.modules.optable.targeting.v1;

import io.vertx.core.Future;
import org.apache.commons.lang3.StringUtils;
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.v1.core.AnalyticTagsResolver;
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.AuctionRequestPayload;

public class OptableHook {

private OptableHook() {
}

public static boolean isTargetingPropertiesValid(OptableTargetingProperties properties) {
return !StringUtils.isEmpty(properties.getOrigin()) && !StringUtils.isEmpty(properties.getTenant());
}

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

return Future.succeededFuture(
InvocationResultImpl.<AuctionRequestPayload>builder()
.status(InvocationStatus.success)
.action(InvocationAction.update)
.analyticsTags(AnalyticTagsResolver.toEnrichRequestAnalyticTags(moduleContext))
.payloadUpdate(payloadUpdate)
.moduleContext(moduleContext)
.build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.prebid.server.hooks.modules.optable.targeting.v1;

import io.vertx.core.Future;
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.AnalyticTagsResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidRequestCleaner;
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.v1.InvocationAction;
import org.prebid.server.hooks.v1.InvocationResult;
import org.prebid.server.hooks.v1.InvocationStatus;
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;

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 double logSamplingRate;

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

this.configResolver = Objects.requireNonNull(configResolver);
this.networkCall = Objects.requireNonNull(networkCall);
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());

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

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

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

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)
.analyticsTags(AnalyticTagsResolver.toEnrichRequestAnalyticTags(moduleContext))
.moduleContext(moduleContext)
.build());
}

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

import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.User;
import io.vertx.core.Future;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.activity.Activity;
import org.prebid.server.activity.ComponentType;
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload;
import org.prebid.server.activity.infrastructure.payload.impl.ActivityInvocationPayloadImpl;
import org.prebid.server.activity.infrastructure.payload.impl.BidRequestActivityInvocationPayload;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
import org.prebid.server.execution.timeout.Timeout;
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.OptableAttributes;
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.AnalyticTagsResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidRequestCleaner;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.BidRequestEnricher;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.ConfigResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.OptableAttributesResolver;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.OptableTargeting;
import org.prebid.server.hooks.modules.optable.targeting.v1.core.NetworkCall;
import org.prebid.server.hooks.v1.InvocationAction;
import org.prebid.server.hooks.v1.InvocationResult;
import org.prebid.server.hooks.v1.InvocationStatus;
Expand All @@ -41,23 +26,22 @@
public class OptableTargetingProcessedAuctionRequestHook implements ProcessedAuctionRequestHook {

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

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

private static final String AUCTION_NOT_PROPERLY_CONFIGURED =
"Account not properly configured: tenant and/or origin is missing.";

private final ConfigResolver configResolver;
private final OptableTargeting optableTargeting;
private final UserFpdActivityMask userFpdActivityMask;
private final NetworkCall networkCall;
private final double logSamplingRate;

public OptableTargetingProcessedAuctionRequestHook(ConfigResolver configResolver,
OptableTargeting optableTargeting,
UserFpdActivityMask userFpdActivityMask,
NetworkCall networkCall,
double logSamplingRate) {

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

Expand All @@ -66,80 +50,51 @@ public Future<InvocationResult<AuctionRequestPayload>> call(AuctionRequestPayloa
AuctionInvocationContext invocationContext) {

final OptableTargetingProperties properties = configResolver.resolve(invocationContext.accountConfig());
final ModuleContext moduleContext = new ModuleContext();
final long callTargetingAPITimestamp = System.currentTimeMillis();
final ModuleContext moduleContext = ModuleContext.of(invocationContext);

if (!isTargetingPropertiesValid(properties)) {
conditionalLogger.error(
"Account not properly configured: tenant and/or origin is missing.", logSamplingRate);
final Future<TargetingResult> optableTargetingCall = moduleContext.isEarlyNetworkCallEnabled()
? moduleContext.getOptableTargetingCall()
: makeOptableTargetingCall(auctionRequestPayload, invocationContext, moduleContext, properties);

moduleContext.setOptableTargetingExecutionTime(System.currentTimeMillis() - callTargetingAPITimestamp);
moduleContext.setEnrichRequestStatus(EnrichmentStatus.failure());
if (optableTargetingCall == null) {
moduleContext.failWithExecutionTime(
System.currentTimeMillis() - moduleContext.getCallTargetingAPITimestamp());
return update(BidRequestCleaner.instance(), moduleContext);
}

final BidRequest bidRequest = applyActivityRestrictions(auctionRequestPayload.bidRequest(), invocationContext);

final Timeout timeout = getHookTimeout(invocationContext);
final OptableAttributes attributes = OptableAttributesResolver.resolveAttributes(
invocationContext.auctionContext(),
properties.getTimeout());

return optableTargeting.getTargeting(properties, bidRequest, attributes, timeout)
final Future<InvocationResult<AuctionRequestPayload>> future = optableTargetingCall
.compose(targetingResult -> {
moduleContext.setOptableTargetingExecutionTime(
System.currentTimeMillis() - callTargetingAPITimestamp);
System.currentTimeMillis() - moduleContext.getCallTargetingAPITimestamp());
return enrichedPayload(targetingResult, moduleContext, properties);
})
.recover(throwable -> {
moduleContext.setOptableTargetingExecutionTime(
System.currentTimeMillis() - callTargetingAPITimestamp);
moduleContext.setEnrichRequestStatus(EnrichmentStatus.failure());
moduleContext.failWithExecutionTime(
System.currentTimeMillis() - moduleContext.getCallTargetingAPITimestamp());
return update(BidRequestCleaner.instance(), moduleContext);
});
}

private boolean isTargetingPropertiesValid(OptableTargetingProperties properties) {
return !StringUtils.isEmpty(properties.getOrigin()) && !StringUtils.isEmpty(properties.getTenant());
return future;
}

private BidRequest applyActivityRestrictions(BidRequest bidRequest,
AuctionInvocationContext auctionInvocationContext) {

final AuctionContext auctionContext = auctionInvocationContext.auctionContext();
final ActivityInvocationPayload activityInvocationPayload = BidRequestActivityInvocationPayload.of(
ActivityInvocationPayloadImpl.of(ComponentType.GENERAL_MODULE, OptableTargetingModule.CODE),
bidRequest);
final ActivityInfrastructure activityInfrastructure = auctionContext.getActivityInfrastructure();

final boolean disallowTransmitUfpd = !activityInfrastructure.isAllowed(
Activity.TRANSMIT_UFPD, activityInvocationPayload);
final boolean disallowTransmitEids = !activityInfrastructure.isAllowed(
Activity.TRANSMIT_EIDS, activityInvocationPayload);
final boolean disallowTransmitGeo = !activityInfrastructure.isAllowed(
Activity.TRANSMIT_GEO, activityInvocationPayload);

return maskUserPersonalInfo(bidRequest, disallowTransmitUfpd, disallowTransmitEids, disallowTransmitGeo);
}

private BidRequest maskUserPersonalInfo(BidRequest bidRequest,
boolean disallowTransmitUfpd,
boolean disallowTransmitEids,
boolean disallowTransmitGeo) {

final User maskedUser = userFpdActivityMask.maskUser(
bidRequest.getUser(), disallowTransmitUfpd, disallowTransmitEids);
final Device maskedDevice = userFpdActivityMask.maskDevice(
bidRequest.getDevice(), disallowTransmitUfpd, disallowTransmitGeo);

return bidRequest.toBuilder()
.user(maskedUser)
.device(maskedDevice)
.build();
}
private Future<TargetingResult> makeOptableTargetingCall(
AuctionRequestPayload payload,
AuctionInvocationContext invocationContext,
ModuleContext moduleContext,
OptableTargetingProperties properties) {
moduleContext.setCallTargetingAPITimestamp(System.currentTimeMillis());
if (!OptableHook.isTargetingPropertiesValid(properties)) {
conditionalLogger.error(AUCTION_NOT_PROPERLY_CONFIGURED, logSamplingRate);

moduleContext.failWithExecutionTime(
System.currentTimeMillis() - moduleContext.getCallTargetingAPITimestamp());
return Future.failedFuture(AUCTION_NOT_PROPERLY_CONFIGURED);
}

private Timeout getHookTimeout(AuctionInvocationContext invocationContext) {
return invocationContext.timeout();
return networkCall.makeRequest(
payload,
invocationContext,
properties);
}

private Future<InvocationResult<AuctionRequestPayload>> enrichedPayload(TargetingResult targetingResult,
Expand Down
Loading