Skip to content

Commit 6554a1f

Browse files
New Akcelo Adapter (#4087)
1 parent 98b7a77 commit 6554a1f

File tree

13 files changed

+772
-3
lines changed

13 files changed

+772
-3
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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> imps = request.getImp();
58+
final List<Imp> modifiedImps = new ArrayList<>();
59+
60+
final ExtImpAkcelo firstExtImp;
61+
try {
62+
firstExtImp = parseImpExt(imps.getFirst());
63+
} catch (PreBidException e) {
64+
return Result.withError(BidderError.badInput(e.getMessage()));
65+
}
66+
67+
for (final Imp imp : imps) {
68+
modifiedImps.add(modifyImp(imp));
69+
}
70+
71+
final BidRequest outgoingRequest = modifyRequest(request, modifiedImps, firstExtImp.getSiteId());
72+
return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper));
73+
}
74+
75+
private ExtImpAkcelo parseImpExt(Imp imp) {
76+
try {
77+
return mapper.mapper().convertValue(imp.getExt(), AKCELO_EXT_TYPE_REFERENCE).getBidder();
78+
} catch (IllegalArgumentException e) {
79+
throw new PreBidException(e.getMessage());
80+
}
81+
}
82+
83+
private Imp modifyImp(Imp imp) {
84+
return imp.toBuilder()
85+
.ext(mapper.mapper().createObjectNode().set(BIDDER_NAME, imp.getExt().get("bidder")))
86+
.build();
87+
}
88+
89+
private BidRequest modifyRequest(BidRequest request, List<Imp> imps, String siteId) {
90+
return request.toBuilder()
91+
.imp(imps)
92+
.site(modifySite(request.getSite(), siteId))
93+
.build();
94+
}
95+
96+
private Site modifySite(Site site, String siteId) {
97+
final Publisher publisher = Optional.ofNullable(site)
98+
.map(Site::getPublisher)
99+
.map(Publisher::toBuilder)
100+
.orElseGet(Publisher::builder)
101+
.ext(ExtPublisher.of(ExtPublisherPrebid.of(siteId)))
102+
.build();
103+
104+
return Optional.ofNullable(site)
105+
.map(Site::toBuilder)
106+
.orElseGet(Site::builder)
107+
.publisher(publisher)
108+
.build();
109+
}
110+
111+
@Override
112+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
113+
try {
114+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
115+
final List<BidderError> errors = new ArrayList<>();
116+
return Result.of(extractBids(bidResponse, errors), errors);
117+
} catch (DecodeException e) {
118+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
119+
}
120+
}
121+
122+
private List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
123+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
124+
return Collections.emptyList();
125+
}
126+
127+
return bidResponse.getSeatbid().stream()
128+
.filter(Objects::nonNull)
129+
.map(SeatBid::getBid)
130+
.filter(Objects::nonNull)
131+
.flatMap(Collection::stream)
132+
.filter(Objects::nonNull)
133+
.map(bid -> makeBid(bid, bidResponse.getCur(), errors))
134+
.filter(Objects::nonNull)
135+
.toList();
136+
}
137+
138+
private BidderBid makeBid(Bid bid, String currency, List<BidderError> errors) {
139+
final BidType bidType = getBidType(bid, errors);
140+
return bidType == null ? null : BidderBid.of(bid, bidType, currency);
141+
}
142+
143+
private BidType getBidType(Bid bid, List<BidderError> errors) {
144+
final Integer mType = bid.getMtype();
145+
if (mType != null) {
146+
return switch (mType) {
147+
case 1 -> BidType.banner;
148+
case 2 -> BidType.video;
149+
case 4 -> BidType.xNative;
150+
default -> {
151+
errors.add(BidderError.badServerResponse("unable to get media type " + mType));
152+
yield null;
153+
}
154+
};
155+
}
156+
157+
return getExtBidPrebidType(bid, errors);
158+
}
159+
160+
private BidType getExtBidPrebidType(Bid bid, List<BidderError> errors) {
161+
return Optional.ofNullable(bid.getExt())
162+
.map(ext -> ext.get("prebid"))
163+
.filter(JsonNode::isObject)
164+
.map(ObjectNode.class::cast)
165+
.map(this::parseExtBidPrebid)
166+
.map(ExtBidPrebid::getType)
167+
.orElseGet(() -> {
168+
errors.add(BidderError.badServerResponse("missing media type for bid " + bid.getId()));
169+
return null;
170+
});
171+
}
172+
173+
private ExtBidPrebid parseExtBidPrebid(ObjectNode prebid) {
174+
try {
175+
return mapper.mapper().treeToValue(prebid, ExtBidPrebid.class);
176+
} catch (JsonProcessingException e) {
177+
return null;
178+
}
179+
}
180+
}
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: [email protected]
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)