Skip to content

Commit b897da3

Browse files
New Exco Adapter (#4111)
1 parent e5c5480 commit b897da3

File tree

13 files changed

+701
-1
lines changed

13 files changed

+701
-1
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package org.prebid.server.bidder.exco;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.iab.openrtb.request.App;
5+
import com.iab.openrtb.request.BidRequest;
6+
import com.iab.openrtb.request.Imp;
7+
import com.iab.openrtb.request.Publisher;
8+
import com.iab.openrtb.request.Site;
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.Result;
19+
import org.prebid.server.exception.PreBidException;
20+
import org.prebid.server.json.DecodeException;
21+
import org.prebid.server.json.JacksonMapper;
22+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
23+
import org.prebid.server.proto.openrtb.ext.request.exco.ExtImpExco;
24+
import org.prebid.server.proto.openrtb.ext.response.BidType;
25+
import org.prebid.server.util.BidderUtil;
26+
import org.prebid.server.util.HttpUtil;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collection;
30+
import java.util.Collections;
31+
import java.util.List;
32+
import java.util.Objects;
33+
import java.util.Optional;
34+
import java.util.stream.Collectors;
35+
36+
public class ExcoBidder implements Bidder<BidRequest> {
37+
38+
private static final TypeReference<ExtPrebid<?, ExtImpExco>> EXCO_EXT_TYPE_REFERENCE = new TypeReference<>() {
39+
};
40+
41+
private final String endpointUrl;
42+
private final JacksonMapper mapper;
43+
44+
public ExcoBidder(String endpointUrl, JacksonMapper mapper) {
45+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
46+
this.mapper = Objects.requireNonNull(mapper);
47+
}
48+
49+
@Override
50+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
51+
final List<Imp> modifiedImps = new ArrayList<>();
52+
53+
String publisherId = null;
54+
55+
for (Imp imp : request.getImp()) {
56+
try {
57+
final ExtImpExco extImp = parseImpExt(imp);
58+
modifiedImps.add(imp.toBuilder().tagid(extImp.getTagId()).build());
59+
publisherId = extImp.getPublisherId();
60+
} catch (PreBidException e) {
61+
return Result.withError(BidderError.badInput(e.getMessage()));
62+
}
63+
}
64+
65+
final BidRequest outgoingRequest = modifyRequest(request, modifiedImps, publisherId);
66+
return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper));
67+
}
68+
69+
private ExtImpExco parseImpExt(Imp imp) {
70+
try {
71+
return mapper.mapper().convertValue(imp.getExt(), EXCO_EXT_TYPE_REFERENCE).getBidder();
72+
} catch (IllegalArgumentException e) {
73+
throw new PreBidException("Invalid imp.ext for impression %s. Error Information: %s"
74+
.formatted(imp.getId(), e.getMessage()));
75+
}
76+
}
77+
78+
private BidRequest modifyRequest(BidRequest request, List<Imp> imps, String publisherId) {
79+
final Site site = request.getSite();
80+
final App app = request.getApp();
81+
82+
return request.toBuilder()
83+
.imp(imps)
84+
.site(site != null ? modifySite(site, publisherId) : null)
85+
.app(app != null ? modifyApp(app, publisherId) : null)
86+
.build();
87+
}
88+
89+
private static Site modifySite(Site site, String publisherId) {
90+
return site.toBuilder().publisher(modifyPublisher(site.getPublisher(), publisherId)).build();
91+
}
92+
93+
private static App modifyApp(App app, String publisherId) {
94+
return app.toBuilder().publisher(modifyPublisher(app.getPublisher(), publisherId)).build();
95+
}
96+
97+
private static Publisher modifyPublisher(Publisher publisher, String publisherId) {
98+
return Optional.ofNullable(publisher)
99+
.map(Publisher::toBuilder)
100+
.orElseGet(Publisher::builder)
101+
.id(publisherId)
102+
.build();
103+
}
104+
105+
@Override
106+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
107+
try {
108+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
109+
final List<BidderError> errors = new ArrayList<>();
110+
return Result.of(extractBids(bidResponse, errors), errors);
111+
} catch (DecodeException e) {
112+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
113+
}
114+
}
115+
116+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
117+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
118+
return Collections.emptyList();
119+
}
120+
return bidsFromResponse(bidResponse, errors);
121+
}
122+
123+
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
124+
return bidResponse.getSeatbid().stream()
125+
.filter(Objects::nonNull)
126+
.map(SeatBid::getBid)
127+
.filter(Objects::nonNull)
128+
.flatMap(Collection::stream)
129+
.filter(Objects::nonNull)
130+
.map(bid -> makeBidderBid(bid, bidResponse.getCur(), errors))
131+
.filter(Objects::nonNull)
132+
.collect(Collectors.toList());
133+
}
134+
135+
private static BidderBid makeBidderBid(Bid bid, String currency, List<BidderError> errors) {
136+
final BidType bidType = getBidType(bid, errors);
137+
return bidType != null
138+
? BidderBid.of(bid, bidType, currency)
139+
: null;
140+
}
141+
142+
private static BidType getBidType(Bid bid, List<BidderError> errors) {
143+
return switch (bid.getMtype()) {
144+
case 1 -> BidType.banner;
145+
case 2 -> BidType.video;
146+
case null, default -> {
147+
errors.add(BidderError.badServerResponse(
148+
"unrecognized bid_ad_type in response from exco: " + bid.getMtype()));
149+
yield null;
150+
}
151+
};
152+
}
153+
}

