Skip to content

Commit a5bb6bb

Browse files
New Mediasquare Adapter (#4031)
1 parent 1dd6e6b commit a5bb6bb

File tree

21 files changed

+1359
-0
lines changed

21 files changed

+1359
-0
lines changed
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
package org.prebid.server.bidder.mediasquare;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import com.iab.openrtb.request.Banner;
6+
import com.iab.openrtb.request.BidRequest;
7+
import com.iab.openrtb.request.Format;
8+
import com.iab.openrtb.request.Imp;
9+
import com.iab.openrtb.request.Native;
10+
import com.iab.openrtb.request.Regs;
11+
import com.iab.openrtb.request.User;
12+
import com.iab.openrtb.request.Video;
13+
import com.iab.openrtb.response.Bid;
14+
import io.vertx.core.http.HttpMethod;
15+
import org.apache.commons.collections4.CollectionUtils;
16+
import org.apache.commons.collections4.MapUtils;
17+
import org.apache.commons.lang3.ObjectUtils;
18+
import org.prebid.server.bidder.Bidder;
19+
import org.prebid.server.bidder.mediasquare.request.MediasquareBanner;
20+
import org.prebid.server.bidder.mediasquare.request.MediasquareCode;
21+
import org.prebid.server.bidder.mediasquare.request.MediasquareFloor;
22+
import org.prebid.server.bidder.mediasquare.request.MediasquareGdpr;
23+
import org.prebid.server.bidder.mediasquare.request.MediasquareMediaTypes;
24+
import org.prebid.server.bidder.mediasquare.request.MediasquareRequest;
25+
import org.prebid.server.bidder.mediasquare.request.MediasquareSupport;
26+
import org.prebid.server.bidder.mediasquare.response.MediasquareBid;
27+
import org.prebid.server.bidder.mediasquare.response.MediasquareResponse;
28+
import org.prebid.server.bidder.model.BidderBid;
29+
import org.prebid.server.bidder.model.BidderCall;
30+
import org.prebid.server.bidder.model.BidderError;
31+
import org.prebid.server.bidder.model.HttpRequest;
32+
import org.prebid.server.bidder.model.Result;
33+
import org.prebid.server.exception.PreBidException;
34+
import org.prebid.server.json.DecodeException;
35+
import org.prebid.server.json.JacksonMapper;
36+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
37+
import org.prebid.server.proto.openrtb.ext.request.ExtRegs;
38+
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
39+
import org.prebid.server.proto.openrtb.ext.request.mediasquare.ExtImpMediasquare;
40+
import org.prebid.server.proto.openrtb.ext.response.BidType;
41+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
42+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
43+
import org.prebid.server.util.BidderUtil;
44+
import org.prebid.server.util.HttpUtil;
45+
46+
import java.util.ArrayList;
47+
import java.util.Collections;
48+
import java.util.HashMap;
49+
import java.util.List;
50+
import java.util.Map;
51+
import java.util.Objects;
52+
import java.util.Optional;
53+
import java.util.stream.Collectors;
54+
55+
public class MediasquareBidder implements Bidder<MediasquareRequest> {
56+
57+
private static final String SIZE_FORMAT = "%dx%d";
58+
private static final TypeReference<ExtPrebid<?, ExtImpMediasquare>> TYPE_REFERENCE = new TypeReference<>() {
59+
};
60+
61+
private final String endpointUrl;
62+
private final JacksonMapper mapper;
63+
64+
public MediasquareBidder(String endpointUrl, JacksonMapper mapper) {
65+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
66+
this.mapper = Objects.requireNonNull(mapper);
67+
}
68+
69+
@Override
70+
public Result<List<HttpRequest<MediasquareRequest>>> makeHttpRequests(BidRequest request) {
71+
final List<MediasquareCode> codes = new ArrayList<>();
72+
final List<BidderError> errors = new ArrayList<>();
73+
74+
for (Imp imp : request.getImp()) {
75+
try {
76+
final ExtImpMediasquare extImp = parseImpExt(imp);
77+
final MediasquareCode mediasquareCode = makeCode(request, imp, extImp);
78+
if (isCodeValid(mediasquareCode)) {
79+
codes.add(mediasquareCode);
80+
}
81+
} catch (PreBidException e) {
82+
errors.add(BidderError.badInput(e.getMessage()));
83+
}
84+
}
85+
86+
if (codes.isEmpty()) {
87+
return Result.withErrors(errors);
88+
}
89+
90+
final MediasquareRequest outgoingRequest = makeRequest(request, codes);
91+
92+
final HttpRequest<MediasquareRequest> httpRequest = HttpRequest.<MediasquareRequest>builder()
93+
.method(HttpMethod.POST)
94+
.uri(endpointUrl)
95+
.headers(HttpUtil.headers())
96+
.body(mapper.encodeToBytes(outgoingRequest))
97+
.payload(outgoingRequest)
98+
.impIds(BidderUtil.impIds(request))
99+
.build();
100+
101+
return Result.of(List.of(httpRequest), errors);
102+
}
103+
104+
private ExtImpMediasquare parseImpExt(Imp imp) {
105+
try {
106+
return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder();
107+
} catch (IllegalArgumentException e) {
108+
throw new PreBidException("can not parse imp.ext" + e.getMessage());
109+
}
110+
}
111+
112+
private static MediasquareCode makeCode(BidRequest bidRequest, Imp imp, ExtImpMediasquare extImp) {
113+
final MediasquareMediaTypes mediaTypes = makeMediaTypes(imp);
114+
final Map<String, MediasquareFloor> floors = mediaTypes == null
115+
? null
116+
: makeFloors(MediasquareFloor.of(imp.getBidfloor(), imp.getBidfloorcur()), mediaTypes);
117+
118+
return MediasquareCode.builder()
119+
.adUnit(imp.getTagid())
120+
.auctionId(bidRequest.getId())
121+
.bidId(imp.getId())
122+
.code(extImp.getCode())
123+
.owner(extImp.getOwner())
124+
.mediaTypes(mediaTypes)
125+
.floor(floors)
126+
.build();
127+
}
128+
129+
private static MediasquareMediaTypes makeMediaTypes(Imp imp) {
130+
final Video video = imp.getVideo();
131+
final Banner banner = imp.getBanner();
132+
final Native xNative = imp.getXNative();
133+
134+
if (video == null && banner == null && xNative == null) {
135+
return null;
136+
}
137+
138+
return MediasquareMediaTypes.builder()
139+
.banner(makeBanner(banner))
140+
.video(video)
141+
.nativeRequest(xNative != null ? xNative.getRequest() : null)
142+
.build();
143+
}
144+
145+
private static MediasquareBanner makeBanner(Banner banner) {
146+
if (banner == null) {
147+
return null;
148+
}
149+
150+
final List<List<Integer>> sizes = new ArrayList<>();
151+
if (CollectionUtils.isNotEmpty(banner.getFormat())) {
152+
for (Format format : banner.getFormat()) {
153+
sizes.add(List.of(format.getW(), format.getH()));
154+
}
155+
} else {
156+
sizes.add(List.of(banner.getW(), banner.getH()));
157+
}
158+
159+
return MediasquareBanner.of(sizes);
160+
}
161+
162+
private static Map<String, MediasquareFloor> makeFloors(MediasquareFloor floor, MediasquareMediaTypes mediaTypes) {
163+
final Map<String, MediasquareFloor> floors = new HashMap<>();
164+
165+
final Video video = mediaTypes.getVideo();
166+
final MediasquareBanner banner = mediaTypes.getBanner();
167+
final String xNative = mediaTypes.getNativeRequest();
168+
169+
if (video != null) {
170+
if (video.getW() != null && video.getH() != null) {
171+
final String videoSize = SIZE_FORMAT.formatted(video.getW(), video.getH());
172+
floors.put(videoSize, floor);
173+
}
174+
floors.put("*", floor);
175+
}
176+
177+
if (banner != null) {
178+
for (List<Integer> format: banner.getSizes()) {
179+
floors.put(SIZE_FORMAT.formatted(format.get(0), format.get(1)), floor);
180+
}
181+
}
182+
183+
if (xNative != null) {
184+
floors.put("*", floor);
185+
}
186+
187+
return MapUtils.isNotEmpty(floors) ? floors : null;
188+
}
189+
190+
private static boolean isCodeValid(MediasquareCode code) {
191+
final MediasquareMediaTypes mediaTypes = code.getMediaTypes();
192+
return mediaTypes != null && ObjectUtils.anyNotNull(
193+
mediaTypes.getBanner(), mediaTypes.getVideo(), mediaTypes.getNativeRequest());
194+
}
195+
196+
private MediasquareRequest makeRequest(BidRequest bidRequest, List<MediasquareCode> codes) {
197+
final User user = bidRequest.getUser();
198+
final Regs regs = bidRequest.getRegs();
199+
200+
return MediasquareRequest.builder()
201+
.codes(codes)
202+
.dsa(getDsa(regs))
203+
.gdpr(makeGdpr(user, regs))
204+
.type("pbs")
205+
.support(MediasquareSupport.of(bidRequest.getDevice(), bidRequest.getApp()))
206+
.test(Objects.equals(bidRequest.getTest(), 1))
207+
.build();
208+
}
209+
210+
private static ExtRegsDsa getDsa(Regs regs) {
211+
return Optional.ofNullable(regs)
212+
.map(Regs::getExt)
213+
.map(ExtRegs::getDsa)
214+
.orElse(null);
215+
}
216+
217+
private static MediasquareGdpr makeGdpr(User user, Regs regs) {
218+
final boolean gdprApplies = Optional.ofNullable(regs)
219+
.map(Regs::getGdpr)
220+
.map(gdpr -> gdpr == 1)
221+
.orElse(false);
222+
final String consent = user != null ? user.getConsent() : null;
223+
return MediasquareGdpr.of(gdprApplies, consent);
224+
}
225+
226+
@Override
227+
public Result<List<BidderBid>> makeBids(BidderCall<MediasquareRequest> httpCall, BidRequest bidRequest) {
228+
try {
229+
final MediasquareResponse response = mapper.decodeValue(
230+
httpCall.getResponse().getBody(),
231+
MediasquareResponse.class);
232+
return Result.withValues(extractBids(response));
233+
} catch (DecodeException e) {
234+
return Result.withError(BidderError.badServerResponse("Failed to decode response: " + e.getMessage()));
235+
}
236+
}
237+
238+
private List<BidderBid> extractBids(MediasquareResponse response) {
239+
if (response == null || CollectionUtils.isEmpty(response.getResponses())) {
240+
return Collections.emptyList();
241+
}
242+
243+
return response.getResponses().stream()
244+
.filter(Objects::nonNull)
245+
.map(this::makeBidderBid)
246+
.collect(Collectors.toList());
247+
}
248+
249+
private BidderBid makeBidderBid(MediasquareBid bid) {
250+
final BidType bidType = getBidType(bid);
251+
return BidderBid.of(makeBid(bid, bidType), bidType, bid.getCurrency());
252+
}
253+
254+
private static BidType getBidType(MediasquareBid bid) {
255+
if (bid.getVideo() != null) {
256+
return BidType.video;
257+
}
258+
if (bid.getNativeResponse() != null) {
259+
return BidType.xNative;
260+
}
261+
return BidType.banner;
262+
}
263+
264+
private Bid makeBid(MediasquareBid bid, BidType bidType) {
265+
return Bid.builder()
266+
.id(bid.getId())
267+
.impid(bid.getBidId())
268+
.price(bid.getCpm())
269+
.adm(bid.getAd())
270+
.adomain(bid.getAdomain())
271+
.w(bid.getWidth())
272+
.h(bid.getHeight())
273+
.crid(bid.getCreativeId())
274+
.mtype(bidType.ordinal() + 1)
275+
.burl(bid.getBurl())
276+
.ext(getBidExt(bid, bidType))
277+
.build();
278+
}
279+
280+
private ObjectNode getBidExt(MediasquareBid bid, BidType bidType) {
281+
final ExtBidPrebidMeta meta = ExtBidPrebidMeta.builder()
282+
.advertiserDomains(bid.getAdomain() != null ? bid.getAdomain() : null)
283+
.mediaType(bidType.getName())
284+
.build();
285+
286+
final ExtBidPrebid prebid = ExtBidPrebid.builder().meta(meta).build();
287+
288+
final ObjectNode bidExt = mapper.mapper().createObjectNode();
289+
if (bid.getDsa() != null) {
290+
bidExt.set("dsa", bid.getDsa());
291+
}
292+
bidExt.set("prebid", mapper.mapper().valueToTree(prebid));
293+
294+
return bidExt;
295+
}
296+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.bidder.mediasquare.request;
2+
3+
import lombok.Value;
4+
5+
import java.util.List;
6+
7+
@Value(staticConstructor = "of")
8+
public class MediasquareBanner {
9+
10+
List<List<Integer>> sizes;
11+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.prebid.server.bidder.mediasquare.request;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Builder;
5+
import lombok.Value;
6+
7+
import java.util.Map;
8+
9+
@Builder
10+
@Value(staticConstructor = "of")
11+
public class MediasquareCode {
12+
13+
@JsonProperty("adunit")
14+
String adUnit;
15+
16+
@JsonProperty("auctionid")
17+
String auctionId;
18+
19+
@JsonProperty("bidid")
20+
String bidId;
21+
22+
String code;
23+
24+
String owner;
25+
26+
@JsonProperty("mediatypes")
27+
MediasquareMediaTypes mediaTypes;
28+
29+
Map<String, MediasquareFloor> floor;
30+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.prebid.server.bidder.mediasquare.request;
2+
3+
import lombok.Value;
4+
5+
import java.math.BigDecimal;
6+
7+
@Value(staticConstructor = "of")
8+
public class MediasquareFloor {
9+
10+
BigDecimal floor;
11+
12+
String currency;
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.bidder.mediasquare.request;
2+
3+
import lombok.Value;
4+
5+
@Value(staticConstructor = "of")
6+
public class MediasquareGdpr {
7+
8+
boolean consentRequired;
9+
10+
String consentString;
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.prebid.server.bidder.mediasquare.request;
2+
3+
import com.iab.openrtb.request.Video;
4+
import lombok.Builder;
5+
import lombok.Value;
6+
7+
@Builder
8+
@Value(staticConstructor = "of")
9+
public class MediasquareMediaTypes {
10+
11+
MediasquareBanner banner;
12+
13+
Video video;
14+
15+
String nativeRequest;
16+
}

0 commit comments

Comments
 (0)