Skip to content

Commit

Permalink
Add bidscube bidder (#1350)
Browse files Browse the repository at this point in the history
  • Loading branch information
And1sS authored and nickluck8 committed Jul 8, 2021
1 parent e7f8786 commit 5a171b4
Show file tree
Hide file tree
Showing 12 changed files with 764 additions and 0 deletions.
136 changes: 136 additions & 0 deletions src/main/java/org/prebid/server/bidder/bidscube/BidscubeBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.prebid.server.bidder.bidscube;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableSet;
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 io.vertx.core.http.HttpMethod;
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.BidderError;
import org.prebid.server.bidder.model.HttpCall;
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.response.BidType;
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.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Bidscube {@link Bidder} implementation.
*/
public class BidscubeBidder implements Bidder<BidRequest> {

private static final Set<String> POSSIBLE_BID_TYPES = ImmutableSet.of("banner", "video", "native");

private final String endpointUrl;
private final JacksonMapper mapper;

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

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

for (Imp imp : request.getImp()) {
try {
requests.add(createRequest(request, imp, getBidderNode(imp)));
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}

return Result.of(requests, errors);
}

private static ObjectNode getBidderNode(Imp imp) {
final JsonNode impExtNode = imp.getExt();
final JsonNode bidderExtNode = isNotEmptyOrMissedNode(impExtNode) ? impExtNode.get("bidder") : null;
if (!isNotEmptyOrMissedNode(bidderExtNode)) {
throw new PreBidException("Missing required bidder parameters");
}
return bidderExtNode.deepCopy();
}

private HttpRequest<BidRequest> createRequest(BidRequest request, Imp imp, ObjectNode bidderNode) {
final Imp internalImp = imp.toBuilder().ext(bidderNode).build();
final BidRequest internalRequest = request.toBuilder().imp(Collections.singletonList(internalImp)).build();
return HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(endpointUrl)
.headers(HttpUtil.headers())
.payload(internalRequest)
.body(mapper.encode(internalRequest))
.build();
}

@Override
public final Result<List<BidderBid>> makeBids(HttpCall<BidRequest> httpCall, BidRequest bidRequest) {
final List<BidderError> errors = new ArrayList<>();

try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.of(extractBids(bidResponse, errors), errors);
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

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

private List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.map(bid -> constructBidderBid(bid, bidResponse, errors))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private BidderBid constructBidderBid(Bid bid, BidResponse bidResponse, List<BidderError> errors) {
final JsonNode extNode = bid.getExt();
final JsonNode typeNode = isNotEmptyOrMissedNode(extNode) ? extNode.at("/prebid/type") : null;
if (typeNode == null || !typeNode.isTextual()) {
errors.add(BidderError.badInput("Unable to read bid.ext.prebid.type"));
return null;
}

return BidderBid.of(bid, resolveBidType(typeNode), bidResponse.getCur());
}

private static boolean isNotEmptyOrMissedNode(JsonNode node) {
return node != null && !node.isEmpty();
}

private BidType resolveBidType(JsonNode bidType) {
if (!POSSIBLE_BID_TYPES.contains(bidType.asText())) {
return BidType.banner;
}
return mapper.mapper().convertValue(bidType, BidType.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.prebid.server.proto.openrtb.ext.request.bidscube;

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

@Value
@AllArgsConstructor(staticName = "of")
public class ExtImpBidscube {

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

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.bidscube.BidscubeBidder;
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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.validation.constraints.NotBlank;

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

private static final String BIDDER_NAME = "bidscube";

@Value("${external-url}")
@NotBlank
private String externalUrl;

@Autowired
private JacksonMapper mapper;

@Autowired
@Qualifier("bidscubeConfigurationProperties")
private BidderConfigurationProperties configProperties;

@Bean("bidscubeConfigurationProperties")
@ConfigurationProperties("adapters.bidscube")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps bidscubeBidderDeps() {
return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(configProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new BidscubeBidder(mapper, config.getEndpoint()))
.assemble();
}
}
27 changes: 27 additions & 0 deletions src/main/resources/bidder-config/bidscube.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
adapters:
bidscube:
enabled: false
endpoint: http://supply.bidscube.com/?c=o&m=rtb
pbs-enforces-gdpr: true
pbs-enforces-ccpa: true
modifying-vast-xml-allowed: true
deprecated-names:
aliases: {}
meta-info:
maintainer-email: support@bidscube.com
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors:
vendor-id: 0
usersync:
url:
redirect-url:
cookie-family-name: bidscube
type: redirect
support-cors: false
18 changes: 18 additions & 0 deletions src/main/resources/static/bidder-params/bidscube.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "BidsCube Adapter Params",
"description": "A schema which validates params accepted by the BidsCube adapter",
"type": "object",
"properties": {
"placementId": {
"type": "string",
"minLength": 1,
"description": "An ID which identifies the BidsCube placement"
},
"customParams": {
"type": "object",
"description": "User-defined targeting key-value pairs."
}
},
"required" : [ "placementId" ]
}
Loading

0 comments on commit 5a171b4

Please sign in to comment.