Skip to content

Commit 3af4507

Browse files
Smartadserver: Add second endpoint for programmatic guaranteed (prebid#4163)
1 parent 9c7fe2b commit 3af4507

File tree

9 files changed

+177
-35
lines changed

9 files changed

+177
-35
lines changed

src/main/java/org/prebid/server/bidder/smartadserver/SmartadserverBidder.java

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.prebid.server.bidder.smartadserver;
22

33
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
45
import com.iab.openrtb.request.BidRequest;
56
import com.iab.openrtb.request.Imp;
67
import com.iab.openrtb.request.Publisher;
@@ -30,6 +31,7 @@
3031
import java.util.ArrayList;
3132
import java.util.Collection;
3233
import java.util.Collections;
34+
import java.util.LinkedHashMap;
3335
import java.util.List;
3436
import java.util.Objects;
3537

@@ -40,38 +42,50 @@ public class SmartadserverBidder implements Bidder<BidRequest> {
4042
};
4143

4244
private final String endpointUrl;
45+
private final String secondaryEndpointUrl;
4346
private final JacksonMapper mapper;
4447

45-
public SmartadserverBidder(String endpointUrl, JacksonMapper mapper) {
48+
public SmartadserverBidder(String endpointUrl, String secondaryEndpointUrl, JacksonMapper mapper) {
4649
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
50+
this.secondaryEndpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(secondaryEndpointUrl));
4751
this.mapper = Objects.requireNonNull(mapper);
4852
}
4953

5054
@Override
5155
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
5256
final List<BidderError> errors = new ArrayList<>();
53-
final List<Imp> imps = new ArrayList<>();
54-
ExtImpSmartadserver extImp = null;
57+
final List<Imp> modifiedImps = new ArrayList<>();
58+
final LinkedHashMap<Imp, ExtImpSmartadserver> impToExtImpMap = new LinkedHashMap<>();
59+
60+
boolean isProgrammaticGuaranteed = false;
5561

5662
for (Imp imp : request.getImp()) {
5763
try {
58-
extImp = parseImpExt(imp);
59-
imps.add(imp);
64+
final ExtImpSmartadserver extImp = parseImpExt(imp);
65+
isProgrammaticGuaranteed |= extImp.isProgrammaticGuaranteed();
66+
impToExtImpMap.put(imp, extImp);
6067
} catch (PreBidException e) {
6168
errors.add(BidderError.badInput(e.getMessage()));
6269
}
6370
}
6471

65-
if (imps.isEmpty()) {
72+
if (impToExtImpMap.isEmpty()) {
6673
return Result.withErrors(errors);
6774
}
6875

76+
final String extImpKey = isProgrammaticGuaranteed ? "smartadserver" : "bidder";
77+
impToExtImpMap.forEach((imp, extImp) -> modifiedImps.add(modifyImp(imp, extImp, extImpKey)));
78+
79+
final ExtImpSmartadserver lastExtImp = impToExtImpMap.lastEntry().getValue();
6980
final BidRequest outgoingRequest = request.toBuilder()
70-
.imp(imps)
71-
.site(modifySite(request.getSite(), extImp.getNetworkId()))
81+
.imp(modifiedImps)
82+
.site(modifySite(request.getSite(), lastExtImp.getNetworkId()))
7283
.build();
7384

74-
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(outgoingRequest, makeUrl(), mapper);
85+
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(
86+
outgoingRequest,
87+
makeUrl(isProgrammaticGuaranteed),
88+
mapper);
7589
return Result.of(Collections.singletonList(httpRequest), errors);
7690
}
7791

@@ -83,6 +97,13 @@ private ExtImpSmartadserver parseImpExt(Imp imp) {
8397
}
8498
}
8599

100+
private Imp modifyImp(Imp imp, ExtImpSmartadserver extImp, String impExtKey) {
101+
final ObjectNode impExt = imp.getExt().deepCopy();
102+
impExt.remove("bidder");
103+
impExt.set(impExtKey, mapper.mapper().valueToTree(extImp));
104+
return imp.toBuilder().ext(impExt).build();
105+
}
106+
86107
private static Site modifySite(Site site, Integer networkId) {
87108
final Site.SiteBuilder siteBuilder = site != null ? site.toBuilder() : Site.builder();
88109
final Publisher sitePublisher = site != null ? site.getPublisher() : null;
@@ -98,17 +119,22 @@ private static Publisher modifyPublisher(Publisher publisher, Integer networkId)
98119
return publisherBuilder.id(String.valueOf(networkId)).build();
99120
}
100121

