Skip to content

Commit 8919e8c

Browse files
authored
Inject stored video to bid.ext.prebid.storedrequestattributes (prebid#537)
1 parent 7771256 commit 8919e8c

23 files changed

+651
-104
lines changed

src/main/java/org/prebid/server/auction/BidResponseCreator.java

Lines changed: 121 additions & 36 deletions
Large diffs are not rendered by default.

src/main/java/org/prebid/server/auction/StoredRequestProcessor.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fasterxml.jackson.databind.node.ObjectNode;
55
import com.iab.openrtb.request.BidRequest;
66
import com.iab.openrtb.request.Imp;
7+
import com.iab.openrtb.request.Video;
78
import io.vertx.core.Future;
89
import io.vertx.core.json.Json;
910
import org.apache.commons.collections4.CollectionUtils;
@@ -19,6 +20,7 @@
1920
import org.prebid.server.proto.openrtb.ext.request.ExtStoredRequest;
2021
import org.prebid.server.settings.ApplicationSettings;
2122
import org.prebid.server.settings.model.StoredDataResult;
23+
import org.prebid.server.settings.model.VideoStoredDataResult;
2224
import org.prebid.server.util.JsonMergeUtil;
2325

2426
import java.util.ArrayList;
@@ -30,6 +32,7 @@
3032
import java.util.Objects;
3133
import java.util.Set;
3234
import java.util.function.Function;
35+
import java.util.stream.Collectors;
3336

3437
/**
3538
* Executes stored request processing
@@ -105,6 +108,59 @@ Future<BidRequest> processAmpRequest(String ampRequestId) {
105108
return storedRequestsToBidRequest(ampStoredDataFuture, bidRequest, ampRequestId, Collections.emptyMap());
106109
}
107110

111+
/**
112+
* Fetches stored request.video and map existing values to imp.id.
113+
*/
114+
Future<VideoStoredDataResult> videoStoredDataResult(List<Imp> imps, List<String> errors, Timeout timeout) {
115+
final Map<String, String> storedIdToImpId =
116+
mapStoredRequestHolderToStoredRequestId(imps, StoredRequestProcessor::getStoredRequestFromImp)
117+
.entrySet().stream()
118+
.collect(Collectors.toMap(Map.Entry::getValue,
119+
impIdToStoredId -> impIdToStoredId.getKey().getId()));
120+
121+
return applicationSettings.getStoredData(Collections.emptySet(), storedIdToImpId.keySet(), timeout)
122+
.map(storedDataResult -> makeVideoStoredDataResult(storedDataResult, storedIdToImpId, errors));
123+
}
124+
125+
private static VideoStoredDataResult makeVideoStoredDataResult(StoredDataResult storedDataResult,
126+
Map<String, String> storedIdToImpId,
127+
List<String> errors) {
128+
final Map<String, String> storedIdToStoredImp = storedDataResult.getStoredIdToImp();
129+
final Map<String, Video> impIdToStoredVideo = new HashMap<>();
130+
131+
for (Map.Entry<String, String> storedIdToImpIdEntry : storedIdToImpId.entrySet()) {
132+
final String storedId = storedIdToImpIdEntry.getKey();
133+
final String storedImp = storedIdToStoredImp.get(storedId);
134+
if (storedImp == null) {
135+
errors.add(String.format("No stored Imp for stored id %s", storedId));
136+
continue;
137+
}
138+
139+
final String impId = storedIdToImpIdEntry.getValue();
140+
final Video video = parseVideoFromImp(storedImp);
141+
if (video == null) {
142+
errors.add(String.format("No stored video found for Imp with id %s", impId));
143+
continue;
144+
}
145+
146+
impIdToStoredVideo.put(impId, video);
147+
}
148+
149+
return VideoStoredDataResult.of(impIdToStoredVideo, errors);
150+
}
151+
152+
private static Video parseVideoFromImp(String storedJson) {
153+
if (StringUtils.isNotBlank(storedJson)) {
154+
try {
155+
final Imp imp = Json.mapper.readValue(storedJson, Imp.class);
156+
return imp.getVideo();
157+
} catch (JsonProcessingException e) {
158+
return null;
159+
}
160+
}
161+
return null;
162+
}
163+
108164
private Future<BidRequest> storedRequestsToBidRequest(Future<StoredDataResult> storedDataFuture,
109165
BidRequest bidRequest, String storedBidRequestId,
110166
Map<Imp, String> impsToStoredRequestId) {

src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtImpPrebid.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public class ExtImpPrebid {
1818
*/
1919
ExtStoredRequest storedrequest;
2020

21+
/**
22+
* Defines the contract for bidrequest.imp[i].ext.prebid.options
23+
*/
24+
ExtOptions options;
25+
2126
/**
2227
* Defines the contract for bidrequest.imp[i].ext.prebid.storedAuctionResponse
2328
*/
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.prebid.server.proto.openrtb.ext.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Value;
6+
7+
/**
8+
* ExtRegs defines the contract for ext.prebid.options
9+
*/
10+
@AllArgsConstructor(staticName = "of")
11+
@Value
12+
public class ExtOptions {
13+
14+
@JsonProperty("echovideoattrs")
15+
Boolean echoVideoAttrs;
16+
}

src/main/java/org/prebid/server/proto/openrtb/ext/response/ExtBidPrebid.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.prebid.server.proto.openrtb.ext.response;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.iab.openrtb.request.Video;
35
import lombok.AllArgsConstructor;
46
import lombok.Value;
57

@@ -18,6 +20,9 @@ public class ExtBidPrebid {
1820

1921
ExtResponseCache cache;
2022

23+
@JsonProperty("storedrequestattributes")
24+
Video storedRequestAttributes;
25+
2126
Events events;
2227

2328
ExtBidPrebidVideo video;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.prebid.server.settings.model;
2+
3+
import com.iab.openrtb.request.Video;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Value;
6+
7+
import java.util.Collections;
8+
import java.util.List;
9+
import java.util.Map;
10+
11+
@AllArgsConstructor(staticName = "of")
12+
@Value
13+
public class VideoStoredDataResult {
14+
15+
private static final VideoStoredDataResult EMPTY = VideoStoredDataResult.of(Collections.emptyMap(),
16+
Collections.emptyList());
17+
18+
Map<String, Video> impIdToStoredVideo;
19+
20+
List<String> errors;
21+
22+
public static VideoStoredDataResult empty() {
23+
return EMPTY;
24+
}
25+
}

src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,10 @@ HttpBidderRequester httpBidderRequester(
361361
BidResponseCreator bidResponseCreator(
362362
CacheService cacheService,
363363
BidderCatalog bidderCatalog,
364-
EventsService eventsService) {
364+
EventsService eventsService,
365+
StoredRequestProcessor storedRequestProcessor) {
365366

366-
return new BidResponseCreator(cacheService, bidderCatalog, eventsService);
367+
return new BidResponseCreator(cacheService, bidderCatalog, eventsService, storedRequestProcessor);
367368
}
368369

369370
@Bean

src/test/java/org/prebid/server/auction/BidResponseCreatorTest.java

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@
4343
import org.prebid.server.execution.TimeoutFactory;
4444
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
4545
import org.prebid.server.proto.openrtb.ext.request.ExtGranularityRange;
46+
import org.prebid.server.proto.openrtb.ext.request.ExtImp;
47+
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid;
4648
import org.prebid.server.proto.openrtb.ext.request.ExtMediaTypePriceGranularity;
49+
import org.prebid.server.proto.openrtb.ext.request.ExtOptions;
4750
import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity;
4851
import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting;
52+
import org.prebid.server.proto.openrtb.ext.request.ExtStoredRequest;
4953
import org.prebid.server.proto.openrtb.ext.response.CacheAsset;
5054
import org.prebid.server.proto.openrtb.ext.response.Events;
5155
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
@@ -54,12 +58,14 @@
5458
import org.prebid.server.proto.openrtb.ext.response.ExtHttpCall;
5559
import org.prebid.server.proto.openrtb.ext.response.ExtResponseCache;
5660
import org.prebid.server.settings.model.Account;
61+
import org.prebid.server.settings.model.VideoStoredDataResult;
5762

5863
import java.io.IOException;
5964
import java.math.BigDecimal;
6065
import java.time.Clock;
6166
import java.time.Instant;
6267
import java.time.ZoneId;
68+
import java.util.Arrays;
6369
import java.util.List;
6470
import java.util.Map;
6571

@@ -98,6 +104,8 @@ public class BidResponseCreatorTest extends VertxTest {
98104
private BidderCatalog bidderCatalog;
99105
@Mock
100106
private EventsService eventsService;
107+
@Mock
108+
private StoredRequestProcessor storedRequestProcessor;
101109

102110
private Timeout timeout;
103111

@@ -109,7 +117,10 @@ public void setUp() {
109117
given(cacheService.getEndpointPath()).willReturn("testPath");
110118
given(cacheService.getCachedAssetURLTemplate()).willReturn("uuid=");
111119

112-
bidResponseCreator = new BidResponseCreator(cacheService, bidderCatalog, eventsService);
120+
given(storedRequestProcessor.videoStoredDataResult(any(), any(), any()))
121+
.willReturn(Future.succeededFuture(VideoStoredDataResult.empty()));
122+
123+
bidResponseCreator = new BidResponseCreator(cacheService, bidderCatalog, eventsService, storedRequestProcessor);
113124

114125
timeout = new TimeoutFactory(Clock.fixed(Instant.now(), ZoneId.systemDefault())).create(500);
115126
}
@@ -382,7 +393,7 @@ public void shouldSetExpectedResponseSeatBidAndBidFields() {
382393
.price(BigDecimal.ONE)
383394
.adm("adm")
384395
.ext(mapper.valueToTree(ExtPrebid.of(
385-
ExtBidPrebid.of(banner, null, null, null, null), singletonMap("bidExt", 1))))
396+
ExtBidPrebid.of(banner, null, null, null, null, null), singletonMap("bidExt", 1))))
386397
.build()))
387398
.build());
388399

@@ -516,7 +527,7 @@ public void shouldTolerateMissingExtInSeatBidAndBid() {
516527
.containsOnly(Bid.builder()
517528
.id("bidId")
518529
.price(BigDecimal.ONE)
519-
.ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.of(banner, null, null, null, null), null)))
530+
.ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.of(banner, null, null, null, null, null), null)))
520531
.build());
521532

