Skip to content

Commit 10adab9

Browse files
New Contxtful Adapter (#4240)
1 parent 624fa64 commit 10adab9

File tree

19 files changed

+1092
-0
lines changed

19 files changed

+1092
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package org.prebid.server.bidder.contxtful;
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.request.User;
7+
import com.iab.openrtb.response.Bid;
8+
import io.vertx.core.http.HttpMethod;
9+
import org.apache.commons.collections4.CollectionUtils;
10+
import org.apache.commons.lang3.StringUtils;
11+
import org.prebid.server.bidder.Bidder;
12+
import org.prebid.server.bidder.contxtful.request.ContxtfulBidRequest;
13+
import org.prebid.server.bidder.contxtful.request.ContxtfulBidRequestParams;
14+
import org.prebid.server.bidder.contxtful.request.ContxtfulBidderRequest;
15+
import org.prebid.server.bidder.contxtful.request.ContxtfulCompositeRequest;
16+
import org.prebid.server.bidder.contxtful.request.ContxtfulConfig;
17+
import org.prebid.server.bidder.contxtful.request.ContxtfulConfigDetails;
18+
import org.prebid.server.bidder.contxtful.response.ContxtfulBid;
19+
import org.prebid.server.bidder.model.BidderBid;
20+
import org.prebid.server.bidder.model.BidderCall;
21+
import org.prebid.server.bidder.model.BidderError;
22+
import org.prebid.server.bidder.model.HttpRequest;
23+
import org.prebid.server.bidder.model.Result;
24+
import org.prebid.server.exception.PreBidException;
25+
import org.prebid.server.json.DecodeException;
26+
import org.prebid.server.json.JacksonMapper;
27+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
28+
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
29+
import org.prebid.server.proto.openrtb.ext.request.ExtUserPrebid;
30+
import org.prebid.server.proto.openrtb.ext.request.contxtful.ExtImpContxtful;
31+
import org.prebid.server.proto.openrtb.ext.response.BidType;
32+
import org.prebid.server.util.BidderUtil;
33+
import org.prebid.server.util.HttpUtil;
34+
35+
import java.util.ArrayList;
36+
import java.util.Collections;
37+
import java.util.List;
38+
import java.util.Map;
39+
import java.util.Objects;
40+
import java.util.Optional;
41+
import java.util.function.Function;
42+
import java.util.stream.Collectors;
43+
44+
public class ContxtfulBidder implements Bidder<ContxtfulCompositeRequest> {
45+
46+
private static final TypeReference<ExtPrebid<?, ExtImpContxtful>> TYPE_REFERENCE = new TypeReference<>() {
47+
};
48+
private static final String ACCOUNT_ID_MACRO = "{{AccountId}}";
49+
private static final String BIDDER_NAME = "contxtful";
50+
private static final String DEFAULT_ADAPTER_VERSION = "v1";
51+
private static final String DEFAULT_CURRENCY = "USD";
52+
53+
private final String endpointUrl;
54+
private final JacksonMapper mapper;
55+
56+
public ContxtfulBidder(String endpointUrl, JacksonMapper mapper) {
57+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
58+
this.mapper = Objects.requireNonNull(mapper);
59+
}
60+
61+
@Override
62+
public Result<List<HttpRequest<ContxtfulCompositeRequest>>> makeHttpRequests(BidRequest request) {
63+
final List<BidderError> errors = new ArrayList<>();
64+
final List<ContxtfulBidRequest> bidRequests = new ArrayList<>();
65+
String customerId = null;
66+
67+
for (Imp imp : request.getImp()) {
68+
try {
69+
final ExtImpContxtful extImp = parseImpExt(imp);
70+
if (customerId == null) {
71+
customerId = extImp.getCustomerId();
72+
}
73+
bidRequests.add(ContxtfulBidRequest.of(
74+
BIDDER_NAME,
75+
ContxtfulBidRequestParams.of(extImp.getPlacementId()), imp.getId()));
76+
} catch (PreBidException e) {
77+
errors.add(BidderError.badInput(e.getMessage()));
78+
}
79+
}
80+
81+
if (CollectionUtils.isEmpty(bidRequests)) {
82+
return Result.withErrors(errors);
83+
}
84+
85+
final ContxtfulCompositeRequest outgoingRequest = ContxtfulCompositeRequest.builder()
86+
.ortb2Request(request.toBuilder().user(modifyUser(request.getUser())).build())
87+
.bidRequests(bidRequests)
88+
.bidderRequest(ContxtfulBidderRequest.of(BIDDER_NAME))
89+
.config(ContxtfulConfig.of(ContxtfulConfigDetails.of(DEFAULT_ADAPTER_VERSION, customerId)))
90+
.build();
91+
92+
final HttpRequest<ContxtfulCompositeRequest> httpRequest = HttpRequest.<ContxtfulCompositeRequest>builder()
93+
.method(HttpMethod.POST)
94+
.uri(makeUrl(customerId))
95+
.headers(HttpUtil.headers())
96+
.body(mapper.encodeToBytes(outgoingRequest))
97+
.payload(outgoingRequest)
98+
.impIds(BidderUtil.impIds(request))
99+
.build();
100+
101+
return Result.of(Collections.singletonList(httpRequest), errors);
102+
}
103+
104+
private ExtImpContxtful parseImpExt(Imp imp) {
105+
try {
106+
return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder();
107+
} catch (IllegalArgumentException e) {
108+
throw new PreBidException("Error parsing imp.ext for impression " + imp.getId());
109+
}
110+
}
111+
112+
private static User modifyUser(User user) {
113+
if (user == null) {
114+
return null;
115+
}
116+
117+
final String buyerUid = user.getBuyeruid();
118+
if (StringUtils.isNotBlank(buyerUid)) {
119+
return user;
120+
}
121+
122+
return Optional.ofNullable(user.getExt())
123+
.map(ExtUser::getPrebid)
124+
.map(ExtUserPrebid::getBuyeruids)
125+
.map(buyerUids -> buyerUids.get(BIDDER_NAME))
126+
.filter(StringUtils::isNotBlank)
127+
.map(uid -> user.toBuilder().buyeruid(uid).build())
128+
.orElse(user);
129+
}
130+
131+
private String makeUrl(String customerId) {
132+
return endpointUrl.replace(ACCOUNT_ID_MACRO, HttpUtil.encodeUrl(customerId));
133+
}
134+
135+
@Override
136+
public Result<List<BidderBid>> makeBids(BidderCall<ContxtfulCompositeRequest> httpCall, BidRequest bidRequest) {
137+
final List<BidderError> errors = new ArrayList<>();
138+
try {
139+
final List<ContxtfulBid> responseBids = mapper.decodeValue(
140+
httpCall.getResponse().getBody(),
141+
new TypeReference<>() {
142+
});
143+
return Result.of(extractBids(bidRequest, responseBids, errors), errors);
144+
} catch (DecodeException e) {
145+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
146+
}
147+
}
148+
149+
private static List<BidderBid> extractBids(BidRequest bidRequest,
150+
List<ContxtfulBid> responseBids,
151+
List<BidderError> errors) {
152+
153+
if (CollectionUtils.isEmpty(responseBids)) {
154+
return Collections.emptyList();
155+
}
156+
return bidsFromResponse(bidRequest, responseBids, errors);
157+
}
158+
159+
private static List<BidderBid> bidsFromResponse(BidRequest bidRequest,
160+
List<ContxtfulBid> responseBids,
161+
List<BidderError> errors) {
162+
163+
final Map<String, Imp> impsMap = bidRequest.getImp().stream()
164+
.collect(Collectors.toMap(Imp::getId, Function.identity()));
165+
166+
return responseBids.stream()
167+
.filter(Objects::nonNull)
168+
.map(responseBid -> makeBidderBid(responseBid, impsMap, errors))
169+
.filter(Objects::nonNull)
170+
.collect(Collectors.toList());
171+
}
172+
173+
private static BidderBid makeBidderBid(ContxtfulBid responseBid,
174+
Map<String, Imp> impsMap,
175+
List<BidderError> errors) {
176+
177+
final String impId = responseBid.getRequestId();
178+
if (responseBid.getCpm() == null || impId == null) {
179+
return null;
180+
}
181+
182+
if (StringUtils.isBlank(responseBid.getMediaType())) {
183+
errors.add(BidderError.badServerResponse("bid %s has no ad media type".formatted(impId)));
184+
return null;
185+
}
186+
187+
if (StringUtils.isBlank(responseBid.getAdm())) {
188+
errors.add(BidderError.badServerResponse("bid %s has no ad markup".formatted(impId)));
189+
return null;
190+
}
191+
192+
final Bid bid = Bid.builder()
193+
.id(BIDDER_NAME + "-" + impId)
194+
.impid(impId)
195+
.price(responseBid.getCpm())
196+
.adm(responseBid.getAdm())
197+
.w(responseBid.getWidth())
198+
.h(responseBid.getHeight())
199+
.crid(responseBid.getCreativeId())
200+
.nurl(responseBid.getNurl())
201+
.burl(responseBid.getBurl())
202+
.lurl(responseBid.getLurl())
203+
.ext(responseBid.getExt())
204+
.build();
205+
206+
final String currency = Objects.toString(responseBid.getCurrency(), DEFAULT_CURRENCY);
207+
return BidderBid.of(bid, getBidType(impsMap.get(impId)), currency);
208+
}
209+
210+
private static BidType getBidType(Imp imp) {
211+
if (imp == null) {
212+
return BidType.banner;
213+
}
214+
215+
if (imp.getVideo() != null) {
216+
return BidType.video;
217+
} else if (imp.getXNative() != null) {
218+
return BidType.xNative;
219+
} else {
220+
return BidType.banner;
221+
}
222+
}
223+
}
224+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.prebid.server.bidder.contxtful.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ContxtfulBidRequest {
8+
9+
String bidder;
10+
11+
ContxtfulBidRequestParams params;
12+
13+
@JsonProperty("bidId")
14+
String bidId;
15+
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.bidder.contxtful.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ContxtfulBidRequestParams {
8+
9+
@JsonProperty("placementId")
10+
String placementId;
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.bidder.contxtful.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ContxtfulBidderRequest {
8+
9+
@JsonProperty("bidderCode")
10+
String bidderCode;
11+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.prebid.server.bidder.contxtful.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.iab.openrtb.request.BidRequest;
5+
import lombok.Builder;
6+
import lombok.Value;
7+
8+
import java.util.List;
9+
10+
@Builder
11+
@Value(staticConstructor = "of")
12+
public class ContxtfulCompositeRequest {
13+
14+
@JsonProperty("ortb2")
15+
BidRequest ortb2Request;
16+
17+
@JsonProperty("bidRequests")
18+
List<ContxtfulBidRequest> bidRequests;
19+
20+
@JsonProperty("bidderRequest")
21+
ContxtfulBidderRequest bidderRequest;
22+
23+
ContxtfulConfig config;
24+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.bidder.contxtful.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ContxtfulConfig {
8+
9+
@JsonProperty("contxtful")
10+
ContxtfulConfigDetails details;
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.bidder.contxtful.request;
2+
3+
import lombok.Value;
4+
5+
@Value(staticConstructor = "of")
6+
public class ContxtfulConfigDetails {
7+
8+
String version;
9+
10+
String customer;
11+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.prebid.server.bidder.contxtful.response;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import lombok.Builder;
6+
import lombok.Value;
7+
8+
import java.math.BigDecimal;
9+
10+
@Builder
11+
@Value(staticConstructor = "of")
12+
public class ContxtfulBid {
13+
14+
@JsonProperty("requestId")
15+
String requestId;
16+
17+
BigDecimal cpm;
18+
19+
String currency;
20+
21+
Integer width;
22+
23+
Integer height;
24+
25+
@JsonProperty("creativeId")
26+
String creativeId;
27+
28+
String adm;
29+
30+
Integer ttl;
31+
32+
@JsonProperty("netRevenue")
33+
Boolean netRevenue;
34+
35+
@JsonProperty("mediaType")
36+
String mediaType;
37+
38+
@JsonProperty("bidderCode")
39+
String bidderCode;
40+
41+
@JsonProperty("placementId")
42+
String placementId;
43+
44+
@JsonProperty("traceId")
45+
String traceId;
46+
47+
BigDecimal random;
48+
49+
String nurl;
50+
51+
String burl;
52+
53+
String lurl;
54+
55+
ObjectNode ext;
56+
}
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.contxtful;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpContxtful {
8+
9+
@JsonProperty("placementId")
10+
String placementId;
11+
12+
@JsonProperty("customerId")
13+
String customerId;
14+
}

0 commit comments

Comments
 (0)