Skip to content

Commit f486ca5

Browse files
authored
OwnAdx: Add new bidder (#2868)
1 parent 8609e25 commit f486ca5

12 files changed

Lines changed: 772 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package org.prebid.server.bidder.ownadx;
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 io.vertx.core.MultiMap;
10+
import io.vertx.core.http.HttpMethod;
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.ownadx.ExtImpOwnAdx;
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+
35+
public class OwnAdxBidder implements Bidder<BidRequest> {
36+
37+
private static final TypeReference<ExtPrebid<?, ExtImpOwnAdx>> OWN_EXT_TYPE_REFERENCE =
38+
new TypeReference<>() {
39+
};
40+
private static final String X_OPEN_RTB_VERSION = "2.5";
41+
private static final String SEAT_ID_MACROS_ENDPOINT = "{{SeatID}}";
42+
private static final String SSP_ID_MACROS_ENDPOINT = "{{SspID}}";
43+
private static final String TOKEN_ID_MACROS_ENDPOINT = "{{TokenID}}";
44+
45+
private final String endpointUrl;
46+
private final JacksonMapper mapper;
47+
48+
public OwnAdxBidder(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 bidRequest) {
55+
final List<BidderError> errors = new ArrayList<>();
56+
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();
57+
for (Imp imp : bidRequest.getImp()) {
58+
try {
59+
final ExtImpOwnAdx impOwnAdx = parseImpExt(imp);
60+
httpRequests.add(createHttpRequest(bidRequest, impOwnAdx));
61+
} catch (PreBidException e) {
62+
errors.add(BidderError.badInput(e.getMessage()));
63+
}
64+
}
65+
66+
return Result.of(httpRequests, errors);
67+
}
68+
69+
private ExtImpOwnAdx parseImpExt(Imp imp) {
70+
try {
71+
return mapper.mapper().convertValue(imp.getExt(), OWN_EXT_TYPE_REFERENCE).getBidder();
72+
} catch (IllegalArgumentException e) {
73+
throw new PreBidException("Missing bidder ext in impression with id: " + imp.getId());
74+
}
75+
}
76+
77+
private HttpRequest<BidRequest> createHttpRequest(BidRequest bidRequest, ExtImpOwnAdx extImpOwnAdx) {
78+
return HttpRequest.<BidRequest>builder()
79+
.method(HttpMethod.POST)
80+
.uri(makeUrl(extImpOwnAdx))
81+
.headers(makeHeaders())
82+
.body(mapper.encodeToBytes(bidRequest))
83+
.impIds(BidderUtil.impIds(bidRequest))
84+
.payload(bidRequest)
85+
.build();
86+
}
87+
88+
private String makeUrl(ExtImpOwnAdx extImpOwnAdx) {
89+
final Optional<ExtImpOwnAdx> ownAdx = Optional.ofNullable(extImpOwnAdx);
90+
return endpointUrl
91+
.replace(SEAT_ID_MACROS_ENDPOINT, ownAdx.map(ExtImpOwnAdx::getSeatId).orElse(StringUtils.EMPTY))
92+
.replace(SSP_ID_MACROS_ENDPOINT, ownAdx.map(ExtImpOwnAdx::getSspId).orElse(StringUtils.EMPTY))
93+
.replace(TOKEN_ID_MACROS_ENDPOINT, ownAdx.map(ExtImpOwnAdx::getTokenId).orElse(StringUtils.EMPTY));
94+
}
95+
96+
private static MultiMap makeHeaders() {
97+
return HttpUtil.headers()
98+
.add(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPEN_RTB_VERSION);
99+
}
100+
101+
@Override
102+
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
103+
try {
104+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
105+
return Result.withValues(extractBids(httpCall.getRequest().getPayload(), bidResponse));
106+
} catch (DecodeException | PreBidException e) {
107+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
108+
}
109+
}
110+
111+
private static List<BidderBid> extractBids(BidRequest bidRequest, BidResponse bidResponse) {
112+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
113+
return Collections.emptyList();
114+
}
115+
return bidsFromResponse(bidRequest, bidResponse);
116+
}
117+
118+
private static List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse) {
119+
return bidResponse.getSeatbid().stream()
120+
.filter(Objects::nonNull)
121+
.map(SeatBid::getBid)
122+
.filter(Objects::nonNull)
123+
.flatMap(Collection::stream)
124+
.map(bid -> BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur()))
125+
.toList();
126+
}
127+
128+
private static BidType getBidMediaType(Bid bid) {
129+
final Integer markupType = bid.getMtype();
130+
if (markupType == null) {
131+
throw new PreBidException("Missing MType for bid: " + bid.getId());
132+
}
133+
134+
return switch (markupType) {
135+
case 1 -> BidType.banner;
136+
case 2 -> BidType.video;
137+
case 3 -> BidType.audio;
138+
case 4 -> BidType.xNative;
139+
default -> throw new PreBidException("Unable to fetch mediaType " + bid.getMtype());
140+
};
141+
}
142+
}
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.ownadx;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpOwnAdx {
8+
9+
@JsonProperty("sspId")
10+
String sspId;
11+
12+
@JsonProperty("seatId")
13+
String seatId;
14+
15+
@JsonProperty("tokenId")
16+
String tokenId;
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.ownadx.OwnAdxBidder;
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/ownadx.yaml", factory = YamlPropertySourceFactory.class)
20+
public class OwnAdxBidderConfiguration {
21+
22+
private static final String BIDDER_NAME = "ownadx";
23+
24+
@Bean("ownAdxConfigurationProperties")
25+
@ConfigurationProperties("adapters.ownadx")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps ownAdxBidderDeps(BidderConfigurationProperties ownAdxConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(ownAdxConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new OwnAdxBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
adapters:
2+
ownadx:
3+
endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{SeatID}}/{{SspID}}?token={{TokenID}}"
4+
endpoint-compression: gzip
5+
meta-info:
6+
maintainer-email: prebid-team@techbravo.com
7+
app-media-types:
8+
- banner
9+
- video
10+
site-media-types:
11+
- banner
12+
- video
13+
supported-vendors:
14+
vendor-id: 0
15+
usersync:
16+
cookie-family-name: ownadx
17+
redirect:
18+
url: https://sync.spoutroserve.com/user-sync?t=image&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&s3={{redirect_url}}
19+
support-cors: false
20+
uid-macro: '{USER_ID}'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "OwnAdx Adapter Params",
4+
"description": "A schema which validates params accepted by the OwnAdx adapter",
5+
"type": "object",
6+
"properties": {
7+
"sspId": {
8+
"type": "string",
9+
"description": "Ssp ID"
10+
},
11+
"seatId": {
12+
"type": "string",
13+
"description": "Seat ID"
14+
},
15+
"tokenId": {
16+
"type": "string",
17+
"description": "Token ID"
18+
}
19+
},
20+
"required": [
21+
"sspId",
22+
"seatId",
23+
"tokenId"
24+
]
25+
}

0 commit comments

Comments
 (0)