Skip to content

Commit cd8f6c7

Browse files
Pubmatic: Support Alternate Bidder Codes (#4113)
1 parent 9056f21 commit cd8f6c7

File tree

3 files changed

+245
-11
lines changed

3 files changed

+245
-11
lines changed

src/main/java/org/prebid/server/bidder/pubmatic/PubmaticBidder.java

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
import com.iab.openrtb.response.Bid;
1414
import com.iab.openrtb.response.SeatBid;
1515
import org.apache.commons.collections4.CollectionUtils;
16+
import org.apache.commons.lang3.BooleanUtils;
1617
import org.apache.commons.lang3.ObjectUtils;
1718
import org.apache.commons.lang3.StringUtils;
1819
import org.apache.commons.lang3.tuple.Pair;
20+
import org.prebid.server.auction.aliases.AlternateBidder;
21+
import org.prebid.server.auction.aliases.AlternateBidderCodesConfig;
1922
import org.prebid.server.bidder.Bidder;
2023
import org.prebid.server.bidder.model.BidderBid;
2124
import org.prebid.server.bidder.model.BidderCall;
@@ -25,6 +28,7 @@
2528
import org.prebid.server.bidder.model.Result;
2629
import org.prebid.server.bidder.pubmatic.model.request.PubmaticBidderImpExt;
2730
import org.prebid.server.bidder.pubmatic.model.request.PubmaticExtDataAdServer;
31+
import org.prebid.server.bidder.pubmatic.model.request.PubmaticMarketplace;
2832
import org.prebid.server.bidder.pubmatic.model.request.PubmaticWrapper;
2933
import org.prebid.server.bidder.pubmatic.model.response.PubmaticBidExt;
3034
import org.prebid.server.bidder.pubmatic.model.response.PubmaticBidResponse;
@@ -59,6 +63,7 @@
5963
import java.util.Map;
6064
import java.util.Objects;
6165
import java.util.Optional;
66+
import java.util.Set;
6267
import java.util.stream.Collectors;
6368
import java.util.stream.Stream;
6469

@@ -70,6 +75,7 @@ public class PubmaticBidder implements Bidder<BidRequest> {
7075
private static final String IMP_EXT_AD_UNIT_KEY = "dfp_ad_unit_code";
7176
private static final String AD_SERVER_GAM = "gam";
7277
private static final String PREBID = "prebid";
78+
private static final String MARKETPLACE_EXT_REQUEST = "marketplace";
7379
private static final String ACAT_EXT_REQUEST = "acat";
7480
private static final String WRAPPER_EXT_REQUEST = "wrapper";
7581
private static final String BIDDER_NAME = "pubmatic";
@@ -80,6 +86,8 @@ public class PubmaticBidder implements Bidder<BidRequest> {
8086
private static final String IMP_EXT_ADSERVER = "adserver";
8187
private static final List<String> IMP_EXT_DATA_RESERVED_FIELD = List.of(IMP_EXT_PBADSLOT, IMP_EXT_ADSERVER);
8288
private static final String DCTR_VALUE_FORMAT = "%s=%s";
89+
private static final String WILDCARD = "*";
90+
private static final String WILDCARD_ALL = "all";
8391

8492
private final String endpointUrl;
8593
private final JacksonMapper mapper;
@@ -98,10 +106,13 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
98106
PubmaticWrapper wrapper;
99107
final List<String> acat;
100108
final Pair<String, String> displayManagerFields;
109+
final List<String> allowedBidders;
101110

102111
try {
103-
acat = extractAcat(request);
104-
wrapper = extractWrapper(request);
112+
final JsonNode bidderparams = getExtRequestPrebidBidderparams(request);
113+
acat = extractAcat(bidderparams);
114+
wrapper = extractWrapper(bidderparams);
115+
allowedBidders = extractAllowedBidders(request);
105116
displayManagerFields = extractDisplayManagerFields(request.getApp());
106117
} catch (IllegalArgumentException e) {
107118
return Result.withError(BidderError.badInput(e.getMessage()));
@@ -130,12 +141,43 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
130141
return Result.withErrors(errors);
131142
}
132143

133-
final BidRequest modifiedBidRequest = modifyBidRequest(request, validImps, publisherId, wrapper, acat);
144+
final BidRequest modifiedBidRequest = modifyBidRequest(
145+
request, validImps, publisherId, wrapper, acat, allowedBidders);
134146
return Result.of(Collections.singletonList(makeHttpRequest(modifiedBidRequest)), errors);
135147
}
136148

137-
private List<String> extractAcat(BidRequest request) {
138-
final JsonNode bidderParams = getExtRequestPrebidBidderparams(request);
149+
private List<String> extractAllowedBidders(BidRequest request) {
150+
final AlternateBidderCodesConfig alternateBidderCodes = Optional.ofNullable(request.getExt())
151+
.map(ExtRequest::getPrebid)
152+
.map(ExtRequestPrebid::getAlternateBidderCodes)
153+
.orElse(null);
154+
155+
if (alternateBidderCodes == null) {
156+
return null;
157+
}
158+
159+
if (BooleanUtils.isNotTrue(alternateBidderCodes.getEnabled())) {
160+
return Collections.singletonList(BIDDER_NAME);
161+
}
162+
163+
final AlternateBidder alternateBidder = Optional.ofNullable(alternateBidderCodes.getBidders())
164+
.map(bidders -> bidders.get(BIDDER_NAME))
165+
.filter(bidder -> BooleanUtils.isTrue(bidder.getEnabled()))
166+
.orElse(null);
167+
168+
if (alternateBidder == null) {
169+
return Collections.singletonList(BIDDER_NAME);
170+
}
171+
172+
final Set<String> allowedBidderCodes = alternateBidder.getAllowedBidderCodes();
173+
if (allowedBidderCodes == null || allowedBidderCodes.contains(WILDCARD)) {
174+
return Collections.singletonList(WILDCARD_ALL);
175+
}
176+
177+
return Stream.concat(Stream.of(BIDDER_NAME), allowedBidderCodes.stream()).toList();
178+
}
179+
180+
private List<String> extractAcat(JsonNode bidderParams) {
139181
final JsonNode acatNode = bidderParams != null ? bidderParams.get(ACAT_EXT_REQUEST) : null;
140182

141183
return acatNode != null && acatNode.isArray()
@@ -145,9 +187,8 @@ private List<String> extractAcat(BidRequest request) {
145187
: null;
146188
}
147189

148-
private PubmaticWrapper extractWrapper(BidRequest request) {
149-
final JsonNode pubmatic = getExtRequestPrebidBidderparams(request);
150-
final JsonNode wrapperNode = pubmatic != null ? pubmatic.get(WRAPPER_EXT_REQUEST) : null;
190+
private PubmaticWrapper extractWrapper(JsonNode bidderParams) {
191+
final JsonNode wrapperNode = bidderParams != null ? bidderParams.get(WRAPPER_EXT_REQUEST) : null;
151192

152193
return wrapperNode != null && wrapperNode.isObject()
153194
? mapper.mapper().convertValue(wrapperNode, PubmaticWrapper.class)
@@ -437,13 +478,14 @@ private BidRequest modifyBidRequest(BidRequest request,
437478
List<Imp> imps,
438479
String publisherId,
439480
PubmaticWrapper wrapper,
440-
List<String> acat) {
481+
List<String> acat,
482+
List<String> allowedBidders) {
441483

442484
return request.toBuilder()
443485
.imp(imps)
444486
.site(modifySite(request.getSite(), publisherId))
445487
.app(modifyApp(request.getApp(), publisherId))
446-
.ext(modifyExtRequest(wrapper, acat))
488+
.ext(modifyExtRequest(wrapper, acat, allowedBidders))
447489
.build();
448490
}
449491

@@ -469,7 +511,7 @@ private static Publisher modifyPublisher(Publisher publisher, String publisherId
469511
: Publisher.builder().id(publisherId).build();
470512
}
471513

472-
private ExtRequest modifyExtRequest(PubmaticWrapper wrapper, List<String> acat) {
514+
private ExtRequest modifyExtRequest(PubmaticWrapper wrapper, List<String> acat, List<String> allowedBidders) {
473515
final ObjectNode extNode = mapper.mapper().createObjectNode();
474516

475517
if (wrapper != null) {
@@ -480,6 +522,10 @@ private ExtRequest modifyExtRequest(PubmaticWrapper wrapper, List<String> acat)
480522
extNode.putPOJO(ACAT_EXT_REQUEST, acat);
481523
}
482524

525+
if (allowedBidders != null) {
526+
extNode.putPOJO(MARKETPLACE_EXT_REQUEST, PubmaticMarketplace.of(allowedBidders));
527+
}
528+
483529
final ExtRequest newExtRequest = ExtRequest.empty();
484530
return extNode.isEmpty()
485531
? newExtRequest
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.prebid.server.bidder.pubmatic.model.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
import java.util.List;
7+
8+
@Value(staticConstructor = "of")
9+
public class PubmaticMarketplace {
10+
11+
@JsonProperty("allowedbidders")
12+
List<String> allowedBidders;
13+
}

src/test/java/org/prebid/server/bidder/pubmatic/PubmaticBidderTest.java

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.prebid.server.bidder.pubmatic;
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.node.ArrayNode;
46
import com.fasterxml.jackson.databind.node.ObjectNode;
57
import com.fasterxml.jackson.databind.node.TextNode;
68
import com.iab.openrtb.request.App;
@@ -17,6 +19,7 @@
1719
import com.iab.openrtb.response.BidResponse;
1820
import com.iab.openrtb.response.SeatBid;
1921
import io.vertx.core.http.HttpMethod;
22+
import org.assertj.core.api.InstanceOfAssertFactories;
2023
import org.junit.jupiter.api.Test;
2124
import org.prebid.server.VertxTest;
2225
import org.prebid.server.bidder.model.BidderBid;
@@ -38,6 +41,8 @@
3841
import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
3942
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
4043
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
44+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAlternateBidderCodes;
45+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidAlternateBidderCodesBidder;
4146
import org.prebid.server.proto.openrtb.ext.request.pubmatic.ExtImpPubmatic;
4247
import org.prebid.server.proto.openrtb.ext.request.pubmatic.ExtImpPubmaticKeyVal;
4348
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
@@ -50,6 +55,7 @@
5055
import java.math.BigDecimal;
5156
import java.util.List;
5257
import java.util.Map;
58+
import java.util.Set;
5359
import java.util.function.UnaryOperator;
5460

5561
import static java.util.Arrays.asList;
@@ -214,6 +220,175 @@ public void makeHttpRequestsShouldReturnBidRequestExtIfAcatFieldIsValidAndTrimWh
214220
.containsExactly(expectedExtRequest);
215221
}
216222

223+
@Test
224+
public void makeHttpRequestsShouldReturnAllowedBidderCodeWithPubmaticAdded() {
225+
// given
226+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
227+
.alternateBidderCodes(ExtRequestPrebidAlternateBidderCodes.of(true, Map.of("pubmatic",
228+
ExtRequestPrebidAlternateBidderCodesBidder.of(true, Set.of("bidder1", "bidder2")))))
229+
.build());
230+
final BidRequest bidRequest = givenBidRequest(
231+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
232+
233+
// when
234+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
235+
236+
// then
237+
assertThat(result.getErrors()).isEmpty();
238+
assertThat(result.getValue()).hasSize(1).first()
239+
.extracting(HttpRequest::getPayload)
240+
.extracting(BidRequest::getExt)
241+
.extracting(ext -> ext.getProperty("marketplace"))
242+
.extracting(marketplace -> (ArrayNode) marketplace.path("allowedbidders"))
243+
.asInstanceOf(InstanceOfAssertFactories.iterable(JsonNode.class))
244+
.extracting(JsonNode::asText)
245+
.containsOnly("pubmatic", "bidder1", "bidder2");
246+
}
247+
248+
@Test
249+
public void makeHttpRequestsShouldReturnOnlyPubmaticWhenPubmaticCodesAreDisabled() {
250+
// given
251+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
252+
.alternateBidderCodes(ExtRequestPrebidAlternateBidderCodes.of(true, Map.of("pubmatic",
253+
ExtRequestPrebidAlternateBidderCodesBidder.of(false, Set.of("bidder1", "bidder2")))))
254+
.build());
255+
final BidRequest bidRequest = givenBidRequest(
256+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
257+
258+
// when
259+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
260+
261+
// then
262+
assertThat(result.getErrors()).isEmpty();
263+
assertThat(result.getValue()).hasSize(1).first()
264+
.extracting(HttpRequest::getPayload)
265+
.extracting(BidRequest::getExt)
266+
.extracting(ext -> ext.getProperty("marketplace"))
267+
.extracting(marketplace -> (ArrayNode) marketplace.path("allowedbidders"))
268+
.asInstanceOf(InstanceOfAssertFactories.iterable(JsonNode.class))
269+
.extracting(JsonNode::asText)
270+
.containsOnly("pubmatic");
271+
}
272+
273+
@Test
274+
public void makeHttpRequestsShouldReturnOnlyAllWhenPubmaticCodesAreAbsent() {
275+
// given
276+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
277+
.alternateBidderCodes(ExtRequestPrebidAlternateBidderCodes.of(true, Map.of("pubmatic",
278+
ExtRequestPrebidAlternateBidderCodesBidder.of(true, null))))
279+
.build());
280+
final BidRequest bidRequest = givenBidRequest(
281+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
282+
283+
// when
284+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
285+
286+
// then
287+
assertThat(result.getErrors()).isEmpty();
288+
assertThat(result.getValue()).hasSize(1).first()
289+
.extracting(HttpRequest::getPayload)
290+
.extracting(BidRequest::getExt)
291+
.extracting(ext -> ext.getProperty("marketplace"))
292+
.extracting(marketplace -> (ArrayNode) marketplace.path("allowedbidders"))
293+
.asInstanceOf(InstanceOfAssertFactories.iterable(JsonNode.class))
294+
.extracting(JsonNode::asText)
295+
.containsOnly("all");
296+
}
297+
298+
@Test
299+
public void makeHttpRequestsShouldReturnOnlyAllWhenPubmaticCodesHasWildcard() {
300+
// given
301+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
302+
.alternateBidderCodes(ExtRequestPrebidAlternateBidderCodes.of(true, Map.of("pubmatic",
303+
ExtRequestPrebidAlternateBidderCodesBidder.of(true, Set.of("*")))))
304+
.build());
305+
final BidRequest bidRequest = givenBidRequest(
306+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
307+
308+
// when
309+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
310+
311+
// then
312+
assertThat(result.getErrors()).isEmpty();
313+
assertThat(result.getValue()).hasSize(1).first()
314+
.extracting(HttpRequest::getPayload)
315+
.extracting(BidRequest::getExt)
316+
.extracting(ext -> ext.getProperty("marketplace"))
317+
.extracting(marketplace -> (ArrayNode) marketplace.path("allowedbidders"))
318+
.asInstanceOf(InstanceOfAssertFactories.iterable(JsonNode.class))
319+
.extracting(JsonNode::asText)
320+
.containsOnly("all");
321+
}
322+
323+
@Test
324+
public void makeHttpRequestsShouldReturnOnlyPubmaticWhenPubmaticCodesAreAbsent() {
325+
// given
326+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
327+
.alternateBidderCodes(ExtRequestPrebidAlternateBidderCodes.of(true, Map.of()))
328+
.build());
329+
final BidRequest bidRequest = givenBidRequest(
330+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
331+
332+
// when
333+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
334+
335+
// then
336+
assertThat(result.getErrors()).isEmpty();
337+
assertThat(result.getValue()).hasSize(1).first()
338+
.extracting(HttpRequest::getPayload)
339+
.extracting(BidRequest::getExt)
340+
.extracting(ext -> ext.getProperty("marketplace"))
341+
.extracting(marketplace -> (ArrayNode) marketplace.path("allowedbidders"))
342+
.asInstanceOf(InstanceOfAssertFactories.iterable(JsonNode.class))
343+
.extracting(JsonNode::asText)
344+
.containsOnly("pubmatic");
345+
}
346+
347+
@Test
348+
public void makeHttpRequestsShouldReturnOnlyPubmaticWhenAlternateBidderCodesAreDisabled() {
349+
// given
350+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
351+
.alternateBidderCodes(ExtRequestPrebidAlternateBidderCodes.of(false, Map.of()))
352+
.build());
353+
final BidRequest bidRequest = givenBidRequest(
354+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
355+
356+
// when
357+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
358+
359+
// then
360+
assertThat(result.getErrors()).isEmpty();
361+
assertThat(result.getValue()).hasSize(1).first()
362+
.extracting(HttpRequest::getPayload)
363+
.extracting(BidRequest::getExt)
364+
.extracting(ext -> ext.getProperty("marketplace"))
365+
.extracting(marketplace -> (ArrayNode) marketplace.path("allowedbidders"))
366+
.asInstanceOf(InstanceOfAssertFactories.iterable(JsonNode.class))
367+
.extracting(JsonNode::asText)
368+
.containsOnly("pubmatic");
369+
}
370+
371+
@Test
372+
public void makeHttpRequestsShouldReturnNothingWhenAlternateBidderCodeIsAbsent() {
373+
// given
374+
final ExtRequest bidRequestExt = ExtRequest.of(ExtRequestPrebid.builder()
375+
.alternateBidderCodes(null)
376+
.build());
377+
final BidRequest bidRequest = givenBidRequest(
378+
bidRequestBuilder -> bidRequestBuilder.ext(bidRequestExt), identity(), identity());
379+
380+
// when
381+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
382+
383+
// then
384+
assertThat(result.getErrors()).isEmpty();
385+
assertThat(result.getValue()).hasSize(1).first()
386+
.extracting(HttpRequest::getPayload)
387+
.extracting(BidRequest::getExt)
388+
.extracting(ext -> ext.getProperty("marketplace"))
389+
.isNull();
390+
}
391+
217392
@Test
218393
public void makeHttpRequestsShouldMergeWrappersFromImpAndBidRequestExt() {
219394
// given

0 commit comments

Comments
 (0)