Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions src/main/java/org/prebid/server/bidder/trustx/TrustxBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package org.prebid.server.bidder.trustx;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.MultiMap;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.trustx.ExtBidBidderTrustx;
import org.prebid.server.proto.openrtb.ext.request.trustx.ExtBidTrustx;
import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustx;
import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustxData;
import org.prebid.server.proto.openrtb.ext.request.trustx.ExtImpTrustxDataAdServer;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;
import org.prebid.server.util.ObjectUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

public class TrustxBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx>> TRUSTX_BID_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};

private static final String OPENRTB_VERSION = "2.6";

private final String endpointUrl;
private final JacksonMapper mapper;

public TrustxBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
this.mapper = Objects.requireNonNull(mapper);
}

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
final List<Imp> updatedImps = request.getImp().stream().map(this::modifyImp).toList();
final BidRequest updatedRequest = request.toBuilder().imp(updatedImps).build();
return Result.withValue(BidderUtil.defaultRequest(updatedRequest, makeHeaders(request), endpointUrl, mapper));
}

private Imp modifyImp(Imp imp) {
final ExtImpTrustx impExt = tryParseImpExt(imp);

return impExt == null ? imp : imp.toBuilder()
.ext(mapper.mapper().valueToTree(modifyImpExt(impExt)))
.build();
}
Comment on lines +68 to +74
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    private Imp modifyImp(Imp imp) {
        final ExtImpTrustx impExt = parseImpExt(imp);
        final ExtImpTrustx modifiedImpExt = impExt != null ? modifyImpExt(impExt) : null;

        return impExt != modifiedImpExt
                ? imp.toBuilder().ext(mapper.mapper().valueToTree(modifiedImpExt)).build()
                : imp;
    }


private ExtImpTrustx tryParseImpExt(Imp imp) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tryParseImpExt -> parseImpExt

try {
return mapper.mapper().convertValue(imp.getExt(), ExtImpTrustx.class);
} catch (IllegalArgumentException e) {
return null;
}
}

private ExtImpTrustx modifyImpExt(ExtImpTrustx impExt) {
final String adSlot = Optional.ofNullable(impExt.getData())
.map(ExtImpTrustxData::getAdServer)
.map(ExtImpTrustxDataAdServer::getAdSlot)
.filter(StringUtils::isNotEmpty)
.orElse(null);

return impExt.toBuilder()
.gpid(adSlot != null ? adSlot : impExt.getGpid())
.build();
Comment on lines +91 to +93
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    private static ExtImpTrustx modifyImpExt(ExtImpTrustx impExt) {
        final String adSlot = Optional.ofNullable(impExt.getData())
                .map(ExtImpTrustxData::getAdServer)
                .map(ExtImpTrustxDataAdServer::getAdSlot)
                .filter(StringUtils::isNotEmpty)
                .orElse(null);
        
        return adSlot != null ? impExt.toBuilder().gpid(adSlot).build() : impExt;
    }

}

private MultiMap makeHeaders(BidRequest request) {
final Site site = request.getSite();
final String referrer = ObjectUtil.getIfNotNull(site, Site::getRef);
final String domain = ObjectUtil.getIfNotNull(site, Site::getDomain);

final Device device = request.getDevice();
final String ip = StringUtils.firstNonEmpty(
ObjectUtil.getIfNotNull(device, Device::getIpv6),
ObjectUtil.getIfNotNull(device, Device::getIp));
final String userAgent = ObjectUtil.getIfNotNull(device, Device::getUa);
Comment on lines +98 to +105
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually use ObjectUtil.getIfNotNull for cases like

ObjectUtil.getIfNotNull(request.getDevice(), Device::getIpv6)

so you don't need to make a local variable. For me,

device != null ? device.getIpv6() : null

looks simpler.


final MultiMap headers = HttpUtil.headers();

headers.add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION);
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, referrer);
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ORIGIN_HEADER, domain);
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, ip);
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, userAgent);

return headers;
}

