Skip to content

Commit c3b5626

Browse files
New Afront Adapter (#4153)
1 parent a149dbe commit c3b5626

File tree

12 files changed

+659
-0
lines changed

12 files changed

+659
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package org.prebid.server.bidder.afront;
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.BidResponse;
8+
import com.iab.openrtb.response.SeatBid;
9+
import io.vertx.core.MultiMap;
10+
import org.apache.commons.collections4.CollectionUtils;
11+
import org.prebid.server.bidder.Bidder;
12+
import org.prebid.server.bidder.model.BidderBid;
13+
import org.prebid.server.bidder.model.BidderCall;
14+
import org.prebid.server.bidder.model.BidderError;
15+
import org.prebid.server.bidder.model.HttpRequest;
16+
import org.prebid.server.bidder.model.Result;
17+
import org.prebid.server.exception.PreBidException;
18+
import org.prebid.server.json.DecodeException;
19+
import org.prebid.server.json.JacksonMapper;
20+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
21+
import org.prebid.server.proto.openrtb.ext.request.afront.ExtImpAfront;
22+
import org.prebid.server.proto.openrtb.ext.response.BidType;
23+
import org.prebid.server.util.BidderUtil;
24+
import org.prebid.server.util.HttpUtil;
25+
26+
import java.util.Collection;
27+
import java.util.Collections;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Objects;
31+
import java.util.function.Function;
32+
import java.util.stream.Collectors;
33+
34+
public class AfrontBidder implements Bidder<BidRequest> {
35+
36+
private static final TypeReference<ExtPrebid<?, ExtImpAfront>> TYPE_REFERENCE = new TypeReference<>() {
37+
};
38+
39+
private static final String ACCOUNT_ID_MACRO = "{{AccountId}}";
40+
private static final String SOURCE_ID_MACRO = "{{SourceId}}";
41+
42+
private final String endpointUrl;
43+
private final JacksonMapper mapper;
44+
45+
public AfrontBidder(String endpointUrl, JacksonMapper mapper) {
46+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
47+
this.mapper = Objects.requireNonNull(mapper);
48+
}
49+
50+
@Override
51+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
52+
final ExtImpAfront extImp;
53+
try {
54+
extImp = parseImpExt(request.getImp().getFirst());
55+
} catch (PreBidException e) {
56+
return Result.withError(BidderError.badInput(e.getMessage()));
57+
}
58+
59+
final String resolvedEndpoint = resolveEndpoint(extImp);
60+
final BidRequest outgoingRequest = modifyRequest(request);
61+
final HttpRequest<BidRequest> httpRequest =
62+
BidderUtil.defaultRequest(outgoingRequest, makeHeaders(request.getDevice()), resolvedEndpoint, mapper);
63+
64+
return Result.withValue(httpRequest);
65+
}
66+
67+
private ExtImpAfront parseImpExt(Imp imp) {
68+
try {
69+
return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder();
70+
} catch (IllegalArgumentException e) {
71+
throw new PreBidException("ext.bidder not provided");
72+
}
73+
}
74+
75+
private String resolveEndpoint(ExtImpAfront extImp) {
76+
return endpointUrl
77+
.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(extImp.getAccountId()))
78+
.replace(SOURCE_ID_MACRO, HttpUtil.encodeUrl(extImp.getSourceId()));
79+
}
80+
81+
private static BidRequest modifyRequest(BidRequest request) {
82+
final List<Imp> modifiedImps = request.getImp().stream()
83+
.map(imp -> imp.toBuilder().ext(null).build())
84+
.toList();
85+
86+
return request.toBuilder()
87+
.imp(modifiedImps)
88+
.build();
89+
}
90+
91+
private static MultiMap makeHeaders(Device device) {
92+
final MultiMap headers = HttpUtil.headers()
93+
.add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5");
94+
95+
if (device != null) {
96+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
97+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
98+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa());
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+
return Result.withValues(extractBids(bidRequest, bidResponse));
109+
} catch (DecodeException e) {
110+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
111+
}
112+
}
113+
114+
private static List<BidderBid> extractBids(BidRequest bidRequest, BidResponse bidResponse) {
115+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
116+
return Collections.emptyList();
117+
}
118+
return bidsFromResponse(bidRequest, bidResponse);
119+
}
120+
121+
private static List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse) {
122+
final Map<String, Imp> imps = bidRequest.getImp().stream()
123+
.collect(Collectors.toMap(Imp::getId, Function.identity()));
124+
125+
return bidResponse.getSeatbid().stream()
126+
.filter(Objects::nonNull)
127+
.map(SeatBid::getBid)
128+
.filter(Objects::nonNull)
129+
.flatMap(Collection::stream)
130+
.filter(Objects::nonNull)
131+
.map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), imps), bidResponse.getCur()))
132+
.collect(Collectors.toList());
133+
}
134+
135+
private static BidType getBidType(String impId, Map<String, Imp> imps) {
136+
final Imp imp = imps.get(impId);
137+
if (imp != null) {
138+
if (imp.getVideo() != null) {
139+
return BidType.video;
140+
} else if (imp.getXNative() != null) {
141+
return BidType.xNative;
142+
}
143+
}
144+
return BidType.banner;
145+
}
146+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.prebid.server.proto.openrtb.ext.request.afront;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpAfront {
8+
9+
@JsonProperty("accountId")
10+
String accountId;
11+
12+
@JsonProperty("sourceId")
13+
String sourceId;
14+
}
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.afront.AfrontBidder;
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/afront.yaml", factory = YamlPropertySourceFactory.class)
20+
public class AfrontConfiguration {
21+
22+
private static final String BIDDER_NAME = "afront";
23+
24+
@Bean("afrontConfigurationProperties")
25+
@ConfigurationProperties("adapters.afront")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps afrontBidderDeps(BidderConfigurationProperties afrontConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(afrontConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new AfrontBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Contact support@afront.io to connect with Afront exchange.
2+
# We have the following regional endpoint sub-domains:
3+
# US East: snt1
4+
# APAC: snt2
5+
# EU: snt3
6+
# Please deploy this config in each of your datacenters with the appropriate regional subdomain
7+
adapters:
8+
afront:
9+
endpoint: https://snt1.afront.io/?rsd={{SourceId}}&sk={{AccountId}}
10+
endpoint-compression: gzip
11+
meta-info:
12+
maintainer-email: support@afront.io
13+
app-media-types:
14+
- banner
15+
- video
16+
- native
17+
site-media-types:
18+
- banner
19+
- video
20+
- native
21+
supported-vendors:
22+
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": "Afront Adapter Params",
4+
"description": "A schema which validates params accepted by the Afront adapter",
5+
"type": "object",
6+
"properties": {
7+
"accountId": {
8+
"type": "string",
9+
"description": "Client account id",
10+
"minLength": 1
11+
},
12+
"sourceId": {
13+
"type": "string",
14+
"description": "Data source id",
15+
"minLength": 1
16+
}
17+
},
18+
"required": [
19+
"accountId",
20+
"sourceId"
21+
]
22+
}

0 commit comments

Comments
 (0)