Skip to content

Commit 7e2cacf

Browse files
authored
Port Mobkoi: New Adapter (#3942)
1 parent 1bcd9eb commit 7e2cacf

File tree

12 files changed

+670
-0
lines changed

12 files changed

+670
-0
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package org.prebid.server.bidder.mobkoi;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.iab.openrtb.request.BidRequest;
5+
import com.iab.openrtb.request.Imp;
6+
import com.iab.openrtb.request.User;
7+
import com.iab.openrtb.response.BidResponse;
8+
import com.iab.openrtb.response.SeatBid;
9+
import org.apache.commons.collections4.CollectionUtils;
10+
import org.apache.commons.lang3.StringUtils;
11+
import org.prebid.server.bidder.Bidder;
12+
import org.prebid.server.bidder.model.BidderBid;
13+
import org.prebid.server.bidder.model.BidderCall;
14+
import org.prebid.server.bidder.model.BidderError;
15+
import org.prebid.server.bidder.model.HttpRequest;
16+
import org.prebid.server.bidder.model.Result;
17+
import org.prebid.server.exception.PreBidException;
18+
import org.prebid.server.json.DecodeException;
19+
import org.prebid.server.json.JacksonMapper;
20+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
21+
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
22+
import org.prebid.server.proto.openrtb.ext.request.mobkoi.ExtImpMobkoi;
23+
import org.prebid.server.proto.openrtb.ext.response.BidType;
24+
import org.prebid.server.util.BidderUtil;
25+
import org.prebid.server.util.HttpUtil;
26+
27+
import java.net.URI;
28+
import java.net.URISyntaxException;
29+
import java.util.ArrayList;
30+
import java.util.Collection;
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Objects;
34+
import java.util.Optional;
35+
36+
public class MobkoiBidder implements Bidder<BidRequest> {
37+
38+
private static final TypeReference<ExtPrebid<?, ExtImpMobkoi>> MOBKOI_EXT_TYPE_REFERENCE =
39+
new TypeReference<>() {
40+
};
41+
42+
private final String endpointUrl;
43+
private final JacksonMapper mapper;
44+
45+
public MobkoiBidder(String endpointUrl, JacksonMapper mapper) {
46+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
47+
this.mapper = Objects.requireNonNull(mapper);
48+
}
49+
50+
@Override
51+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
52+
final Imp firstImp = bidRequest.getImp().getFirst();
53+
54+
final ExtImpMobkoi extImpMobkoi;
55+
final Imp modifiedFirstImp;
56+
try {
57+
extImpMobkoi = parseExtImp(firstImp);
58+
modifiedFirstImp = modifyImp(firstImp, extImpMobkoi);
59+
} catch (PreBidException e) {
60+
return Result.withError(BidderError.badInput(e.getMessage()));
61+
}
62+
63+
final String selectedEndpointUrl = resolveEndpoint(extImpMobkoi.getAdServerBaseUrl());
64+
65+
return Result.withValue(BidderUtil.defaultRequest(
66+
modifyBidRequest(bidRequest, modifiedFirstImp),
67+
selectedEndpointUrl,
68+
mapper));
69+
}
70+
71+
private ExtImpMobkoi parseExtImp(Imp imp) {
72+
try {
73+
return mapper.mapper().convertValue(imp.getExt(), MOBKOI_EXT_TYPE_REFERENCE).getBidder();
74+
} catch (IllegalArgumentException e) {
75+
throw new PreBidException(
76+
"Invalid imp.ext for impression id %s. Error Information: %s"
77+
.formatted(imp.getId(), e.getMessage()));
78+
}
79+
}
80+
81+
private Imp modifyImp(Imp firstImp, ExtImpMobkoi extImpMobkoi) {
82+
if (StringUtils.isNotBlank(firstImp.getTagid())) {
83+
return firstImp;
84+
}
85+
86+
if (StringUtils.isNotBlank(extImpMobkoi.getPlacementId())) {
87+
return firstImp.toBuilder().tagid(extImpMobkoi.getPlacementId()).build();
88+
}
89+
90+
throw new PreBidException("invalid because it comes with neither request.imp[0].tagId nor "
91+
+ "req.imp[0].ext.Bidder.placementId");
92+
}
93+
94+
// url is already validated with `bidder-params` json schema
95+
private String resolveEndpoint(String customUri) {
96+
if (customUri == null) {
97+
return endpointUrl;
98+
}
99+
try {
100+
final URI uri = new URI(customUri);
101+
return uri.resolve("/bid").toString();
102+
} catch (IllegalArgumentException | URISyntaxException e) {
103+
return endpointUrl;
104+
}
105+
}
106+
107+
private static BidRequest modifyBidRequest(BidRequest bidRequest, Imp modifiedFirstImp) {
108+
final User user = modifyUser(bidRequest.getUser());
109+
final List<Imp> imps = updateFirstImpWith(bidRequest.getImp(), modifiedFirstImp);
110+
return bidRequest.toBuilder().user(user).imp(imps).build();
111+
}
112+
113+
private static User modifyUser(User user) {
114+
return Optional.ofNullable(user)
115+
.map(User::getConsent)
116+
.map(consent -> ExtUser.builder().consent(consent).build())
117+
.map(ext -> user.toBuilder().ext(ext).build())
118+
.orElse(user);
119+
}
120+
121+
private static List<Imp> updateFirstImpWith(List<Imp> imps, Imp imp) {
122+
final List<Imp> modifiedImps = new ArrayList<>(imps);
123+
modifiedImps.set(0, imp);
124+
return Collections.unmodifiableList(modifiedImps);
125+
}
126+
127+
@Override
128+
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
129+
try {
130+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
131+
return Result.withValues(extractBids(bidResponse));
132+
} catch (DecodeException | PreBidException e) {
133+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
134+
}
135+
}
136+
137+
private static List<BidderBid> extractBids(BidResponse bidResponse) {
138+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
139+
return Collections.emptyList();
140+
}
141+
return bidsFromResponse(bidResponse);
142+
}
143+
144+
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse) {
145+
return bidResponse.getSeatbid()
146+
.stream()
147+
.filter(Objects::nonNull)
148+
.map(SeatBid::getBid)
149+
.filter(Objects::nonNull)
150+
.flatMap(Collection::stream)
151+
.map(bid -> BidderBid.of(bid, BidType.banner, "mobkoi", bidResponse.getCur()))
152+
.toList();
153+
}
154+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.prebid.server.proto.openrtb.ext.request.mobkoi;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpMobkoi {
8+
9+
@JsonProperty("placementId")
10+
String placementId;
11+
12+
@JsonProperty("adServerBaseUrl")
13+
String adServerBaseUrl;
14+
}
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.mobkoi.MobkoiBidder;
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/mobkoi.yaml", factory = YamlPropertySourceFactory.class)
20+
public class MobkoiConfiguration {
21+
22+
private static final String BIDDER_NAME = "mobkoi";
23+
24+
@Bean("mobkoiConfigurationProperties")
25+
@ConfigurationProperties("adapters.mobkoi")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps mobkoiBidderDeps(BidderConfigurationProperties mobkoiConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(mobkoiConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new MobkoiBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
adapters:
2+
mobkoi:
3+
endpoint: "https://pbs.maximus.mobkoi.com/bid"
4+
ortb-version: "2.6"
5+
meta-info:
6+
maintainer-email: [email protected]
7+
app-media-types:
8+
site-media-types:
9+
- banner
10+
supported-vendors:
11+
vendor-id: 898
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Mobkoi Adapter Params",
4+
"description": "A schema which validates params accepted by the Mobkoi adapter",
5+
"type": "object",
6+
"properties": {
7+
"placementId": {
8+
"type": "string",
9+
"description": "Placement ID"
10+
},
11+
"adServerBaseUrl": {
12+
"type": "string",
13+
"description": "Mobkoi's ad server url",
14+
"pattern": "^https?://[^.]+\\.mobkoi\\.com$"
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)