522533
verify(cacheService, never()).cacheBidsOpenrtb(anyList(), anyList(), any(), any(), any());
@@ -924,6 +935,106 @@ public void shouldPopulateBidResponseExtension() throws JsonProcessingException
924935
verify(cacheService).cacheBidsOpenrtb(anyList(), anyList(), any(), any(), any());
925936
}
926937

938+
@Test
939+
public void impToStoredVideoJsonShouldTolerateWhenStoredVideoFetchIsFailed() {
940+
// given
941+
final Imp imp = Imp.builder().id("impId1").ext(
942+
Json.mapper.valueToTree(
943+
ExtImp.of(ExtImpPrebid.of(ExtStoredRequest.of("st1"), ExtOptions.of(true), null, null), null)))
944+
.build();
945+
final BidRequest bidRequest = givenBidRequest(imp);
946+
947+
final Bid bid = Bid.builder().id("bidId1").impid("impId1").price(BigDecimal.valueOf(5.67)).build();
948+
final List<BidderResponse> bidderResponses = singletonList(BidderResponse.of("bidder1",
949+
givenSeatBid(BidderBid.of(bid, banner, "USD")), 100));
950+
951+
given(storedRequestProcessor.videoStoredDataResult(any(), any(), any())).willReturn(Future.failedFuture("Fetch failed"));
952+
953+
// when
954+
final Future<BidResponse> result =
955+
bidResponseCreator.create(bidderResponses, bidRequest, null, CACHE_INFO, ACCOUNT, timeout, false);
956+
957+
// then
958+
verify(storedRequestProcessor).videoStoredDataResult(eq(singletonList(imp)), any(), eq(timeout));
959+
960+
assertThat(result.succeeded()).isTrue();
961+
}
962+
963+
@Test
964+
public void impToStoredVideoJsonShouldInjectStoredVideoWhenExtOptionsIsTrueAndVideoNotEmpty() {
965+
// given
966+
final Imp imp1 = Imp.builder().id("impId1").ext(
967+
Json.mapper.valueToTree(
968+
ExtImp.of(ExtImpPrebid.of(ExtStoredRequest.of("st1"), ExtOptions.of(true), null, null), null)))
969+
.build();
970+
final Imp imp2 = Imp.builder().id("impId2").ext(
971+
Json.mapper.valueToTree(
972+
ExtImp.of(ExtImpPrebid.of(ExtStoredRequest.of("st2"), ExtOptions.of(false), null, null), null)))
973+
.build();
974+
final Imp imp3 = Imp.builder().id("impId3").ext(
975+
Json.mapper.valueToTree(
976+
ExtImp.of(ExtImpPrebid.of(ExtStoredRequest.of("st3"), ExtOptions.of(true), null, null), null)))
977+
.build();
978+
final BidRequest bidRequest = givenBidRequest(imp1, imp2, imp3);
979+
980+
final Bid bid1 = Bid.builder().id("bidId1").impid("impId1").price(BigDecimal.valueOf(5.67)).build();
981+
final Bid bid2 = Bid.builder().id("bidId2").impid("impId2").price(BigDecimal.valueOf(2)).build();
982+
final Bid bid3 = Bid.builder().id("bidId3").impid("impId3").price(BigDecimal.valueOf(3)).build();
983+
final List<BidderBid> bidderBids = Arrays.asList(
984+
BidderBid.of(bid1, banner, "USD"),
985+
BidderBid.of(bid2, banner, "USD"),
986+
BidderBid.of(bid3, banner, "USD"));
987+
final List<BidderResponse> bidderResponses = singletonList(
988+
BidderResponse.of("bidder1", BidderSeatBid.of(bidderBids, emptyList(), emptyList()), 100));
989+
990+
final Video storedVideo = Video.builder().maxduration(100).h(2).w(2).build();
991+
given(storedRequestProcessor.videoStoredDataResult(any(), any(), any()))
992+
.willReturn(Future.succeededFuture(VideoStoredDataResult.of(singletonMap("impId1", storedVideo), emptyList())));
993+
994+
// when
995+
final Future<BidResponse> result =
996+
bidResponseCreator.create(bidderResponses, bidRequest, null, CACHE_INFO, ACCOUNT, timeout, false);
997+
998+
// then
999+
verify(storedRequestProcessor).videoStoredDataResult(eq(Arrays.asList(imp1, imp3)), any(), eq(timeout));
1000+
1001+
assertThat(result.result().getSeatbid())
1002+
.flatExtracting(SeatBid::getBid).hasSize(3)
1003+
.extracting(extractedBid -> toExtPrebid(extractedBid.getExt()).getPrebid().getStoredRequestAttributes())
1004+
.containsOnly(storedVideo, null, null);
1005+
}
1006+
1007+
@Test
1008+
public void impToStoredVideoJsonShouldAddErrorsWithPrebidBidderWhenStoredVideoRequestFailed() {
1009+
// given
1010+
final Imp imp1 = Imp.builder().id("impId1").ext(
1011+
Json.mapper.valueToTree(
1012+
ExtImp.of(ExtImpPrebid.of(ExtStoredRequest.of("st1"), ExtOptions.of(true), null, null), null)))
1013+
.build();
1014+
final BidRequest bidRequest = givenBidRequest(imp1);
1015+
1016+
final Bid bid1 = Bid.builder().id("bidId1").impid("impId1").price(BigDecimal.valueOf(5.67)).build();
1017+
final List<BidderBid> bidderBids = Arrays.asList(
1018+
BidderBid.of(bid1, banner, "USD"));
1019+
final List<BidderResponse> bidderResponses = singletonList(
1020+
BidderResponse.of("bidder1", BidderSeatBid.of(bidderBids, emptyList(), emptyList()), 100));
1021+
1022+
given(storedRequestProcessor.videoStoredDataResult(any(), any(), any()))
1023+
.willReturn(Future.failedFuture("Bad timeout"));
1024+
1025+
// when
1026+
final Future<BidResponse> result =
1027+
bidResponseCreator.create(bidderResponses, bidRequest, null, CACHE_INFO, ACCOUNT, timeout, false);
1028+
1029+
// then
1030+
verify(storedRequestProcessor).videoStoredDataResult(eq(singletonList(imp1)), any(), eq(timeout));
1031+
1032+
assertThat(result.result().getExt()).isEqualTo(
1033+
mapper.valueToTree(ExtBidResponse.of(null, singletonMap(
1034+
"prebid", singletonList(ExtBidderError.of(BidderError.Type.generic.getCode(),
1035+
"Bad timeout"))), singletonMap("bidder1", 100), 1000L, null)));
1036+
}
1037+
9271038
@Test
9281039
public void shouldProcessRequestAndAddErrorAboutDeprecatedBidder() {
9291040
// given

src/test/java/org/prebid/server/auction/ExchangeServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,7 @@ private static Bid givenBid(Function<Bid.BidBuilder, Bid.BidBuilder> bidBuilder)
16811681
return bidBuilder.apply(Bid.builder()
16821682
.id("bidId")
16831683
.price(BigDecimal.ONE)
1684-
.ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.of(null, null, null, null, null), null))))
1684+
.ext(mapper.valueToTree(ExtPrebid.of(ExtBidPrebid.of(null, null, null, null, null, null), null))))
16851685
.build();
16861686
}
16871687

0 commit comments

Comments
 (0)