src/main/java/org/prebid/server/bidder/zentotem/ZentotemBidder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequ
6161
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
6262
final List<BidderError> errors = new ArrayList<>();
6363
return Result.of(extractBids(bidResponse, errors), errors);
64-
} catch (DecodeException | PreBidException e) {
64+
} catch (DecodeException e) {
6565
return Result.withError(BidderError.badServerResponse(e.getMessage()));
6666
}
6767
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.prebid.server.proto.openrtb.ext.request.exco;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpExco {
8+
9+
@JsonProperty("accountId")
10+
String accountId;
11+
12+
@JsonProperty("publisherId")
13+
String publisherId;
14+
15+
@JsonProperty("tagId")
16+
String tagId;
17+
}
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.exco.ExcoBidder;
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/exco.yaml", factory = YamlPropertySourceFactory.class)
20+
public class ExcoConfiguration {
21+
22+
private static final String BIDDER_NAME = "exco";
23+
24+
@Bean("excoConfigurationProperties")
25+
@ConfigurationProperties("adapters.exco")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps excoBidderDeps(BidderConfigurationProperties excoConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(excoConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new ExcoBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
adapters:
2+
exco:
3+
endpoint: https://v.ex.co/se/openrtb/hb/pbs
4+
meta-info:
5+
maintainer-email: [email protected]
6+
app-media-types:
7+
- banner
8+
- video
9+
site-media-types:
10+
- banner
11+
- video
12+
supported-vendors:
13+
vendor-id: 444
14+
usersync:
15+
cookie-family-name: exco
16+
redirect:
17+
url: https://sync.ex.co/v1/user_sync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
18+
support-cors: false
19+
uid-macro: '[UID]'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "The Exco Adapter Params",
4+
"description": "A schema which validates params accepted by Exco adapter",
5+
"type": "object",
6+
"properties": {
7+
"accountId": {
8+
"type": "string",
9+
"minLength": 1,
10+
"description": "A unique account identifier provided by EX.CO."
11+
},
12+
"publisherId": {
13+
"type": "string",
14+
"minLength": 1,
15+
"description": "Publisher ID provided by EX.CO."
16+
},
17+
"tagId": {
18+
"type": "string",
19+
"minLength": 1,
20+
"description": "A unique Tag ID (supply id) identifier provided by EX.CO."
21+
}
22+
},
23+
"required": [
24+
"accountId",
25+
"publisherId",
26+
"tagId"
27+
]
28+
}

0 commit comments

Comments
 (0)