From 6e773d2d82894d3b53cea72d340aee6dc1229763 Mon Sep 17 00:00:00 2001 From: DGarbar Date: Mon, 26 Apr 2021 14:46:20 +0300 Subject: [PATCH 1/4] Make bid immutable --- .../java/com/iab/openrtb/response/Bid.java | 60 +++++--- .../server/auction/BidResponseCreator.java | 140 ++++++++++++------ .../server/auction/ExchangeService.java | 7 +- .../prebid/server/auction/model/BidInfo.java | 3 + .../bidder/appnexus/AppnexusBidder.java | 9 +- .../bidder/beachfront/BeachfrontBidder.java | 32 ++-- .../bidder/facebook/FacebookBidder.java | 8 +- .../server/bidder/gamma/GammaBidder.java | 13 +- .../server/bidder/gamma/model/GammaBid.java | 24 +-- .../prebid/server/bidder/model/BidderBid.java | 4 + .../server/bidder/model/BidderSeatBid.java | 4 + .../server/bidder/rubicon/RubiconBidder.java | 26 ++-- .../server/bidder/sovrn/SovrnBidder.java | 5 +- .../server/auction/ExchangeServiceTest.java | 9 +- .../server/bidder/gamma/GammaBidderTest.java | 112 +++++++------- 15 files changed, 281 insertions(+), 175 deletions(-) diff --git a/src/main/java/com/iab/openrtb/response/Bid.java b/src/main/java/com/iab/openrtb/response/Bid.java index 3e7922afa72..54ea0bbe945 100644 --- a/src/main/java/com/iab/openrtb/response/Bid.java +++ b/src/main/java/com/iab/openrtb/response/Bid.java @@ -1,9 +1,8 @@ package com.iab.openrtb.response; import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; +import lombok.Builder; +import lombok.Value; import java.math.BigDecimal; import java.util.List; @@ -13,22 +12,19 @@ * relates to a specific impression in the bid request via the {@code impid} * attribute and constitutes an offer to buy that impression for a given * {@code price}. - *

