-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactoring: Extract bidAdjustments code from exchangeService (#3476)
- Loading branch information
1 parent
ed717af
commit f552612
Showing
8 changed files
with
1,468 additions
and
1,141 deletions.
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
src/main/java/org/prebid/server/auction/AnalyticsTagsEnricher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package org.prebid.server.auction; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.iab.openrtb.request.BidRequest; | ||
import com.iab.openrtb.response.BidResponse; | ||
import org.apache.commons.lang3.ObjectUtils; | ||
import org.prebid.server.auction.model.AuctionContext; | ||
import org.prebid.server.bidder.model.BidderError; | ||
import org.prebid.server.proto.openrtb.ext.request.ExtRequest; | ||
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; | ||
import org.prebid.server.proto.openrtb.ext.response.ExtAnalytics; | ||
import org.prebid.server.proto.openrtb.ext.response.ExtAnalyticsTags; | ||
import org.prebid.server.proto.openrtb.ext.response.ExtBidResponse; | ||
import org.prebid.server.proto.openrtb.ext.response.ExtBidResponsePrebid; | ||
import org.prebid.server.proto.openrtb.ext.response.ExtBidderError; | ||
import org.prebid.server.settings.model.Account; | ||
import org.prebid.server.settings.model.AccountAnalyticsConfig; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
public class AnalyticsTagsEnricher { | ||
|
||
private AnalyticsTagsEnricher() { | ||
} | ||
|
||
public static AuctionContext enrichWithAnalyticsTags(AuctionContext context) { | ||
final boolean clientDetailsEnabled = isClientDetailsEnabled(context); | ||
if (!clientDetailsEnabled) { | ||
return context; | ||
} | ||
|
||
final boolean allowClientDetails = Optional.ofNullable(context.getAccount()) | ||
.map(Account::getAnalytics) | ||
.map(AccountAnalyticsConfig::isAllowClientDetails) | ||
.orElse(false); | ||
|
||
if (!allowClientDetails) { | ||
return addClientDetailsWarning(context); | ||
} | ||
|
||
final List<ExtAnalyticsTags> extAnalyticsTags = HookDebugInfoEnricher.toExtAnalyticsTags(context); | ||
|
||
if (extAnalyticsTags == null) { | ||
return context; | ||
} | ||
|
||
final BidResponse bidResponse = context.getBidResponse(); | ||
final Optional<ExtBidResponse> ext = Optional.ofNullable(bidResponse.getExt()); | ||
final Optional<ExtBidResponsePrebid> extPrebid = ext.map(ExtBidResponse::getPrebid); | ||
|
||
final ExtBidResponsePrebid updatedExtPrebid = extPrebid | ||
.map(ExtBidResponsePrebid::toBuilder) | ||
.orElse(ExtBidResponsePrebid.builder()) | ||
.analytics(ExtAnalytics.of(extAnalyticsTags)) | ||
.build(); | ||
|
||
final ExtBidResponse updatedExt = ext | ||
.map(ExtBidResponse::toBuilder) | ||
.orElse(ExtBidResponse.builder()) | ||
.prebid(updatedExtPrebid) | ||
.build(); | ||
|
||
final BidResponse updatedBidResponse = bidResponse.toBuilder().ext(updatedExt).build(); | ||
return context.with(updatedBidResponse); | ||
} | ||
|
||
private static boolean isClientDetailsEnabled(AuctionContext context) { | ||
final JsonNode analytics = Optional.ofNullable(context.getBidRequest()) | ||
.map(BidRequest::getExt) | ||
.map(ExtRequest::getPrebid) | ||
.map(ExtRequestPrebid::getAnalytics) | ||
.orElse(null); | ||
|
||
if (notObjectNode(analytics)) { | ||
return false; | ||
} | ||
|
||
final JsonNode options = analytics.get("options"); | ||
if (notObjectNode(options)) { | ||
return false; | ||
} | ||
|
||
final JsonNode enableClientDetails = options.get("enableclientdetails"); | ||
return enableClientDetails != null | ||
&& enableClientDetails.isBoolean() | ||
&& enableClientDetails.asBoolean(); | ||
} | ||
|
||
private static boolean notObjectNode(JsonNode jsonNode) { | ||
return jsonNode == null || !jsonNode.isObject(); | ||
} | ||
|
||
private static AuctionContext addClientDetailsWarning(AuctionContext context) { | ||
final BidResponse bidResponse = context.getBidResponse(); | ||
final Optional<ExtBidResponse> ext = Optional.ofNullable(bidResponse.getExt()); | ||
|
||
final Map<String, List<ExtBidderError>> warnings = ext | ||
.map(ExtBidResponse::getWarnings) | ||
.orElse(Collections.emptyMap()); | ||
final List<ExtBidderError> prebidWarnings = ObjectUtils.defaultIfNull( | ||
warnings.get(BidResponseCreator.DEFAULT_DEBUG_KEY), | ||
Collections.emptyList()); | ||
|
||
final List<ExtBidderError> updatedPrebidWarnings = new ArrayList<>(prebidWarnings); | ||
updatedPrebidWarnings.add(ExtBidderError.of( | ||
BidderError.Type.generic.getCode(), | ||
"analytics.options.enableclientdetails not enabled for account")); | ||
final Map<String, List<ExtBidderError>> updatedWarnings = new HashMap<>(warnings); | ||
updatedWarnings.put(BidResponseCreator.DEFAULT_DEBUG_KEY, updatedPrebidWarnings); | ||
|
||
final ExtBidResponse updatedExt = ext | ||
.map(ExtBidResponse::toBuilder) | ||
.orElse(ExtBidResponse.builder()) | ||
.warnings(updatedWarnings) | ||
.build(); | ||
|
||
final BidResponse updatedBidResponse = bidResponse.toBuilder().ext(updatedExt).build(); | ||
return context.with(updatedBidResponse); | ||
} | ||
} |
241 changes: 241 additions & 0 deletions
241
src/main/java/org/prebid/server/auction/BidsAdjuster.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
package org.prebid.server.auction; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.node.DecimalNode; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import com.fasterxml.jackson.databind.node.TextNode; | ||
import com.iab.openrtb.request.BidRequest; | ||
import com.iab.openrtb.response.Bid; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.prebid.server.auction.adjustment.BidAdjustmentFactorResolver; | ||
import org.prebid.server.auction.model.AuctionContext; | ||
import org.prebid.server.auction.model.AuctionParticipation; | ||
import org.prebid.server.auction.model.BidderResponse; | ||
import org.prebid.server.bidder.model.BidderBid; | ||
import org.prebid.server.bidder.model.BidderError; | ||
import org.prebid.server.bidder.model.BidderSeatBid; | ||
import org.prebid.server.currency.CurrencyConversionService; | ||
import org.prebid.server.exception.PreBidException; | ||
import org.prebid.server.floors.PriceFloorEnforcer; | ||
import org.prebid.server.json.JacksonMapper; | ||
import org.prebid.server.proto.openrtb.ext.request.ExtRequestBidAdjustmentFactors; | ||
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; | ||
import org.prebid.server.proto.openrtb.ext.request.ImpMediaType; | ||
import org.prebid.server.util.ObjectUtil; | ||
import org.prebid.server.util.PbsUtil; | ||
import org.prebid.server.validation.ResponseBidValidator; | ||
import org.prebid.server.validation.model.ValidationResult; | ||
|
||
import java.math.BigDecimal; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
public class BidsAdjuster { | ||
|
||
private static final String ORIGINAL_BID_CPM = "origbidcpm"; | ||
private static final String ORIGINAL_BID_CURRENCY = "origbidcur"; | ||
|
||
private final ResponseBidValidator responseBidValidator; | ||
private final CurrencyConversionService currencyService; | ||
private final BidAdjustmentFactorResolver bidAdjustmentFactorResolver; | ||
private final PriceFloorEnforcer priceFloorEnforcer; | ||
private final DsaEnforcer dsaEnforcer; | ||
private final JacksonMapper mapper; | ||
|
||
public BidsAdjuster(ResponseBidValidator responseBidValidator, | ||
CurrencyConversionService currencyService, | ||
BidAdjustmentFactorResolver bidAdjustmentFactorResolver, | ||
PriceFloorEnforcer priceFloorEnforcer, | ||
DsaEnforcer dsaEnforcer, | ||
JacksonMapper mapper) { | ||
|
||
this.responseBidValidator = Objects.requireNonNull(responseBidValidator); | ||
this.currencyService = Objects.requireNonNull(currencyService); | ||
this.bidAdjustmentFactorResolver = Objects.requireNonNull(bidAdjustmentFactorResolver); | ||
this.priceFloorEnforcer = Objects.requireNonNull(priceFloorEnforcer); | ||
this.dsaEnforcer = Objects.requireNonNull(dsaEnforcer); | ||
this.mapper = Objects.requireNonNull(mapper); | ||
} | ||
|
||
public List<AuctionParticipation> validateAndAdjustBids(List<AuctionParticipation> auctionParticipations, | ||
AuctionContext auctionContext, | ||
BidderAliases aliases) { | ||
|
||
return auctionParticipations.stream() | ||
.map(auctionParticipation -> validBidderResponse(auctionParticipation, auctionContext, aliases)) | ||
.map(auctionParticipation -> applyBidPriceChanges(auctionParticipation, auctionContext.getBidRequest())) | ||
.map(auctionParticipation -> priceFloorEnforcer.enforce( | ||
auctionContext.getBidRequest(), | ||
auctionParticipation, | ||
auctionContext.getAccount(), | ||
auctionContext.getBidRejectionTrackers().get(auctionParticipation.getBidder()))) | ||
.map(auctionParticipation -> dsaEnforcer.enforce( | ||
auctionContext.getBidRequest(), | ||
auctionParticipation, | ||
auctionContext.getBidRejectionTrackers().get(auctionParticipation.getBidder()))) | ||
.toList(); | ||
} | ||
|
||
private AuctionParticipation validBidderResponse(AuctionParticipation auctionParticipation, | ||
AuctionContext auctionContext, | ||
BidderAliases aliases) { | ||
|
||
if (auctionParticipation.isRequestBlocked()) { | ||
return auctionParticipation; | ||
} | ||
|
||
final BidRequest bidRequest = auctionContext.getBidRequest(); | ||
final BidderResponse bidderResponse = auctionParticipation.getBidderResponse(); | ||
final BidderSeatBid seatBid = bidderResponse.getSeatBid(); | ||
final List<BidderError> errors = new ArrayList<>(seatBid.getErrors()); | ||
final List<BidderError> warnings = new ArrayList<>(seatBid.getWarnings()); | ||
|
||
final List<String> requestCurrencies = bidRequest.getCur(); | ||
if (requestCurrencies.size() > 1) { | ||
errors.add(BidderError.badInput("Cur parameter contains more than one currency. %s will be used" | ||
.formatted(requestCurrencies.getFirst()))); | ||
} | ||
|
||
final List<BidderBid> bids = seatBid.getBids(); | ||
final List<BidderBid> validBids = new ArrayList<>(bids.size()); | ||
|
||
for (final BidderBid bid : bids) { | ||
final ValidationResult validationResult = responseBidValidator.validate( | ||
bid, | ||
bidderResponse.getBidder(), | ||
auctionContext, | ||
aliases); | ||
|
||
if (validationResult.hasWarnings() || validationResult.hasErrors()) { | ||
errors.add(makeValidationBidderError(bid.getBid(), validationResult)); | ||
} | ||
|
||
if (!validationResult.hasErrors()) { | ||
validBids.add(bid); | ||
} | ||
} | ||
|
||
final BidderResponse resultBidderResponse = errors.size() == seatBid.getErrors().size() | ||
? bidderResponse | ||
: bidderResponse.with( | ||
seatBid.toBuilder() | ||
.bids(validBids) | ||
.errors(errors) | ||
.warnings(warnings) | ||
.build()); | ||
return auctionParticipation.with(resultBidderResponse); | ||
} | ||
|
||
private BidderError makeValidationBidderError(Bid bid, ValidationResult validationResult) { | ||
final String validationErrors = Stream.concat( | ||
validationResult.getErrors().stream().map(message -> "Error: " + message), | ||
validationResult.getWarnings().stream().map(message -> "Warning: " + message)) | ||
.collect(Collectors.joining(". ")); | ||
|
||
final String bidId = ObjectUtil.getIfNotNullOrDefault(bid, Bid::getId, () -> "unknown"); | ||
return BidderError.invalidBid("BidId `" + bidId + "` validation messages: " + validationErrors); | ||
} | ||
|
||
private AuctionParticipation applyBidPriceChanges(AuctionParticipation auctionParticipation, | ||
BidRequest bidRequest) { | ||
if (auctionParticipation.isRequestBlocked()) { | ||
return auctionParticipation; | ||
} | ||
|
||
final BidderResponse bidderResponse = auctionParticipation.getBidderResponse(); | ||
final BidderSeatBid seatBid = bidderResponse.getSeatBid(); | ||
|
||
final List<BidderBid> bidderBids = seatBid.getBids(); | ||
if (bidderBids.isEmpty()) { | ||
return auctionParticipation; | ||
} | ||
|
||
final List<BidderBid> updatedBidderBids = new ArrayList<>(bidderBids.size()); | ||
final List<BidderError> errors = new ArrayList<>(seatBid.getErrors()); | ||
final String adServerCurrency = bidRequest.getCur().getFirst(); | ||
|
||
for (final BidderBid bidderBid : bidderBids) { | ||
try { | ||
final BidderBid updatedBidderBid = | ||
updateBidderBidWithBidPriceChanges(bidderBid, bidderResponse, bidRequest, adServerCurrency); | ||
updatedBidderBids.add(updatedBidderBid); | ||
} catch (PreBidException e) { | ||
errors.add(BidderError.generic(e.getMessage())); | ||
} | ||
} | ||
|
||
final BidderResponse resultBidderResponse = bidderResponse.with(seatBid.toBuilder() | ||
.bids(updatedBidderBids) | ||
.errors(errors) | ||
.build()); | ||
return auctionParticipation.with(resultBidderResponse); | ||
} | ||
|
||
private BidderBid updateBidderBidWithBidPriceChanges(BidderBid bidderBid, | ||
BidderResponse bidderResponse, | ||
BidRequest bidRequest, | ||
String adServerCurrency) { | ||
final Bid bid = bidderBid.getBid(); | ||
final String bidCurrency = bidderBid.getBidCurrency(); | ||
final BigDecimal price = bid.getPrice(); | ||
|
||
final BigDecimal priceInAdServerCurrency = currencyService.convertCurrency( | ||
price, bidRequest, StringUtils.stripToNull(bidCurrency), adServerCurrency); | ||
|
||
final BigDecimal priceAdjustmentFactor = | ||
bidAdjustmentForBidder(bidderResponse.getBidder(), bidRequest, bidderBid); | ||
final BigDecimal adjustedPrice = adjustPrice(priceAdjustmentFactor, priceInAdServerCurrency); | ||
|
||
final ObjectNode bidExt = bid.getExt(); | ||
final ObjectNode updatedBidExt = bidExt != null ? bidExt : mapper.mapper().createObjectNode(); | ||
|
||
updateExtWithOrigPriceValues(updatedBidExt, price, bidCurrency); | ||
|
||
final Bid.BidBuilder bidBuilder = bid.toBuilder(); | ||
if (adjustedPrice.compareTo(price) != 0) { | ||
bidBuilder.price(adjustedPrice); | ||
} | ||
|
||
if (!updatedBidExt.isEmpty()) { | ||
bidBuilder.ext(updatedBidExt); | ||
} | ||
|
||
return bidderBid.toBuilder().bid(bidBuilder.build()).build(); | ||
} | ||
|
||
private BigDecimal bidAdjustmentForBidder(String bidder, BidRequest bidRequest, BidderBid bidderBid) { | ||
final ExtRequestBidAdjustmentFactors adjustmentFactors = extBidAdjustmentFactors(bidRequest); | ||
if (adjustmentFactors == null) { | ||
return null; | ||
} | ||
final ImpMediaType mediaType = ImpMediaTypeResolver.resolve( | ||
bidderBid.getBid().getImpid(), bidRequest.getImp(), bidderBid.getType()); | ||
|
||
return bidAdjustmentFactorResolver.resolve(mediaType, adjustmentFactors, bidder); | ||
} | ||
|
||
private static ExtRequestBidAdjustmentFactors extBidAdjustmentFactors(BidRequest bidRequest) { | ||
final ExtRequestPrebid prebid = PbsUtil.extRequestPrebid(bidRequest); | ||
return prebid != null ? prebid.getBidadjustmentfactors() : null; | ||
} | ||
|
||
private static BigDecimal adjustPrice(BigDecimal priceAdjustmentFactor, BigDecimal price) { | ||
return priceAdjustmentFactor != null && priceAdjustmentFactor.compareTo(BigDecimal.ONE) != 0 | ||
? price.multiply(priceAdjustmentFactor) | ||
: price; | ||
} | ||
|
||
private static void updateExtWithOrigPriceValues(ObjectNode updatedBidExt, BigDecimal price, String bidCurrency) { | ||
addPropertyToNode(updatedBidExt, ORIGINAL_BID_CPM, new DecimalNode(price)); | ||
if (StringUtils.isNotBlank(bidCurrency)) { | ||
addPropertyToNode(updatedBidExt, ORIGINAL_BID_CURRENCY, new TextNode(bidCurrency)); | ||
} | ||
} | ||
|
||
private static void addPropertyToNode(ObjectNode node, String propertyName, JsonNode propertyValue) { | ||
node.set(propertyName, propertyValue); | ||
} | ||
} |
Oops, something went wrong.