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
155 changes: 155 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,155 @@
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 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.validateUrlSyntax(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 -> seatBid.getBid().stream()
.filter(Objects::nonNull)
.map(bid -> updateBid(bid, rendererVersion, errors))
.filter(Objects::nonNull)
.map(bid -> BidderBid.of(bid, BidderUtil.getBidType(bid, impMap), bidResponse.getCur()))
.toList())
.flatMap(Collection::stream)
.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 = updateBidExt(bid, rendererVersion);
} catch (PreBidException e) {
errors.add(BidderError.badServerResponse(e.getMessage()));
return bid;
}

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

private ObjectNode updateBidExt(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)
.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);
}
Comment on lines 78 to 152
Copy link
Collaborator

Choose a reason for hiding this comment

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

    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 static 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 = updateBidExt(bid, rendererVersion);
        } catch (PreBidException e) {
            errors.add(BidderError.badServerResponse(e.getMessage()));
            return bid;
        }

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

    private ObjectNode updateBidExt(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)
                .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);
        }
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

refactored

}

}
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 @@ -115,28 +115,6 @@ adapters:
url: https://sync.adsinteractive.com/getuid?{{redirect_url}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}
support-cors: false
uid-macro: '$AUID'
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see that in GO version this adapter supports Ortb2.6

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

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 @@ -125,8 +125,6 @@ LIMIT 1
"adapters.generic.aliases.blue.meta-info.site-media-types" : "",
"adapters.generic.aliases.adsinteractive.meta-info.app-media-types" : "",
"adapters.generic.aliases.adsinteractive.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