Skip to content

Commit

Permalink
Add silvermob bidder (#1034)
Browse files Browse the repository at this point in the history
  • Loading branch information
SerhiiNahornyi authored and nickluck8 committed Aug 9, 2021
1 parent 1356591 commit f7bd0a1
Show file tree
Hide file tree
Showing 14 changed files with 880 additions and 0 deletions.
167 changes: 167 additions & 0 deletions src/main/java/org/prebid/server/bidder/silvermob/SilvermobBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package org.prebid.server.bidder.silvermob;

import com.fasterxml.jackson.core.type.TypeReference;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
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.StringUtils;
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.ExtPrebid;
import org.prebid.server.proto.openrtb.ext.request.silvermob.ExtImpSilvermob;
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.stream.Collectors;

/**
* Silvermob {@link Bidder} implementation.
*/
public class SilvermobBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpSilvermob>> SILVERMOB_EXT_TYPE_REFERENCE =
new TypeReference<ExtPrebid<?, ExtImpSilvermob>>() {
};

private static final String URL_HOST_MACRO = "{{Host}}";
private static final String URL_ZONE_ID_MACRO = "{{ZoneID}}";

private final String endpointUrl;
private final JacksonMapper mapper;

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

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

return Result.of(requests, errors);
}

private HttpRequest<BidRequest> createRequestForImp(Imp imp, BidRequest request) {
final ExtImpSilvermob extImp = parseImpExt(imp);

final BidRequest outgoingRequest = request.toBuilder()
.imp(Collections.singletonList(imp))
.build();
return HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(resolveEndpoint(extImp))
.headers(resolveHeaders(request.getDevice()))
.payload(outgoingRequest)
.body(mapper.encode(outgoingRequest))
.build();
}

private ExtImpSilvermob parseImpExt(Imp imp) {
final ExtImpSilvermob extImp;
try {
extImp = mapper.mapper().convertValue(imp.getExt(), SILVERMOB_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
throw new PreBidException(String.format("error unmarshalling imp.ext.bidder: %s", e.getMessage()));
}
if (StringUtils.isBlank(extImp.getHost())) {
throw new PreBidException("host is a required silvermob ext.imp param");
}

if (StringUtils.isBlank(extImp.getZoneId())) {
throw new PreBidException("zoneId is a required silvermob ext.imp param");
}
return extImp;
}

private String resolveEndpoint(ExtImpSilvermob extImp) {
return endpointUrl
.replace(URL_HOST_MACRO, extImp.getHost())
.replace(URL_ZONE_ID_MACRO, HttpUtil.encodeUrl(extImp.getZoneId()));
}

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

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 {
return Result.of(extractBids(httpCall), Collections.emptyList());
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private List<BidderBid> extractBids(HttpCall<BidRequest> httpCall) {
final BidResponse bidResponse;
try {
bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
} catch (DecodeException e) {
throw new PreBidException(String.format("Error unmarshalling server Response: %s", e.getMessage()));
}
if (bidResponse == null) {
throw new PreBidException("Response in not present");
}
if (CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
throw new PreBidException("Empty SeatBid array");
}
return bidsFromResponse(bidResponse, httpCall.getRequest().getPayload());
}

private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, BidRequest bidRequest) {
return bidResponse.getSeatbid().stream()
.map(SeatBid::getBid)
.flatMap(Collection::stream)
.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 (imp.getId().equals(impId)) {
if (imp.getVideo() != null) {
return BidType.video;
}
if (imp.getXNative() != null) {
return BidType.xNative;
}
}
}
return BidType.banner;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.prebid.server.proto.openrtb.ext.request.silvermob;

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

/**
* Defines the contract for bidRequest.imp[i].ext.silvermob
*/
@AllArgsConstructor(staticName = "of")
@Value
public class ExtImpSilvermob {

@JsonProperty("zoneid")
String zoneId;

String host;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.silvermob.SilvermobBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.model.UsersyncConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
import org.prebid.server.spring.config.bidder.util.BidderInfoCreator;
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/silvermob.yaml", factory = YamlPropertySourceFactory.class)
public class SilvermobConfiguration {

private static final String BIDDER_NAME = "silvermob";

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

@Autowired
private JacksonMapper mapper;

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

@Bean("silvermobConfigurationProperties")
@ConfigurationProperties("adapters.silvermob")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps silvermobBidderDeps() {
final UsersyncConfigurationProperties usersync = configProperties.getUsersync();

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(configProperties)
.bidderInfo(BidderInfoCreator.create(configProperties))
.usersyncerCreator(UsersyncerCreator.create(usersync, externalUrl))
.bidderCreator(() -> new SilvermobBidder(configProperties.getEndpoint(), mapper))
.assemble();
}
}
24 changes: 24 additions & 0 deletions src/main/resources/bidder-config/silvermob.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
adapters:
silvermob:
enabled: false
endpoint: http://{{Host}}.silvermob.com/marketplace/api/dsp/bid/{{ZoneID}}
pbs-enforces-gdpr: true
pbs-enforces-ccpa: true
modifying-vast-xml-allowed: true
deprecated-names:
aliases:
meta-info:
maintainer-email: [email protected]
app-media-types:
- banner
- video
- native
site-media-types:
supported-vendors:
vendor-id: 0
usersync:
url:
redirect-url:
cookie-family-name: silvermob
type: redirect
support-cors: false
17 changes: 17 additions & 0 deletions src/main/resources/static/bidder-params/silvermob.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "SilverMob Adapter Params",
"description": "A schema which validates params accepted by the SilverMob adapter",
"type": "object",
"properties": {
"zoneid": {
"type": "string",
"description": "Zone ID"
},
"host": {
"type": "string",
"description": "Host"
}
},
"required": ["zoneid", "host"]
}
Loading

0 comments on commit f7bd0a1

Please sign in to comment.