Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.prebid.server.bidder.smartadserver;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Publisher;
Expand Down Expand Up @@ -30,6 +31,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;

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

private final String endpointUrl;
private final String secondaryEndpointUrl;
private final JacksonMapper mapper;

public SmartadserverBidder(String endpointUrl, JacksonMapper mapper) {
public SmartadserverBidder(String endpointUrl, String secondaryEndpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
this.secondaryEndpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(secondaryEndpointUrl));
this.mapper = Objects.requireNonNull(mapper);
}

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
final List<BidderError> errors = new ArrayList<>();
final List<Imp> imps = new ArrayList<>();
ExtImpSmartadserver extImp = null;
final List<Imp> modifiedImps = new ArrayList<>();
final LinkedHashMap<Imp, ExtImpSmartadserver> impToExtImpMap = new LinkedHashMap<>();

boolean isProgrammaticGuaranteed = false;

for (Imp imp : request.getImp()) {
try {
extImp = parseImpExt(imp);
imps.add(imp);
final ExtImpSmartadserver extImp = parseImpExt(imp);
isProgrammaticGuaranteed |= extImp.isProgrammaticGuaranteed();
impToExtImpMap.put(imp, extImp);
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}

if (imps.isEmpty()) {
if (impToExtImpMap.isEmpty()) {
return Result.withErrors(errors);
}

final String extImpKey = isProgrammaticGuaranteed ? "smartadserver" : "bidder";
impToExtImpMap.forEach((imp, extImp) -> modifiedImps.add(modifyImp(imp, extImp, extImpKey)));

final ExtImpSmartadserver lastExtImp = impToExtImpMap.lastEntry().getValue();
final BidRequest outgoingRequest = request.toBuilder()
.imp(imps)
.site(modifySite(request.getSite(), extImp.getNetworkId()))
.imp(modifiedImps)
.site(modifySite(request.getSite(), lastExtImp.getNetworkId()))
.build();

final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(outgoingRequest, makeUrl(), mapper);
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(
outgoingRequest,
makeUrl(isProgrammaticGuaranteed),
mapper);
return Result.of(Collections.singletonList(httpRequest), errors);
}

Expand All @@ -83,6 +97,13 @@ private ExtImpSmartadserver parseImpExt(Imp imp) {
}
}

private Imp modifyImp(Imp imp, ExtImpSmartadserver extImp, String impExtKey) {
final ObjectNode impExt = imp.getExt().deepCopy();
impExt.remove("bidder");
impExt.set(impExtKey, mapper.mapper().valueToTree(extImp));
return imp.toBuilder().ext(impExt).build();
}

private static Site modifySite(Site site, Integer networkId) {
final Site.SiteBuilder siteBuilder = site != null ? site.toBuilder() : Site.builder();
final Publisher sitePublisher = site != null ? site.getPublisher() : null;
Expand All @@ -98,17 +119,22 @@ private static Publisher modifyPublisher(Publisher publisher, Integer networkId)
return publisherBuilder.id(String.valueOf(networkId)).build();
}

