Skip to content

Commit d35eaea

Browse files
committed
New Akcelo Adapter
1 parent ea32800 commit d35eaea

13 files changed

Lines changed: 799 additions & 3 deletions

File tree

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package org.prebid.server.bidder.akcelo;
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.request.Publisher;
10+
import com.iab.openrtb.request.Site;
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.prebid.server.bidder.Bidder;
16+
import org.prebid.server.bidder.model.BidderBid;
17+
import org.prebid.server.bidder.model.BidderCall;
18+
import org.prebid.server.bidder.model.BidderError;
19+
import org.prebid.server.bidder.model.HttpRequest;
20+
import org.prebid.server.bidder.model.Result;
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.ExtPublisher;
26+
import org.prebid.server.proto.openrtb.ext.request.ExtPublisherPrebid;
27+
import org.prebid.server.proto.openrtb.ext.request.akcelo.ExtImpAkcelo;
28+
import org.prebid.server.proto.openrtb.ext.response.BidType;
29+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
30+
import org.prebid.server.util.BidderUtil;
31+
import org.prebid.server.util.HttpUtil;
32+
33+
import java.util.ArrayList;
34+
import java.util.Collection;
35+
import java.util.Collections;
36+
import java.util.List;
37+
import java.util.Objects;
38+
import java.util.Optional;
39+
40+
public class AkceloBidder implements Bidder<BidRequest> {
41+
42+
private static final TypeReference<ExtPrebid<?, ExtImpAkcelo>> AKCELO_EXT_TYPE_REFERENCE =
43+
new TypeReference<>() {
44+
};
45+
private static final String BIDDER_NAME = "akcelo";
46+
47+
private final String endpointUrl;
48+
private final JacksonMapper mapper;
49+
50+
public AkceloBidder(String endpointUrl, JacksonMapper mapper) {
51+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
52+
this.mapper = Objects.requireNonNull(mapper);
53+
}
54+
55+
@Override
56+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
57+
final List<Imp> modifiedImps = new ArrayList<>();
58+
final List<BidderError> errors = new ArrayList<>();
59+
60+
String siteId = null;
61+
62+
try {
63+
final List<Imp> imps = request.getImp();
64+
for (int i = 0; i < imps.size(); i++) {
65+
final Imp imp = imps.get(i);
66+
if (i == 0) {
67+
final ExtImpAkcelo extImp = parseImpExt(imp);
68+
siteId = extImp.getSiteId();
69+
}
70+
modifiedImps.add(modifyImp(imp));
71+
}
72+
} catch (PreBidException e) {
73+
errors.add(BidderError.badInput(e.getMessage()));
74+
}
75+
76+
if (modifiedImps.isEmpty()) {
77+
return Result.withErrors(errors);
78+
}
79+
80+
final BidRequest outgoingRequest = modifyRequest(request, modifiedImps, siteId);
81+
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper);
82+
return Result.of(Collections.singletonList(httpRequest), errors);
83+
}
84+
85+
private ExtImpAkcelo parseImpExt(Imp imp) {
86+
try {
87+
return mapper.mapper().convertValue(imp.getExt(), AKCELO_EXT_TYPE_REFERENCE).getBidder();
88+
} catch (IllegalArgumentException e) {
89+
throw new PreBidException(e.getMessage());
90+
}
91+
}
92+
93+
private Imp modifyImp(Imp imp) {
94+
return Optional.ofNullable(imp.getExt())
95+
.map(prebid -> prebid.get("bidder"))
96+
.filter(JsonNode::isObject)
97+
.map(bidder -> (ObjectNode) mapper.mapper().createObjectNode().set(BIDDER_NAME, bidder))
98+
.map(ext -> imp.toBuilder().ext(ext).build())
99+
.orElseThrow(() -> new PreBidException("imp.ext.prebid.bidder can't be parsed"));
100+
}
101+
102+
private BidRequest modifyRequest(BidRequest request, List<Imp> imps, String siteId) {
103+
return request.toBuilder()
104+
.imp(imps)
105+
.site(modifySite(request.getSite(), siteId))
106+
.build();
107+
}
108+
109+
private Site modifySite(Site site, String siteId) {
110+
final Publisher publisher = Optional.ofNullable(site)
111+
.map(Site::getPublisher)
112+
.map(Publisher::toBuilder)
113+
.orElseGet(Publisher::builder)
114+
.ext(ExtPublisher.of(ExtPublisherPrebid.of(siteId)))
115+
.build();
116+
117+
return Optional.ofNullable(site)
118+
.map(Site::toBuilder)
119+
.orElseGet(Site::builder)
120+
.publisher(publisher)
121+
.build();
122+
}
123+
124+
@Override
125+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
126+
try {
127+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
128+
final List<BidderError> errors = new ArrayList<>();
129+
return Result.of(extractBids(bidResponse, errors), errors);
130+
} catch (DecodeException e) {
131+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
132+
}
133+
}
134+
135+
private List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
136+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
137+
return Collections.emptyList();
138+
}
139+
140+
return bidResponse.getSeatbid().stream()
141+
.filter(Objects::nonNull)
142+
.map(SeatBid::getBid)
143+
.filter(Objects::nonNull)
144+
.flatMap(Collection::stream)
145+
.filter(Objects::nonNull)
146+
.map(bid -> makeBid(bid, bidResponse.getCur(), errors))
147+
.filter(Objects::nonNull)
148+
.toList();
149+
}
150+
151+
private BidderBid makeBid(Bid bid, String currency, List<BidderError> errors) {
152+
final BidType bidType = getBidType(bid, errors);
153+
return bidType == null ? null : BidderBid.of(bid, bidType, currency);
154+
}
155+
156+
private BidType getBidType(Bid bid, List<BidderError> errors) {
157+
final Integer mType = bid.getMtype();
158+
if (mType != null) {
159+
return switch (mType) {
160+
case 1 -> BidType.banner;
161+
case 2 -> BidType.video;
162+
case 4 -> BidType.xNative;
163+
default -> {
164+
errors.add(BidderError.badServerResponse("unable to get media type " + mType));
165+
yield null;
166+
}
167+
};
168+
}
169+
170+
return getExtBidPrebidType(bid, errors);
171+
}
172+
173+
private BidType getExtBidPrebidType(Bid bid, List<BidderError> errors) {
174+
return Optional.ofNullable(bid.getExt())
175+
.map(ext -> ext.get("prebid"))
176+
.map(ObjectNode.class::cast)
177+
.map(this::parseExtBidPrebid)
178+
.map(ExtBidPrebid::getType)
179+
.orElseGet(() -> {
180+
errors.add(BidderError.badServerResponse("missing media type for bid " + bid.getId()));
181+
return null;
182+
});
183+
}
184+
185+
private ExtBidPrebid parseExtBidPrebid(ObjectNode prebid) {
186+
try {
187+
return mapper.mapper().treeToValue(prebid, ExtBidPrebid.class);
188+
} catch (JsonProcessingException e) {
189+
return null;
190+
}
191+
}
192+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.prebid.server.proto.openrtb.ext.request.akcelo;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpAkcelo {
8+
9+
@JsonProperty("adUnitId")
10+
Integer adUnitId;
11+
12+
@JsonProperty("siteId")
13+
String siteId;
14+
15+
Integer test;
16+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.akcelo.AkceloBidder;
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 jakarta.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/akcelo.yaml",
20+
factory = YamlPropertySourceFactory.class)
21+
public class AkceloConfiguration {
22+
23+
private static final String BIDDER_NAME = "akcelo";
24+
25+
@Bean("akceloConfigurationProperties")
26+
@ConfigurationProperties("adapters.akcelo")
27+
BidderConfigurationProperties configurationProperties() {
28+
return new BidderConfigurationProperties();
29+
}
30+
31+
@Bean
32+
BidderDeps akceloBidderDeps(BidderConfigurationProperties akceloConfigurationProperties,
33+
@NotBlank @Value("${external-url}") String externalUrl,
34+
JacksonMapper mapper) {
35+
36+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
37+
.withConfig(akceloConfigurationProperties)
38+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
39+
.bidderCreator(config -> new AkceloBidder(config.getEndpoint(), mapper))
40+
.assemble();
41+
}
42+
43+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
adapters:
2+
akcelo:
3+
endpoint: https://s2s.sportslocalmedia.com/openrtb2/auction
4+
meta-info:
5+
maintainer-email: tech@akcelo.io
6+
app-media-types:
7+
site-media-types:
8+
- banner
9+
- video
10+
- native
11+
supported-vendors:
12+
vendor-id: 0
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Akcelo Adapter Params",
4+
"description": "A schema which validates params accepted by the Akcelo adapter",
5+
"type": "object",
6+
"properties": {
7+
"adUnitID": {
8+
"type": "number",
9+
"description": "The identifier of the ad unit. Will be provided by your account manager."
10+
},
11+
"siteId": {
12+
"type": "number",
13+
"description": "The identifier of the site. Will be provided by your account manager."
14+
},
15+
"test": {
16+
"type": "number",
17+
"description": "Whether to display test creatives or not. Default is 0."
18+
}
19+
},
20+
"required": [
21+
"adUnitId",
22+
"siteId"
23+
]
24+
}

0 commit comments

Comments
 (0)