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

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.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.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.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSdk;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSdkRenderer;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

public class NativoBidder implements Bidder<BidRequest> {

private static final String NATIVO_RENDERER_NAME = "NativoRenderer";
private static final String PREBID_EXT = "prebid";
private static final TypeReference<ExtPrebid<ExtBidPrebid, ?>> EXT_PREBID_TYPE_REFERENCE =
new TypeReference<>() {
};

private final String endpointUrl;
private final JacksonMapper mapper;

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

@Override
public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
return Result.withValue(BidderUtil.defaultRequest(bidRequest, endpointUrl, mapper));
}

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

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

private List<BidderBid> bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse, List<BidderError> errors) {
final Map<String, Imp> impMap = bidRequest.getImp().stream()
.collect(Collectors.toMap(Imp::getId, Function.identity()));
final String rendererVersion = getNativoRendererVersion(bidRequest);

return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(bid -> updateBid(bid, rendererVersion, errors))
.map(bid -> BidderBid.of(bid, BidderUtil.getBidType(bid, impMap), bidResponse.getCur()))
.toList();
}

private String getNativoRendererVersion(BidRequest bidRequest) {
return Optional.ofNullable(bidRequest.getExt())
.map(ExtRequest::getPrebid)
.map(ExtRequestPrebid::getSdk)
.map(ExtRequestPrebidSdk::getRenderers)
.orElse(Collections.emptyList())
.stream()
.filter(renderer -> NATIVO_RENDERER_NAME.equals(renderer.getName()))
.map(ExtRequestPrebidSdkRenderer::getVersion)
.findFirst()
.orElse(null);
}

private Bid updateBid(Bid bid, String rendererVersion, List<BidderError> errors) {
if (rendererVersion == null) {
return bid;
}

final ObjectNode updateBidExt;
try {
updateBidExt = setRendererToResponse(bid, rendererVersion);
} catch (PreBidException e) {
errors.add(BidderError.badServerResponse(e.getMessage()));
return bid;
}

return bid.toBuilder().ext(updateBidExt).build();
}

private ObjectNode setRendererToResponse(Bid bid, String rendererVersion) {
final ObjectNode bidExt = bid.getExt();
final Optional<ExtBidPrebid> extBidPrebid = Optional.ofNullable(bidExt)
.map(ext -> parseExtBidPrebid(bidExt, bid.getId()));

final ExtBidPrebidMeta updatedMeta = extBidPrebid
.map(ExtBidPrebid::getMeta)
.map(ExtBidPrebidMeta::toBuilder)
.orElseGet(ExtBidPrebidMeta::builder)
.rendererName(NATIVO_RENDERER_NAME)
.rendererVersion(rendererVersion)
.build();

final ExtBidPrebid modifiedExtBidPrebid = extBidPrebid
.map(ExtBidPrebid::toBuilder)
.orElseGet(ExtBidPrebid::builder)
.meta(updatedMeta)
.build();

final ObjectNode updatedBidExt = bidExt != null ? bidExt : mapper.mapper().createObjectNode();
updatedBidExt.set(PREBID_EXT, mapper.mapper().valueToTree(modifiedExtBidPrebid));
return updatedBidExt;
}

private ExtBidPrebid parseExtBidPrebid(ObjectNode bidExt, String bidId) {
try {
return mapper.mapper().convertValue(bidExt, EXT_PREBID_TYPE_REFERENCE).getPrebid();
} catch (IllegalArgumentException e) {
throw new PreBidException("Invalid ext passed in bid with id: " + bidId);
}
}

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

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

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