private String makeUrl() {
final URI uri;
private String makeUrl(boolean isProgrammaticGuaranteed) {
final String url = isProgrammaticGuaranteed ? secondaryEndpointUrl : endpointUrl;
try {
uri = new URI(endpointUrl);
final URI uri = new URI(url);
final String path = isProgrammaticGuaranteed ? "/ortb" : "/api/bid";
final URIBuilder uriBuilder = new URIBuilder(uri)
.setPath(StringUtils.removeEnd(uri.getPath(), "/") + path);

if (!isProgrammaticGuaranteed) {
uriBuilder.addParameter("callerId", "5");
}

return uriBuilder.toString();
} catch (URISyntaxException e) {
throw new PreBidException("Malformed URL: %s.".formatted(endpointUrl));
throw new PreBidException("Malformed URL: %s.".formatted(url));
}
return new URIBuilder(uri)
.setPath(StringUtils.removeEnd(uri.getPath(), "/") + "/api/bid")
.addParameter("callerId", "5")
.toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

/**
* Defines the contract for bidrequest.imp[i].ext.smartadserver
*/
@Value(staticConstructor = "of")
public class ExtImpSmartadserver {

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

@JsonProperty("networkId")
Integer networkId;

@JsonProperty(value = "programmaticGuaranteed", access = JsonProperty.Access.WRITE_ONLY)
boolean programmaticGuaranteed;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.prebid.server.spring.config.bidder;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.smartadserver.SmartadserverBidder;
import org.prebid.server.json.JacksonMapper;
Expand All @@ -23,19 +26,28 @@ public class SmartadserverConfiguration {

@Bean("smartadserverConfigurationProperties")
@ConfigurationProperties("adapters.smartadserver")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
SmartadserverConfigurationProperties configurationProperties() {
return new SmartadserverConfigurationProperties();
}

@Bean
BidderDeps smartadserverBidderDeps(BidderConfigurationProperties smartadserverConfigurationProperties,
BidderDeps smartadserverBidderDeps(SmartadserverConfigurationProperties smartadserverConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
return BidderDepsAssembler.<SmartadserverConfigurationProperties>forBidder(BIDDER_NAME)
.withConfig(smartadserverConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new SmartadserverBidder(config.getEndpoint(), mapper))
.bidderCreator(config -> new SmartadserverBidder(
config.getEndpoint(), config.getSecondaryEndpoint(), mapper))
.assemble();
}

@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
private static class SmartadserverConfigurationProperties extends BidderConfigurationProperties {

private String secondaryEndpoint;
}
}
1 change: 1 addition & 0 deletions src/main/resources/bidder-config/smartadserver.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
adapters:
smartadserver:
endpoint: https://ssb-global.smartadserver.com
secondary-endpoint: https://prebid-global.smartadserver.com
endpoint-compression: gzip
aliases:
equativ:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.prebid.server.bidder.smartadserver;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
Expand Down Expand Up @@ -37,12 +38,20 @@
public class SmartadserverBidderTest extends VertxTest {

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

private final SmartadserverBidder target = new SmartadserverBidder(ENDPOINT_URL, jacksonMapper);
private final SmartadserverBidder target = new SmartadserverBidder(ENDPOINT_URL, SECONDARY_URL, jacksonMapper);

@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new SmartadserverBidder("invalid_url", jacksonMapper));
assertThatIllegalArgumentException()
.isThrownBy(() -> new SmartadserverBidder("invalid_url", SECONDARY_URL, jacksonMapper));
}

@Test
public void creationShouldFailOnInvalidSecondaryEndpointUrl() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new SmartadserverBidder(ENDPOINT_URL, "invalid_url", jacksonMapper));
}

@Test
Expand All @@ -64,7 +73,7 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() {
}

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

@Test
public void makeHttpRequestsShouldCreateCorrectSecondaryURLWhenProgrammaticGuaranteedIsTrue() {
// given
final ObjectNode givenImpExt = mapper.createObjectNode()
.set("bidder", mapper.createObjectNode().put("programmaticGuaranteed", true));

final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(givenImp(imp -> imp.ext(givenImpExt))))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(1);
assertThat(result.getValue().getFirst().getUri())
.isEqualTo("https://test.endpoint2.com/path/ortb?testParam=testVal");
}

@Test
public void makeHttpRequestsShouldCreateCorrectPrimaryURLWhenProgrammaticGuaranteedIsFalse() {
// given
final ObjectNode givenImpExt = mapper.createObjectNode()
.set("bidder", mapper.createObjectNode().put("programmaticGuaranteed", false));

final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(givenImp(imp -> imp.ext(givenImpExt))))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(1);
assertThat(result.getValue().getFirst().getUri())
.isEqualTo("https://test.endpoint.com/path/api/bid?testParam=testVal&callerId=5");
}

