Skip to content

Commit 010555c

Browse files
Showheroes bidder (#4190)
1 parent 6ca71a7 commit 010555c

22 files changed

+1250
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package org.prebid.server.bidder.showheroes;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.node.ObjectNode;
6+
import com.iab.openrtb.request.App;
7+
import com.iab.openrtb.request.BidRequest;
8+
import com.iab.openrtb.request.Imp;
9+
import com.iab.openrtb.request.Site;
10+
import com.iab.openrtb.request.Source;
11+
import com.iab.openrtb.response.Bid;
12+
import com.iab.openrtb.response.BidResponse;
13+
import com.iab.openrtb.response.SeatBid;
14+
import org.apache.commons.collections4.CollectionUtils;
15+
import org.apache.commons.lang3.StringUtils;
16+
import org.prebid.server.bidder.Bidder;
17+
import org.prebid.server.bidder.model.BidderBid;
18+
import org.prebid.server.bidder.model.BidderCall;
19+
import org.prebid.server.bidder.model.BidderError;
20+
import org.prebid.server.bidder.model.HttpRequest;
21+
import org.prebid.server.bidder.model.Result;
22+
import org.prebid.server.currency.CurrencyConversionService;
23+
import org.prebid.server.exception.PreBidException;
24+
import org.prebid.server.json.DecodeException;
25+
import org.prebid.server.json.JacksonMapper;
26+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
27+
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
28+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
29+
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidChannel;
30+
import org.prebid.server.proto.openrtb.ext.request.ExtSource;
31+
import org.prebid.server.proto.openrtb.ext.request.showheroes.ExtImpShowheroes;
32+
import org.prebid.server.proto.openrtb.ext.response.BidType;
33+
import org.prebid.server.util.BidderUtil;
34+
import org.prebid.server.util.HttpUtil;
35+
import org.prebid.server.version.PrebidVersionProvider;
36+
37+
import java.math.BigDecimal;
38+
import java.util.ArrayList;
39+
import java.util.Collection;
40+
import java.util.Collections;
41+
import java.util.List;
42+
import java.util.Objects;
43+
import java.util.Optional;
44+
45+
public class ShowheroesBidder implements Bidder<BidRequest> {
46+
47+
private static final String BID_CURRENCY = "EUR";
48+
private static final String PBSP_JAVA = "java";
49+
private static final TypeReference<ExtPrebid<?, ExtImpShowheroes>> SHOWHEROES_EXT_TYPE_REFERENCE =
50+
new TypeReference<>() {
51+
};
52+
53+
private final String endpointUrl;
54+
private final CurrencyConversionService currencyConversionService;
55+
private final JacksonMapper mapper;
56+
private final String pbsVersion;
57+
58+
public ShowheroesBidder(String endpointUrl,
59+
CurrencyConversionService currencyConversionService,
60+
PrebidVersionProvider prebidVersionProvider,
61+
JacksonMapper mapper) {
62+
63+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
64+
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
65+
this.mapper = Objects.requireNonNull(mapper);
66+
67+
this.pbsVersion = prebidVersionProvider.getNameVersionRecord();
68+
}
69+
70+
@Override
71+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
72+
final BidderError validationError = validate(request.getSite(), request.getApp());
73+
if (validationError != null) {
74+
return Result.withError(validationError);
75+
}
76+
77+
final List<BidderError> errors = new ArrayList<>();
78+
79+
final ExtRequestPrebidChannel prebidChannel = getPrebidChannel(request);
80+
final List<Imp> modifiedImps = new ArrayList<>(request.getImp().size());
81+
82+
for (Imp impression : request.getImp()) {
83+
try {
84+
modifiedImps.add(modifyImp(request, impression, prebidChannel));
85+
} catch (Exception e) {
86+
errors.add(BidderError.badInput(e.getMessage()));
87+
}
88+
}
89+
90+
if (modifiedImps.isEmpty()) {
91+
return Result.withErrors(errors);
92+
}
93+
94+
final Source source = modifySource(request);
95+
final BidRequest modifiedRequest = request.toBuilder().imp(modifiedImps).source(source).build();
96+
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpointUrl, mapper);
97+
98+
return Result.of(Collections.singletonList(httpRequest), errors);
99+
}
100+
101+
private static BidderError validate(Site site, App app) {
102+
if (site == null && app == null) {
103+
return BidderError.badInput("BidRequest must contain one of site or app");
104+
}
105+
if (site != null && site.getPage() == null) {
106+
return BidderError.badInput("BidRequest.site.page is required");
107+
}
108+
if (app != null && app.getBundle() == null) {
109+
return BidderError.badInput("BidRequest.app.bundle is required");
110+
}
111+
return null;
112+
}
113+
114+
private static ExtRequestPrebidChannel getPrebidChannel(BidRequest bidRequest) {
115+
return Optional.ofNullable(bidRequest.getExt())
116+
.map(ExtRequest::getPrebid)
117+
.map(ExtRequestPrebid::getChannel)
118+
.orElse(null);
119+
}
120+
121+
private Imp modifyImp(BidRequest bidRequest, Imp imp, ExtRequestPrebidChannel prebidChannel) {
122+
final ExtImpShowheroes extImpShowheroes = parseImpExt(imp);
123+
124+
final boolean shouldSetDisplayManager = prebidChannel != null && imp.getDisplaymanager() == null;
125+
final boolean shouldConvertFloor = shouldConvertFloor(imp);
126+
127+
return imp.toBuilder()
128+
.displaymanager(shouldSetDisplayManager ? prebidChannel.getName() : imp.getDisplaymanager())
129+
.displaymanagerver(shouldSetDisplayManager ? prebidChannel.getVersion() : imp.getDisplaymanagerver())
130+
.bidfloorcur(shouldConvertFloor ? BID_CURRENCY : imp.getBidfloorcur())
131+
.bidfloor(shouldConvertFloor ? resolveBidFloor(bidRequest, imp) : imp.getBidfloor())
132+
.ext(modifyImpExt(imp.getExt(), extImpShowheroes))
133+
.build();
134+
}
135+
136+
private ExtImpShowheroes parseImpExt(Imp imp) {
137+
try {
138+
return mapper.mapper().convertValue(imp.getExt(), SHOWHEROES_EXT_TYPE_REFERENCE).getBidder();
139+
} catch (IllegalArgumentException e) {
140+
throw new PreBidException(e.getMessage());
141+
}
142+
}
143+
144+
private ObjectNode modifyImpExt(ObjectNode impExt, ExtImpShowheroes shImpExt) {
145+
impExt.set("params", mapper.mapper().createObjectNode().put("unitId", shImpExt.getUnitId()));
146+
return impExt;
147+
}
148+
149+
private static boolean shouldConvertFloor(Imp imp) {
150+
return BidderUtil.isValidPrice(imp.getBidfloor())
151+
&& !StringUtils.equalsIgnoreCase(imp.getBidfloorcur(), BID_CURRENCY);
152+
}
153+
154+
private BigDecimal resolveBidFloor(BidRequest bidRequest, Imp imp) {
155+
return currencyConversionService.convertCurrency(
156+
imp.getBidfloor(), bidRequest, imp.getBidfloorcur(), BID_CURRENCY);
157+
}
158+
159+
private Source modifySource(BidRequest bidRequest) {
160+
if (pbsVersion == null) {
161+
return bidRequest.getSource();
162+
}
163+
164+
final Source source = bidRequest.getSource();
165+
166+
final ExtSource extSource = Optional.ofNullable(source)
167+
.map(Source::getExt)
168+
.orElse(ExtSource.of(null));
169+
final ObjectNode prebidExtSource = Optional.ofNullable(extSource.getProperty("pbs"))
170+
.filter(JsonNode::isObject)
171+
.map(ObjectNode.class::cast)
172+
.orElseGet(mapper.mapper()::createObjectNode)
173+
.put("pbsv", pbsVersion)
174+
.put("pbsp", PBSP_JAVA);
175+
extSource.addProperty("pbs", prebidExtSource);
176+
177+
return Optional.ofNullable(source)
178+
.map(Source::toBuilder)
179+
.orElseGet(Source::builder)
180+
.ext(extSource)
181+
.build();
182+
}
183+
184+
@Override
185+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
186+
try {
187+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
188+
return Result.of(extractBids(bidResponse), Collections.emptyList());
189+
} catch (DecodeException e) {
190+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
191+
}
192+
193+
}
194+
195+
private List<BidderBid> extractBids(BidResponse bidResponse) {
196+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
197+
return Collections.emptyList();
198+
}
199+
200+
return bidResponse.getSeatbid().stream()
201+
.filter(Objects::nonNull)
202+
.map(SeatBid::getBid)
203+
.filter(Objects::nonNull)
204+
.flatMap(Collection::stream)
205+
.filter(Objects::nonNull)
206+
.map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur()))
207+
.filter(Objects::nonNull)
208+
.toList();
209+
}
210+
211+
private static BidType getBidType(Bid bid) {
212+
return switch (bid.getMtype()) {
213+
case 1 -> BidType.banner;
214+
case 2 -> BidType.video;
215+
case null, default -> BidType.video;
216+
};
217+
}
218+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.proto.openrtb.ext.request.showheroes;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpShowheroes {
8+
9+
@JsonProperty("unitId")
10+
String unitId;
11+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.showheroes.ShowheroesBidder;
5+
import org.prebid.server.currency.CurrencyConversionService;
6+
import org.prebid.server.json.JacksonMapper;
7+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
8+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
9+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
10+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
11+
import org.prebid.server.version.PrebidVersionProvider;
12+
import org.springframework.beans.factory.annotation.Value;
13+
import org.springframework.boot.context.properties.ConfigurationProperties;
14+
import org.springframework.context.annotation.Bean;
15+
import org.springframework.context.annotation.Configuration;
16+
import org.springframework.context.annotation.PropertySource;
17+
18+
import jakarta.validation.constraints.NotBlank;
19+
20+
@Configuration
21+
@PropertySource(value = "classpath:/bidder-config/showheroes.yaml", factory = YamlPropertySourceFactory.class)
22+
public class ShowheroesConfiguration {
23+
24+
private static final String BIDDER_NAME = "showheroes";
25+
26+
@Bean("showheroesConfigurationProperties")
27+
@ConfigurationProperties("adapters.showheroes")
28+
BidderConfigurationProperties configurationProperties() {
29+
return new BidderConfigurationProperties();
30+
}
31+
32+
@Bean
33+
BidderDeps showheroesBidderDeps(BidderConfigurationProperties showheroesConfigurationProperties,
34+
@NotBlank @Value("${external-url}") String externalUrl,
35+
CurrencyConversionService currencyConversionService,
36+
PrebidVersionProvider prebidVersionProvider,
37+
JacksonMapper mapper) {
38+
39+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
40+
.withConfig(showheroesConfigurationProperties)
41+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
42+
.bidderCreator(config -> new ShowheroesBidder(
43+
config.getEndpoint(),
44+
currencyConversionService,
45+
prebidVersionProvider,
46+
mapper))
47+
.assemble();
48+
}
49+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
adapters:
2+
showheroes:
3+
endpoint: https://ads.viralize.tv/openrtb2/auction/
4+
aliases:
5+
showheroes-bs: ~
6+
showheroesBs: ~
7+
ortb-version: '2.6'
8+
meta-info:
9+
maintainer-email: tech@showheroes.com
10+
app-media-types:
11+
- banner
12+
- video
13+
site-media-types:
14+
- banner
15+
- video
16+
supported-vendors:
17+
vendor-id: 111
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Showheroes Adapter Params",
4+
"description": "A schema which validates params accepted by the Showheroes adapter",
5+
"type": "object",
6+
"properties": {
7+
"unitId": {
8+
"type": "string",
9+
"description": "Unit ID",
10+
"minLength": 8
11+
}
12+
},
13+
"required": ["unitId"]
14+
}

0 commit comments

Comments
 (0)