@JsonProperty("placementId")
Object placementId;
}
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.nativo.NativoBidder;
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/nativo.yaml", factory = YamlPropertySourceFactory.class)
public class NativoConfiguration {

private static final String BIDDER_NAME = "nativo";

@Bean("nativoConfigurationProperties")
@ConfigurationProperties("adapters.nativo")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps nativoBidderDeps(BidderConfigurationProperties nativoConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(nativoConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new NativoBidder(config.getEndpoint(), mapper))
.assemble();
}
}
22 changes: 0 additions & 22 deletions src/main/resources/bidder-config/generic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,28 +97,6 @@ adapters:
url: https://prebid.cwi.re/v1/usersync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&rd={{redirect_url}}
support-cors: false
uid-macro: '$UID'
nativo:
enabled: false
endpoint: https://exchange.postrelease.com/esi?ntv_epid=7
pbs-enforces-ccpa: false
meta-info:
maintainer-email: prebiddev@nativo.com
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors:
vendor-id: 263
usersync:
cookie-family-name: nativo
redirect:
url: https://jadserve.postrelease.com/suid/101787?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&ntv_gpp_consent={{gpp}}&ntv_r={{redirect_url}}
support-cors: false
uid-macro: 'NTV_USER_ID'
meta-info:
maintainer-email: maintainer@example.com
app-media-types:
Expand Down
23 changes: 23 additions & 0 deletions src/main/resources/bidder-config/nativo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
adapters:
nativo:
endpoint: https://exchange.postrelease.com/esi?ntv_epid=7
ortb-version: "2.6"
pbs-enforces-ccpa: false
meta-info:
maintainer-email: prebiddev@nativo.com
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors:
vendor-id: 263
usersync:
cookie-family-name: nativo
redirect:
url: https://jadserve.postrelease.com/suid/101787?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&ntv_gpp_consent={{gpp}}&ntv_r={{redirect_url}}
support-cors: false
uid-macro: 'NTV_USER_ID'
12 changes: 12 additions & 0 deletions src/main/resources/static/bidder-params/nativo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Nativo Adapter Params",
"description": "A schema which validates params accepted by the Nativo adapter",
"type": "object",
"properties": {
"placementId": {
"type": ["integer", "string"],
"description": "Placement ID"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ LIMIT 1
"adapters.generic.aliases.cwire.meta-info.app-media-types" : "",
"adapters.generic.aliases.blue.meta-info.app-media-types" : "",
"adapters.generic.aliases.blue.meta-info.site-media-types" : "",
"adapters.generic.aliases.nativo.meta-info.app-media-types" : "",
"adapters.generic.aliases.nativo.meta-info.site-media-types" : "",
"adapters.generic.aliases.infytv.meta-info.app-media-types" : "",
"adapters.generic.aliases.infytv.meta-info.site-media-types" : "",
"adapters.generic.aliases.zeta-global-ssp.meta-info.app-media-types" : "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1348,8 +1348,8 @@ class BidderParamsSpec extends BaseSpec {
def bidRequest = BidRequest.defaultBidRequest.tap {
imp[0].ext.prebid.bidder.tap {
it.generic.exampleProperty = PBSUtils.randomNumber
//Nativo hard coded bidder alias in generic.yaml
it.nativo = new Generic(exampleProperty: PBSUtils.randomNumber)
//Adrino hard coded bidder alias in generic.yaml
it.adrino = new Adrino(hash: PBSUtils.randomNumber)
}
}

Expand All @@ -1362,9 +1362,9 @@ class BidderParamsSpec extends BaseSpec {
["WARNING: request.imp[0].ext.prebid.bidder.generic was dropped with a reason: " +
"request.imp[0].ext.prebid.bidder.generic failed validation.\n" +
"\$.exampleProperty: integer found, string expected",
"WARNING: request.imp[0].ext.prebid.bidder.nativo was dropped with a reason: " +
"request.imp[0].ext.prebid.bidder.nativo failed validation.\n" +
"\$.exampleProperty: integer found, string expected",
"WARNING: request.imp[0].ext.prebid.bidder.adrino was dropped with a reason: " +
"request.imp[0].ext.prebid.bidder.adrino failed validation.\n" +
"\$.hash: integer found, string expected",
"WARNING: request.imp[0].ext must contain at least one valid bidder"]

and: "PBS should not call bidder"
Expand Down
Loading