101-
private String makeUrl() {
102-
final URI uri;
122+
private String makeUrl(boolean isProgrammaticGuaranteed) {
123+
final String url = isProgrammaticGuaranteed ? secondaryEndpointUrl : endpointUrl;
103124
try {
104-
uri = new URI(endpointUrl);
125+
final URI uri = new URI(url);
126+
final String path = isProgrammaticGuaranteed ? "/ortb" : "/api/bid";
127+
final URIBuilder uriBuilder = new URIBuilder(uri)
128+
.setPath(StringUtils.removeEnd(uri.getPath(), "/") + path);
129+
130+
if (!isProgrammaticGuaranteed) {
131+
uriBuilder.addParameter("callerId", "5");
132+
}
133+
134+
return uriBuilder.toString();
105135
} catch (URISyntaxException e) {
106-
throw new PreBidException("Malformed URL: %s.".formatted(endpointUrl));
136+
throw new PreBidException("Malformed URL: %s.".formatted(url));
107137
}
108-
return new URIBuilder(uri)
109-
.setPath(StringUtils.removeEnd(uri.getPath(), "/") + "/api/bid")
110-
.addParameter("callerId", "5")
111-
.toString();
112138
}
113139

114140
@Override

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import lombok.Value;
55

6-
/**
7-
* Defines the contract for bidrequest.imp[i].ext.smartadserver
8-
*/
96
@Value(staticConstructor = "of")
107
public class ExtImpSmartadserver {
118

@@ -20,4 +17,7 @@ public class ExtImpSmartadserver {
2017

2118
@JsonProperty("networkId")
2219
Integer networkId;
20+
21+
@JsonProperty(value = "programmaticGuaranteed", access = JsonProperty.Access.WRITE_ONLY)
22+
boolean programmaticGuaranteed;
2323
}

src/main/java/org/prebid/server/spring/config/bidder/SmartadserverConfiguration.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.prebid.server.spring.config.bidder;
22

3+
import lombok.Data;
4+
import lombok.EqualsAndHashCode;
5+
import lombok.NoArgsConstructor;
36
import org.prebid.server.bidder.BidderDeps;
47
import org.prebid.server.bidder.smartadserver.SmartadserverBidder;
58
import org.prebid.server.json.JacksonMapper;
@@ -23,19 +26,28 @@ public class SmartadserverConfiguration {
2326

2427
@Bean("smartadserverConfigurationProperties")
2528
@ConfigurationProperties("adapters.smartadserver")
26-
BidderConfigurationProperties configurationProperties() {
27-
return new BidderConfigurationProperties();
29+
SmartadserverConfigurationProperties configurationProperties() {
30+
return new SmartadserverConfigurationProperties();
2831
}
2932

3033
@Bean
31-
BidderDeps smartadserverBidderDeps(BidderConfigurationProperties smartadserverConfigurationProperties,
34+
BidderDeps smartadserverBidderDeps(SmartadserverConfigurationProperties smartadserverConfigurationProperties,
3235
@NotBlank @Value("${external-url}") String externalUrl,
3336
JacksonMapper mapper) {
3437

35-
return BidderDepsAssembler.forBidder(BIDDER_NAME)
38+
return BidderDepsAssembler.<SmartadserverConfigurationProperties>forBidder(BIDDER_NAME)
3639
.withConfig(smartadserverConfigurationProperties)
3740
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38-
.bidderCreator(config -> new SmartadserverBidder(config.getEndpoint(), mapper))
41+
.bidderCreator(config -> new SmartadserverBidder(
42+
config.getEndpoint(), config.getSecondaryEndpoint(), mapper))
3943
.assemble();
4044
}
45+
46+
@Data
47+
@EqualsAndHashCode(callSuper = true)
48+
@NoArgsConstructor
49+
private static class SmartadserverConfigurationProperties extends BidderConfigurationProperties {
50+
51+
private String secondaryEndpoint;
52+
}
4153
}

src/main/resources/bidder-config/smartadserver.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
adapters:
22
smartadserver:
33
endpoint: https://ssb-global.smartadserver.com
4+
secondary-endpoint: https://prebid-global.smartadserver.com
45
endpoint-compression: gzip
56
aliases:
67
equativ:

src/test/java/org/prebid/server/bidder/smartadserver/SmartadserverBidderTest.java

Lines changed: 108 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.prebid.server.bidder.smartadserver;
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
45
import com.iab.openrtb.request.Banner;
56
import com.iab.openrtb.request.BidRequest;
67
import com.iab.openrtb.request.Imp;
@@ -37,12 +38,20 @@
3738
public class SmartadserverBidderTest extends VertxTest {
3839

3940
private static final String ENDPOINT_URL = "https://test.endpoint.com/path?testParam=testVal";
41+
private static final String SECONDARY_URL = "https://test.endpoint2.com/path?testParam=testVal";
4042

41-
private final SmartadserverBidder target = new SmartadserverBidder(ENDPOINT_URL, jacksonMapper);
43+
private final SmartadserverBidder target = new SmartadserverBidder(ENDPOINT_URL, SECONDARY_URL, jacksonMapper);
4244

4345
@Test
4446
public void creationShouldFailOnInvalidEndpointUrl() {
45-
assertThatIllegalArgumentException().isThrownBy(() -> new SmartadserverBidder("invalid_url", jacksonMapper));
47+
assertThatIllegalArgumentException()
48+
.isThrownBy(() -> new SmartadserverBidder("invalid_url", SECONDARY_URL, jacksonMapper));
49+
}
50+
51+
@Test
52+
public void creationShouldFailOnInvalidSecondaryEndpointUrl() {
53+
assertThatIllegalArgumentException()
54+
.isThrownBy(() -> new SmartadserverBidder(ENDPOINT_URL, "invalid_url", jacksonMapper));
4655
}
4756

4857
@Test
@@ -64,7 +73,7 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() {
6473
}
6574

6675
@Test
67-
public void makeHttpRequestsShouldCreateCorrectURL() {
76+
public void makeHttpRequestsShouldCreateCorrectPrimaryURLWhenProgrammaticGuaranteedIsAbsent() {
6877
// given
6978
final BidRequest bidRequest = BidRequest.builder()
7079
.imp(singletonList(givenImp(identity())))
@@ -80,6 +89,46 @@ public void makeHttpRequestsShouldCreateCorrectURL() {
8089
.isEqualTo("https://test.endpoint.com/path/api/bid?testParam=testVal&callerId=5");
8190
}
8291

92+
@Test
93+
public void makeHttpRequestsShouldCreateCorrectSecondaryURLWhenProgrammaticGuaranteedIsTrue() {
94+
// given
95+
final ObjectNode givenImpExt = mapper.createObjectNode()
96+
.set("bidder", mapper.createObjectNode().put("programmaticGuaranteed", true));
97+
98+
final BidRequest bidRequest = BidRequest.builder()
99+
.imp(singletonList(givenImp(imp -> imp.ext(givenImpExt))))
100+
.build();
101+
102+
// when
103+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
104+
105+
// then
106+
assertThat(result.getErrors()).isEmpty();
107+
assertThat(result.getValue()).hasSize(1);
108+
assertThat(result.getValue().getFirst().getUri())
109+
.isEqualTo("https://test.endpoint2.com/path/ortb?testParam=testVal");
110+
}
111+
112+
@Test
113+
public void makeHttpRequestsShouldCreateCorrectPrimaryURLWhenProgrammaticGuaranteedIsFalse() {
114+
// given
115+
final ObjectNode givenImpExt = mapper.createObjectNode()
116+
.set("bidder", mapper.createObjectNode().put("programmaticGuaranteed", false));
117+
118+
final BidRequest bidRequest = BidRequest.builder()
119+
.imp(singletonList(givenImp(imp -> imp.ext(givenImpExt))))
120+
.build();
121+
122+
// when
123+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
124+
125+
// then
126+
assertThat(result.getErrors()).isEmpty();
127+
assertThat(result.getValue()).hasSize(1);
128+
assertThat(result.getValue().getFirst().getUri())
129+
.isEqualTo("https://test.endpoint.com/path/api/bid?testParam=testVal&callerId=5");
130+
}
131+
83132
@Test
84133
public void makeHttpRequestsShouldUpdateSiteObjectIfPresent() {
85134
// given
@@ -152,6 +201,56 @@ public void makeHttpRequestsShouldCreateSingleRequestWithValidImpsOnly() {
152201
.containsExactly("123");
153202
}
154203

204+
@Test
205+
public void makeHttpRequestsShouldModifyImpWhenProgrammaticGuaranteedIsTrueAtLeastInOneValidImp() {
206+
// given
207+
final ObjectNode givenImpExt1 = mapper.createObjectNode()
208+
.set("bidder", mapper.createObjectNode()
209+
.put("programmaticGuaranteed", false)
210+
.put("networkId", 1)
211+
.put("siteId", 2)
212+
.put("formatId", 3)
213+
.put("pageId", 4));
214+
final ObjectNode givenImpExt2 = mapper.createObjectNode()
215+
.set("bidder", mapper.createObjectNode()
216+
.put("programmaticGuaranteed", true)
217+
.put("networkId", 5)
218+
.put("siteId", 6)
219+
.put("formatId", 7)
220+
.put("pageId", 8));
221+
222+
final BidRequest bidRequest = BidRequest.builder()
223+
.imp(List.of(
224+
givenImp(imp -> imp.id("impId1").ext(givenImpExt1)),
225+
givenImp(imp -> imp.id("impId2").ext(givenImpExt2))))
226+
.build();
227+
228+
// when
229+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
230+
231+
// then
232+
final ObjectNode expectedImpExt1 = mapper.createObjectNode()
233+
.set("smartadserver", mapper.createObjectNode()
234+
.put("networkId", 1)
235+
.put("siteId", 2)
236+
.put("formatId", 3)
237+
.put("pageId", 4));
238+
final ObjectNode expectedImpExt2 = mapper.createObjectNode()
239+
.set("smartadserver", mapper.createObjectNode()
240+
.put("networkId", 5)
241+
.put("siteId", 6)
242+
.put("formatId", 7)
243+
.put("pageId", 8));
244+
245+
assertThat(result.getErrors()).isEmpty();
246+
assertThat(result.getValue()).hasSize(1);
247+
assertThat(result.getValue())
248+
.extracting(HttpRequest::getPayload)
249+
.flatExtracting(BidRequest::getImp)
250+
.extracting(Imp::getExt)
251+
.containsExactly(expectedImpExt1, expectedImpExt2);
252+
}
253+
155254
@Test
156255
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
157256
// given
@@ -299,10 +398,12 @@ public void makeBidsShouldReturnNativeBidIfMarkupTypeIsNative() throws JsonProce
299398

300399
private static Imp givenImp(Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer) {
301400
return impCustomizer.apply(Imp.builder()
302-
.id("123"))
303-
.banner(Banner.builder().build())
304-
.video(Video.builder().build())
305-
.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmartadserver.of(1, 2, 3, 4))))
401+
.id("123")
402+
.banner(Banner.builder().build())
403+
.video(Video.builder().build())
404+
.ext(mapper.valueToTree(ExtPrebid.of(
405+
null,
406+
ExtImpSmartadserver.of(1, 2, 3, 4, false)))))
306407
.build();
307408
}
308409

src/test/java/org/prebid/server/it/SmartadserverTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class SmartadserverTest extends IntegrationTest {
1818
@Test
1919
public void openrtb2AuctionShouldRespondWithBidsFromSmartadserver() throws IOException, JSONException {
2020
// given
21-
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smartadserver-exchange/api/bid"))
21+
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smartadserver-secondary-exchange/ortb"))
2222
.withRequestBody(equalToJson(jsonFrom("openrtb2/smartadserver/test-smartadserver-bid-request.json")))
2323
.willReturn(aResponse()
2424
.withBody(jsonFrom("openrtb2/smartadserver/test-smartadserver-bid-response.json"))));

src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-auction-smartadserver-request.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"siteId": 1,
1515
"pageId": 2,
1616
"formatId": 3,
17-
"networkId": 73
17+
"networkId": 73,
18+
"programmaticGuaranteed": true
1819
}
1920
}
2021
}

src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-smartadserver-bid-request.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"ext": {
1212
"tid": "${json-unit.any-string}",
13-
"bidder": {
13+
"smartadserver": {
1414
"siteId": 1,
1515
"pageId": 2,
1616
"formatId": 3,

src/test/resources/org/prebid/server/it/test-application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ adapters.smaato.enabled=true
493493
adapters.smaato.endpoint=http://localhost:8090/smaato-exchange
494494
adapters.smartadserver.enabled=true
495495
adapters.smartadserver.endpoint=http://localhost:8090/smartadserver-exchange
496+
adapters.smartadserver.secondary-endpoint=http://localhost:8090/smartadserver-secondary-exchange
496497
adapters.smartadserver.aliases.equativ.enabled=true
497498
adapters.smartrtb.enabled=true
498499
adapters.smartrtb.endpoint=http://localhost:8090/smartrtb-exchange/

0 commit comments

Comments
 (0)