Skip to content

Commit 4c8bcc0

Browse files
Kobler: New adapter ported from Go (#3684)
1 parent b6efb77 commit 4c8bcc0

File tree

12 files changed

+747
-0
lines changed

12 files changed

+747
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package org.prebid.server.bidder.kobler;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.core.type.TypeReference;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
7+
import com.iab.openrtb.request.BidRequest;
8+
import com.iab.openrtb.request.Imp;
9+
import com.iab.openrtb.response.Bid;
10+
import com.iab.openrtb.response.BidResponse;
11+
import com.iab.openrtb.response.SeatBid;
12+
import org.apache.commons.collections4.CollectionUtils;
13+
import org.prebid.server.bidder.Bidder;
14+
import org.prebid.server.bidder.model.BidderBid;
15+
import org.prebid.server.bidder.model.BidderCall;
16+
import org.prebid.server.bidder.model.BidderError;
17+
import org.prebid.server.bidder.model.HttpRequest;
18+
import org.prebid.server.bidder.model.Price;
19+
import org.prebid.server.bidder.model.Result;
20+
import org.prebid.server.currency.CurrencyConversionService;
21+
import org.prebid.server.exception.PreBidException;
22+
import org.prebid.server.json.DecodeException;
23+
import org.prebid.server.json.JacksonMapper;
24+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
25+
import org.prebid.server.proto.openrtb.ext.request.kobler.ExtImpKobler;
26+
import org.prebid.server.proto.openrtb.ext.response.BidType;
27+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
28+
import org.prebid.server.util.BidderUtil;
29+
import org.prebid.server.util.HttpUtil;
30+
31+
import java.math.BigDecimal;
32+
import java.util.ArrayList;
33+
import java.util.Collection;
34+
import java.util.Collections;
35+
import java.util.List;
36+
import java.util.Objects;
37+
import java.util.Optional;
38+
39+
public class KoblerBidder implements Bidder<BidRequest> {
40+
41+
private static final TypeReference<ExtPrebid<?, ExtImpKobler>> KOBLER_EXT_TYPE_REFERENCE =
42+
new TypeReference<>() {
43+
};
44+
45+
private static final String DEFAULT_BID_CURRENCY = "USD";
46+
private static final String EXT_PREBID = "prebid";
47+
48+
private final String endpointUrl;
49+
private final String devEndpoint;
50+
private final CurrencyConversionService currencyConversionService;
51+
private final JacksonMapper mapper;
52+
53+
public KoblerBidder(String endpointUrl,
54+
String devEndpoint,
55+
CurrencyConversionService currencyConversionService,
56+
JacksonMapper mapper) {
57+
58+
this.endpointUrl = HttpUtil.validateUrl(endpointUrl);
59+
this.devEndpoint = Objects.requireNonNull(devEndpoint);
60+
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
61+
this.mapper = Objects.requireNonNull(mapper);
62+
}
63+
64+
@Override
65+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
66+
final List<BidderError> errors = new ArrayList<>();
67+
final List<Imp> modifiedImps = new ArrayList<>();
68+
69+
final List<Imp> imps = bidRequest.getImp();
70+
for (Imp imp : imps) {
71+
try {
72+
modifiedImps.add(modifyImp(bidRequest, imp));
73+
} catch (PreBidException e) {
74+
errors.add(BidderError.badInput(e.getMessage()));
75+
return Result.withErrors(errors);
76+
}
77+
}
78+
79+
final BidRequest modifiedRequest = bidRequest.toBuilder()
80+
.imp(modifiedImps)
81+
.cur(normalizeCurrencies(bidRequest))
82+
.build();
83+
84+
final String endpoint = isTest(imps.getFirst(), errors) ? devEndpoint : endpointUrl;
85+
86+
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(modifiedRequest, endpoint, mapper);
87+
return Result.of(Collections.singletonList(httpRequest), errors);
88+
}
89+
90+
private Imp modifyImp(BidRequest bidRequest, Imp imp) {
91+
final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest);
92+
93+
return imp.toBuilder()
94+
.bidfloor(resolvedBidFloor.getValue())
95+
.bidfloorcur(resolvedBidFloor.getCurrency())
96+
.build();
97+
}
98+
99+
private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
100+
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
101+
return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY)
102+
? convertBidFloor(initialBidFloorPrice, bidRequest)
103+
: initialBidFloorPrice;
104+
}
105+
106+
private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) {
107+
final BigDecimal convertedPrice = currencyConversionService.convertCurrency(
108+
bidFloorPrice.getValue(),
109+
bidRequest,
110+
bidFloorPrice.getCurrency(),
111+
DEFAULT_BID_CURRENCY);
112+
113+
return Price.of(DEFAULT_BID_CURRENCY, convertedPrice);
114+
}
115+
116+
private List<String> normalizeCurrencies(BidRequest bidRequest) {
117+
final List<String> currencies = bidRequest.getCur();
118+
if (currencies.contains(DEFAULT_BID_CURRENCY)) {
119+
return currencies;
120+
}
121+
122+
final List<String> newCurrencies = new ArrayList<>(currencies);
123+
newCurrencies.add(DEFAULT_BID_CURRENCY);
124+
return newCurrencies;
125+
}
126+
127+
private boolean isTest(Imp imp, List<BidderError> errors) {
128+
try {
129+
return parseImpExt(imp).getTest();
130+
} catch (PreBidException e) {
131+
errors.add(BidderError.badInput(e.getMessage()));
132+
return false;
133+
}
134+
}
135+
136+
private ExtImpKobler parseImpExt(Imp imp) {
137+
try {
138+
return mapper.mapper().convertValue(imp.getExt(), KOBLER_EXT_TYPE_REFERENCE).getBidder();
139+
} catch (IllegalArgumentException e) {
140+
throw new PreBidException(e.getMessage());
141+
}
142+
}
143+
144+
@Override
145+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
146+
try {
147+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
148+
return Result.withValues(extractBids(bidResponse));
149+
} catch (DecodeException e) {
150+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
151+
}
152+
}
153+
154+
private List<BidderBid> extractBids(BidResponse bidResponse) {
155+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
156+
return Collections.emptyList();
157+
}
158+
return bidsFromResponse(bidResponse);
159+
}
160+
161+
private List<BidderBid> bidsFromResponse(BidResponse bidResponse) {
162+
return bidResponse.getSeatbid().stream()
163+
.filter(Objects::nonNull)
164+
.map(SeatBid::getBid)
165+
.filter(Objects::nonNull)
166+
.flatMap(Collection::stream)
167+
.filter(Objects::nonNull)
168+
.map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur()))
169+
.toList();
170+
}
171+
172+
private BidType getBidType(Bid bid) {
173+
return Optional.ofNullable(bid.getExt())
174+
.map(ext -> ext.get(EXT_PREBID))
175+
.filter(JsonNode::isObject)
176+
.map(ObjectNode.class::cast)
177+
.filter(JsonNode::isObject)
178+
.map(this::parseExtBidPrebid)
179+
.map(ExtBidPrebid::getType)
180+
.orElse(BidType.banner);
181+
}
182+
183+
private ExtBidPrebid parseExtBidPrebid(ObjectNode prebid) {
184+
try {
185+
return mapper.mapper().treeToValue(prebid, ExtBidPrebid.class);
186+
} catch (JsonProcessingException e) {
187+
return null;
188+
}
189+
}
190+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.prebid.server.proto.openrtb.ext.request.kobler;
2+
3+
import lombok.Value;
4+
5+
@Value(staticConstructor = "of")
6+
public class ExtImpKobler {
7+
8+
Boolean test;
9+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import lombok.Data;
4+
import lombok.EqualsAndHashCode;
5+
import lombok.NoArgsConstructor;
6+
import org.prebid.server.bidder.BidderDeps;
7+
import org.prebid.server.bidder.kobler.KoblerBidder;
8+
import org.prebid.server.currency.CurrencyConversionService;
9+
import org.prebid.server.json.JacksonMapper;
10+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
11+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
12+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
13+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.boot.context.properties.ConfigurationProperties;
16+
import org.springframework.context.annotation.Bean;
17+
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.context.annotation.PropertySource;
19+
import org.springframework.validation.annotation.Validated;
20+
21+
import jakarta.validation.constraints.NotBlank;
22+
23+
@Configuration
24+
@PropertySource(value = "classpath:/bidder-config/kobler.yaml", factory = YamlPropertySourceFactory.class)
25+
public class KoblerConfiguration {
26+
27+
private static final String BIDDER_NAME = "kobler";
28+
29+
@Bean("koblerConfigurationProperties")
30+
@ConfigurationProperties("adapters.kobler")
31+
KoblerConfigurationProperties configurationProperties() {
32+
return new KoblerConfigurationProperties();
33+
}
34+
35+
@Bean
36+
BidderDeps koblerBidderDeps(KoblerConfigurationProperties config,
37+
CurrencyConversionService currencyConversionService,
38+
@NotBlank @Value("${external-url}") String externalUrl,
39+
JacksonMapper mapper) {
40+
41+
return BidderDepsAssembler.<KoblerConfigurationProperties>forBidder(BIDDER_NAME)
42+
.withConfig(config)
43+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
44+
.bidderCreator(cfg -> new KoblerBidder(
45+
cfg.getEndpoint(),
46+
cfg.getDevEndpoint(),
47+
currencyConversionService,
48+
mapper))
49+
.assemble();
50+
51+
}
52+
53+
@Validated
54+
@Data
55+
@EqualsAndHashCode(callSuper = true)
56+
@NoArgsConstructor
57+
private static class KoblerConfigurationProperties extends BidderConfigurationProperties {
58+
59+
@NotBlank
60+
private String devEndpoint;
61+
}
62+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
adapters:
2+
kobler:
3+
endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call"
4+
dev-endpoint: "https://bid-service.dev.essrtb.com/bid/prebid_server_rtb_call"
5+
endpoint-compression: gzip
6+
geoscope:
7+
- NOR
8+
- SWE
9+
- DNK
10+
meta-info:
11+
maintainer-email: bidding-support@kobler.no
12+
site-media-types:
13+
- banner
14+
vendor-id: 0
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Kobler Adapter Params",
4+
"description": "A schema which validates params accepted by the Kobler adapter",
5+
"type": "object",
6+
7+
"properties": {
8+
"test": {
9+
"type": "boolean",
10+
"description": "Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one."
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)