@Override
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final BidResponse bidResponse = decodeBodyToBidResponse(httpCall);
return bidsFromResponse(bidResponse);
} catch (PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private BidResponse decodeBodyToBidResponse(BidderCall<BidRequest> httpCall) {
try {
return mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
} catch (DecodeException e) {
throw new PreBidException("Failed to parse response as BidResponse: " + e.getMessage());
}
}

private Result<List<BidderBid>> bidsFromResponse(BidResponse bidResponse) {
final List<BidderError> errors = new ArrayList<>();
final List<BidderBid> bidderBids = Stream.ofNullable(bidResponse)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't be null

.map(BidResponse::getSeatbid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(bid -> makeBid(bid, errors))
.filter(Objects::nonNull)
.toList();

return Result.of(bidderBids, errors);
}

private BidderBid makeBid(Bid bid, List<BidderError> errors) {
try {
final BidType bidType = getBidType(bid);

final ExtBidPrebidVideo videoInfo = (bidType == BidType.video) ? ExtBidPrebidVideo.of(
bid.getDur() != null && bid.getDur() > 0 ? bid.getDur() : null,
CollectionUtils.isNotEmpty(bid.getCat()) ? bid.getCat().getFirst() : null
) : null;

final Bid modifiedBid = Optional.ofNullable(tryParseBidExt(bid.getExt()))
.map(TrustxBidder::modifyBidExt)
.map(mapper.mapper()::<ObjectNode>valueToTree)
.map(extBid -> bid.toBuilder().ext(extBid).build())
.orElse(bid);

return BidderBid.builder()
.bid(modifiedBid)
.type(bidType)
.videoInfo(videoInfo)
.build();
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
return null;
}
}

private static BidType getBidType(Bid bid) {
final Integer markupType = bid.getMtype();
if (markupType == null) {
throw new PreBidException("Missing MType for bid: " + bid.getId());
}

return switch (markupType) {
case 1 -> BidType.banner;
case 2 -> BidType.video;
default -> throw new PreBidException("Unsupported MType: %d".formatted(markupType));
};
}

private ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> tryParseBidExt(ObjectNode bidExt) {
try {
return mapper.mapper().convertValue(bidExt, TRUSTX_BID_EXT_TYPE_REFERENCE);
} catch (IllegalArgumentException e) {
return null;
}
}

private static ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> modifyBidExt(
ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> extBid) {

return Optional.ofNullable(extBid.getBidder())
.map(ExtBidBidderTrustx::getTrustx)
.map(ExtBidTrustx::getNetworkName)
.filter(StringUtils::isNotEmpty)
.map(networkName -> ExtBidPrebidMeta.builder().networkName(networkName).build())
.map(extBidPrebidMeta -> modifyBidExtMeta(extBid, extBidPrebidMeta))
.orElse(extBid);
}

private static ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> modifyBidExtMeta(
ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> extBid, ExtBidPrebidMeta extBidPrebidMeta) {

final ExtBidPrebid updatedExtBidPrebid = Optional.ofNullable(extBid.getPrebid())
.map(ExtBidPrebid::toBuilder)
.orElseGet(ExtBidPrebid::builder)
.meta(extBidPrebidMeta)
.build();

return ExtPrebid.of(updatedExtBidPrebid, extBid.getBidder());
}
Comment on lines +154 to +223
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    private BidderBid makeBidderBid(Bid bid, List<BidderError> errors) {
        final BidType bidType;
        try {
            bidType = getBidType(bid);
        } catch (PreBidException e) {
            errors.add(BidderError.badInput(e.getMessage()));
            return null;
        }

        return BidderBid.builder()
                .bid(modifyBid(bid))
                .type(bidType)
                .videoInfo(bidType == BidType.video ? makeExtBidPrebidVideo(bid) : null)
                .build();
    }

    private Bid modifyBid(Bid bid) {
        final ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> ext = parseBidExt(bid.getExt());
        if (ext == null) {
            return bid;
        }

        final ExtBidBidderTrustx extBidder = ext.getBidder();
        final String networkName = Optional.ofNullable(extBidder)
                .map(ExtBidBidderTrustx::getTrustx)
                .map(ExtBidTrustx::getNetworkName)
                .filter(StringUtils::isNotEmpty)
                .orElse(null);
        if (networkName == null) {
            return bid;
        }

        final ExtBidPrebid modifiedExtPrebid = Optional.ofNullable(ext.getPrebid())
                .map(ExtBidPrebid::toBuilder)
                .orElseGet(ExtBidPrebid::builder)
                .meta(ExtBidPrebidMeta.builder().networkName(networkName).build())
                .build();
        final ObjectNode modifiedExt = mapper.mapper().valueToTree(ExtPrebid.of(modifiedExtPrebid, extBidder));
        
        return bid.toBuilder().ext(modifiedExt).build();
    }

    private ExtPrebid<ExtBidPrebid, ExtBidBidderTrustx> parseBidExt(ObjectNode bidExt) {
        try {
            return mapper.mapper().convertValue(bidExt, TRUSTX_BID_EXT_TYPE_REFERENCE);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    private static BidType getBidType(Bid bid) {
        final Integer markupType = bid.getMtype();
        if (markupType == null) {
            throw new PreBidException("Missing MType for bid: " + bid.getId());
        }

        return switch (markupType) {
            case 1 -> BidType.banner;
            case 2 -> BidType.video;
            default -> throw new PreBidException("Unsupported MType: %d".formatted(markupType));
        };
    }

    private static ExtBidPrebidVideo makeExtBidPrebidVideo(Bid bid) {
        final Integer dur = bid.getDur();
        final List<String> cat = bid.getCat();

        return ExtBidPrebidVideo.of(
                dur != null && dur > 0 ? dur : null,
                CollectionUtils.isNotEmpty(cat) ? cat.getFirst() : null);
    }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.prebid.server.proto.openrtb.ext.request.trustx;

import lombok.Value;

@Value(staticConstructor = "of")
public class ExtBidBidderTrustx {

ExtBidTrustx trustx;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.prebid.server.proto.openrtb.ext.request.trustx;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

@Value(staticConstructor = "of")
public class ExtBidTrustx {

@JsonProperty("networkName")
String networkName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.prebid.server.proto.openrtb.ext.request.trustx;

import com.fasterxml.jackson.databind.JsonNode;
import lombok.Builder;
import lombok.Value;
import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid;

@Value
@Builder(toBuilder = true)
public class ExtImpTrustx {

ExtImpPrebid prebid;

JsonNode bidder;

ExtImpTrustxData data;

String gpid;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.prebid.server.proto.openrtb.ext.request.trustx;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

@Value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add static constructor

public class ExtImpTrustxData {

@JsonProperty("pbadslot")
String pbAdSlot;

@JsonProperty("adserver")
ExtImpTrustxDataAdServer adServer;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.prebid.server.proto.openrtb.ext.request.trustx;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;

@Value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

public class ExtImpTrustxDataAdServer {

String name;

@JsonProperty("adslot")
String adSlot;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.trustx.TrustxBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
import org.prebid.server.spring.env.YamlPropertySourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import jakarta.validation.constraints.NotBlank;

@Configuration
@PropertySource(value = "classpath:/bidder-config/trustx.yaml", factory = YamlPropertySourceFactory.class)
public class TrustxConfiguration {

private static final String BIDDER_NAME = "trustx";

@Bean("trustxConfigurationProperties")
@ConfigurationProperties("adapters.trustx")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps trustxBidderDeps(BidderConfigurationProperties trustxConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
JacksonMapper mapper) {
Comment on lines +31 to +33
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

align lines


return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(trustxConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new TrustxBidder(config.getEndpoint(), mapper))
.assemble();
}
}
23 changes: 23 additions & 0 deletions src/main/resources/bidder-config/trustx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
adapters:
trustx:
endpoint: https://ads.trustx.org/pbs
meta-info:
maintainer-email: prebid@trustx.org
app-media-types:
- banner
- video
site-media-types:
- banner
- video
supported-vendors:
vendor-id: 0
usersync:
cookie-family-name: trustx
iframe:
url: https://static.cdn.trustx.org/x/user_sync.html?source=pbs&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redirect={{redirect_url}}
support-cors: false
uid-macro: '$UID'
redirect:
url: https://sync.trustx.org/usync-pbs?us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redirect={{redirect_url}}
support-cors: false
uid-macro: '$UID'
16 changes: 16 additions & 0 deletions src/main/resources/static/bidder-params/trustx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "TRUSTX Adapter Params",
"description": "A schema which validates params accepted by the TRUSTX adapter",
"type": "object",
"properties": {
"uid": {
"type": "integer",
"description": "The ad slot id"
},
"keywords": {
"type": "object",
"description": "Keywords"
}
}
}
Loading
Loading