Skip to content

Commit a9d0144

Browse files
New Flatads Adapter (#4045)
1 parent 35b6562 commit a9d0144

File tree

12 files changed

+715
-0
lines changed

12 files changed

+715
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package org.prebid.server.bidder.flatads;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.iab.openrtb.request.BidRequest;
5+
import com.iab.openrtb.request.Device;
6+
import com.iab.openrtb.request.Imp;
7+
import com.iab.openrtb.response.Bid;
8+
import com.iab.openrtb.response.BidResponse;
9+
import com.iab.openrtb.response.SeatBid;
10+
import io.vertx.core.MultiMap;
11+
import org.apache.commons.collections4.CollectionUtils;
12+
import org.apache.commons.lang3.StringUtils;
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.flatads.ExtImpFlatads;
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.Map;
33+
import java.util.Objects;
34+
import java.util.function.Function;
35+
import java.util.stream.Collectors;
36+
37+
public class FlatadsBidder implements Bidder<BidRequest> {
38+
39+
private static final TypeReference<ExtPrebid<?, ExtImpFlatads>> FLATADS_EXT_TYPE_REFERENCE = new TypeReference<>() {
40+
};
41+
42+
private static final String PUBLISHER_ID_MACRO = "{{PublisherID}}";
43+
private static final String TOKEN_ID_MACRO = "{{TokenID}}";
44+
45+
private final String endpointUrl;
46+
private final JacksonMapper mapper;
47+
48+
public FlatadsBidder(String endpointUrl, JacksonMapper mapper) {
49+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
50+
this.mapper = Objects.requireNonNull(mapper);
51+
}
52+
53+
@Override
54+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
55+
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();
56+
final List<BidderError> errors = new ArrayList<>();
57+
58+
for (Imp imp : request.getImp()) {
59+
try {
60+
final ExtImpFlatads extImpFlatads = parseImpExt(imp);
61+
final String resolvedEndpoint = resolveEndpoint(extImpFlatads);
62+
final BidRequest outgoingRequest = request.toBuilder()
63+
.imp(Collections.singletonList(imp))
64+
.build();
65+
httpRequests.add(makeHttpRequest(outgoingRequest, resolvedEndpoint));
66+
} catch (PreBidException e) {
67+
errors.add(BidderError.badInput(e.getMessage()));
68+
}
69+
}
70+
71+
return Result.of(httpRequests, errors);
72+
}
73+
74+
private ExtImpFlatads parseImpExt(Imp imp) {
75+
try {
76+
return mapper.mapper().convertValue(imp.getExt(), FLATADS_EXT_TYPE_REFERENCE).getBidder();
77+
} catch (IllegalArgumentException e) {
78+
throw new PreBidException("Failed to deserialize Flatads extension: " + e.getMessage());
79+
}
80+
}
81+
82+
private String resolveEndpoint(ExtImpFlatads extImp) {
83+
return endpointUrl
84+
.replace(PUBLISHER_ID_MACRO, HttpUtil.encodeUrl(StringUtils.defaultString(extImp.getPublisherId())))
85+
.replace(TOKEN_ID_MACRO, HttpUtil.encodeUrl(StringUtils.defaultString(extImp.getToken())));
86+
}
87+
88+
private HttpRequest<BidRequest> makeHttpRequest(BidRequest request, String endpoint) {
89+
return BidderUtil.defaultRequest(request, makeHeaders(request.getDevice()), endpoint, mapper);
90+
}
91+
92+
private static MultiMap makeHeaders(Device device) {
93+
final MultiMap headers = HttpUtil.headers();
94+
95+
if (device != null) {
96+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa());
97+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
98+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
99+
}
100+
101+
return headers;
102+
}
103+
104+
@Override
105+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
106+
try {
107+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
108+
final List<BidderError> errors = new ArrayList<>();
109+
return Result.of(extractBids(bidRequest, bidResponse, errors), errors);
110+
} catch (DecodeException e) {
111+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
112+
}
113+
}
114+
115+
private static List<BidderBid> extractBids(BidRequest bidRequest,
116+
BidResponse bidResponse,
117+
List<BidderError> errors) {
118+
119+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
120+
return Collections.emptyList();
121+
}
122+
return bidsFromResponse(bidRequest, bidResponse, errors);
123+
}
124+
125+
private static List<BidderBid> bidsFromResponse(BidRequest bidRequest,
126+
BidResponse bidResponse,
127+
List<BidderError> errors) {
128+
129+
final Map<String, Imp> imps = bidRequest.getImp().stream()
130+
.collect(Collectors.toMap(Imp::getId, Function.identity()));
131+
132+
return bidResponse.getSeatbid().stream()
133+
.filter(Objects::nonNull)
134+
.map(SeatBid::getBid)
135+
.filter(Objects::nonNull)
136+
.flatMap(Collection::stream)
137+
.filter(Objects::nonNull)
138+
.map(bid -> makeBid(bid, imps, bidResponse.getCur(), errors))
139+
.filter(Objects::nonNull)
140+
.collect(Collectors.toList());
141+
}
142+
143+
private static BidderBid makeBid(Bid bid, Map<String, Imp> imps, String currency, List<BidderError> errors) {
144+
final BidType bidType = getBidType(bid.getImpid(), imps, errors);
145+
return bidType == null ? null : BidderBid.of(bid, bidType, currency);
146+
}
147+
148+
private static BidType getBidType(String impId, Map<String, Imp> imps, List<BidderError> errors) {
149+
final Imp imp = imps.get(impId);
150+
if (imp != null) {
151+
if (imp.getBanner() != null) {
152+
return BidType.banner;
153+
} else if (imp.getVideo() != null) {
154+
return BidType.video;
155+
} else if (imp.getXNative() != null) {
156+
return BidType.xNative;
157+
}
158+
}
159+
160+
errors.add(BidderError.badServerResponse("The impression with ID %s is not present into the request"
161+
.formatted(impId)));
162+
return null;
163+
}
164+
}
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.flatads;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpFlatads {
8+
9+
String token;
10+
11+
@JsonProperty("publisherId")
12+
String publisherId;
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.flatads.FlatadsBidder;
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/flatads.yaml", factory = YamlPropertySourceFactory.class)
20+
public class FlatadsConfiguration {
21+
22+
private static final String BIDDER_NAME = "flatads";
23+
24+
@Bean("flatadsConfigurationProperties")
25+
@ConfigurationProperties("adapters.flatads")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps flatadsBidderDeps(BidderConfigurationProperties flatadsConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(flatadsConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new FlatadsBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
adapters:
2+
flatads:
3+
endpoint: https://bid.rtbshark.com/api/rtbs/adx/rtb?x-net-id={{PublisherID}}&x-net-token={{TokenID}}
4+
ortb-version: "2.6"
5+
endpoint-compression: gzip
6+
meta-info:
7+
maintainer-email: [email protected]
8+
app-media-types:
9+
- banner
10+
- video
11+
- native
12+
site-media-types:
13+
- banner
14+
- video
15+
- native
16+
supported-vendors:
17+
vendor-id: 0
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Flatads Adapter Params",
4+
"description": "A schema which validates params accepted by the Flatads adapter",
5+
"type": "object",
6+
"properties": {
7+
"token": {
8+
"type": "string",
9+
"description": "Token of the publisher",
10+
"minLength": 1
11+
},
12+
"publisherId": {
13+
"type": "string",
14+
"description": "Flatads Publisher Id",
15+
"minLength": 1
16+
}
17+
},
18+
"required": [
19+
"token",
20+
"publisherId"
21+
]
22+
}

0 commit comments

Comments
 (0)