Skip to content

Commit 35b6562

Browse files
New BidTheatre Adapter (#4023)
1 parent f26254f commit 35b6562

File tree

12 files changed

+618
-0
lines changed

12 files changed

+618
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package org.prebid.server.bidder.bidtheatre;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import com.iab.openrtb.request.BidRequest;
6+
import com.iab.openrtb.response.Bid;
7+
import com.iab.openrtb.response.BidResponse;
8+
import com.iab.openrtb.response.SeatBid;
9+
import org.apache.commons.lang3.StringUtils;
10+
import org.prebid.server.bidder.Bidder;
11+
import org.prebid.server.bidder.model.BidderBid;
12+
import org.prebid.server.bidder.model.BidderCall;
13+
import org.prebid.server.bidder.model.BidderError;
14+
import org.prebid.server.bidder.model.HttpRequest;
15+
import org.prebid.server.bidder.model.Result;
16+
import org.prebid.server.exception.PreBidException;
17+
import org.prebid.server.json.DecodeException;
18+
import org.prebid.server.json.JacksonMapper;
19+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
20+
import org.prebid.server.proto.openrtb.ext.response.BidType;
21+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
22+
import org.prebid.server.util.BidderUtil;
23+
import org.prebid.server.util.HttpUtil;
24+
25+
import java.math.BigDecimal;
26+
import java.util.ArrayList;
27+
import java.util.Collection;
28+
import java.util.Collections;
29+
import java.util.List;
30+
import java.util.Objects;
31+
import java.util.Optional;
32+
33+
public class BidTheatreBidder implements Bidder<BidRequest> {
34+
35+
private static final TypeReference<ExtPrebid<ExtBidPrebid, ObjectNode>> EXT_PREBID_TYPE_REFERENCE =
36+
new TypeReference<>() {
37+
};
38+
39+
private static final String PRICE_MACRO = "${AUCTION_PRICE}";
40+
41+
private final String endpointUrl;
42+
private final JacksonMapper mapper;
43+
44+
public BidTheatreBidder(String endpointUrl, JacksonMapper mapper) {
45+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
46+
this.mapper = Objects.requireNonNull(mapper);
47+
}
48+
49+
@Override
50+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
51+
return Result.withValue(BidderUtil.defaultRequest(request, endpointUrl, mapper));
52+
}
53+
54+
@Override
55+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
56+
try {
57+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
58+
final List<BidderError> errors = new ArrayList<>();
59+
return Result.of(extractBids(bidResponse, errors), errors);
60+
} catch (DecodeException | PreBidException e) {
61+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
62+
}
63+
}
64+
65+
private List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
66+
if (bidResponse == null || bidResponse.getSeatbid() == null) {
67+
return Collections.emptyList();
68+
}
69+
return bidsFromResponse(bidResponse, errors);
70+
}
71+
72+
private List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
73+
return bidResponse.getSeatbid().stream()
74+
.filter(Objects::nonNull)
75+
.map(SeatBid::getBid)
76+
.filter(Objects::nonNull)
77+
.flatMap(Collection::stream)
78+
.filter(Objects::nonNull)
79+
.map(bid -> makeBid(bid, bidResponse.getCur(), errors))
80+
.filter(Objects::nonNull)
81+
.toList();
82+
}
83+
84+
private BidderBid makeBid(Bid bid, String currency, List<BidderError> errors) {
85+
final BidType mediaType = getMediaType(bid);
86+
if (mediaType == null) {
87+
errors.add(BidderError.badServerResponse("Failed to parse impression \"%s\" mediatype"
88+
.formatted(bid.getImpid())));
89+
return null;
90+
}
91+
92+
final BigDecimal price = bid.getPrice();
93+
final String priceAsString = price != null ? price.toPlainString() : "0";
94+
95+
final Bid modifiedBid = bid.toBuilder()
96+
.nurl(StringUtils.replace(bid.getNurl(), PRICE_MACRO, priceAsString))
97+
.adm(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString))
98+
.build();
99+
100+
return BidderBid.of(modifiedBid, mediaType, currency);
101+
}
102+
103+
private BidType getMediaType(Bid bid) {
104+
return Optional.ofNullable(bid.getExt())
105+
.map(this::parseBidExt)
106+
.map(ExtPrebid::getPrebid)
107+
.map(ExtBidPrebid::getType)
108+
.orElse(null);
109+
}
110+
111+
private ExtPrebid<ExtBidPrebid, ObjectNode> parseBidExt(ObjectNode ext) {
112+
try {
113+
return mapper.mapper().convertValue(ext, EXT_PREBID_TYPE_REFERENCE);
114+
} catch (IllegalArgumentException e) {
115+
return null;
116+
}
117+
}
118+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.prebid.server.proto.openrtb.ext.request.bidtheatre;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpBidTheatre {
8+
9+
@JsonProperty("publisherId")
10+
String publisherId;
11+
12+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.bidtheatre.BidTheatreBidder;
5+
import org.prebid.server.json.JacksonMapper;
6+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
7+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
8+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
9+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.context.annotation.PropertySource;
15+
16+
import javax.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/bidtheatre.yaml", factory = YamlPropertySourceFactory.class)
20+
public class BidTheatreConfiguration {
21+
22+
private static final String BIDDER_NAME = "bidtheatre";
23+
24+
@Bean("bidtheatreConfigurationProperties")
25+
@ConfigurationProperties("adapters.bidtheatre")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps bidtheatreBidderDeps(BidderConfigurationProperties bidtheatreConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(bidtheatreConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new BidTheatreBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
adapters:
2+
bidtheatre:
3+
endpoint: https://prebidjs-bids.bidtheatre.net/prebidjsbid
4+
modifying-vast-xml-allowed: true
5+
meta-info:
6+
maintainer-email: [email protected]
7+
app-media-types:
8+
- banner
9+
- video
10+
site-media-types:
11+
- banner
12+
- video
13+
supported-vendors:
14+
vendor-id: 30
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Bidtheatre Adapter Params",
4+
"description": "A schema which validates params accepted by the Bidtheatre adapter",
5+
"type": "object",
6+
"properties": {
7+
"publisherId": {
8+
"type": "string",
9+
"description": "Publisher ID",
10+
"format": "uuid"
11+
}
12+
},
13+
"required": [
14+
"publisherId"
15+
]
16+
}

0 commit comments

Comments
 (0)