Skip to content
149 changes: 149 additions & 0 deletions src/main/java/org/prebid/server/bidder/adplayx/AdplayxBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package org.prebid.server.bidder.adplayx;

import com.fasterxml.jackson.core.type.TypeReference;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
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.adplayx.ExtImpAdplayx;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.Uri;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class AdplayxBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpAdplayx>> ADPLAYX_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};

private final Uri endpointUri;
private final JacksonMapper mapper;

public AdplayxBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUri = Uri.of(endpointUrl);
this.mapper = Objects.requireNonNull(mapper);
}

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
final List<BidderError> errors = new ArrayList<>();
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();

for (Imp imp : request.getImp()) {
try {
final ExtImpAdplayx extImp = parseImpExt(imp);

if (StringUtils.isBlank(extImp.getApptoken())) {
errors.add(BidderError.badInput("apptoken is required"));
continue;
}

final String uri = buildEndpointUrl(extImp);

final BidRequest outgoingRequest = request.toBuilder()
.imp(Collections.singletonList(imp))
.build();

httpRequests.add(BidderUtil.defaultRequest(outgoingRequest, uri, mapper));
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}

return Result.of(httpRequests, errors);
}

private ExtImpAdplayx parseImpExt(Imp imp) {
try {
return mapper.mapper().convertValue(imp.getExt(), ADPLAYX_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
throw new PreBidException("Error parsing imp.ext: " + e.getMessage());
}
}

private String buildEndpointUrl(ExtImpAdplayx extImp) {
return endpointUri.parameterized()
.addQueryParam("apptoken", extImp.getApptoken())
.addQueryParam("placementid", StringUtils.trimToNull(extImp.getPlacementid()))
.toString();
}

@Override
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
final List<BidderError> errors = new ArrayList<>();
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
final List<Imp> imps = httpCall.getRequest().getPayload().getImp();
final List<BidderBid> bidderBids = extractBids(bidResponse, errors, imps);
return Result.of(bidderBids, errors);
} catch (DecodeException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors, List<Imp> imps) {
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
return Collections.emptyList();
}

return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(bid -> resolveBidderBid(bidResponse.getCur(), imps, bid, errors))
.filter(Objects::nonNull)
.toList();
}

private BidderBid resolveBidderBid(String currency, List<Imp> imps, Bid bid, List<BidderError> errors) {
final BidType bidType;
try {
bidType = getBidType(bid.getImpid(), imps);
} catch (PreBidException e) {
errors.add(BidderError.badServerResponse(e.getMessage()));
return null;
}
return BidderBid.of(bid, bidType, currency);
}

private BidType getBidType(String impId, List<Imp> imps) {
for (Imp imp : imps) {
if (imp.getId().equals(impId)) {
if (imp.getBanner() != null) {
return BidType.banner;
}
if (imp.getVideo() != null) {
return BidType.video;
}
if (imp.getAudio() != null) {
return BidType.audio;
}
if (imp.getXNative() != null) {
return BidType.xNative;
}
}
}
throw new PreBidException("Failed to find impression \"%s\"".formatted(impId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.prebid.server.proto.openrtb.ext.request.adplayx;

import lombok.Value;

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

String apptoken;

String placementid;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.adplayx.AdplayxBidder;
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.env.YamlPropertySourceFactory;
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;

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

private static final String BIDDER_NAME = "adplayx";

@Bean("adplayxConfigurationProperties")
@ConfigurationProperties("adapters.adplayx")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps adplayxBidderDeps(BidderConfigurationProperties adplayxConfigurationProperties,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(adplayxConfigurationProperties)
.bidderCreator(config -> new AdplayxBidder(config.getEndpoint(), mapper))
.assemble();
}
}
15 changes: 15 additions & 0 deletions src/main/resources/bidder-config/adplayx.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
adapters:
adplayx:
endpoint: "https://api.adplayx.net/v1.0/ortb"
pbs-enforces-ccpa: true
modifying-vast-xml-allowed: true
meta-info:
maintainer-email: "support@adplayx.com"
app-media-types:
- banner
- video
site-media-types:
- banner
- video
Comment thread
anuraj700 marked this conversation as resolved.
supported-vendors: []
vendor-id: 0
16 changes: 16 additions & 0 deletions src/main/resources/static/bidder-params/adplayx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AdPlayx Adapter Params",
"type": "object",
"properties": {
"apptoken": {
"type": "string",
"description": "Publisher app token"
},
"placementid": {
"type": "string",
"description": "Placement ID (optional)"
}
},
"required": ["apptoken"]
}
Loading