Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Ix: Update bidder #3299

Merged
merged 4 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 124 additions & 119 deletions src/main/java/org/prebid/server/bidder/ix/IxBidder.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidChannel;
import org.prebid.server.proto.openrtb.ext.request.ix.ExtImpIx;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
import org.prebid.server.proto.openrtb.ext.response.FledgeAuctionConfig;
import org.prebid.server.util.BidderUtil;
Expand Down Expand Up @@ -107,21 +106,6 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequ
return Result.of(httpRequests, errors);
}

@Override
public CompositeBidderResponse makeBidderResponse(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final IxBidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), IxBidResponse.class);
final List<BidderError> bidderErrors = new ArrayList<>();
return CompositeBidderResponse.builder()
.bids(extractIxBids(bidRequest, bidResponse, bidderErrors))
.fledgeAuctionConfigs(extractFledge(bidResponse))
.errors(bidderErrors)
.build();
} catch (DecodeException e) {
return CompositeBidderResponse.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private ExtImpIx parseImpExt(Imp imp) {
try {
return mapper.mapper().convertValue(imp.getExt(), IX_EXT_TYPE_REFERENCE).getBidder();
Expand Down Expand Up @@ -175,11 +159,7 @@ private UpdateResult<Banner> modifyImpBanner(Banner banner) {
}

private BidRequest modifyBidRequest(BidRequest bidRequest, List<Imp> imps, Set<String> siteIds) {
final String publisherId = Optional.of(siteIds)
.filter(siteIdsSet -> siteIdsSet.size() == 1)
.map(Collection::stream)
.flatMap(Stream::findFirst)
.orElse(null);
final String publisherId = siteIds.size() == 1 ? siteIds.stream().findAny().get() : null;

return bidRequest.toBuilder()
.imp(imps)
Expand All @@ -189,12 +169,36 @@ private BidRequest modifyBidRequest(BidRequest bidRequest, List<Imp> imps, Set<S
.build();
}

private static Site modifySite(Site site, String id) {
return Optional.ofNullable(site)
.map(Site::toBuilder)
.map(builder -> builder.publisher(modifyPublisher(site.getPublisher(), id)))
.map(Site.SiteBuilder::build)
.orElse(null);
}

private static App modifyApp(App app, String id) {
return Optional.ofNullable(app)
.map(App::toBuilder)
.map(builder -> builder.publisher(modifyPublisher(app.getPublisher(), id)))
.map(App.AppBuilder::build)
.orElse(null);
}

private static Publisher modifyPublisher(Publisher publisher, String id) {
return Optional.ofNullable(publisher)
.map(Publisher::toBuilder)
.orElseGet(Publisher::builder)
.id(id)
.build();
}

private ExtRequest modifyRequestExt(ExtRequest extRequest, Set<String> siteIds) {
final ExtRequest modifiedExt;

if (extRequest != null) {
modifiedExt = ExtRequest.of(extRequest.getPrebid());
modifiedExt.addProperties(extRequest.getProperties());
mapper.fillExtension(modifiedExt, extRequest);
} else {
modifiedExt = ExtRequest.empty();
}
Expand All @@ -219,44 +223,41 @@ private IxDiag makeDiagData(ExtRequest extRequest, Set<String> siteIds) {
return IxDiag.of(pbsv, pbjsv, multipleSiteIds);
}

private static Site modifySite(Site site, String id) {
return Optional.ofNullable(site)
.map(Site::toBuilder)
.map(builder -> builder.publisher(modifyPublisher(site.getPublisher(), id)))
.map(Site.SiteBuilder::build)
.orElse(null);
}

private static App modifyApp(App app, String id) {
return Optional.ofNullable(app)
.map(App::toBuilder)
.map(builder -> builder.publisher(modifyPublisher(app.getPublisher(), id)))
.map(App.AppBuilder::build)
.orElse(null);
}

private static Publisher modifyPublisher(Publisher publisher, String id) {
return Optional.ofNullable(publisher)
.map(Publisher::toBuilder)
.orElseGet(Publisher::builder)
.id(id)
.build();
}

@Override
@Deprecated(since = "Not used, since Bidder.makeBidderResponse(...) was overridden.")
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
return Result.withError(BidderError.generic("Invalid method call"));
}

private List<BidderBid> bidsFromResponse(IxBidResponse bidResponse,
BidRequest bidRequest,
List<BidderError> errors) {
@Override
public CompositeBidderResponse makeBidderResponse(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final IxBidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), IxBidResponse.class);
final List<BidderError> bidderErrors = new ArrayList<>();
return CompositeBidderResponse.builder()
.bids(extractBids(bidRequest, bidResponse, bidderErrors))
.fledgeAuctionConfigs(extractFledge(bidResponse))
.errors(bidderErrors)
.build();
} catch (DecodeException e) {
return CompositeBidderResponse.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private List<BidderBid> extractBids(BidRequest bidRequest,
IxBidResponse bidResponse,
List<BidderError> errors) {

if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
return Collections.emptyList();
}

return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(bid -> toBidderBid(bid, bidRequest, bidResponse, errors))
.filter(Objects::nonNull)
.toList();
Expand All @@ -271,68 +272,21 @@ private BidderBid toBidderBid(Bid bid, BidRequest bidRequest, IxBidResponse bidR
return null;
}

final ExtBidPrebidVideo extBidPrebidVideo = bidType == BidType.video
? parseBidExtPrebidVideo(bid.getExt())
: null;

final Bid updatedBid = switch (bidType) {
case video -> updateBidWithVideoAttributes(bid);
case xNative -> bid.toBuilder().adm(updateBidAdmWithNativeAttributes(bid.getAdm())).build();
case video -> updateBidWithVideoAttributes(bid, extBidPrebidVideo);
case xNative -> updateBidAdmWithNativeAttributes(bid);
default -> bid;
};

return BidderBid.of(updatedBid, bidType, bidResponse.getCur());
}

private Bid updateBidWithVideoAttributes(Bid bid) {
final ObjectNode bidExt = bid.getExt();
final ExtBidPrebid extPrebid = bidExt != null ? parseBidExt(bidExt) : null;
final ExtBidPrebidVideo extVideo = extPrebid != null ? extPrebid.getVideo() : null;
final Bid updatedBid;
if (extVideo != null) {
final Bid.BidBuilder bidBuilder = bid.toBuilder();
bidBuilder.ext(resolveBidExt(extVideo.getDuration()));
if (CollectionUtils.isEmpty(bid.getCat())) {
bidBuilder.cat(Collections.singletonList(extVideo.getPrimaryCategory())).build();
}
updatedBid = bidBuilder.build();
} else {
updatedBid = bid;
}
return updatedBid;
}

private String updateBidAdmWithNativeAttributes(String adm) {
final NativeV11Wrapper nativeV11 = parseBidAdm(adm, NativeV11Wrapper.class);
final Response responseV11 = ObjectUtil.getIfNotNull(nativeV11, NativeV11Wrapper::getNativeResponse);
final boolean isV11 = responseV11 != null;
final Response response = isV11 ? responseV11 : parseBidAdm(adm, Response.class);
final List<EventTracker> trackers = ObjectUtil.getIfNotNull(response, Response::getEventtrackers);
final String updatedAdm = CollectionUtils.isNotEmpty(trackers) ? mapper.encodeToString(isV11
? NativeV11Wrapper.of(mergeNativeImpTrackers(response, trackers))
: mergeNativeImpTrackers(response, trackers))
: null;

return updatedAdm != null ? updatedAdm : adm;
}

private <T> T parseBidAdm(String adm, Class<T> clazz) {
try {
return mapper.decodeValue(adm, clazz);
} catch (IllegalArgumentException | DecodeException e) {
return null;
}
}

private static Response mergeNativeImpTrackers(Response response, List<EventTracker> eventTrackers) {
final List<EventTracker> impressionAndImageTrackers = eventTrackers.stream()
.filter(tracker -> Objects.equals(tracker.getMethod(), EventType.IMPRESSION.getValue())
|| Objects.equals(tracker.getEvent(), EventTrackingMethod.IMAGE.getValue()))
.toList();
final List<String> impTrackers = Stream.concat(
impressionAndImageTrackers.stream().map(EventTracker::getUrl),
response.getImptrackers().stream())
.distinct()
.toList();

return response.toBuilder()
.imptrackers(impTrackers)
return BidderBid.builder()
.bid(updatedBid)
.type(bidType)
.bidCurrency(bidResponse.getCur())
.videoInfo(bidType == BidType.video ? videoInfo(extBidPrebidVideo) : null)
.build();
}

Expand Down Expand Up @@ -379,26 +333,77 @@ private static BidType getBidTypeFromImp(List<Imp> imps, String impId) {
throw new PreBidException("Unmatched impression id " + impId);
}

private ExtBidPrebid parseBidExt(ObjectNode bidExt) {
private ExtBidPrebidVideo parseBidExtPrebidVideo(ObjectNode bidExt) {
if (bidExt == null) {
return null;
}

try {
return mapper.mapper().treeToValue(bidExt, ExtBidPrebid.class);
return mapper.mapper().treeToValue(bidExt.path("prebid").path("video"), ExtBidPrebidVideo.class);
} catch (JsonProcessingException e) {
return null;
}
}

private ObjectNode resolveBidExt(Integer duration) {
return mapper.mapper().valueToTree(ExtBidPrebid.builder()
.video(ExtBidPrebidVideo.of(duration, null))
.build());
private Bid updateBidWithVideoAttributes(Bid bid, ExtBidPrebidVideo extBidPrebidVideo) {
return CollectionUtils.isEmpty(bid.getCat()) && extBidPrebidVideo != null
? bid.toBuilder()
.cat(Collections.singletonList(extBidPrebidVideo.getPrimaryCategory()))
.build()
: bid;
}

private Bid updateBidAdmWithNativeAttributes(Bid bid) {
final String adm = bid.getAdm();
final NativeV11Wrapper nativeV11 = parseBidAdm(adm, NativeV11Wrapper.class);
final Response responseV11 = ObjectUtil.getIfNotNull(nativeV11, NativeV11Wrapper::getNativeResponse);
final boolean isV11 = responseV11 != null;
final Response response = isV11 ? responseV11 : parseBidAdm(adm, Response.class);
final List<EventTracker> trackers = ObjectUtil.getIfNotNull(response, Response::getEventtrackers);
final String updatedAdm = CollectionUtils.isNotEmpty(trackers)
? mergeNativeImpTrackers(isV11, response, trackers)
: null;

return updatedAdm != null
? bid.toBuilder().adm(updatedAdm).build()
: bid;
}

private <T> T parseBidAdm(String adm, Class<T> clazz) {
try {
return mapper.decodeValue(adm, clazz);
} catch (IllegalArgumentException | DecodeException e) {
return null;
}
}

private String mergeNativeImpTrackers(boolean isV11, Response response, List<EventTracker> trackers) {
return mapper.encodeToString(isV11
? NativeV11Wrapper.of(mergeNativeImpTrackers(response, trackers))
: mergeNativeImpTrackers(response, trackers));
}

private List<BidderBid> extractIxBids(BidRequest bidRequest,
IxBidResponse bidResponse,
List<BidderError> bidderErrors) {
return bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())
? Collections.emptyList()
: bidsFromResponse(bidResponse, bidRequest, bidderErrors);
private static Response mergeNativeImpTrackers(Response response, List<EventTracker> eventTrackers) {
return response.toBuilder()
.imptrackers(Stream.concat(
eventTrackers.stream()
.filter(IxBidder::isImpTracker)
.map(EventTracker::getUrl),
response.getImptrackers().stream())
.distinct()
.toList())
.build();
}

private static boolean isImpTracker(EventTracker tracker) {
return Objects.equals(tracker.getMethod(), EventType.IMPRESSION.getValue())
|| Objects.equals(tracker.getEvent(), EventTrackingMethod.IMAGE.getValue());
}

private static ExtBidPrebidVideo videoInfo(ExtBidPrebidVideo extBidPrebidVideo) {
return extBidPrebidVideo != null
? ExtBidPrebidVideo.of(extBidPrebidVideo.getDuration(), null)
: null;
}

private List<FledgeAuctionConfig> extractFledge(IxBidResponse bidResponse) {
Expand Down
12 changes: 5 additions & 7 deletions src/test/java/org/prebid/server/bidder/ix/IxBidderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ public void makeBidderResponseShouldReturnErrorIfImpNotMatched() throws JsonProc
}

@Test
public void makeBidderResponseShouldReturnBidWithVideoExt() throws JsonProcessingException {
public void makeBidderResponseShouldReturnBidWithVideoInfo() throws JsonProcessingException {
// given
final Video video = Video.builder().build();
final BidRequest bidRequest = BidRequest.builder()
Expand All @@ -445,7 +445,7 @@ public void makeBidderResponseShouldReturnBidWithVideoExt() throws JsonProcessin
givenBidResponse(
bidBuilder -> bidBuilder
.impid("123")
.ext(mapper.valueToTree(ExtBidPrebid.builder()
.ext(mapper.createObjectNode().putPOJO("prebid", ExtBidPrebid.builder()
.video(ExtBidPrebidVideo.of(1, "cat"))
.build())))));

Expand All @@ -455,10 +455,7 @@ public void makeBidderResponseShouldReturnBidWithVideoExt() throws JsonProcessin
// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getBids())
.extracting(BidderBid::getBid)
.extracting(Bid::getExt)
.extracting(node -> mapper.treeToValue(node, ExtBidPrebid.class))
.extracting(ExtBidPrebid::getVideo)
.extracting(BidderBid::getVideoInfo)
.extracting(ExtBidPrebidVideo::getDuration, ExtBidPrebidVideo::getPrimaryCategory)
.containsExactly(tuple(1, null));
}
Expand Down Expand Up @@ -662,7 +659,8 @@ public void makeBidderResponseShouldReturnVideoBidIfMTypeIsTwo() throws JsonProc
.imp(singletonList(Imp.builder()
.id("123")
.banner(banner)
.video(video).build()))
.video(video)
.build()))
.build();
final BidderCall<BidRequest> httpCall = givenHttpCall(
bidRequest,
Expand Down
Loading