Skip to content

Commit

Permalink
Add invibes bidder
Browse files Browse the repository at this point in the history
  • Loading branch information
Serhii Nahornyi committed Oct 20, 2020
1 parent 85ddc35 commit c09c041
Show file tree
Hide file tree
Showing 22 changed files with 1,135 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/main/java/com/iab/openrtb/request/Banner.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public class Banner {
* permitted. If none are specified, then use of the h and w attributes is
* highly recommended.
*/
List<Format> format;
List<Format>
format;

/**
* Exact width in device independent pixels (DIPS);
Expand Down
331 changes: 331 additions & 0 deletions src/main/java/org/prebid/server/bidder/invibes/InvibesBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
package org.prebid.server.bidder.invibes;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.request.Regs;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.invibes.model.InvibesBidParams;
import org.prebid.server.bidder.invibes.model.InvibesBidRequest;
import org.prebid.server.bidder.invibes.model.InvibesBidderResponse;
import org.prebid.server.bidder.invibes.model.InvibesInternalParams;
import org.prebid.server.bidder.invibes.model.InvibesPlacementProperty;
import org.prebid.server.bidder.invibes.model.InvibesTypedBid;
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.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.invibes.ExtImpInvibes;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.HttpUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class InvibesBidder implements Bidder<InvibesBidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpInvibes>> INVIBES_EXT_TYPE_REFERENCE =
new TypeReference<ExtPrebid<?, ExtImpInvibes>>() {
};
private static final String INVIBES_BID_VERSION = "4";
private static final String ADAPTER_VERSION = "prebid_1.0.0";
private static final String URL_HOST_MACRO = "{{Host}}";

private final String endpointUrl;
private final JacksonMapper mapper;

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

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

final String consentString = resolveConsentString(request.getUser());
final Boolean gdprApplies = resolveGDPRApplies(request.getRegs());

InvibesInternalParams invibesInternalParams =
InvibesInternalParams.builder()
.bidParams(InvibesBidParams.builder()
.properties(new HashMap<String, InvibesPlacementProperty>())
.bidVersion(INVIBES_BID_VERSION)
.build())
.build();

for (final Imp imp : request.getImp()) {
final ExtImpInvibes extImpInvibes;
try {
extImpInvibes = mapper.mapper().convertValue(imp.getExt(), INVIBES_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
errors.add(BidderError.badInput("Error parsing invibesExt parameters"));
continue;
}
final Banner banner = imp.getBanner();
if (banner == null) {
errors.add(BidderError.badInput("Banner not specified"));
continue;
}

invibesInternalParams = updateInvibesInternalParams(invibesInternalParams, extImpInvibes, imp);
}
//TODO add AMP parameter to invibesInternalParams, after reqInfo will be implemented

if (invibesInternalParams.getBidParams()
.getPlacementIDs() == null || invibesInternalParams.getBidParams()
.getPlacementIDs()
.size() == 0) {
return Result.of(Collections.emptyList(), errors);
}

invibesInternalParams = updateWithGDPRParams(invibesInternalParams, consentString, gdprApplies);

try {
final HttpRequest<InvibesBidRequest> httpRequest = makeRequest(invibesInternalParams, request);
return Result.of(Collections.singletonList(httpRequest), errors);
} catch (PreBidException e) {
return Result.emptyWithError(BidderError.badInput(e.getMessage()));
}
}

private String resolveConsentString(User user) {
if (user != null && user.getExt() != null) {
return user.getExt().getConsent();
}
return StringUtils.EMPTY;
}

private Boolean resolveGDPRApplies(Regs regs) {
return regs != null && regs.getExt() != null && regs.getExt().getGdpr() != null
? regs.getExt().getGdpr() == 1 : Boolean.TRUE;

}

private InvibesInternalParams updateInvibesInternalParams(
InvibesInternalParams invibesInternalParams, ExtImpInvibes invibesExt,
Imp imp) {
final String placementId = invibesExt.getPlacementId();
final List<String> updatedPlacementIds = getUpdatedPlacementIds(
placementId, invibesInternalParams.getBidParams().getPlacementIDs());

final Map<String, InvibesPlacementProperty> updatedProperties
= getUpdatedProperties(invibesInternalParams.getBidParams().getProperties(),
placementId, imp.getId(), resolveAdFormats(imp.getBanner()));

final InvibesBidParams updatedBidParams = invibesInternalParams.getBidParams().toBuilder()
.placementIDs(updatedPlacementIds)
.properties(updatedProperties)
.build();

final InvibesInternalParams.InvibesInternalParamsBuilder internalParamsBuilder
= invibesInternalParams.toBuilder()
.domainID(invibesExt.getDomainId())
.bidParams(updatedBidParams);

if (invibesExt.getDebug() != null) {
if (!invibesExt.getDebug().getTestBvid().equals(StringUtils.EMPTY)) {
internalParamsBuilder.testBvid(invibesExt.getDebug().getTestBvid());
}
internalParamsBuilder.testLog(invibesExt.getDebug().getTestLog());
}
return internalParamsBuilder.build();
}

private HttpRequest<InvibesBidRequest> makeRequest(InvibesInternalParams invibesParams, BidRequest request) {
final String url = makeUrl(invibesParams.getDomainID());
final InvibesBidRequest parameter = resolveParameter(invibesParams, request);

final MultiMap headers = resolveHeaders(request.getDevice(), request.getSite());

final String body = mapper.encode(parameter);

return HttpRequest.<InvibesBidRequest>builder()
.method(HttpMethod.POST)
.uri(url)
.headers(headers)
.payload(parameter)
.body(body)
.build();

}

private MultiMap resolveHeaders(Device device, Site site) {
final MultiMap headers = HttpUtil.headers();
if (device != null) {
if (StringUtils.isNotEmpty(device.getIp())) {
headers.add("X-Forwarded-For", device.getIp());
} else if (StringUtils.isNotEmpty(device.getIpv6())) {
headers.add("X-Forwarded-For", device.getIpv6());
}
}
if (site != null) {
headers.add("Referer", site.getPage());
}
headers.add("Aver", ADAPTER_VERSION);
return headers;
}

private InvibesBidRequest resolveParameter(InvibesInternalParams invibesParams, BidRequest request) {
final String lid = (request.getUser() != null && !request.getUser().getBuyeruid().equals(StringUtils.EMPTY))
? request.getUser().getBuyeruid() : StringUtils.EMPTY;
if (request.getSite() == null) {
throw new PreBidException("Site not specified");
}

return InvibesBidRequest.builder()
.isTestBid(invibesParams.getTestBvid() != null
&& !invibesParams.getTestBvid().equals(StringUtils.EMPTY))
.bidParamsJson(mapper.encode(invibesParams.getBidParams()))
.location(request.getSite().getPage())
.lid(lid)
.kw(request.getSite().getKeywords())
.isAMP(invibesParams.getIsAMP())
.width(resolveWidth(request.getDevice()))
.height(resolveHeight(request.getDevice()))
.gdprConsent(invibesParams.getGdprConsent())
.gdpr(invibesParams.getGdpr())
.bvid(invibesParams.getTestBvid())
.invibBVLog(invibesParams.getTestLog())
.videoAdDebug(invibesParams.getTestLog())
.build();
}

private String resolveHeight(Device device) {
return device != null && device.getH() != null && device.getH() > 0
? device.getH().toString() : null;
}

private String resolveWidth(Device device) {
return device != null && device.getW() != null && device.getW() > 0
? device.getW().toString() : null;
}

private String makeUrl(Integer domainId) {
final String host = resolveHost(domainId);
final String url = endpointUrl.replace(URL_HOST_MACRO, host);
try {
HttpUtil.validateUrl(Objects.requireNonNull(url));
} catch (IllegalArgumentException e) {
throw new PreBidException(e.getMessage());
}

return url;
}

private String resolveHost(Integer domainId) {
if (domainId == null) {
return "bid.videostep.com";
} else if (domainId >= 1002) {
return String.format("bid%s.videostep.com", domainId - 1000);
} else if (domainId == 1) {
return "adweb.videostepstage.com";
} else if (domainId == 2) {
return "adweb.invibesstage.com";
} else {
return "bid.videostep.com";
}
}

private InvibesInternalParams updateWithGDPRParams(InvibesInternalParams internalParams,
String consentString, Boolean gdprApplies) {
return internalParams.toBuilder()
.gdpr(gdprApplies)
.gdprConsent(consentString)
.build();
}

private List<String> getUpdatedPlacementIds(String placementId, List<String> placementIds) {
final List<String> updatedPlacementIds = placementIds != null
? placementIds : new ArrayList<>();
if (StringUtils.isNotEmpty(placementId)) {
updatedPlacementIds.add(placementId.trim());
}
return updatedPlacementIds;
}

private Map<String, InvibesPlacementProperty> getUpdatedProperties(
Map<String, InvibesPlacementProperty> properties, String placementId,
String impId, List<Format> adFormats) {
properties.put(placementId, InvibesPlacementProperty.builder()
.impId(impId)
.formats(adFormats)
.build());
return properties;
}

private List<Format> resolveAdFormats(Banner currentBanner) {
if (currentBanner.getFormat() != null) {
return currentBanner.getFormat();
} else if (currentBanner.getW() != null && currentBanner.getH() != null) {
return Collections.singletonList(Format.builder()
.w(currentBanner.getW())
.h(currentBanner.getH())
.build());
}
return Collections.emptyList();
}

@Override
public final Result<List<BidderBid>> makeBids(HttpCall<InvibesBidRequest> httpCall, BidRequest bidRequest) {
final int statusCode = httpCall.getResponse().getStatusCode();
if (statusCode == HttpResponseStatus.NO_CONTENT.code()) {
return Result.of(Collections.emptyList(), Collections.emptyList());
}

try {
final InvibesBidderResponse bidResponse =
mapper.decodeValue(httpCall.getResponse().getBody(), InvibesBidderResponse.class);
if (bidResponse != null && StringUtils.isNotEmpty(bidResponse.getError())) {
return Result.emptyWithError(
BidderError.badServerResponse(String.format("Server error: %s.", bidResponse.getError())));
}
return Result.of(extractBids(httpCall.getRequest().getPayload(), bidResponse), Collections.emptyList());
} catch (DecodeException | PreBidException e) {
return Result.emptyWithError(BidderError.badServerResponse(e.getMessage()));
}
}

private List<BidderBid> extractBids(InvibesBidRequest bidRequest, InvibesBidderResponse bidResponse) {
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getTypedBids())) {
return Collections.emptyList();
}
return bidsFromResponse(bidRequest, bidResponse);
}

private List<BidderBid> bidsFromResponse(InvibesBidRequest bidRequest, InvibesBidderResponse bidResponse) {
return bidResponse.getTypedBids().stream()
.filter(Objects::nonNull)
.map(InvibesTypedBid::getBid)
.filter(Objects::nonNull)
//TODO add DealPriority
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCurrency()))
.collect(Collectors.toList());
}

@Override
public Map<String, String> extractTargeting(ObjectNode ext) {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.prebid.server.bidder.invibes.model;

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

import java.util.List;
import java.util.Map;

@Builder(toBuilder = true)
@Value
public class InvibesBidParams {

@JsonProperty("PlacementIds")
List<String> placementIDs;

@JsonProperty("BidVersion")
String bidVersion;

@JsonProperty("Properties")
Map<String, InvibesPlacementProperty> properties;
}
Loading

0 comments on commit c09c041

Please sign in to comment.