diff --git a/src/main/java/org/prebid/server/bidder/pubmatic/PubmaticBidder.java b/src/main/java/org/prebid/server/bidder/pubmatic/PubmaticBidder.java index fb8de81d5a2..56c9ba372f9 100644 --- a/src/main/java/org/prebid/server/bidder/pubmatic/PubmaticBidder.java +++ b/src/main/java/org/prebid/server/bidder/pubmatic/PubmaticBidder.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; import com.iab.openrtb.request.Banner; @@ -32,13 +33,11 @@ import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.pubmatic.ExtImpPubmatic; -import org.prebid.server.proto.openrtb.ext.request.pubmatic.ExtImpPubmaticKeyVal; 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.util.HttpUtil; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -51,6 +50,9 @@ public class PubmaticBidder implements Bidder { private static final Logger logger = LoggerFactory.getLogger(PubmaticBidder.class); private static final String PREBID = "prebid"; + private static final String DCTR_KEY_NAME = "key_val"; + private static final String PM_ZONE_ID_KEY_NAME = "pmZoneId"; + private static final String PM_ZONE_ID_OLD_KEY_NAME = "pmZoneID"; private static final TypeReference> PUBMATIC_EXT_TYPE_REFERENCE = new TypeReference>() { }; @@ -163,29 +165,33 @@ private Imp modifyImp(Imp imp, ExtImpPubmatic extImpPubmatic) throws PreBidExcep } if (CollectionUtils.isNotEmpty(extImpPubmatic.getKeywords())) { - modifiedImp.ext(makeKeywords(extImpPubmatic.getKeywords())); + modifiedImp.ext(makeKeywords(extImpPubmatic)); } else { modifiedImp.ext(null); } return modifiedImp.build(); } - private ObjectNode makeKeywords(List keywords) { - final List eachKv = new ArrayList<>(); - for (ExtImpPubmaticKeyVal keyVal : keywords) { - if (CollectionUtils.isEmpty(keyVal.getValue())) { - logger.error(String.format("No values present for key = %s", keyVal.getKey())); - } else { - eachKv.add(String.format("\"%s\":\"%s\"", keyVal.getKey(), - String.join(",", keyVal.getValue()))); + private ObjectNode makeKeywords(ExtImpPubmatic extImpPubmatic) { + final ObjectNode keywordsNode = mapper.mapper().createObjectNode(); + extImpPubmatic.getKeywords().forEach(keyword -> { + if (CollectionUtils.isEmpty(keyword.getValue())) { + logger.error(String.format("No values present for key = %s", keyword.getValue())); + return; } + keywordsNode.put(keyword.getKey(), String.join(",", keyword.getValue())); + }); + final JsonNode pmZoneIdKeyWords = keywordsNode.remove(PM_ZONE_ID_OLD_KEY_NAME); + if (StringUtils.isNotEmpty(extImpPubmatic.getPmzoneid())) { + keywordsNode.put(PM_ZONE_ID_KEY_NAME, extImpPubmatic.getPmzoneid()); + } else if (pmZoneIdKeyWords != null) { + keywordsNode.set(PM_ZONE_ID_KEY_NAME, pmZoneIdKeyWords); } - final String keywordsString = "{" + String.join(",", eachKv) + "}"; - try { - return mapper.mapper().readValue(keywordsString, ObjectNode.class); - } catch (IOException e) { - throw new PreBidException(String.format("Failed to create keywords with error: %s", e.getMessage()), e); + if (StringUtils.isNotEmpty(extImpPubmatic.getDctr())) { + keywordsNode.put(DCTR_KEY_NAME, extImpPubmatic.getDctr()); } + + return keywordsNode; } private HttpRequest makeRequest(BidRequest bidRequest, List imps, @@ -230,9 +236,7 @@ private static void modifySite(String pubId, BidRequest bidRequest, final Publisher modifiedPublisher = site.getPublisher().toBuilder().id(pubId).build(); bidRequestBuilder.site(site.toBuilder().publisher(modifiedPublisher).build()); } else { - bidRequestBuilder.site(site.toBuilder() - .publisher(Publisher.builder().id(pubId).build()) - .build()); + bidRequestBuilder.site(site.toBuilder().publisher(Publisher.builder().id(pubId).build()).build()); } } @@ -243,9 +247,7 @@ private static void modifyApp(String pubId, BidRequest bidRequest, final Publisher modifiedPublisher = app.getPublisher().toBuilder().id(pubId).build(); bidRequestBuilder.app(app.toBuilder().publisher(modifiedPublisher).build()); } else { - bidRequestBuilder.app(app.toBuilder() - .publisher(Publisher.builder().id(pubId).build()) - .build()); + bidRequestBuilder.app(app.toBuilder().publisher(Publisher.builder().id(pubId).build()).build()); } } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/pubmatic/ExtImpPubmatic.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/pubmatic/ExtImpPubmatic.java index 5f592de2610..396afcb52e7 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/pubmatic/ExtImpPubmatic.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/pubmatic/ExtImpPubmatic.java @@ -27,4 +27,8 @@ public class ExtImpPubmatic { ObjectNode wrapper; List keywords; + + String dctr; + + String pmzoneid; } diff --git a/src/main/resources/static/bidder-params/pubmatic.json b/src/main/resources/static/bidder-params/pubmatic.json index f76322da121..4cd9e8b3f26 100644 --- a/src/main/resources/static/bidder-params/pubmatic.json +++ b/src/main/resources/static/bidder-params/pubmatic.json @@ -12,6 +12,14 @@ "type": "string", "description": "An ID which identifies the ad slot" }, + "pmzoneid": { + "type": "string", + "description": "Comma separated zone id. Used im deal targeting & site section targeting. e.g drama,sport" + }, + "dctr": { + "type": "string", + "description": "Deals Custom Targeting, pipe separated key-value pairs e.g key1=V1,V2,V3|key2=v1|key3=v3,v5" + }, "wrapper": { "type": "object", "description": "Specifies pubmatic openwrap configuration for a publisher", diff --git a/src/test/java/org/prebid/server/bidder/pubmatic/PubmaticBidderTest.java b/src/test/java/org/prebid/server/bidder/pubmatic/PubmaticBidderTest.java index 087338eb86c..8d868374d6c 100644 --- a/src/test/java/org/prebid/server/bidder/pubmatic/PubmaticBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/pubmatic/PubmaticBidderTest.java @@ -161,24 +161,6 @@ public void makeHttpRequestsShouldReturnErrorIfAdSlotHasInvalidHeight() { assertThat(result.getValue()).isEmpty(); } - @Test - public void makeHttpRequestsShouldReturnErrorIfKeywordsAreInvalid() { - // given - final BidRequest bidRequest = givenBidRequest( - identity(), - extImpPubmaticBuilder -> extImpPubmaticBuilder - .keywords(singletonList(ExtImpPubmaticKeyVal.of("\"", singletonList("\""))))); - - // when - final Result>> result = pubmaticBidder.makeHttpRequests(bidRequest); - - // then - assertThat(result.getErrors()).hasSize(1); - assertThat(result.getErrors().get(0).getMessage()) - .startsWith("Failed to create keywords with error: Unexpected character"); - assertThat(result.getValue()).isEmpty(); - } - @Test public void makeHttpRequestsShouldReturnErrorIfWrapExtHasInvalidParams() { // given @@ -644,6 +626,70 @@ public void makeBidsShouldReturnBannerBidIfExtBidContainsIllegalBidType() throws .containsOnly(BidderBid.of(Bid.builder().impid("123").ext(bidType).build(), banner, "USD")); } + @Test + public void makeHttpRequestsShouldReplaceDctrIfPresent() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + extImpPubmaticBuilder -> extImpPubmaticBuilder.dctr("dctr") + .keywords(singletonList(ExtImpPubmaticKeyVal.of("key_val", asList("value1", "value2"))))); + + // when + final Result>> result = pubmaticBidder.makeHttpRequests(bidRequest); + + // then + final Map expectedKeyWords = singletonMap("key_val", "dctr"); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .containsExactly(mapper.convertValue(expectedKeyWords, ObjectNode.class)); + } + + @Test + public void makeHttpRequestsShouldReplacePmZoneIdIfPresent() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + extImpPubmaticBuilder -> extImpPubmaticBuilder.pmzoneid("pmzoneid") + .keywords(singletonList(ExtImpPubmaticKeyVal.of("pmZoneId", asList("value1", "value2"))))); + + // when + final Result>> result = pubmaticBidder.makeHttpRequests(bidRequest); + + // then + final Map expectedKeyWords = singletonMap("pmZoneId", "pmzoneid"); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .containsExactly(mapper.convertValue(expectedKeyWords, ObjectNode.class)); + } + + @Test + public void makeHttpRequestsShouldReplacePmZoneIDOldKeyNameWithNew() { + // given + final BidRequest bidRequest = givenBidRequest( + identity(), + extImpPubmaticBuilder -> extImpPubmaticBuilder + .keywords(singletonList(ExtImpPubmaticKeyVal.of("pmZoneID", asList("value1", "value2"))))); + + // when + final Result>> result = pubmaticBidder.makeHttpRequests(bidRequest); + + // then + + final Map expectedKeyWords = singletonMap("pmZoneId", "value1,value2"); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1) + .extracting(HttpRequest::getPayload) + .flatExtracting(BidRequest::getImp) + .extracting(Imp::getExt) + .containsExactly(mapper.convertValue(expectedKeyWords, ObjectNode.class)); + } + private static BidRequest givenBidRequest( Function bidRequestCustomizer, Function impCustomizer, diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-request.json b/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-request.json index c2b3cdf870e..00d9b85de99 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-request.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-auction-pubmatic-request.json @@ -19,7 +19,7 @@ "publisherId": " publisherId ", "keywords": [ { - "key": "pmZoneID", + "key": "pmZoneId", "value": [ "Zone1", "Zone2" @@ -53,9 +53,11 @@ "pubmatic": { "adSlot": "slot9@300x250:zzz", "publisherId": "publisherId", + "pmzoneid": "drama,sport", + "dctr": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id", "keywords": [ { - "key": "pmZoneID", + "key": "pmZoneId", "value": [ "Zone1", "Zone2" diff --git a/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-pubmatic-bid-request-1.json b/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-pubmatic-bid-request-1.json index 6693f9f1bc7..fe1de5052c7 100644 --- a/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-pubmatic-bid-request-1.json +++ b/src/test/resources/org/prebid/server/it/openrtb2/pubmatic/test-pubmatic-bid-request-1.json @@ -15,7 +15,7 @@ }, "tagid": "slot9", "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "preference": "sports,movies" } }, @@ -29,7 +29,8 @@ "h": 600 }, "ext": { - "pmZoneID": "Zone1,Zone2" + "pmZoneId": "drama,sport", + "key_val": "abBucket=4|adType=page|entity=|paidByCategory=|sku=|userLevel=free|platform=android|majorVersion=3.54|version=3.54.0|mobileApplication=true|showId=20166|show=Kisah Untuk Geri|genre=Drama|contentUrl=https://www.iflix.com/title/show/20166|rating=TV-MA|contentLanguage=id" } } ],