Skip to content

Commit c86aa47

Browse files
New Adapter: BeOp (#4476)
1 parent c619f54 commit c86aa47

12 files changed

Lines changed: 671 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package org.prebid.server.bidder.beop;
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.response.Bid;
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.apache.http.client.utils.URIBuilder;
12+
import org.prebid.server.bidder.Bidder;
13+
import org.prebid.server.bidder.model.BidderBid;
14+
import org.prebid.server.bidder.model.BidderCall;
15+
import org.prebid.server.bidder.model.BidderError;
16+
import org.prebid.server.bidder.model.HttpRequest;
17+
import org.prebid.server.bidder.model.Result;
18+
import org.prebid.server.exception.PreBidException;
19+
import org.prebid.server.json.DecodeException;
20+
import org.prebid.server.json.JacksonMapper;
21+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
22+
import org.prebid.server.proto.openrtb.ext.request.beop.ExtImpBeop;
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.URISyntaxException;
28+
import java.util.ArrayList;
29+
import java.util.Collection;
30+
import java.util.List;
31+
import java.util.Objects;
32+
33+
public class BeopBidder implements Bidder<BidRequest> {
34+
35+
private static final TypeReference<ExtPrebid<?, ExtImpBeop>> BEOP_EXT_TYPE_REFERENCE = new TypeReference<>() {
36+
};
37+
38+
private final String endpointUrl;
39+
private final JacksonMapper mapper;
40+
41+
public BeopBidder(String endpointUrl, JacksonMapper mapper) {
42+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
43+
this.mapper = Objects.requireNonNull(mapper);
44+
}
45+
46+
@Override
47+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
48+
final List<Imp> imps = bidRequest.getImp();
49+
50+
final ExtImpBeop extImpBeop;
51+
try {
52+
extImpBeop = parseImpExt(imps.getFirst());
53+
} catch (PreBidException e) {
54+
return Result.withError(BidderError.badInput(e.getMessage()));
55+
}
56+
57+
final String resolvedUrl;
58+
try {
59+
resolvedUrl = buildEndpointUrl(extImpBeop);
60+
} catch (PreBidException e) {
61+
return Result.withError(BidderError.badInput(e.getMessage()));
62+
}
63+
64+
return Result.withValue(BidderUtil.defaultRequest(bidRequest, resolvedUrl, mapper));
65+
}
66+
67+
private ExtImpBeop parseImpExt(Imp imp) {
68+
try {
69+
return mapper.mapper().convertValue(imp.getExt(), BEOP_EXT_TYPE_REFERENCE).getBidder();
70+
} catch (IllegalArgumentException e) {
71+
throw new PreBidException("ext.bidder not provided: " + e.getMessage());
72+
}
73+
}
74+
75+
private String buildEndpointUrl(ExtImpBeop ext) {
76+
final URIBuilder uriBuilder;
77+
try {
78+
uriBuilder = new URIBuilder(endpointUrl);
79+
} catch (URISyntaxException e) {
80+
throw new PreBidException("Invalid endpoint URL: " + e.getMessage());
81+
}
82+
83+
final String pid = StringUtils.trimToNull(ext.getPid());
84+
if (StringUtils.isNotEmpty(pid)) {
85+
uriBuilder.addParameter("pid", pid);
86+
}
87+
88+
final String nid = StringUtils.trimToNull(ext.getNid());
89+
if (StringUtils.isNotEmpty(nid)) {
90+
uriBuilder.addParameter("nid", nid);
91+
}
92+
93+
final String nptnid = StringUtils.trimToNull(ext.getNtpnid());
94+
if (StringUtils.isNotEmpty(nptnid)) {
95+
uriBuilder.addParameter("nptnid", nptnid);
96+
}
97+
98+
return uriBuilder.toString();
99+
}
100+
101+
@Override
102+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
103+
final BidResponse bidResponse;
104+
try {
105+
bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
106+
} catch (DecodeException e) {
107+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
108+
}
109+
110+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
111+
return Result.empty();
112+
}
113+
114+
final List<BidderError> errors = new ArrayList<>();
115+
final List<BidderBid> bids = extractBids(bidResponse, errors);
116+
return Result.of(bids, errors);
117+
}
118+
119+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
120+
return bidResponse.getSeatbid().stream()
121+
.filter(Objects::nonNull)
122+
.map(SeatBid::getBid)
123+
.filter(Objects::nonNull)
124+
.flatMap(Collection::stream)
125+
.filter(Objects::nonNull)
126+
.map(bid -> makeBidderBid(bid, bidResponse.getCur(), errors))
127+
.filter(Objects::nonNull)
128+
.toList();
129+
}
130+
131+
private static BidderBid makeBidderBid(Bid bid, String currency, List<BidderError> errors) {
132+
try {
133+
return BidderBid.of(bid, resolveBidType(bid), currency);
134+
} catch (PreBidException e) {
135+
errors.add(BidderError.badServerResponse(e.getMessage()));
136+
return null;
137+
}
138+
}
139+
140+
private static BidType resolveBidType(Bid bid) {
141+
final Integer mtype = bid.getMtype();
142+
return switch (mtype) {
143+
case 1 -> BidType.banner;
144+
case 2 -> BidType.video;
145+
case null, default -> throw new PreBidException(
146+
"Failed to parse bid mtype for impression \"%s\"".formatted(bid.getImpid()));
147+
};
148+
}
149+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.prebid.server.proto.openrtb.ext.request.beop;
2+
3+
import lombok.Value;
4+
5+
@Value(staticConstructor = "of")
6+
public class ExtImpBeop {
7+
8+
String pid;
9+
10+
String nid;
11+
12+
String ntpnid;
13+
}
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.beop.BeopBidder;
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/beop.yaml", factory = YamlPropertySourceFactory.class)
20+
public class BeopConfiguration {
21+
22+
private static final String BIDDER_NAME = "beop";
23+
24+
@Bean("beopConfigurationProperties")
25+
@ConfigurationProperties("adapters.beop")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps beopBidderDeps(BidderConfigurationProperties beopConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(beopConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new BeopBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
adapters:
2+
beop:
3+
endpoint: "https://hb.collectiveaudience.co/rtb/bid"
4+
ortb-version: "2.6"
5+
endpoint-compression: gzip
6+
geoscope:
7+
- USA
8+
meta-info:
9+
maintainer-email: "tech@collectiveaudience.co"
10+
site-media-types:
11+
- banner
12+
- video
13+
supported-vendors: []
14+
vendor-id: 666
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "BeOp Adapter Params",
4+
"description": "A schema which validates params accepted by the BeOp adapter",
5+
"type": "object",
6+
"properties": {
7+
"pid": {
8+
"type": "string",
9+
"description": "Beop publisher ID"
10+
},
11+
"nid": {
12+
"type": "string",
13+
"description": "Beop Network ID"
14+
},
15+
"ntpnid": {
16+
"type": "string",
17+
"description": "Network partner ID"
18+
}
19+
},
20+
"oneOf": [
21+
{
22+
"required": [
23+
"pid"
24+
]
25+
},
26+
{
27+
"required": [
28+
"nid",
29+
"ntpnid"
30+
]
31+
}
32+
]
33+
}

0 commit comments

Comments
 (0)