- * IMPORTANT: unlike other data classes this one is mutable (annotated with {@link Data} instead of - * {@link lombok.Value}). Motivation: during the course of processing bids could be altered several times (price - * adjustment, post-processing). Creating new instance of the bid in each of these cases seems to cause unnecessary - * memory pressure. In order to avoid unnecessary allocations this class is made mutable (as an exception) i.e. this - * decision could be seen as a performance optimisation. */ -@SuperBuilder(toBuilder = true) -@NoArgsConstructor -@Data +@Builder(toBuilder = true) +@Value public class Bid { - /** Bidder generated bid ID to assist with logging/tracking. (required) */ + /** + * Bidder generated bid ID to assist with logging/tracking. (required) + */ String id; - /** ID of the Imp object in the related bid request. (required) */ + /** + * ID of the Imp object in the related bid request. (required) + */ String impid; /** @@ -72,7 +68,9 @@ public class Bid { */ String adm; - /** ID of a preloaded ad to be served if the bid wins. */ + /** + * ID of a preloaded ad to be served if the bid wins. + */ String adid; /** @@ -110,19 +108,29 @@ public class Bid { */ String crid; - /** IAB content categories of the creative. Refer to List 5.1. */ + /** + * IAB content categories of the creative. Refer to List 5.1. + */ List cat; - /** Set of attributes describing the creative. Refer to List 5.3. */ + /** + * Set of attributes describing the creative. Refer to List 5.3. + */ List attr; - /** API required by the markup if applicable. Refer to List 5.6. */ + /** + * API required by the markup if applicable. Refer to List 5.6. + */ Integer api; - /** Video response protocol of the markup if applicable. Refer to List 5.8. */ + /** + * Video response protocol of the markup if applicable. Refer to List 5.8. + */ Integer protocol; - /** Creative media rating per IQG guidelines. Refer to List 5.19. */ + /** + * Creative media rating per IQG guidelines. Refer to List 5.19. + */ Integer qagmediarating; /** @@ -138,10 +146,14 @@ public class Bid { */ String dealid; - /** Width of the creative in device independent pixels (DIPS). */ + /** + * Width of the creative in device independent pixels (DIPS). + */ Integer w; - /** Height of the creative in device independent pixels (DIPS). */ + /** + * Height of the creative in device independent pixels (DIPS). + */ Integer h; /** @@ -162,6 +174,8 @@ public class Bid { */ Integer exp; - /** Placeholder for bidder-specific extensions to OpenRTB. */ + /** + * Placeholder for bidder-specific extensions to OpenRTB. + */ ObjectNode ext; } diff --git a/src/main/java/org/prebid/server/auction/BidResponseCreator.java b/src/main/java/org/prebid/server/auction/BidResponseCreator.java index 49baeef1ade..4551cbf986c 100644 --- a/src/main/java/org/prebid/server/auction/BidResponseCreator.java +++ b/src/main/java/org/prebid/server/auction/BidResponseCreator.java @@ -167,9 +167,10 @@ Future create(List bidderResponses, .build(); final Map bidIdToGeneratedBidId = new HashMap<>(); - updateBidAdmInBidderResponses(bidderResponses, account, bidIdToGeneratedBidId, eventsContext); + final List modifiedBidderResponses = updateBidAdmInBidderResponses(bidderResponses, account, + bidIdToGeneratedBidId, eventsContext); - if (isEmptyBidderResponses(bidderResponses)) { + if (isEmptyBidderResponses(modifiedBidderResponses)) { final BidRequest bidRequest = auctionContext.getBidRequest(); return Future.succeededFuture(BidResponse.builder() .id(bidRequest.getId()) @@ -177,7 +178,7 @@ Future create(List bidderResponses, .nbr(0) // signal "Unknown Error" .seatbid(Collections.emptyList()) .ext(mapper.mapper().valueToTree(toExtBidResponse( - bidderResponses, + modifiedBidderResponses, auctionContext, CacheServiceResult.empty(), VideoStoredDataResult.empty(), @@ -188,7 +189,7 @@ Future create(List bidderResponses, } return cacheBidsAndCreateResponse( - bidderResponses, + modifiedBidderResponses, auctionContext, cacheInfo, bidderToMultiBids, @@ -197,15 +198,18 @@ Future create(List bidderResponses, debugEnabled); } - private void updateBidAdmInBidderResponses(List bidderResponses, - Account account, - Map bidIdToGeneratedBidId, - EventsContext eventsContext) { + private List updateBidAdmInBidderResponses(List bidderResponses, + Account account, + Map bidIdToGeneratedBidId, + EventsContext eventsContext) { + final List result = new ArrayList<>(); for (BidderResponse bidderResponse : bidderResponses) { final String bidder = bidderResponse.getBidder(); - for (BidderBid bidderBid : bidderResponse.getSeatBid().getBids()) { - final Bid bid = bidderBid.getBid(); + final List modifiedBidderBid = new ArrayList<>(); + final BidderSeatBid seatBid = bidderResponse.getSeatBid(); + for (BidderBid bidderBid : seatBid.getBids()) { + Bid bid = bidderBid.getBid(); final String generatedBidId = bidIdGenerator.getType() != IdGeneratorType.none ? bidIdGenerator.generateId() : null; @@ -221,10 +225,17 @@ private void updateBidAdmInBidderResponses(List bidderResponses, account.getId(), eventsContext); - bid.setAdm(adm); + bid = bid.toBuilder().adm(adm).build(); } + + modifiedBidderBid.add(bidderBid.with(bid)); } + + final BidderSeatBid modifiedSeatBid = seatBid.with(modifiedBidderBid); + result.add(bidderResponse.with(modifiedSeatBid)); } + + return result; } private static int validateTruncateAttrChars(int truncateAttrChars) { @@ -267,10 +278,10 @@ private Future cacheBidsAndCreateResponse(List bidd final Set winningBidInfos = targeting == null ? null : bidderResponseToTargetingBidInfos.values().stream() - .flatMap(Collection::stream) - .filter(TargetingBidInfo::isWinningBid) - .map(TargetingBidInfo::getBidInfo) - .collect(Collectors.toSet()); + .flatMap(Collection::stream) + .filter(TargetingBidInfo::isWinningBid) + .map(TargetingBidInfo::getBidInfo) + .collect(Collectors.toSet()); final Set bidsToCache = cacheInfo.isShouldCacheWinningBidsOnly() ? winningBidInfos : bidInfos; @@ -847,16 +858,21 @@ private SeatBid toSeatBid(List targetingBidInfos, .orElseThrow(() -> new IllegalArgumentException("Bidder was not defined for bidInfo")); final List bids = targetingBidInfos.stream() + .map(targetingBidInfo -> injectAdmWithCacheInfo( + targetingBidInfo, + requestCacheInfo, + bidToCacheInfo, + bidRequest, + bidErrors + )) + .filter(Objects::nonNull) .map(targetingBidInfo -> toBid( targetingBidInfo, targeting, bidRequest, - requestCacheInfo, - bidToCacheInfo, videoStoredDataResult.getImpIdToStoredVideo(), account, - eventsContext, - bidErrors)) + eventsContext)) .filter(Objects::nonNull) .collect(Collectors.toList()); @@ -867,36 +883,32 @@ private SeatBid toSeatBid(List targetingBidInfos, .build(); } - /** - * Returns an OpenRTB {@link Bid} with "prebid" and "bidder" extension fields populated. - */ - private Bid toBid(TargetingBidInfo targetingBidInfo, - ExtRequestTargeting targeting, - BidRequest bidRequest, - BidRequestCacheInfo requestCacheInfo, - Map bidsWithCacheIds, - Map impIdToStoredVideo, - Account account, - EventsContext eventsContext, - Map> bidErrors) { + private TargetingBidInfo injectAdmWithCacheInfo(TargetingBidInfo targetingBidInfo, + BidRequestCacheInfo requestCacheInfo, + Map bidsWithCacheIds, + BidRequest bidRequest, + Map> bidErrors) { final BidInfo bidInfo = targetingBidInfo.getBidInfo(); final Bid bid = bidInfo.getBid(); final BidType bidType = bidInfo.getBidType(); final String bidder = bidInfo.getBidder(); + final Imp correspondingImp = bidInfo.getCorrespondingImp(); final CacheInfo cacheInfo = bidsWithCacheIds.get(bid); + final boolean isApp = bidRequest.getApp() != null; + final String cacheId = cacheInfo != null ? cacheInfo.getCacheId() : null; final String videoCacheId = cacheInfo != null ? cacheInfo.getVideoCacheId() : null; + String modifiedBidAdm = bid.getAdm(); if ((videoCacheId != null && !requestCacheInfo.isReturnCreativeVideoBids()) || (cacheId != null && !requestCacheInfo.isReturnCreativeBids())) { - bid.setAdm(null); + modifiedBidAdm = null; } - final boolean isApp = bidRequest.getApp() != null; - if (isApp && bidType.equals(BidType.xNative) && bid.getAdm() != null) { + if (isApp && bidType.equals(BidType.xNative) && modifiedBidAdm != null) { try { - addNativeMarkup(bid, bidRequest.getImp()); + modifiedBidAdm = crateNativeMarkup(modifiedBidAdm, correspondingImp); } catch (PreBidException e) { bidErrors.computeIfAbsent(bidder, ignored -> new ArrayList<>()) .add(ExtBidderError.of(BidderError.Type.bad_server_response.getCode(), e.getMessage())); @@ -904,6 +916,34 @@ private Bid toBid(TargetingBidInfo targetingBidInfo, } } + final Bid modifiedBid = bid.toBuilder().adm(modifiedBidAdm).build(); + final BidInfo modifiedBidInfo = bidInfo.toBuilder() + .bid(modifiedBid) + .cacheInfo(cacheInfo) + .build(); + return targetingBidInfo.toBuilder().bidInfo(modifiedBidInfo).build(); + } + + /** + * Returns an OpenRTB {@link Bid} with "prebid" and "bidder" extension fields populated. + */ + private Bid toBid(TargetingBidInfo targetingBidInfo, + ExtRequestTargeting targeting, + BidRequest bidRequest, + Map impIdToStoredVideo, + Account account, + EventsContext eventsContext) { + final BidInfo bidInfo = targetingBidInfo.getBidInfo(); + final BidType bidType = bidInfo.getBidType(); + final String bidder = bidInfo.getBidder(); + final Bid bid = bidInfo.getBid(); + + final CacheInfo cacheInfo = bidInfo.getCacheInfo(); + final String cacheId = cacheInfo != null ? cacheInfo.getCacheId() : null; + final String videoCacheId = cacheInfo != null ? cacheInfo.getVideoCacheId() : null; + + final boolean isApp = bidRequest.getApp() != null; + final Map targetingKeywords; final String bidderCode = targetingBidInfo.getBidderCode(); if (targeting != null && targetingBidInfo.isTargetingEnabled() && targetingBidInfo.isBidderWinningBid()) { @@ -911,8 +951,8 @@ private Bid toBid(TargetingBidInfo targetingBidInfo, bidRequest, account); final boolean isWinningBid = targetingBidInfo.isWinningBid(); - targetingKeywords = keywordsCreator.makeFor(bid, bidderCode, isWinningBid, cacheId, bidType.getName(), - videoCacheId); + targetingKeywords = keywordsCreator.makeFor(bid, bidderCode, isWinningBid, cacheId, + bidType.getName(), videoCacheId); } else { targetingKeywords = null; } @@ -936,29 +976,29 @@ private Bid toBid(TargetingBidInfo targetingBidInfo, .video(extBidPrebidVideo) .build(); - bid.setExt(createBidExt(bid.getExt(), extBidPrebid)); - + final ObjectNode bidExt = createBidExt(bid.getExt(), extBidPrebid); final Integer ttl = cacheInfo != null ? ObjectUtils.max(cacheInfo.getTtl(), cacheInfo.getVideoTtl()) : null; - bid.setExp(ttl); - return bid; + return bid.toBuilder() + .ext(bidExt) + .exp(ttl) + .build(); } - private void addNativeMarkup(Bid bid, List imps) { + private String crateNativeMarkup(String bidAdm, Imp correspondingImp) { final Response nativeMarkup; try { - nativeMarkup = mapper.decodeValue(bid.getAdm(), Response.class); + nativeMarkup = mapper.decodeValue(bidAdm, Response.class); } catch (DecodeException e) { throw new PreBidException(e.getMessage()); } final List responseAssets = nativeMarkup.getAssets(); if (CollectionUtils.isNotEmpty(responseAssets)) { - final Native nativeImp = imps.stream() - .filter(imp -> imp.getId().equals(bid.getImpid()) && imp.getXNative() != null) - .findFirst() - .map(Imp::getXNative) - .orElseThrow(() -> new PreBidException("Could not find native imp")); + final Native nativeImp = correspondingImp != null ? correspondingImp.getXNative() : null; + if (nativeImp == null) { + throw new PreBidException("Could not find native imp"); + } final Request nativeRequest; try { @@ -968,8 +1008,10 @@ private void addNativeMarkup(Bid bid, List imps) { } responseAssets.forEach(asset -> setAssetTypes(asset, nativeRequest.getAssets())); - bid.setAdm(mapper.encode(nativeMarkup)); + return mapper.encode(nativeMarkup); } + + return bidAdm; } private static void setAssetTypes(Asset responseAsset, List requestAssets) { diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 10cdcb17486..85aadc445cd 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -1096,15 +1096,16 @@ private BidderBid updateBidderBidWithBidPriceChanges(BidderBid bidderBid, updateExtWithOrigPriceValues(updatedBidExt, price, bidCurrency); + final Bid.BidBuilder bidBuilder = bid.toBuilder(); if (adjustedPrice.compareTo(price) != 0) { - bid.setPrice(adjustedPrice); + bidBuilder.price(adjustedPrice); } if (!updatedBidExt.isEmpty()) { - bid.setExt(updatedBidExt); + bidBuilder.ext(updatedBidExt); } - return bidderBid; + return bidderBid.with(bidBuilder.build()); } private static BidAdjustmentMediaType resolveBidAdjustmentMediaType(String bidImpId, diff --git a/src/main/java/org/prebid/server/auction/model/BidInfo.java b/src/main/java/org/prebid/server/auction/model/BidInfo.java index 27a7dfd73ea..a2d9ba2ab8b 100644 --- a/src/main/java/org/prebid/server/auction/model/BidInfo.java +++ b/src/main/java/org/prebid/server/auction/model/BidInfo.java @@ -4,6 +4,7 @@ import com.iab.openrtb.response.Bid; import lombok.Builder; import lombok.Value; +import org.prebid.server.cache.model.CacheInfo; import org.prebid.server.proto.openrtb.ext.response.BidType; @Builder(toBuilder = true) @@ -20,6 +21,8 @@ public class BidInfo { BidType bidType; + CacheInfo cacheInfo; + public String getBidId() { return generatedBidId != null ? generatedBidId : bid.getId(); } diff --git a/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java b/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java index 52568155086..19b875b2ab7 100644 --- a/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java +++ b/src/main/java/org/prebid/server/bidder/appnexus/AppnexusBidder.java @@ -444,14 +444,17 @@ private BidderBid bidderBid(Bid bid, String currency) { } final String iabCategory = iabCategory(appnexus.getBrandCategoryId()); + + List cat = bid.getCat(); if (iabCategory != null) { - bid.setCat(Collections.singletonList(iabCategory)); + cat = Collections.singletonList(iabCategory); } else if (CollectionUtils.isNotEmpty(bid.getCat())) { //create empty categories array to force bid to be rejected - bid.setCat(Collections.emptyList()); + cat = Collections.emptyList(); } - return BidderBid.of(bid, bidType(appnexus.getBidAdType()), currency); + final Bid modifiedBid = bid.toBuilder().cat(cat).build(); + return BidderBid.of(modifiedBid, bidType(appnexus.getBidAdType()), currency); } private static String iabCategory(Integer brandId) { diff --git a/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java b/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java index f37945066a6..500ab36b1ae 100644 --- a/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java +++ b/src/main/java/org/prebid/server/bidder/beachfront/BeachfrontBidder.java @@ -453,30 +453,40 @@ private Result> processVideoResponse(String responseBody, HttpRe final List bids = bidResponse.getSeatbid().get(0).getBid(); final List imps = videoRequest.getRequest().getImp(); if (httpRequest.getUri().contains(NURL_VIDEO_ENDPOINT_SUFFIX)) { - return Result.withValues(updateVideoBids(bids, imps).stream() + return Result.withValues(updateNurlVideoBids(bids, imps).stream() .map(bid -> BidderBid.of(bid, BidType.video, bidResponse.getCur())) .collect(Collectors.toList())); } else { - return Result.withValues(bids.stream() - .peek(bid -> bid.setId(bid.getImpid() + "AdmVideo")) + return Result.withValues(updateVideoBids(bids).stream() .map(bid -> BidderBid.of(bid, BidType.video, bidResponse.getCur())) .collect(Collectors.toList())); } } - private static List updateVideoBids(List bids, List imps) { + private static List updateNurlVideoBids(List bids, List imps) { + final List result = new ArrayList<>(); for (int i = 0; i < bids.size(); i++) { - final Bid bid = bids.get(i); + Bid bid = bids.get(i); final Imp imp = imps.get(i); final String impId = imp.getId(); - bid.setCrid(getCrId(bid.getNurl())); - bid.setImpid(impId); - bid.setH(imp.getVideo().getH()); - bid.setW(imp.getVideo().getW()); - bid.setId(impId + "NurlVideo"); + + bid = bid.toBuilder() + .crid(getCrId(bid.getNurl())) + .impid(impId) + .h(imp.getVideo().getH()) + .w(imp.getVideo().getW()) + .id(impId + "NurlVideo") + .build(); + result.add(bid); } - return bids; + return result; + } + + private static List updateVideoBids(List bids) { + return bids.stream() + .map(bid -> bid.toBuilder().id(bid.getImpid() + "AdmVideo").build()) + .collect(Collectors.toList()); } private static String getCrId(String nurl) { diff --git a/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java b/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java index 7c0ba8b4776..0738c26e496 100644 --- a/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java +++ b/src/main/java/org/prebid/server/bidder/facebook/FacebookBidder.java @@ -317,10 +317,12 @@ private BidderBid toBidderBid(Bid bid, List imps, String currency, List> makeBids(HttpCall httpCall, BidRequest bidR try { final GammaBidResponse bidResponse = mapper.decodeValue(body, GammaBidResponse.class); final List errors = new ArrayList<>(); - return Result.of(extractBidsAndFillErorrs(bidResponse, bidRequest, errors), errors); + return Result.of(extractBidsAndFillErrors(bidResponse, bidRequest, errors), errors); } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse( String.format("bad server response: %s", e.getMessage()))); } } - private static List extractBidsAndFillErorrs(GammaBidResponse bidResponse, + private static List extractBidsAndFillErrors(GammaBidResponse bidResponse, BidRequest bidRequest, List errors) { return bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid()) @@ -248,28 +248,29 @@ private static BidType getMediaTypes(String impId, List imps) { private static Bid convertBid(GammaBid gammaBid, BidType bidType) { final boolean isVideo = BidType.video.equals(bidType); - if (!isVideo && StringUtils.isBlank(gammaBid.getAdm())) { + if (!isVideo && StringUtils.isBlank(gammaBid.getBid().getAdm())) { throw new PreBidException("Missing Ad Markup. Run with request.debug = 1 for more info"); } + Bid bid = gammaBid.getBid(); if (isVideo) { //Return inline VAST XML Document (Section 6.4.2) final String vastXml = gammaBid.getVastXml(); if (StringUtils.isNotBlank(vastXml)) { - final Bid.BidBuilder bidBuilder = gammaBid.toBuilder().adm(vastXml); + final Bid.BidBuilder bidBuilder = gammaBid.getBid().toBuilder().adm(vastXml); final String vastUrl = gammaBid.getVastUrl(); if (StringUtils.isNotBlank(vastUrl)) { bidBuilder.nurl(vastUrl); } - return bidBuilder.build(); + bid = bidBuilder.build(); } else { throw new PreBidException("Missing Ad Markup. Run with request.debug = 1 for more info"); } } - return gammaBid; + return bid; } } diff --git a/src/main/java/org/prebid/server/bidder/gamma/model/GammaBid.java b/src/main/java/org/prebid/server/bidder/gamma/model/GammaBid.java index 26f24d9c498..991c1982b96 100644 --- a/src/main/java/org/prebid/server/bidder/gamma/model/GammaBid.java +++ b/src/main/java/org/prebid/server/bidder/gamma/model/GammaBid.java @@ -1,21 +1,27 @@ package org.prebid.server.bidder.gamma.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.iab.openrtb.response.Bid; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; -@EqualsAndHashCode(callSuper = true) -@SuperBuilder -@NoArgsConstructor -@Data -public class GammaBid extends Bid { +@Getter +@Builder +@AllArgsConstructor(onConstructor = @__(@JsonIgnore)) +@RequiredArgsConstructor +public class GammaBid { + + @JsonUnwrapped + Bid bid; @JsonProperty("vastXml") String vastXml; @JsonProperty("vastUrl") String vastUrl; + } diff --git a/src/main/java/org/prebid/server/bidder/model/BidderBid.java b/src/main/java/org/prebid/server/bidder/model/BidderBid.java index 11aba88396e..52bd90db136 100644 --- a/src/main/java/org/prebid/server/bidder/model/BidderBid.java +++ b/src/main/java/org/prebid/server/bidder/model/BidderBid.java @@ -27,4 +27,8 @@ public class BidderBid { * Will be used for converting to ad server currency */ String bidCurrency; + + public BidderBid with(Bid bid) { + return BidderBid.of(bid, this.type, this.bidCurrency); + } } diff --git a/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java b/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java index 93e9afb809a..18c396a62c7 100644 --- a/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java +++ b/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java @@ -41,4 +41,8 @@ public class BidderSeatBid { * Error messages should help publishers understand what might account for "bad" bids. */ List errors; + + public BidderSeatBid with(List bids) { + return BidderSeatBid.of(bids, this.httpCalls, this.errors); + } } diff --git a/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java b/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java index 1f978943d22..7e225119457 100644 --- a/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java +++ b/src/main/java/org/prebid/server/bidder/rubicon/RubiconBidder.java @@ -930,7 +930,7 @@ private static ExtUserEidUid cleanExtUserEidUidStype(ExtUserEidUid extUserEidUid return extUserEidUidExt == null || !STYPE_TO_REMOVE.contains(extUserEidUidExt.getStype()) ? extUserEidUid : ExtUserEidUid.of(extUserEidUid.getId(), extUserEidUid.getAtype(), - ExtUserEidUidExt.of(extUserEidUidExt.getRtiPartner(), null)); + ExtUserEidUidExt.of(extUserEidUidExt.getRtiPartner(), null)); } private static Map> specialExtUserEids(List eids) { @@ -1092,10 +1092,10 @@ private Site makeSite(Site site, String impLanguage, ExtImpRubicon rubiconImpExt .content(makeSiteContent(null, impLanguage)) .build() : site.toBuilder() - .publisher(makePublisher(rubiconImpExt)) - .content(makeSiteContent(site.getContent(), impLanguage)) - .ext(makeSiteExt(site, rubiconImpExt)) - .build(); + .publisher(makePublisher(rubiconImpExt)) + .content(makeSiteContent(site.getContent(), impLanguage)) + .ext(makeSiteExt(site, rubiconImpExt)) + .build(); } private static Content makeSiteContent(Content siteContent, String impLanguage) { @@ -1181,23 +1181,27 @@ private static boolean validatePrice(Bid bid) { } private Bid updateBid(Bid bid, Imp imp, Float cmpOverrideFromRequest, BidResponse bidResponse) { + String bidId = bid.getId(); if (generateBidId) { // Since Rubicon XAPI returns openrtb_response.seatbid.bid.id not unique enough // generate new value for it - bid.setId(UUID.randomUUID().toString()); + bidId = UUID.randomUUID().toString(); } else if (Objects.equals(bid.getId(), "0")) { // Since Rubicon XAPI returns only one bid per response // copy bidResponse.bidid to openrtb_response.seatbid.bid.id - bid.setId(bidResponse.getBidid()); + bidId = bidResponse.getBidid(); } // Unconditionally set price if coming from CPM override final Float cpmOverride = ObjectUtils.defaultIfNull(cpmOverrideFromImp(imp), cmpOverrideFromRequest); - if (cpmOverride != null) { - bid.setPrice(new BigDecimal(String.valueOf(cpmOverride))); - } + final BigDecimal bidPrice = cpmOverride != null + ? new BigDecimal(String.valueOf(cpmOverride)) + : bid.getPrice(); - return bid; + return bid.toBuilder() + .id(bidId) + .price(bidPrice) + .build(); } private Float cmpOverrideFromRequest(BidRequest bidRequest) { diff --git a/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java b/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java index 3ee974870c1..646d7f6dac6 100644 --- a/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java +++ b/src/main/java/org/prebid/server/bidder/sovrn/SovrnBidder.java @@ -157,7 +157,8 @@ private static List bidsFromResponse(BidResponse bidResponse) { } private static Bid updateBid(Bid bid) { - bid.setAdm(HttpUtil.decodeUrl(bid.getAdm())); - return bid; + return bid.toBuilder() + .adm(HttpUtil.decodeUrl(bid.getAdm())) + .build(); } } diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index fec38e9c305..6a05b10758b 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -780,9 +780,16 @@ public void shouldCallBidResponseCreatorWithExpectedParamsAndUpdateDebugErrors() verify(bidResponseCreator).create(captor.capture(), eq(expectedAuctionContext), eq(expectedCacheInfo), eq(expectedMultiBidMap), eq(false)); + final ObjectNode expectedBidExt = mapper.createObjectNode().put("origbidcpm", new BigDecimal("7.89")); + final Bid expectedThirdBid = Bid.builder() + .id("bidId3") + .impid("impId1") + .price(BigDecimal.valueOf(7.89)) + .ext(expectedBidExt) + .build(); assertThat(captor.getValue()).containsOnly( BidderResponse.of("bidder2", BidderSeatBid.of(singletonList( - BidderBid.of(thirdBid, banner, null)), emptyList(), emptyList()), 0), + BidderBid.of(expectedThirdBid, banner, null)), emptyList(), emptyList()), 0), BidderResponse.of("bidder1", BidderSeatBid.of(emptyList(), emptyList(), emptyList()), 0)); } diff --git a/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java b/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java index 62169af095c..c77413323f4 100644 --- a/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java @@ -16,6 +16,8 @@ import org.junit.Test; import org.prebid.server.VertxTest; import org.prebid.server.bidder.gamma.model.GammaBid; +import org.prebid.server.bidder.gamma.model.GammaBidResponse; +import org.prebid.server.bidder.gamma.model.GammaSeatBid; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; import org.prebid.server.bidder.model.HttpCall; @@ -238,13 +240,14 @@ public void makeBidsShouldSetAdmFromVastXmlIsPresentAndVideoType() throws JsonPr final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); final String adm = "ADM"; - final GammaBid bid = GammaBid.builder().id("impId").vastXml(adm).build(); + final Bid bid = Bid.builder().id("impId").build(); + final GammaBid gammaBid = GammaBid.builder().bid(bid).vastXml(adm).build(); final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( - BidResponse.builder() + GammaBidResponse.builder() .id("impId") .cur("USD") - .seatbid(singletonList(SeatBid.builder() - .bid(singletonList(bid)) + .seatbid(singletonList(GammaSeatBid.builder() + .bid(singletonList(gammaBid)) .build())) .build())); @@ -258,55 +261,56 @@ public void makeBidsShouldSetAdmFromVastXmlIsPresentAndVideoType() throws JsonPr assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBid, video, "USD")); } - @Test - public void makeBidsShouldSetAdmFromVastXmlAndNurlFromVastUrlAndVideoType() throws JsonProcessingException { - // given - final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); - - final String adm = "ADM"; - final String nurl = "NURL"; - final GammaBid bid = GammaBid.builder().id("impId").vastXml(adm).vastUrl(nurl).build(); - final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( - BidResponse.builder() - .id("impId") - .cur("USD") - .seatbid(singletonList(SeatBid.builder() - .bid(singletonList(bid)) - .build())) - .build())); - - // when - final Result> result = gammaBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).isEmpty(); - - final Bid expectedBid = Bid.builder().id("impId").adm(adm).nurl(nurl).build(); - assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBid, video, "USD")); - } - - @Test - public void makeBidsShouldReturnErrorWhenNoVastXmlAndVideoType() throws JsonProcessingException { - // given - final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); - final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); - - final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( - BidResponse.builder() - .id("impId") - .seatbid(singletonList(SeatBid.builder() - .bid(singletonList(Bid.builder().build())) - .build())) - .build())); - - // when - final Result> result = gammaBidder.makeBids(httpCall, bidRequest); - - // then - assertThat(result.getErrors()).containsOnly(BidderError.badServerResponse( - "Missing Ad Markup. Run with request.debug = 1 for more info")); - } + // @Test + // public void makeBidsShouldSetAdmFromVastXmlAndNurlFromVastUrlAndVideoType() throws JsonProcessingException { + // // given + // final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); + // final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); + // + // final String adm = "ADM"; + // final String nurl = "NURL"; + // final Bid bid = Bid.builder().id("impId").build(); + // final GammaBid gammaBid = GammaBid.of(bid, adm, nurl); + // final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( + // GammaBidResponse.builder() + // .id("impId") + // .cur("USD") + // .seatbid(singletonList(GammaSeatBid.builder() + // .bid(singletonList(gammaBid)) + // .build())) + // .build())); + // + // // when + // final Result> result = gammaBidder.makeBids(httpCall, bidRequest); + // + // // then + // assertThat(result.getErrors()).isEmpty(); + // + // final Bid expectedBid = Bid.builder().id("impId").adm(adm).nurl(nurl).build(); + // assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBid, video, "USD")); + // } + + // @Test + // public void makeBidsShouldReturnErrorWhenNoVastXmlAndVideoType() throws JsonProcessingException { + // // given + // final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); + // final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); + // + // final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( + // BidResponse.builder() + // .id("impId") + // .seatbid(singletonList(SeatBid.builder() + // .bid(singletonList(Bid.builder().build())) + // .build())) + // .build())); + // + // // when + // final Result> result = gammaBidder.makeBids(httpCall, bidRequest); + // + // // then + // assertThat(result.getErrors()).containsOnly(BidderError.badServerResponse( + // "Missing Ad Markup. Run with request.debug = 1 for more info")); + // } @Test public void makeBidsShouldReturnErrorWhenNoAdmAndNotVideoType() throws JsonProcessingException { @@ -350,7 +354,7 @@ public void makeBidsShouldReturnBannerWhenNoProvided() throws JsonProcessingExce // then assertThat(result.getErrors()).isEmpty(); - final GammaBid expectedBid = GammaBid.builder().adm("ADM").build(); + final Bid expectedBid = Bid.builder().adm("ADM").build(); assertThat(result.getValue()) .containsOnly(BidderBid.of(expectedBid, banner, "USD")); } From ba3a16c955d494778b058c28bc304ae6db2756c4 Mon Sep 17 00:00:00 2001 From: DGarbar Date: Mon, 26 Apr 2021 15:28:43 +0300 Subject: [PATCH 2/4] Fix tests --- .../server/bidder/gamma/GammaBidderTest.java | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java b/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java index c77413323f4..d6069fcd6fe 100644 --- a/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/gamma/GammaBidderTest.java @@ -261,56 +261,56 @@ public void makeBidsShouldSetAdmFromVastXmlIsPresentAndVideoType() throws JsonPr assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBid, video, "USD")); } - // @Test - // public void makeBidsShouldSetAdmFromVastXmlAndNurlFromVastUrlAndVideoType() throws JsonProcessingException { - // // given - // final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); - // final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); - // - // final String adm = "ADM"; - // final String nurl = "NURL"; - // final Bid bid = Bid.builder().id("impId").build(); - // final GammaBid gammaBid = GammaBid.of(bid, adm, nurl); - // final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( - // GammaBidResponse.builder() - // .id("impId") - // .cur("USD") - // .seatbid(singletonList(GammaSeatBid.builder() - // .bid(singletonList(gammaBid)) - // .build())) - // .build())); - // - // // when - // final Result> result = gammaBidder.makeBids(httpCall, bidRequest); - // - // // then - // assertThat(result.getErrors()).isEmpty(); - // - // final Bid expectedBid = Bid.builder().id("impId").adm(adm).nurl(nurl).build(); - // assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBid, video, "USD")); - // } - - // @Test - // public void makeBidsShouldReturnErrorWhenNoVastXmlAndVideoType() throws JsonProcessingException { - // // given - // final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); - // final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); - // - // final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( - // BidResponse.builder() - // .id("impId") - // .seatbid(singletonList(SeatBid.builder() - // .bid(singletonList(Bid.builder().build())) - // .build())) - // .build())); - // - // // when - // final Result> result = gammaBidder.makeBids(httpCall, bidRequest); - // - // // then - // assertThat(result.getErrors()).containsOnly(BidderError.badServerResponse( - // "Missing Ad Markup. Run with request.debug = 1 for more info")); - // } + @Test + public void makeBidsShouldSetAdmFromVastXmlAndNurlFromVastUrlAndVideoType() throws JsonProcessingException { + // given + final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); + final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); + + final String adm = "ADM"; + final String nurl = "NURL"; + final Bid bid = Bid.builder().id("impId").build(); + final GammaBid gammaBid = GammaBid.builder().bid(bid).vastXml(adm).vastUrl(nurl).build(); + final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( + GammaBidResponse.builder() + .id("impId") + .cur("USD") + .seatbid(singletonList(GammaSeatBid.builder() + .bid(singletonList(gammaBid)) + .build())) + .build())); + + // when + final Result> result = gammaBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + + final Bid expectedBid = Bid.builder().id("impId").adm(adm).nurl(nurl).build(); + assertThat(result.getValue()).containsOnly(BidderBid.of(expectedBid, video, "USD")); + } + + @Test + public void makeBidsShouldReturnErrorWhenNoVastXmlAndVideoType() throws JsonProcessingException { + // given + final Imp imp = Imp.builder().id("impId").video(Video.builder().build()).build(); + final BidRequest bidRequest = BidRequest.builder().imp(singletonList(imp)).build(); + + final HttpCall httpCall = givenHttpCall(mapper.writeValueAsString( + BidResponse.builder() + .id("impId") + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(Bid.builder().build())) + .build())) + .build())); + + // when + final Result> result = gammaBidder.makeBids(httpCall, bidRequest); + + // then + assertThat(result.getErrors()).containsOnly(BidderError.badServerResponse( + "Missing Ad Markup. Run with request.debug = 1 for more info")); + } @Test public void makeBidsShouldReturnErrorWhenNoAdmAndNotVideoType() throws JsonProcessingException { From a2b13ff7d4e623511a442c91df468175a4a73e81 Mon Sep 17 00:00:00 2001 From: DGarbar Date: Wed, 28 Apr 2021 15:36:33 +0300 Subject: [PATCH 3/4] Add bidderInfoResponse container as a first step to use BidInfo in workflow --- .../server/auction/BidResponseCreator.java | 373 +++++++++--------- .../prebid/server/auction/model/BidInfo.java | 4 + .../auction/model/BidderInfoResponse.java | 24 ++ .../server/auction/model/TargetingInfo.java | 19 + .../bidder/model/BidderInfoSeatBid.java | 49 +++ .../auction/BidResponseCreatorTest.java | 35 +- 6 files changed, 315 insertions(+), 189 deletions(-) create mode 100644 src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java create mode 100644 src/main/java/org/prebid/server/auction/model/TargetingInfo.java create mode 100644 src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java diff --git a/src/main/java/org/prebid/server/auction/BidResponseCreator.java b/src/main/java/org/prebid/server/auction/BidResponseCreator.java index 4551cbf986c..6397c671b09 100644 --- a/src/main/java/org/prebid/server/auction/BidResponseCreator.java +++ b/src/main/java/org/prebid/server/auction/BidResponseCreator.java @@ -26,12 +26,14 @@ import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidInfo; import org.prebid.server.auction.model.BidRequestCacheInfo; +import org.prebid.server.auction.model.BidderInfoResponse; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.auction.model.MultiBidConfig; -import org.prebid.server.auction.model.TargetingBidInfo; +import org.prebid.server.auction.model.TargetingInfo; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.BidderInfoSeatBid; import org.prebid.server.bidder.model.BidderSeatBid; import org.prebid.server.cache.CacheService; import org.prebid.server.cache.model.CacheContext; @@ -155,7 +157,8 @@ Future create(List bidderResponses, BidRequestCacheInfo cacheInfo, Map bidderToMultiBids, boolean debugEnabled) { - + final BidRequest bidRequest = auctionContext.getBidRequest(); + final List imps = bidRequest.getImp(); final long auctionTimestamp = auctionTimestamp(auctionContext); final Account account = auctionContext.getAccount(); @@ -166,19 +169,17 @@ Future create(List bidderResponses, .integration(integrationFrom(auctionContext)) .build(); - final Map bidIdToGeneratedBidId = new HashMap<>(); - final List modifiedBidderResponses = updateBidAdmInBidderResponses(bidderResponses, account, - bidIdToGeneratedBidId, eventsContext); + final List bidderInfoResponses = toBidderInfoResponses(bidderResponses, imps, account, + eventsContext); - if (isEmptyBidderResponses(modifiedBidderResponses)) { - final BidRequest bidRequest = auctionContext.getBidRequest(); + if (isEmptyBidderResponses(bidderInfoResponses)) { return Future.succeededFuture(BidResponse.builder() .id(bidRequest.getId()) .cur(bidRequest.getCur().get(0)) .nbr(0) // signal "Unknown Error" .seatbid(Collections.emptyList()) .ext(mapper.mapper().valueToTree(toExtBidResponse( - modifiedBidderResponses, + bidderInfoResponses, auctionContext, CacheServiceResult.empty(), VideoStoredDataResult.empty(), @@ -189,98 +190,135 @@ Future create(List bidderResponses, } return cacheBidsAndCreateResponse( - modifiedBidderResponses, + bidderInfoResponses, auctionContext, cacheInfo, bidderToMultiBids, - bidIdToGeneratedBidId, eventsContext, debugEnabled); } - private List updateBidAdmInBidderResponses(List bidderResponses, - Account account, - Map bidIdToGeneratedBidId, - EventsContext eventsContext) { - final List result = new ArrayList<>(); + private static int validateTruncateAttrChars(int truncateAttrChars) { + if (truncateAttrChars < 0 || truncateAttrChars > 255) { + throw new IllegalArgumentException("truncateAttrChars must be between 0 and 255"); + } + return truncateAttrChars; + } + + /** + * Checks whether bidder responses are empty or contain no bids. + */ + private static boolean isEmptyBidderResponses(List bidderInfoResponses) { + return bidderInfoResponses.isEmpty() || bidderInfoResponses.stream() + .map(bidderInfoResponse -> bidderInfoResponse.getSeatBid().getBids()) + .allMatch(CollectionUtils::isEmpty); + } + + private List toBidderInfoResponses(List bidderResponses, + List imps, + Account account, + EventsContext eventsContext) { + final List result = new ArrayList<>(); for (BidderResponse bidderResponse : bidderResponses) { final String bidder = bidderResponse.getBidder(); - final List modifiedBidderBid = new ArrayList<>(); + final List bidInfos = new ArrayList<>(); final BidderSeatBid seatBid = bidderResponse.getSeatBid(); - for (BidderBid bidderBid : seatBid.getBids()) { - Bid bid = bidderBid.getBid(); - final String generatedBidId = bidIdGenerator.getType() != IdGeneratorType.none - ? bidIdGenerator.generateId() - : null; - final String bidId = bid.getId(); - bidIdToGeneratedBidId.put(bidId, generatedBidId); - - if (bidderBid.getType().equals(BidType.video)) { - final String adm = vastModifier.createBidVastXml( - bidder, - bid.getAdm(), - bid.getNurl(), - generatedBidId == null ? bidId : generatedBidId, - account.getId(), - eventsContext); - - bid = bid.toBuilder().adm(adm).build(); - } - modifiedBidderBid.add(bidderBid.with(bid)); + for (BidderBid bidderBid : seatBid.getBids()) { + final Bid bid = bidderBid.getBid(); + final BidType type = bidderBid.getType(); + final BidInfo bidInfo = toBidInfoWithGeneratedAdm(bid, type, imps, bidder, account, eventsContext); + bidInfos.add(bidInfo); } - final BidderSeatBid modifiedSeatBid = seatBid.with(modifiedBidderBid); - result.add(bidderResponse.with(modifiedSeatBid)); + final BidderInfoSeatBid bidderInfoSeatBid = BidderInfoSeatBid.of( + bidInfos, + seatBid.getHttpCalls(), + seatBid.getErrors()); + + result.add(BidderInfoResponse.of(bidder, bidderInfoSeatBid, bidderResponse.getResponseTime())); } return result; } - private static int validateTruncateAttrChars(int truncateAttrChars) { - if (truncateAttrChars < 0 || truncateAttrChars > 255) { - throw new IllegalArgumentException("truncateAttrChars must be between 0 and 255"); + private BidInfo toBidInfoWithGeneratedAdm(Bid bid, + BidType type, + List imps, + String bidder, + Account account, + EventsContext eventsContext) { + final String generatedBidId = bidIdGenerator.getType() != IdGeneratorType.none + ? bidIdGenerator.generateId() + : null; + + final String bidId = bid.getId(); + final String eventBidId = generatedBidId == null ? bidId : generatedBidId; + final Bid modifiedBid = modifyBidAdm(bid, type, bidder, account, eventsContext, eventBidId); + + return BidInfo.builder() + .generatedBidId(generatedBidId) + .bid(modifiedBid) + .bidType(type) + .bidder(bidder) + .correspondingImp(correspondingImp(bid, imps)) + .build(); + } + + private Bid modifyBidAdm(Bid bid, + BidType bidType, + String bidder, + Account account, + EventsContext eventsContext, + String eventBidId) { + if (BidType.video.equals(bidType)) { + final String adm = vastModifier.createBidVastXml( + bidder, + bid.getAdm(), + bid.getNurl(), + eventBidId, + account.getId(), + eventsContext); + + return bid.toBuilder().adm(adm).build(); } - return truncateAttrChars; + return bid; } - /** - * Checks whether bidder responses are empty or contain no bids. - */ - private static boolean isEmptyBidderResponses(List bidderResponses) { - return bidderResponses.isEmpty() || bidderResponses.stream() - .map(bidderResponse -> bidderResponse.getSeatBid().getBids()) - .allMatch(CollectionUtils::isEmpty); + private static Imp correspondingImp(Bid bid, List imps) { + final String impId = bid.getImpid(); + return imps.stream() + .filter(imp -> Objects.equals(impId, imp.getId())) + .findFirst() + // Should never occur. See ResponseBidValidator + .orElseThrow( + () -> new PreBidException(String.format("Bid with impId %s doesn't have matched imp", impId))); } - private Future cacheBidsAndCreateResponse(List bidderResponses, + private Future cacheBidsAndCreateResponse(List bidderResponses, AuctionContext auctionContext, BidRequestCacheInfo cacheInfo, Map bidderToMultiBids, - Map bidIdToGeneratedBidId, EventsContext eventsContext, boolean debugEnabled) { - final BidRequest bidRequest = auctionContext.getBidRequest(); - - final List imps = bidRequest.getImp(); - final Map> bidderResponseToTargetingBidInfos = - toBidderResponseWithTargetingBidInfos(bidderResponses, imps, bidderToMultiBids, bidIdToGeneratedBidId); + final List bidderInfoResponses = + toBidderResponseWithTargetingBidInfos(bidderResponses, bidderToMultiBids); - final Set bidInfos = bidderResponseToTargetingBidInfos.values().stream() + final Set bidInfos = bidderInfoResponses.stream() + .map(BidderInfoResponse::getSeatBid) + .map(BidderInfoSeatBid::getBids) .filter(CollectionUtils::isNotEmpty) .flatMap(Collection::stream) - .map(TargetingBidInfo::getBidInfo) .collect(Collectors.toSet()); + final BidRequest bidRequest = auctionContext.getBidRequest(); final ExtRequestTargeting targeting = targeting(bidRequest); final Set winningBidInfos = targeting == null ? null - : bidderResponseToTargetingBidInfos.values().stream() - .flatMap(Collection::stream) - .filter(TargetingBidInfo::isWinningBid) - .map(TargetingBidInfo::getBidInfo) + : bidInfos.stream() + .filter(bidInfo -> bidInfo.getTargetingInfo().isWinningBid()) .collect(Collectors.toSet()); final Set bidsToCache = cacheInfo.isShouldCacheWinningBidsOnly() ? winningBidInfos : bidInfos; @@ -288,7 +326,7 @@ private Future cacheBidsAndCreateResponse(List bidd return cacheBids(bidsToCache, auctionContext, cacheInfo, eventsContext) .compose(cacheResult -> videoStoredDataResult(auctionContext) .map(videoStoredDataResult -> toBidResponse( - bidderResponseToTargetingBidInfos, + bidderInfoResponses, auctionContext, targeting, cacheInfo, @@ -304,17 +342,14 @@ private static ExtRequestTargeting targeting(BidRequest bidRequest) { return prebid != null ? prebid.getTargeting() : null; } - private Map> toBidderResponseWithTargetingBidInfos( - List bidderResponses, - List imps, - Map bidderToMultiBids, - Map bidIdToGeneratedBidId) { + private List toBidderResponseWithTargetingBidInfos( + List bidderResponses, + Map bidderToMultiBids) { - final Map> bidderResponseToReducedBidInfos = bidderResponses.stream() + final Map> bidderResponseToReducedBidInfos = bidderResponses.stream() .collect(Collectors.toMap( Function.identity(), - bidderResponse -> toSortedMultiBidInfo(bidderResponse, imps, bidderToMultiBids, - bidIdToGeneratedBidId))); + bidderResponse -> toSortedMultiBidInfo(bidderResponse, bidderToMultiBids))); final Map>> impIdToBidderToBidInfos = bidderResponseToReducedBidInfos.values() .stream() @@ -339,21 +374,19 @@ private Map> toBidderResponseWithTargetin } return bidderResponseToReducedBidInfos.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - responseToBidInfos -> toTargetingBidInfo( - responseToBidInfos.getValue(), - responseToBidInfos.getKey().getBidder(), - bidderToMultiBids, - winningBids, - winningBidsByBidder))); - } - - private List toSortedMultiBidInfo(BidderResponse bidderResponse, - List imps, - Map bidderToMultiBids, - Map bidIdToGeneratedBidId) { - final List bidInfos = toBidInfo(bidderResponse, imps, bidIdToGeneratedBidId); + .map(responseToBidInfos -> injectBidInfoWithTargeting( + responseToBidInfos.getKey(), + responseToBidInfos.getValue(), + bidderToMultiBids, + winningBids, + winningBidsByBidder + )) + .collect(Collectors.toList()); + } + + private List toSortedMultiBidInfo(BidderInfoResponse bidderResponse, + Map bidderToMultiBids) { + final List bidInfos = bidderResponse.getSeatBid().getBids(); final Map> impIdToBidInfos = bidInfos.stream() .collect(Collectors.groupingBy(bidInfo -> bidInfo.getCorrespondingImp().getId())); @@ -366,48 +399,6 @@ private List toSortedMultiBidInfo(BidderResponse bidderResponse, .collect(Collectors.toList()); } - private List toBidInfo(BidderResponse bidderResponse, - List imps, - Map bidIdToGeneratedBidId) { - return Stream.of(bidderResponse) - .map(BidderResponse::getSeatBid) - .filter(Objects::nonNull) - .map(BidderSeatBid::getBids) - .filter(Objects::nonNull) - .flatMap(Collection::stream) - .map(bidderBid -> - toBidInfo(bidderBid.getBid(), - bidderBid.getType(), - imps, - bidderResponse.getBidder(), - bidIdToGeneratedBidId)) - .collect(Collectors.toList()); - } - - private BidInfo toBidInfo(Bid bid, - BidType type, - List imps, - String bidder, - Map bidIdToGeneratedBidId) { - return BidInfo.builder() - .generatedBidId(bidIdToGeneratedBidId.get(bid.getId())) - .bid(bid) - .bidType(type) - .bidder(bidder) - .correspondingImp(correspondingImp(bid, imps)) - .build(); - } - - private static Imp correspondingImp(Bid bid, List imps) { - final String impId = bid.getImpid(); - return imps.stream() - .filter(imp -> Objects.equals(impId, imp.getId())) - .findFirst() - // Should never occur. See ResponseBidValidator - .orElseThrow( - () -> new PreBidException(String.format("Bid with impId %s doesn't have matched imp", impId))); - } - private List sortReducedBidInfo(List bidInfos, int limit) { return bidInfos.stream() .sorted(winningBidComparator.reversed()) @@ -415,27 +406,40 @@ private List sortReducedBidInfo(List bidInfos, int limit) { .collect(Collectors.toList()); } - private List toTargetingBidInfo(List bidderBidInfos, - String bidder, - Map bidderToMultiBids, - Set winningBids, - Set winningBidsByBidder) { + private BidderInfoResponse injectBidInfoWithTargeting(BidderInfoResponse bidderInfoResponse, + List bidderBidInfos, + Map bidderToMultiBids, + Set winningBids, + Set winningBidsByBidder) { + final String bidder = bidderInfoResponse.getBidder(); + final List bidInfosWithTargeting = toBidInfoWithTargeting(bidderBidInfos, bidder, bidderToMultiBids, + winningBids, winningBidsByBidder); + + final BidderInfoSeatBid seatBid = bidderInfoResponse.getSeatBid(); + final BidderInfoSeatBid modifiedSeatBid = seatBid.with(bidInfosWithTargeting); + return bidderInfoResponse.with(modifiedSeatBid); + } + + private List toBidInfoWithTargeting(List bidderBidInfos, + String bidder, + Map bidderToMultiBids, + Set winningBids, + Set winningBidsByBidder) { final Map> impIdToBidInfos = bidderBidInfos.stream() .collect(Collectors.groupingBy(bidInfo -> bidInfo.getCorrespondingImp().getId())); return impIdToBidInfos.values().stream() - .map(bidInfos -> createTargetingBidInfo(bidInfos, bidder, bidderToMultiBids, winningBids, - winningBidsByBidder)) + .map(bidInfos -> injectTargeting(bidInfos, bidder, bidderToMultiBids, winningBids, winningBidsByBidder)) .flatMap(Collection::stream) .collect(Collectors.toList()); } - private List createTargetingBidInfo(List bidderImpIdBidInfos, - String bidder, - Map bidderToMultiBids, - Set winningBids, - Set winningBidsByBidder) { - final List targetingBidInfos = new ArrayList<>(); + private List injectTargeting(List bidderImpIdBidInfos, + String bidder, + Map bidderToMultiBids, + Set winningBids, + Set winningBidsByBidder) { + final List result = new ArrayList<>(); final MultiBidConfig multiBid = bidderToMultiBids.get(bidder); final String bidderCodePrefix = multiBid != null ? multiBid.getTargetBidderCodePrefix() : null; @@ -449,8 +453,7 @@ private List createTargetingBidInfo(List bidderImpIdB : bidderCodePrefix == null ? null : String.format("%s%s", bidderCodePrefix, i + 1); final BidInfo bidInfo = bidderImpIdBidInfos.get(i); - final TargetingBidInfo targetingBidInfo = TargetingBidInfo.builder() - .bidInfo(bidInfo) + final TargetingInfo targetingInfo = TargetingInfo.builder() .isTargetingEnabled(targetingBidderCode != null) .isBidderWinningBid(winningBidsByBidder.contains(bidInfo)) .isWinningBid(winningBids.contains(bidInfo)) @@ -458,10 +461,11 @@ private List createTargetingBidInfo(List bidderImpIdB .bidderCode(targetingBidderCode) .build(); - targetingBidInfos.add(targetingBidInfo); + final BidInfo modifiedBidInfo = bidInfo.toBuilder().targetingInfo(targetingInfo).build(); + result.add(modifiedBidInfo); } - return targetingBidInfos; + return result; } /** @@ -478,7 +482,7 @@ private long auctionTimestamp(AuctionContext auctionContext) { * Returns {@link ExtBidResponse} object, populated with response time, errors and debug info (if requested) * from all bidders. */ - private ExtBidResponse toExtBidResponse(Collection bidderResponses, + private ExtBidResponse toExtBidResponse(List bidderInfoResponses, AuctionContext auctionContext, CacheServiceResult cacheResult, VideoStoredDataResult videoStoredDataResult, @@ -489,15 +493,15 @@ private ExtBidResponse toExtBidResponse(Collection bidderRespons final BidRequest bidRequest = auctionContext.getBidRequest(); final ExtResponseDebug extResponseDebug = debugEnabled - ? ExtResponseDebug.of(toExtHttpCalls(bidderResponses, cacheResult), bidRequest) + ? ExtResponseDebug.of(toExtHttpCalls(bidderInfoResponses, cacheResult), bidRequest) : null; final Map> errors = - toExtBidderErrors(bidderResponses, auctionContext, cacheResult, videoStoredDataResult, bidErrors); + toExtBidderErrors(bidderInfoResponses, auctionContext, cacheResult, videoStoredDataResult, bidErrors); final Map> warnings = debugEnabled ? toExtBidderWarnings(auctionContext) : null; - final Map responseTimeMillis = toResponseTimes(bidderResponses, cacheResult); + final Map responseTimeMillis = toResponseTimes(bidderInfoResponses, cacheResult); return ExtBidResponse.of(extResponseDebug, errors, warnings, responseTimeMillis, bidRequest.getTmax(), null, ExtBidResponsePrebid.of(auctionTimestamp)); @@ -564,11 +568,11 @@ private static CacheServiceResult addNotCachedBids(CacheServiceResult cacheResul return cacheResult; } - private static Map> toExtHttpCalls(Collection bidderResponses, + private static Map> toExtHttpCalls(List bidderResponses, CacheServiceResult cacheResult) { final Map> bidderHttpCalls = bidderResponses.stream() .collect(Collectors.toMap( - BidderResponse::getBidder, + BidderInfoResponse::getBidder, bidderResponse -> ListUtils.emptyIfNull(bidderResponse.getSeatBid().getHttpCalls()))); final DebugHttpCall httpCall = cacheResult.getHttpCall(); @@ -592,7 +596,7 @@ private static ExtHttpCall toExtHttpCall(DebugHttpCall debugHttpCall) { .build(); } - private Map> toExtBidderErrors(Collection bidderResponses, + private Map> toExtBidderErrors(List bidderResponses, AuctionContext auctionContext, CacheServiceResult cacheResult, VideoStoredDataResult videoStoredDataResult, @@ -613,10 +617,12 @@ private Map> toExtBidderErrors(Collection> extractBidderErrors(Collection bidderResponses) { + private static Map> extractBidderErrors( + Collection bidderResponses) { + return bidderResponses.stream() .filter(bidderResponse -> CollectionUtils.isNotEmpty(bidderResponse.getSeatBid().getErrors())) - .collect(Collectors.toMap(BidderResponse::getBidder, + .collect(Collectors.toMap(BidderInfoResponse::getBidder, bidderResponse -> errorsDetails(bidderResponse.getSeatBid().getErrors()))); } @@ -733,10 +739,10 @@ private static Stream asStream(Iterator iterator) { /** * Returns a map with response time by bidders and cache. */ - private static Map toResponseTimes(Collection bidderResponses, + private static Map toResponseTimes(Collection bidderResponses, CacheServiceResult cacheResult) { final Map responseTimeMillis = bidderResponses.stream() - .collect(Collectors.toMap(BidderResponse::getBidder, BidderResponse::getResponseTime)); + .collect(Collectors.toMap(BidderInfoResponse::getBidder, BidderInfoResponse::getResponseTime)); final DebugHttpCall debugHttpCall = cacheResult.getHttpCall(); final Integer cacheResponseTime = debugHttpCall != null ? debugHttpCall.getResponseTimeMillis() : null; @@ -749,7 +755,7 @@ private static Map toResponseTimes(Collection b /** * Returns {@link BidResponse} based on list of {@link BidderResponse}s and {@link CacheServiceResult}. */ - private BidResponse toBidResponse(Map> bidderResponseToTargetingBidInfos, + private BidResponse toBidResponse(List bidderInfoResponses, AuctionContext auctionContext, ExtRequestTargeting targeting, BidRequestCacheInfo requestCacheInfo, @@ -762,10 +768,12 @@ private BidResponse toBidResponse(Map> bi final Account account = auctionContext.getAccount(); final Map> bidErrors = new HashMap<>(); - final List seatBids = bidderResponseToTargetingBidInfos.values().stream() + final List seatBids = bidderInfoResponses.stream() + .map(BidderInfoResponse::getSeatBid) + .map(BidderInfoSeatBid::getBids) .filter(CollectionUtils::isNotEmpty) - .map(targetingBidInfos -> toSeatBid( - targetingBidInfos, + .map(bidInfos -> toSeatBid( + bidInfos, targeting, bidRequest, requestCacheInfo, @@ -778,7 +786,7 @@ private BidResponse toBidResponse(Map> bi final Long auctionTimestamp = eventsContext.getAuctionTimestamp(); final ExtBidResponse extBidResponse = toExtBidResponse( - bidderResponseToTargetingBidInfos.keySet(), + bidderInfoResponses, auctionContext, cacheResult, videoStoredDataResult, @@ -840,7 +848,7 @@ private boolean checkEchoVideoAttrs(Imp imp) { * Creates an OpenRTB {@link SeatBid} for a bidder. It will contain all the bids supplied by a bidder and a "bidder" * extension field populated. */ - private SeatBid toSeatBid(List targetingBidInfos, + private SeatBid toSeatBid(List bidInfos, ExtRequestTargeting targeting, BidRequest bidRequest, BidRequestCacheInfo requestCacheInfo, @@ -850,24 +858,23 @@ private SeatBid toSeatBid(List targetingBidInfos, Map> bidErrors, EventsContext eventsContext) { - final String bidder = targetingBidInfos.stream() - .map(TargetingBidInfo::getBidInfo) + final String bidder = bidInfos.stream() .map(BidInfo::getBidder) .findFirst() // Should never occur .orElseThrow(() -> new IllegalArgumentException("Bidder was not defined for bidInfo")); - final List bids = targetingBidInfos.stream() - .map(targetingBidInfo -> injectAdmWithCacheInfo( - targetingBidInfo, + final List bids = bidInfos.stream() + .map(bidInfo -> injectAdmWithCacheInfo( + bidInfo, requestCacheInfo, bidToCacheInfo, bidRequest, bidErrors )) .filter(Objects::nonNull) - .map(targetingBidInfo -> toBid( - targetingBidInfo, + .map(bidInfo -> toBid( + bidInfo, targeting, bidRequest, videoStoredDataResult.getImpIdToStoredVideo(), @@ -883,19 +890,17 @@ private SeatBid toSeatBid(List targetingBidInfos, .build(); } - private TargetingBidInfo injectAdmWithCacheInfo(TargetingBidInfo targetingBidInfo, - BidRequestCacheInfo requestCacheInfo, - Map bidsWithCacheIds, - BidRequest bidRequest, - Map> bidErrors) { - final BidInfo bidInfo = targetingBidInfo.getBidInfo(); + private BidInfo injectAdmWithCacheInfo(BidInfo bidInfo, + BidRequestCacheInfo requestCacheInfo, + Map bidsWithCacheIds, + BidRequest bidRequest, + Map> bidErrors) { final Bid bid = bidInfo.getBid(); final BidType bidType = bidInfo.getBidType(); final String bidder = bidInfo.getBidder(); final Imp correspondingImp = bidInfo.getCorrespondingImp(); final CacheInfo cacheInfo = bidsWithCacheIds.get(bid); - final boolean isApp = bidRequest.getApp() != null; final String cacheId = cacheInfo != null ? cacheInfo.getCacheId() : null; final String videoCacheId = cacheInfo != null ? cacheInfo.getVideoCacheId() : null; @@ -906,6 +911,7 @@ private TargetingBidInfo injectAdmWithCacheInfo(TargetingBidInfo targetingBidInf modifiedBidAdm = null; } + final boolean isApp = bidRequest.getApp() != null; if (isApp && bidType.equals(BidType.xNative) && modifiedBidAdm != null) { try { modifiedBidAdm = crateNativeMarkup(modifiedBidAdm, correspondingImp); @@ -917,23 +923,22 @@ private TargetingBidInfo injectAdmWithCacheInfo(TargetingBidInfo targetingBidInf } final Bid modifiedBid = bid.toBuilder().adm(modifiedBidAdm).build(); - final BidInfo modifiedBidInfo = bidInfo.toBuilder() + return bidInfo.toBuilder() .bid(modifiedBid) .cacheInfo(cacheInfo) .build(); - return targetingBidInfo.toBuilder().bidInfo(modifiedBidInfo).build(); } /** * Returns an OpenRTB {@link Bid} with "prebid" and "bidder" extension fields populated. */ - private Bid toBid(TargetingBidInfo targetingBidInfo, + private Bid toBid(BidInfo bidInfo, ExtRequestTargeting targeting, BidRequest bidRequest, Map impIdToStoredVideo, Account account, EventsContext eventsContext) { - final BidInfo bidInfo = targetingBidInfo.getBidInfo(); + final TargetingInfo targetingInfo = bidInfo.getTargetingInfo(); final BidType bidType = bidInfo.getBidType(); final String bidder = bidInfo.getBidder(); final Bid bid = bidInfo.getBid(); @@ -945,12 +950,12 @@ private Bid toBid(TargetingBidInfo targetingBidInfo, final boolean isApp = bidRequest.getApp() != null; final Map targetingKeywords; - final String bidderCode = targetingBidInfo.getBidderCode(); - if (targeting != null && targetingBidInfo.isTargetingEnabled() && targetingBidInfo.isBidderWinningBid()) { + final String bidderCode = targetingInfo.getBidderCode(); + if (targeting != null && targetingInfo.isTargetingEnabled() && targetingInfo.isBidderWinningBid()) { final TargetingKeywordsCreator keywordsCreator = resolveKeywordsCreator(bidType, targeting, isApp, bidRequest, account); - final boolean isWinningBid = targetingBidInfo.isWinningBid(); + final boolean isWinningBid = targetingInfo.isWinningBid(); targetingKeywords = keywordsCreator.makeFor(bid, bidderCode, isWinningBid, cacheId, bidType.getName(), videoCacheId); } else { @@ -969,7 +974,7 @@ private Bid toBid(TargetingBidInfo targetingBidInfo, .bidid(bidInfo.getGeneratedBidId()) .type(bidType) .targeting(targetingKeywords) - .targetBidderCode(targetingBidInfo.isAddTargetBidderCode() ? bidderCode : null) + .targetBidderCode(targetingInfo.isAddTargetBidderCode() ? bidderCode : null) .cache(cache) .storedRequestAttributes(storedVideo) .events(events) diff --git a/src/main/java/org/prebid/server/auction/model/BidInfo.java b/src/main/java/org/prebid/server/auction/model/BidInfo.java index a2d9ba2ab8b..301b8097c76 100644 --- a/src/main/java/org/prebid/server/auction/model/BidInfo.java +++ b/src/main/java/org/prebid/server/auction/model/BidInfo.java @@ -17,12 +17,16 @@ public class BidInfo { Imp correspondingImp; + String bidCurrency; + String bidder; BidType bidType; CacheInfo cacheInfo; + TargetingInfo targetingInfo; + public String getBidId() { return generatedBidId != null ? generatedBidId : bid.getId(); } diff --git a/src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java b/src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java new file mode 100644 index 00000000000..77e1a98ccb8 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java @@ -0,0 +1,24 @@ +package org.prebid.server.auction.model; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.prebid.server.bidder.model.BidderInfoSeatBid; +import org.prebid.server.bidder.model.BidderSeatBid; + +/** + * Structure to pass {@link BidderSeatBid} along with bidder name and extra tracking data generated during bidding + */ +@AllArgsConstructor(staticName = "of") +@Value +public class BidderInfoResponse { + + String bidder; + + BidderInfoSeatBid seatBid; + + int responseTime; + + public BidderInfoResponse with(BidderInfoSeatBid seatBid) { + return of(this.bidder, seatBid, this.responseTime); + } +} diff --git a/src/main/java/org/prebid/server/auction/model/TargetingInfo.java b/src/main/java/org/prebid/server/auction/model/TargetingInfo.java new file mode 100644 index 00000000000..a85b1041971 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/TargetingInfo.java @@ -0,0 +1,19 @@ +package org.prebid.server.auction.model; + +import lombok.Builder; +import lombok.Value; + +@Builder(toBuilder = true) +@Value +public class TargetingInfo { + + String bidderCode; + + boolean isTargetingEnabled; + + boolean isWinningBid; + + boolean isBidderWinningBid; + + boolean isAddTargetBidderCode; +} diff --git a/src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java b/src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java new file mode 100644 index 00000000000..2a7f41c52c3 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java @@ -0,0 +1,49 @@ +package org.prebid.server.bidder.model; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.prebid.server.auction.model.BidInfo; +import org.prebid.server.bidder.Bidder; +import org.prebid.server.proto.openrtb.ext.response.ExtHttpCall; + +import java.util.List; + +/** + * Seatid returned by a {@link Bidder}. + *

+ * This is distinct from the {@link com.iab.openrtb.response.SeatBid} so that the prebid-server ext can be passed + * back with type safety. + */ +@AllArgsConstructor(staticName = "of") +@Value +public class BidderInfoSeatBid { + + /** + * List of bids which bidder wishes to make. + */ + List bids; + + /** + * List of debugging info. It should only be populated if the request.test == 1. + * This will become response.ext.debug.httpcalls.{bidder} on the final OpenRTB response + */ + List httpCalls; + + /** + * List of errors produced by bidder. Errors should describe situations which + * make the bid (or no-bid) "less than ideal." Common examples include: + *

+ * 1. Connection issues. + * 2. Imps with Media Types which this Bidder doesn't support. + * 3. Timeout expired before all expected bids were returned. + * 4. The Server sent back an unexpected Response, so some bids were ignored. + *

+ * Any errors will be user-facing in the API. + * Error messages should help publishers understand what might account for "bad" bids. + */ + List errors; + + public BidderInfoSeatBid with(List bids) { + return BidderInfoSeatBid.of(bids, this.httpCalls, this.errors); + } +} diff --git a/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java b/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java index bc63d2418db..86de0bdc66a 100644 --- a/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java +++ b/src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java @@ -31,6 +31,7 @@ import org.prebid.server.auction.model.BidRequestCacheInfo; import org.prebid.server.auction.model.BidderResponse; import org.prebid.server.auction.model.MultiBidConfig; +import org.prebid.server.auction.model.TargetingInfo; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; @@ -245,8 +246,9 @@ public void shouldRequestCacheServiceWithExpectedArguments() { // then final BidInfo bidInfo1 = toBidInfo(bid1, imp1, "bidder1", banner); final BidInfo bidInfo2 = toBidInfo(bid2, imp2, "bidder1", banner); - final BidInfo bidInfo3 = toBidInfo(bid3, imp1, "bidder2", banner); - final BidInfo bidInfo4 = toBidInfo(bid4, imp2, "bidder2", banner); + final TargetingInfo loosedTargetingInfo = toTargetingInfo("bidder2", false); + final BidInfo bidInfo3 = toBidInfo(bid3, null, imp1, "bidder2", banner, loosedTargetingInfo); + final BidInfo bidInfo4 = toBidInfo(bid4, null, imp2, "bidder2", banner, loosedTargetingInfo); ArgumentCaptor contextArgumentCaptor = ArgumentCaptor.forClass(CacheContext.class); verify(cacheService).cacheBidsOpenrtb( argThat(t -> t.containsAll(asList(bidInfo1, bidInfo2, bidInfo3, bidInfo4))), @@ -305,10 +307,11 @@ public void shouldRequestCacheServiceWithWinningBidsOnlyWhenWinningonlyIsTrue() bidResponseCreator.create(bidderResponses, auctionContext, cacheInfo, MULTI_BIDS, false); // then + final BidInfo bidInfo1 = toBidInfo(bid1, imp1, "bidder1", banner); final BidInfo bidInfo2 = toBidInfo(bid2, imp2, "bidder1", banner); verify(cacheService).cacheBidsOpenrtb( - argThat(t -> t.containsAll(asList(bidInfo1, bidInfo2))), + argThat(t -> t.containsAll(asList(bidInfo2, bidInfo1))), same(auctionContext), any(), eq(EventsContext.builder().auctionTimestamp(1000L).build())); @@ -615,7 +618,16 @@ public void shouldUseGeneratedBidIdForEventAndCacheWhenIdGeneratorIsUUIDAndEvent bidResponseCreator.create(bidderResponses, auctionContext, cacheInfo, MULTI_BIDS, false).result(); // then - final BidInfo expectedBidInfo = toBidInfo(bid, generatedBid, imp, bidder, banner); + TargetingInfo.builder() + .bidderCode(bidder) + .isTargetingEnabled(true) + .isWinningBid(true) + .isBidderWinningBid(true) + .isAddTargetBidderCode(false) + .build(); + + final TargetingInfo targetingInfo = toTargetingInfo(bidder, true); + final BidInfo expectedBidInfo = toBidInfo(bid, generatedBid, imp, bidder, banner, targetingInfo); verify(cacheService).cacheBidsOpenrtb(eq(singletonList(expectedBidInfo)), any(), any(), any()); verify(eventsService).createEvent(eq(generatedBid), anyString(), anyString(), anyLong(), anyString()); @@ -2252,6 +2264,7 @@ private static BidInfo toBidInfo(Bid bid, .correspondingImp(correspondingImp) .bidder(bidder) .bidType(bidType) + .targetingInfo(toTargetingInfo(bidder, true)) .build(); } @@ -2259,13 +2272,25 @@ private static BidInfo toBidInfo(Bid bid, String generatedBidId, Imp correspondingImp, String bidder, - BidType bidType) { + BidType bidType, + TargetingInfo targetingInfo) { return BidInfo.builder() .generatedBidId(generatedBidId) .bid(bid) .correspondingImp(correspondingImp) .bidder(bidder) .bidType(bidType) + .targetingInfo(targetingInfo) + .build(); + } + + private static TargetingInfo toTargetingInfo(String bidder, boolean isWinningBid) { + return TargetingInfo.builder() + .bidderCode(bidder) + .isTargetingEnabled(true) + .isWinningBid(isWinningBid) + .isBidderWinningBid(true) + .isAddTargetBidderCode(false) .build(); } From 102d76b6de1439fb2297522336febf6575d033e1 Mon Sep 17 00:00:00 2001 From: DGarbar Date: Thu, 29 Apr 2021 15:18:24 +0300 Subject: [PATCH 4/4] fixed after review --- .../server/auction/BidResponseCreator.java | 104 +++++++++--------- .../prebid/server/auction/model/BidInfo.java | 1 + .../auction/model/BidderInfoResponse.java | 24 ---- .../auction/model/BidderResponseInfo.java | 20 ++++ .../bidder/model/BidderInfoSeatBid.java | 49 --------- .../server/bidder/model/BidderSeatBid.java | 2 +- .../bidder/model/BidderSeatBidInfo.java | 23 ++++ .../org/prebid/server/cache/CacheService.java | 2 +- 8 files changed, 98 insertions(+), 127 deletions(-) delete mode 100644 src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java create mode 100644 src/main/java/org/prebid/server/auction/model/BidderResponseInfo.java delete mode 100644 src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java create mode 100644 src/main/java/org/prebid/server/bidder/model/BidderSeatBidInfo.java diff --git a/src/main/java/org/prebid/server/auction/BidResponseCreator.java b/src/main/java/org/prebid/server/auction/BidResponseCreator.java index 87b2da35a92..97b394da6db 100644 --- a/src/main/java/org/prebid/server/auction/BidResponseCreator.java +++ b/src/main/java/org/prebid/server/auction/BidResponseCreator.java @@ -26,15 +26,15 @@ import org.prebid.server.auction.model.AuctionContext; import org.prebid.server.auction.model.BidInfo; import org.prebid.server.auction.model.BidRequestCacheInfo; -import org.prebid.server.auction.model.BidderInfoResponse; import org.prebid.server.auction.model.BidderResponse; +import org.prebid.server.auction.model.BidderResponseInfo; import org.prebid.server.auction.model.MultiBidConfig; import org.prebid.server.auction.model.TargetingInfo; import org.prebid.server.bidder.BidderCatalog; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderError; -import org.prebid.server.bidder.model.BidderInfoSeatBid; import org.prebid.server.bidder.model.BidderSeatBid; +import org.prebid.server.bidder.model.BidderSeatBidInfo; import org.prebid.server.cache.CacheService; import org.prebid.server.cache.model.CacheContext; import org.prebid.server.cache.model.CacheInfo; @@ -148,6 +148,13 @@ public BidResponseCreator(CacheService cacheService, cacheAssetUrlTemplate = Objects.requireNonNull(cacheService.getCachedAssetURLTemplate()); } + private static int validateTruncateAttrChars(int truncateAttrChars) { + if (truncateAttrChars < 0 || truncateAttrChars > 255) { + throw new IllegalArgumentException("truncateAttrChars must be between 0 and 255"); + } + return truncateAttrChars; + } + /** * Creates an OpenRTB {@link BidResponse} from the bids supplied by the bidder, * including processing of winning bids with cache IDs. @@ -169,17 +176,17 @@ Future create(List bidderResponses, .integration(integrationFrom(auctionContext)) .build(); - final List bidderInfoResponses = toBidderInfoResponses(bidderResponses, imps, account, + final List bidderResponseInfos = toBidderResponseInfos(bidderResponses, imps, account, eventsContext); - if (isEmptyBidderResponses(bidderInfoResponses)) { + if (isEmptyBidderResponses(bidderResponseInfos)) { return Future.succeededFuture(BidResponse.builder() .id(bidRequest.getId()) .cur(bidRequest.getCur().get(0)) .nbr(0) // signal "Unknown Error" .seatbid(Collections.emptyList()) .ext(mapper.mapper().valueToTree(toExtBidResponse( - bidderInfoResponses, + bidderResponseInfos, auctionContext, CacheServiceResult.empty(), VideoStoredDataResult.empty(), @@ -190,7 +197,7 @@ Future create(List bidderResponses, } return cacheBidsAndCreateResponse( - bidderInfoResponses, + bidderResponseInfos, auctionContext, cacheInfo, bidderToMultiBids, @@ -198,27 +205,20 @@ Future create(List bidderResponses, debugEnabled); } - private static int validateTruncateAttrChars(int truncateAttrChars) { - if (truncateAttrChars < 0 || truncateAttrChars > 255) { - throw new IllegalArgumentException("truncateAttrChars must be between 0 and 255"); - } - return truncateAttrChars; - } - /** * Checks whether bidder responses are empty or contain no bids. */ - private static boolean isEmptyBidderResponses(List bidderInfoResponses) { - return bidderInfoResponses.isEmpty() || bidderInfoResponses.stream() - .map(bidderInfoResponse -> bidderInfoResponse.getSeatBid().getBids()) + private static boolean isEmptyBidderResponses(List bidderResponseInfos) { + return bidderResponseInfos.isEmpty() || bidderResponseInfos.stream() + .map(bidderResponseInfo -> bidderResponseInfo.getSeatBid().getBidsInfos()) .allMatch(CollectionUtils::isEmpty); } - private List toBidderInfoResponses(List bidderResponses, + private List toBidderResponseInfos(List bidderResponses, List imps, Account account, EventsContext eventsContext) { - final List result = new ArrayList<>(); + final List result = new ArrayList<>(); for (BidderResponse bidderResponse : bidderResponses) { final String bidder = bidderResponse.getBidder(); @@ -232,12 +232,12 @@ private List toBidderInfoResponses(List bidd bidInfos.add(bidInfo); } - final BidderInfoSeatBid bidderInfoSeatBid = BidderInfoSeatBid.of( + final BidderSeatBidInfo bidderSeatBidInfo = BidderSeatBidInfo.of( bidInfos, seatBid.getHttpCalls(), seatBid.getErrors()); - result.add(BidderInfoResponse.of(bidder, bidderInfoSeatBid, bidderResponse.getResponseTime())); + result.add(BidderResponseInfo.of(bidder, bidderSeatBidInfo, bidderResponse.getResponseTime())); } return result; @@ -296,18 +296,18 @@ private static Imp correspondingImp(Bid bid, List imps) { () -> new PreBidException(String.format("Bid with impId %s doesn't have matched imp", impId))); } - private Future cacheBidsAndCreateResponse(List bidderResponses, + private Future cacheBidsAndCreateResponse(List bidderResponses, AuctionContext auctionContext, BidRequestCacheInfo cacheInfo, Map bidderToMultiBids, EventsContext eventsContext, boolean debugEnabled) { - final List bidderInfoResponses = + final List bidderResponseInfos = toBidderResponseWithTargetingBidInfos(bidderResponses, bidderToMultiBids); - final Set bidInfos = bidderInfoResponses.stream() - .map(BidderInfoResponse::getSeatBid) - .map(BidderInfoSeatBid::getBids) + final Set bidInfos = bidderResponseInfos.stream() + .map(BidderResponseInfo::getSeatBid) + .map(BidderSeatBidInfo::getBidsInfos) .filter(CollectionUtils::isNotEmpty) .flatMap(Collection::stream) .collect(Collectors.toSet()); @@ -326,7 +326,7 @@ private Future cacheBidsAndCreateResponse(List return cacheBids(bidsToCache, auctionContext, cacheInfo, eventsContext) .compose(cacheResult -> videoStoredDataResult(auctionContext) .map(videoStoredDataResult -> toBidResponse( - bidderInfoResponses, + bidderResponseInfos, auctionContext, targeting, cacheInfo, @@ -342,11 +342,11 @@ private static ExtRequestTargeting targeting(BidRequest bidRequest) { return prebid != null ? prebid.getTargeting() : null; } - private List toBidderResponseWithTargetingBidInfos( - List bidderResponses, + private List toBidderResponseWithTargetingBidInfos( + List bidderResponses, Map bidderToMultiBids) { - final Map> bidderResponseToReducedBidInfos = bidderResponses.stream() + final Map> bidderResponseToReducedBidInfos = bidderResponses.stream() .collect(Collectors.toMap( Function.identity(), bidderResponse -> toSortedMultiBidInfo(bidderResponse, bidderToMultiBids))); @@ -384,9 +384,9 @@ private List toBidderResponseWithTargetingBidInfos( .collect(Collectors.toList()); } - private List toSortedMultiBidInfo(BidderInfoResponse bidderResponse, + private List toSortedMultiBidInfo(BidderResponseInfo bidderResponse, Map bidderToMultiBids) { - final List bidInfos = bidderResponse.getSeatBid().getBids(); + final List bidInfos = bidderResponse.getSeatBid().getBidsInfos(); final Map> impIdToBidInfos = bidInfos.stream() .collect(Collectors.groupingBy(bidInfo -> bidInfo.getCorrespondingImp().getId())); @@ -406,18 +406,18 @@ private List sortReducedBidInfo(List bidInfos, int limit) { .collect(Collectors.toList()); } - private BidderInfoResponse injectBidInfoWithTargeting(BidderInfoResponse bidderInfoResponse, + private BidderResponseInfo injectBidInfoWithTargeting(BidderResponseInfo bidderResponseInfo, List bidderBidInfos, Map bidderToMultiBids, Set winningBids, Set winningBidsByBidder) { - final String bidder = bidderInfoResponse.getBidder(); + final String bidder = bidderResponseInfo.getBidder(); final List bidInfosWithTargeting = toBidInfoWithTargeting(bidderBidInfos, bidder, bidderToMultiBids, winningBids, winningBidsByBidder); - final BidderInfoSeatBid seatBid = bidderInfoResponse.getSeatBid(); - final BidderInfoSeatBid modifiedSeatBid = seatBid.with(bidInfosWithTargeting); - return bidderInfoResponse.with(modifiedSeatBid); + final BidderSeatBidInfo seatBid = bidderResponseInfo.getSeatBid(); + final BidderSeatBidInfo modifiedSeatBid = seatBid.with(bidInfosWithTargeting); + return bidderResponseInfo.with(modifiedSeatBid); } private List toBidInfoWithTargeting(List bidderBidInfos, @@ -482,7 +482,7 @@ private long auctionTimestamp(AuctionContext auctionContext) { * Returns {@link ExtBidResponse} object, populated with response time, errors and debug info (if requested) * from all bidders. */ - private ExtBidResponse toExtBidResponse(List bidderInfoResponses, + private ExtBidResponse toExtBidResponse(List bidderResponseInfos, AuctionContext auctionContext, CacheServiceResult cacheResult, VideoStoredDataResult videoStoredDataResult, @@ -493,15 +493,15 @@ private ExtBidResponse toExtBidResponse(List bidderInfoRespo final BidRequest bidRequest = auctionContext.getBidRequest(); final ExtResponseDebug extResponseDebug = debugEnabled - ? ExtResponseDebug.of(toExtHttpCalls(bidderInfoResponses, cacheResult), bidRequest) + ? ExtResponseDebug.of(toExtHttpCalls(bidderResponseInfos, cacheResult), bidRequest) : null; final Map> errors = - toExtBidderErrors(bidderInfoResponses, auctionContext, cacheResult, videoStoredDataResult, bidErrors); + toExtBidderErrors(bidderResponseInfos, auctionContext, cacheResult, videoStoredDataResult, bidErrors); final Map> warnings = debugEnabled ? toExtBidderWarnings(auctionContext) : null; - final Map responseTimeMillis = toResponseTimes(bidderInfoResponses, cacheResult); + final Map responseTimeMillis = toResponseTimes(bidderResponseInfos, cacheResult); return ExtBidResponse.of(extResponseDebug, errors, warnings, responseTimeMillis, bidRequest.getTmax(), null, ExtBidResponsePrebid.of(auctionTimestamp)); @@ -568,11 +568,11 @@ private static CacheServiceResult addNotCachedBids(CacheServiceResult cacheResul return cacheResult; } - private static Map> toExtHttpCalls(List bidderResponses, + private static Map> toExtHttpCalls(List bidderResponses, CacheServiceResult cacheResult) { final Map> bidderHttpCalls = bidderResponses.stream() .collect(Collectors.toMap( - BidderInfoResponse::getBidder, + BidderResponseInfo::getBidder, bidderResponse -> ListUtils.emptyIfNull(bidderResponse.getSeatBid().getHttpCalls()))); final DebugHttpCall httpCall = cacheResult.getHttpCall(); @@ -596,7 +596,7 @@ private static ExtHttpCall toExtHttpCall(DebugHttpCall debugHttpCall) { .build(); } - private Map> toExtBidderErrors(List bidderResponses, + private Map> toExtBidderErrors(List bidderResponses, AuctionContext auctionContext, CacheServiceResult cacheResult, VideoStoredDataResult videoStoredDataResult, @@ -618,11 +618,11 @@ private Map> toExtBidderErrors(List> extractBidderErrors( - Collection bidderResponses) { + Collection bidderResponses) { return bidderResponses.stream() .filter(bidderResponse -> CollectionUtils.isNotEmpty(bidderResponse.getSeatBid().getErrors())) - .collect(Collectors.toMap(BidderInfoResponse::getBidder, + .collect(Collectors.toMap(BidderResponseInfo::getBidder, bidderResponse -> errorsDetails(bidderResponse.getSeatBid().getErrors()))); } @@ -739,10 +739,10 @@ private static Stream asStream(Iterator iterator) { /** * Returns a map with response time by bidders and cache. */ - private static Map toResponseTimes(Collection bidderResponses, + private static Map toResponseTimes(Collection bidderResponses, CacheServiceResult cacheResult) { final Map responseTimeMillis = bidderResponses.stream() - .collect(Collectors.toMap(BidderInfoResponse::getBidder, BidderInfoResponse::getResponseTime)); + .collect(Collectors.toMap(BidderResponseInfo::getBidder, BidderResponseInfo::getResponseTime)); final DebugHttpCall debugHttpCall = cacheResult.getHttpCall(); final Integer cacheResponseTime = debugHttpCall != null ? debugHttpCall.getResponseTimeMillis() : null; @@ -755,7 +755,7 @@ private static Map toResponseTimes(Collection bidderInfoResponses, + private BidResponse toBidResponse(List bidderResponseInfos, AuctionContext auctionContext, ExtRequestTargeting targeting, BidRequestCacheInfo requestCacheInfo, @@ -768,9 +768,9 @@ private BidResponse toBidResponse(List bidderInfoResponses, final Account account = auctionContext.getAccount(); final Map> bidErrors = new HashMap<>(); - final List seatBids = bidderInfoResponses.stream() - .map(BidderInfoResponse::getSeatBid) - .map(BidderInfoSeatBid::getBids) + final List seatBids = bidderResponseInfos.stream() + .map(BidderResponseInfo::getSeatBid) + .map(BidderSeatBidInfo::getBidsInfos) .filter(CollectionUtils::isNotEmpty) .map(bidInfos -> toSeatBid( bidInfos, @@ -786,7 +786,7 @@ private BidResponse toBidResponse(List bidderInfoResponses, final Long auctionTimestamp = eventsContext.getAuctionTimestamp(); final ExtBidResponse extBidResponse = toExtBidResponse( - bidderInfoResponses, + bidderResponseInfos, auctionContext, cacheResult, videoStoredDataResult, diff --git a/src/main/java/org/prebid/server/auction/model/BidInfo.java b/src/main/java/org/prebid/server/auction/model/BidInfo.java index 301b8097c76..4f916dcb307 100644 --- a/src/main/java/org/prebid/server/auction/model/BidInfo.java +++ b/src/main/java/org/prebid/server/auction/model/BidInfo.java @@ -15,6 +15,7 @@ public class BidInfo { Bid bid; + // Can be null Imp correspondingImp; String bidCurrency; diff --git a/src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java b/src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java deleted file mode 100644 index 77e1a98ccb8..00000000000 --- a/src/main/java/org/prebid/server/auction/model/BidderInfoResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.prebid.server.auction.model; - -import lombok.AllArgsConstructor; -import lombok.Value; -import org.prebid.server.bidder.model.BidderInfoSeatBid; -import org.prebid.server.bidder.model.BidderSeatBid; - -/** - * Structure to pass {@link BidderSeatBid} along with bidder name and extra tracking data generated during bidding - */ -@AllArgsConstructor(staticName = "of") -@Value -public class BidderInfoResponse { - - String bidder; - - BidderInfoSeatBid seatBid; - - int responseTime; - - public BidderInfoResponse with(BidderInfoSeatBid seatBid) { - return of(this.bidder, seatBid, this.responseTime); - } -} diff --git a/src/main/java/org/prebid/server/auction/model/BidderResponseInfo.java b/src/main/java/org/prebid/server/auction/model/BidderResponseInfo.java new file mode 100644 index 00000000000..f02a9af5b72 --- /dev/null +++ b/src/main/java/org/prebid/server/auction/model/BidderResponseInfo.java @@ -0,0 +1,20 @@ +package org.prebid.server.auction.model; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.prebid.server.bidder.model.BidderSeatBidInfo; + +@AllArgsConstructor(staticName = "of") +@Value +public class BidderResponseInfo { + + String bidder; + + BidderSeatBidInfo seatBid; + + int responseTime; + + public BidderResponseInfo with(BidderSeatBidInfo seatBid) { + return of(this.bidder, seatBid, this.responseTime); + } +} diff --git a/src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java b/src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java deleted file mode 100644 index 2a7f41c52c3..00000000000 --- a/src/main/java/org/prebid/server/bidder/model/BidderInfoSeatBid.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.prebid.server.bidder.model; - -import lombok.AllArgsConstructor; -import lombok.Value; -import org.prebid.server.auction.model.BidInfo; -import org.prebid.server.bidder.Bidder; -import org.prebid.server.proto.openrtb.ext.response.ExtHttpCall; - -import java.util.List; - -/** - * Seatid returned by a {@link Bidder}. - *

- * This is distinct from the {@link com.iab.openrtb.response.SeatBid} so that the prebid-server ext can be passed - * back with type safety. - */ -@AllArgsConstructor(staticName = "of") -@Value -public class BidderInfoSeatBid { - - /** - * List of bids which bidder wishes to make. - */ - List bids; - - /** - * List of debugging info. It should only be populated if the request.test == 1. - * This will become response.ext.debug.httpcalls.{bidder} on the final OpenRTB response - */ - List httpCalls; - - /** - * List of errors produced by bidder. Errors should describe situations which - * make the bid (or no-bid) "less than ideal." Common examples include: - *

- * 1. Connection issues. - * 2. Imps with Media Types which this Bidder doesn't support. - * 3. Timeout expired before all expected bids were returned. - * 4. The Server sent back an unexpected Response, so some bids were ignored. - *

- * Any errors will be user-facing in the API. - * Error messages should help publishers understand what might account for "bad" bids. - */ - List errors; - - public BidderInfoSeatBid with(List bids) { - return BidderInfoSeatBid.of(bids, this.httpCalls, this.errors); - } -} diff --git a/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java b/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java index 18c396a62c7..2f87e64608c 100644 --- a/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java +++ b/src/main/java/org/prebid/server/bidder/model/BidderSeatBid.java @@ -8,7 +8,7 @@ import java.util.List; /** - * Seatid returned by a {@link Bidder}. + * Seatbid returned by a {@link Bidder}. *

* This is distinct from the {@link com.iab.openrtb.response.SeatBid} so that the prebid-server ext can be passed * back with type safety. diff --git a/src/main/java/org/prebid/server/bidder/model/BidderSeatBidInfo.java b/src/main/java/org/prebid/server/bidder/model/BidderSeatBidInfo.java new file mode 100644 index 00000000000..4d8f7bf575c --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/model/BidderSeatBidInfo.java @@ -0,0 +1,23 @@ +package org.prebid.server.bidder.model; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.prebid.server.auction.model.BidInfo; +import org.prebid.server.proto.openrtb.ext.response.ExtHttpCall; + +import java.util.List; + +@AllArgsConstructor(staticName = "of") +@Value +public class BidderSeatBidInfo { + + List bidsInfos; + + List httpCalls; + + List errors; + + public BidderSeatBidInfo with(List bids) { + return BidderSeatBidInfo.of(bids, this.httpCalls, this.errors); + } +} diff --git a/src/main/java/org/prebid/server/cache/CacheService.java b/src/main/java/org/prebid/server/cache/CacheService.java index c7ce05d6bf3..77cbcff60c4 100644 --- a/src/main/java/org/prebid/server/cache/CacheService.java +++ b/src/main/java/org/prebid/server/cache/CacheService.java @@ -252,7 +252,7 @@ private CacheBid toCacheBid(BidInfo bidInfo, final com.iab.openrtb.response.Bid bid = bidInfo.getBid(); final Integer bidTtl = bid.getExp(); final Imp correspondingImp = bidInfo.getCorrespondingImp(); - final Integer impTtl = correspondingImp.getExp(); + final Integer impTtl = correspondingImp != null ? correspondingImp.getExp() : null; final Integer accountMediaTypeTtl = isVideoBid ? accountCacheTtl.getVideoCacheTtl() : accountCacheTtl.getBannerCacheTtl();