@Test
public void makeHttpRequestsShouldUpdateSiteObjectIfPresent() {
// given
Expand Down Expand Up @@ -152,6 +201,56 @@ public void makeHttpRequestsShouldCreateSingleRequestWithValidImpsOnly() {
.containsExactly("123");
}

@Test
public void makeHttpRequestsShouldModifyImpWhenProgrammaticGuaranteedIsTrueAtLeastInOneValidImp() {
// given
final ObjectNode givenImpExt1 = mapper.createObjectNode()
.set("bidder", mapper.createObjectNode()
.put("programmaticGuaranteed", false)
.put("networkId", 1)
.put("siteId", 2)
.put("formatId", 3)
.put("pageId", 4));
final ObjectNode givenImpExt2 = mapper.createObjectNode()
.set("bidder", mapper.createObjectNode()
.put("programmaticGuaranteed", true)
.put("networkId", 5)
.put("siteId", 6)
.put("formatId", 7)
.put("pageId", 8));

final BidRequest bidRequest = BidRequest.builder()
.imp(List.of(
givenImp(imp -> imp.id("impId1").ext(givenImpExt1)),
givenImp(imp -> imp.id("impId2").ext(givenImpExt2))))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
final ObjectNode expectedImpExt1 = mapper.createObjectNode()
.set("smartadserver", mapper.createObjectNode()
.put("networkId", 1)
.put("siteId", 2)
.put("formatId", 3)
.put("pageId", 4));
final ObjectNode expectedImpExt2 = mapper.createObjectNode()
.set("smartadserver", mapper.createObjectNode()
.put("networkId", 5)
.put("siteId", 6)
.put("formatId", 7)
.put("pageId", 8));

assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue()).hasSize(1);
assertThat(result.getValue())
.extracting(HttpRequest::getPayload)
.flatExtracting(BidRequest::getImp)
.extracting(Imp::getExt)
.containsExactly(expectedImpExt1, expectedImpExt2);
}

@Test
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
// given
Expand Down Expand Up @@ -299,10 +398,12 @@ public void makeBidsShouldReturnNativeBidIfMarkupTypeIsNative() throws JsonProce

private static Imp givenImp(Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer) {
return impCustomizer.apply(Imp.builder()
.id("123"))
.banner(Banner.builder().build())
.video(Video.builder().build())
.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmartadserver.of(1, 2, 3, 4))))
.id("123")
.banner(Banner.builder().build())
.video(Video.builder().build())
.ext(mapper.valueToTree(ExtPrebid.of(
null,
ExtImpSmartadserver.of(1, 2, 3, 4, false)))))
.build();
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/java/org/prebid/server/it/SmartadserverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class SmartadserverTest extends IntegrationTest {
@Test
public void openrtb2AuctionShouldRespondWithBidsFromSmartadserver() throws IOException, JSONException {
// given
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smartadserver-exchange/api/bid"))
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smartadserver-secondary-exchange/ortb"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/smartadserver/test-smartadserver-bid-request.json")))
.willReturn(aResponse()
.withBody(jsonFrom("openrtb2/smartadserver/test-smartadserver-bid-response.json"))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"siteId": 1,
"pageId": 2,
"formatId": 3,
"networkId": 73
"networkId": 73,
"programmaticGuaranteed": true
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"ext": {
"tid": "${json-unit.any-string}",
"bidder": {
"smartadserver": {
"siteId": 1,
"pageId": 2,
"formatId": 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ adapters.smaato.enabled=true
adapters.smaato.endpoint=http://localhost:8090/smaato-exchange
adapters.smartadserver.enabled=true
adapters.smartadserver.endpoint=http://localhost:8090/smartadserver-exchange
adapters.smartadserver.secondary-endpoint=http://localhost:8090/smartadserver-secondary-exchange
adapters.smartadserver.aliases.equativ.enabled=true
adapters.smartrtb.enabled=true
adapters.smartrtb.endpoint=http://localhost:8090/smartrtb-exchange/
Expand Down
Loading