Skip to content

Commit

Permalink
Add Vidoomy bidder (#1717)
Browse files Browse the repository at this point in the history
  • Loading branch information
marki1an authored Feb 11, 2022
1 parent 0207639 commit d92dc5b
Show file tree
Hide file tree
Showing 12 changed files with 722 additions and 6 deletions.
166 changes: 166 additions & 0 deletions src/main/java/org/prebid/server/bidder/vidoomy/VidoomyBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package org.prebid.server.bidder.vidoomy;

import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Format;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
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 org.prebid.server.util.ObjectUtil;

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

public class VidoomyBidder implements Bidder<BidRequest> {

private static final String OPENRTB_VERSION = "2.5";

private final String endpointUrl;
private final JacksonMapper mapper;

public VidoomyBidder(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<HttpRequest<BidRequest>> requests = new ArrayList<>();
final List<BidderError> errors = new ArrayList<>();

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

return Result.of(requests, errors);
}

private static Imp modifyImp(Imp imp) {
final Banner banner = imp.getBanner();
if (banner == null) {
return imp;
}

final Integer width = banner.getW();
final Integer height = banner.getH();
final List<Format> formats = banner.getFormat();
validateBannerSizes(width, height, formats);

final boolean useFormatSize = width == null || height == null;
final Format firstFormat = useFormatSize ? formats.get(0) : null;
return imp.toBuilder()
.banner(banner.toBuilder()
.w(useFormatSize ? zeroIfFormatMeasureNull(firstFormat, Format::getW) : width)
.h(useFormatSize ? zeroIfFormatMeasureNull(firstFormat, Format::getH) : height)
.build())
.build();
}

private static void validateBannerSizes(Integer width, Integer height, List<Format> formats) {
final boolean sizePresent = width != null && height != null;
if (sizePresent && (width == 0 || height == 0)) {
throw new PreBidException(String.format("invalid sizes provided for Banner %s x %s", width, height));
}

if (!sizePresent && CollectionUtils.isEmpty(formats)) {
throw new PreBidException("no sizes provided for Banner []");
}
}

private static Integer zeroIfFormatMeasureNull(Format format, Function<Format, Integer> measureExtractor) {
return ObjectUtils.defaultIfNull(ObjectUtil.getIfNotNull(format, measureExtractor), 0);
}

private HttpRequest<BidRequest> createRequest(BidRequest bidRequest, Imp imp) {
final BidRequest outgoingRequest = bidRequest.toBuilder()
.imp(Collections.singletonList(imp))
.build();

return HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(endpointUrl)
.headers(headers(bidRequest.getDevice()))
.payload(outgoingRequest)
.body(mapper.encodeToBytes(outgoingRequest))
.build();
}

private static MultiMap headers(Device device) {
final MultiMap headers = HttpUtil.headers()
.add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION);

if (device != null) {
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa());
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
}

return headers;
}

@Override
public Result<List<BidderBid>> makeBids(HttpCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.withValues(extractBids(bidResponse, bidRequest));
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private static List<BidderBid> extractBids(BidResponse bidResponse, BidRequest bidRequest) {
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 -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), bidResponse.getCur()))
.collect(Collectors.toList());
}

private static BidType getBidType(String impId, List<Imp> imps) {
for (Imp imp : imps) {
if (Objects.equals(impId, imp.getId())) {
if (imp.getVideo() != null) {
return BidType.video;
}
if (imp.getBanner() != null) {
return BidType.banner;
}
}
}

throw new PreBidException(String.format("Unknown ad unit code '%s'", impId));
}
}
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.vidoomy.VidoomyBidder;
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 javax.validation.constraints.NotBlank;

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

private static final String BIDDER_NAME = "vidoomy";

@Bean("vidoomyConfigurationProperties")
@ConfigurationProperties("adapters.vidoomy")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

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

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(vidoomyConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new VidoomyBidder(config.getEndpoint(), mapper))
.assemble();
}
}
19 changes: 19 additions & 0 deletions src/main/resources/bidder-config/vidoomy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
adapters:
vidoomy:
endpoint: https://p.vidoomy.com/api/rtbserver/pbs
meta-info:
maintainer-email: support@vidoomy.com
app-media-types:
- banner
- video
site-media-types:
- banner
- video
supported-vendors:
vendor-id: 380
usersync:
url: https://vid.vidoomy.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=
redirect-url: /setuid?bidder=vidoomy&gdpr=0&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&uid={{VID}}
cookie-family-name: vidoomy
type: iframe
supportCors: false
16 changes: 16 additions & 0 deletions src/main/resources/static/bidder-params/vidoomy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Vidoomy Adapter Params",
"description": "A schema which validates params accepted by the Vidoomy adapter",
"type": "object",
"properties": {
"zoneId": {
"type": "string",
"description": "Specific zone ID for this placement, belonging to app/site",
"minLength": 1
}
},
"required": [
"zoneId"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import org.junit.Before;
import org.junit.Test;
import org.prebid.server.VertxTest;
import org.prebid.server.bidder.between.BetweenBidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpCall;
Expand Down Expand Up @@ -48,7 +47,7 @@ public void setUp() {

@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new BetweenBidder("invalid_url", jacksonMapper));
assertThatIllegalArgumentException().isThrownBy(() -> new AxonixBidder("invalid_url", jacksonMapper));
}

@Test
Expand Down Expand Up @@ -209,7 +208,7 @@ private static BidRequest givenBidRequest(
Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer) {

return bidRequestCustomizer.apply(BidRequest.builder()
.imp(singletonList(givenImp(impCustomizer))))
.imp(singletonList(givenImp(impCustomizer))))
.build();
}

Expand All @@ -219,9 +218,9 @@ private static BidRequest givenBidRequest(Function<Imp.ImpBuilder, Imp.ImpBuilde

private static Imp givenImp(Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer) {
return impCustomizer.apply(Imp.builder()
.id("123")
.banner(Banner.builder().w(23).h(25).build())
.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpBetween.of("127.0.0.1", "pubId")))))
.id("123")
.banner(Banner.builder().w(23).h(25).build())
.ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpBetween.of("127.0.0.1", "pubId")))))
.build();
}

Expand Down
Loading

0 comments on commit d92dc5b

Please sign in to comment.