Skip to content

Commit a475313

Browse files
Yieldlab: Add Digital Service Act (DSA) support (prebid#3011)
1 parent 591e2f6 commit a475313

4 files changed

Lines changed: 322 additions & 3 deletions

File tree

src/main/java/org/prebid/server/bidder/yieldlab/YieldlabBidder.java

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import com.fasterxml.jackson.core.type.TypeReference;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
57
import com.iab.openrtb.request.App;
68
import com.iab.openrtb.request.BidRequest;
79
import com.iab.openrtb.request.Device;
@@ -14,6 +16,9 @@
1416
import io.netty.handler.codec.http.HttpHeaderValues;
1517
import io.vertx.core.MultiMap;
1618
import io.vertx.core.http.HttpMethod;
19+
import io.vertx.core.logging.Logger;
20+
import io.vertx.core.logging.LoggerFactory;
21+
import org.apache.commons.collections4.CollectionUtils;
1722
import org.apache.commons.lang3.ObjectUtils;
1823
import org.apache.commons.lang3.StringUtils;
1924
import org.apache.http.client.utils.URIBuilder;
@@ -23,11 +28,15 @@
2328
import org.prebid.server.bidder.model.BidderError;
2429
import org.prebid.server.bidder.model.HttpRequest;
2530
import org.prebid.server.bidder.model.Result;
31+
import org.prebid.server.bidder.yieldlab.model.YieldlabDigitalServicesActResponse;
2632
import org.prebid.server.bidder.yieldlab.model.YieldlabResponse;
2733
import org.prebid.server.exception.PreBidException;
2834
import org.prebid.server.json.DecodeException;
2935
import org.prebid.server.json.JacksonMapper;
3036
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.ExtRegsDsaTransparency;
3140
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
3241
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
3342
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
@@ -41,13 +50,16 @@
4150
import java.util.ArrayList;
4251
import java.util.Calendar;
4352
import java.util.Collections;
53+
import java.util.HashMap;
4454
import java.util.List;
4555
import java.util.Map;
4656
import java.util.Objects;
57+
import java.util.Optional;
4758
import java.util.stream.Collectors;
4859

4960
public class YieldlabBidder implements Bidder<Void> {
5061

62+
private static final Logger logger = LoggerFactory.getLogger(YieldlabBidder.class);
5163
private static final TypeReference<ExtPrebid<?, ExtImpYieldlab>> YIELDLAB_EXT_TYPE_REFERENCE =
5264
new TypeReference<>() {
5365
};
@@ -58,6 +70,9 @@ public class YieldlabBidder implements Bidder<Void> {
5870
private static final String CREATIVE_ID = "%s%s%s";
5971
private static final String AD_SOURCE_BANNER = "<script src=\"%s\"></script>";
6072
private static final String AD_SOURCE_URL = "https://ad.yieldlab.net/d/%s/%s/%s?%s";
73+
private static final String TRANSPARENCY_TEMPLATE = "%s~%s";
74+
private static final String TRANSPARENCY_TEMPLATE_PARAMS_DELIMITER = "_";
75+
private static final String TRANSPARENCY_TEMPLATE_DELIMITER = "~~";
6176
private static final String VAST_MARKUP = """
6277
<VAST version="2.0"><Ad id="%s"><Wrapper>
6378
<AdSystem>Yieldlab</AdSystem>
@@ -189,6 +204,8 @@ private String makeUrl(ExtImpYieldlab extImpYieldlab, BidRequest request) {
189204
uriBuilder.addParameter("consent", consent);
190205
}
191206

207+
extractDsaRequestParamsFromBidRequest(request).forEach(uriBuilder::addParameter);
208+
192209
return uriBuilder.toString();
193210
}
194211

@@ -231,6 +248,63 @@ private static String getConsentParameter(User user) {
231248
return ObjectUtils.defaultIfNull(consent, "");
232249
}
233250

251+
private static Map<String, String> extractDsaRequestParamsFromBidRequest(BidRequest request) {
252+
return Optional.ofNullable(request.getRegs())
253+
.map(Regs::getExt)
254+
.map(ExtRegs::getDsa)
255+
.map(YieldlabBidder::extractDsaRequestParamsFromDsaRegsExtension)
256+
.orElse(Collections.emptyMap());
257+
}
258+
259+
private static Map<String, String> extractDsaRequestParamsFromDsaRegsExtension(final ExtRegsDsa dsa) {
260+
final Map<String, String> dsaRequestParams = new HashMap<>();
261+
262+
if (dsa.getDsaRequired() != null) {
263+
dsaRequestParams.put("dsarequired", dsa.getDsaRequired().toString());
264+
}
265+
266+
if (dsa.getPubRender() != null) {
267+
dsaRequestParams.put("dsapubrender", dsa.getPubRender().toString());
268+
}
269+
270+
if (dsa.getDataToPub() != null) {
271+
dsaRequestParams.put("dsadatatopub", dsa.getDataToPub().toString());
272+
}
273+
274+
final List<ExtRegsDsaTransparency> dsaTransparency = dsa.getTransparency();
275+
if (CollectionUtils.isNotEmpty(dsaTransparency)) {
276+
final String encodedTransparencies = encodeTransparenciesAsString(dsaTransparency);
277+
if (StringUtils.isNotBlank(encodedTransparencies)) {
278+
dsaRequestParams.put("dsatransparency", encodedTransparencies);
279+
}
280+
}
281+
282+
return dsaRequestParams;
283+
}
284+
285+
private static String encodeTransparenciesAsString(List<ExtRegsDsaTransparency> transparencies) {
286+
return transparencies.stream()
287+
.filter(YieldlabBidder::isTransparencyValid)
288+
.map(YieldlabBidder::encodeTransparency)
289+
.collect(Collectors.joining(TRANSPARENCY_TEMPLATE_DELIMITER));
290+
}
291+
292+
private static boolean isTransparencyValid(ExtRegsDsaTransparency transparency) {
293+
return StringUtils.isNotBlank(transparency.getDomain())
294+
&& transparency.getDsaParams() != null
295+
&& CollectionUtils.isNotEmpty(transparency.getDsaParams());
296+
}
297+
298+
private static String encodeTransparency(ExtRegsDsaTransparency transparency) {
299+
return TRANSPARENCY_TEMPLATE.formatted(transparency.getDomain(),
300+
encodeTransparencyParams(transparency.getDsaParams()));
301+
}
302+
303+
private static String encodeTransparencyParams(List<Integer> dsaParams) {
304+
return dsaParams.stream().map(Objects::toString).collect(Collectors.joining(
305+
TRANSPARENCY_TEMPLATE_PARAMS_DELIMITER));
306+
}
307+
234308
private static MultiMap resolveHeaders(Site site, Device device, User user) {
235309
final MultiMap headers = MultiMap.caseInsensitiveMultiMap()
236310
.add(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON);
@@ -339,7 +413,8 @@ private Bid.BidBuilder addBidParams(YieldlabResponse yieldlabResponse, BidReques
339413
.dealid(resolveNumberParameter(yieldlabResponse.getPid()))
340414
.crid(makeCreativeId(bidRequest, yieldlabResponse, matchedExtImp))
341415
.w(resolveSizeParameter(yieldlabResponse.getAdSize(), true))
342-
.h(resolveSizeParameter(yieldlabResponse.getAdSize(), false));
416+
.h(resolveSizeParameter(yieldlabResponse.getAdSize(), false))
417+
.ext(resolveExtParameter(yieldlabResponse));
343418

344419
return updatedBid;
345420
}
@@ -408,4 +483,21 @@ private String makeNurl(BidRequest bidRequest, ExtImpYieldlab extImpYieldlab, Yi
408483
yieldlabResponse.getAdSize(),
409484
uriBuilder.toString().replace("?", ""));
410485
}
486+
487+
private ObjectNode resolveExtParameter(YieldlabResponse yieldlabResponse) {
488+
final YieldlabDigitalServicesActResponse dsa = yieldlabResponse.getDsa();
489+
if (dsa == null) {
490+
return null;
491+
}
492+
final ObjectNode ext = mapper.mapper().createObjectNode();
493+
final JsonNode dsaNode;
494+
try {
495+
dsaNode = mapper.mapper().valueToTree(dsa);
496+
} catch (IllegalArgumentException e) {
497+
logger.error("Failed to serialize DSA object for adslot {}", yieldlabResponse.getId(), e);
498+
return null;
499+
}
500+
ext.set("dsa", dsaNode);
501+
return ext;
502+
}
411503
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.prebid.server.bidder.yieldlab.model;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Value;
5+
6+
import java.util.List;
7+
8+
@AllArgsConstructor(staticName = "of")
9+
@Value(staticConstructor = "of")
10+
public class YieldlabDigitalServicesActResponse {
11+
12+
String behalf;
13+
14+
String paid;
15+
16+
Integer adrender;
17+
18+
List<Transparency> transparency;
19+
20+
@AllArgsConstructor(staticName = "of")
21+
@Value(staticConstructor = "of")
22+
public static class Transparency {
23+
String domain;
24+
List<Integer> dsaparams;
25+
}
26+
}

src/main/java/org/prebid/server/bidder/yieldlab/model/YieldlabResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ public class YieldlabResponse {
2222
Integer did;
2323

2424
String pvid;
25+
26+
YieldlabDigitalServicesActResponse dsa;
2527
}

0 commit comments

Comments
 (0)