-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1614b69
commit 4b8d039
Showing
16 changed files
with
1,046 additions
and
0 deletions.
There are no files selected for viewing
240 changes: 240 additions & 0 deletions
240
src/main/java/org/prebid/server/bidder/unicorn/UnicornBidder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
package org.prebid.server.bidder.unicorn; | ||
|
||
import com.fasterxml.jackson.core.type.TypeReference; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.node.IntNode; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import com.fasterxml.jackson.databind.node.TextNode; | ||
import com.iab.openrtb.request.BidRequest; | ||
import com.iab.openrtb.request.Device; | ||
import com.iab.openrtb.request.Imp; | ||
import com.iab.openrtb.request.Regs; | ||
import com.iab.openrtb.request.Source; | ||
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.bidder.unicorn.model.UnicornImpExt; | ||
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.ExtRegs; | ||
import org.prebid.server.proto.openrtb.ext.request.ExtRequest; | ||
import org.prebid.server.proto.openrtb.ext.request.ExtSource; | ||
import org.prebid.server.proto.openrtb.ext.request.unicorn.ExtImpUnicorn; | ||
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; | ||
|
||
/** | ||
* Unicorn {@link Bidder} implementation. | ||
*/ | ||
public class UnicornBidder implements Bidder<BidRequest> { | ||
|
||
private static final TypeReference<ExtPrebid<?, ExtImpUnicorn>> UNICORN_EXT_TYPE_REFERENCE = | ||
new TypeReference<ExtPrebid<?, ExtImpUnicorn>>() { | ||
}; | ||
|
||
private final String endpointUrl; | ||
private final JacksonMapper mapper; | ||
|
||
public UnicornBidder(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<Imp> requestImps = request.getImp(); | ||
final List<Imp> modifiedImps; | ||
final Source source; | ||
final Integer firstImpAccountId; | ||
try { | ||
validateRegs(request.getRegs()); | ||
modifiedImps = modifyImps(requestImps); | ||
source = updateSource(request.getSource()); | ||
firstImpAccountId = parseImpExtBidder(requestImps.get(0)).getAccountId(); | ||
} catch (PreBidException e) { | ||
return Result.withError(BidderError.badInput(e.getMessage())); | ||
} | ||
final ExtRequest modifiedExtRequest = modifyExtRequest(request.getExt(), firstImpAccountId); | ||
return Result.withValue(createRequest(request, modifiedImps, source, modifiedExtRequest)); | ||
} | ||
|
||
private void validateRegs(Regs regs) { | ||
if (regs != null) { | ||
if (Objects.equals(regs.getCoppa(), 1)) { | ||
throw new PreBidException("COPPA is not supported"); | ||
} | ||
final ExtRegs extRegs = regs.getExt(); | ||
if (extRegs != null) { | ||
if (Objects.equals(extRegs.getGdpr(), 1)) { | ||
throw new PreBidException("GDPR is not supported"); | ||
} | ||
if (StringUtils.isNotEmpty(extRegs.getUsPrivacy())) { | ||
throw new PreBidException("CCPA is not supported"); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private List<Imp> modifyImps(List<Imp> imps) { | ||
final List<Imp> modifiedImps = new ArrayList<>(); | ||
for (Imp imp : imps) { | ||
final UnicornImpExt unicornImpExt = parseImpExt(imp); | ||
final ExtImpUnicorn extImpBidder = unicornImpExt.getBidder(); | ||
final Imp.ImpBuilder impBuilder = imp.toBuilder().secure(1); | ||
final String placementId = extImpBidder.getPlacementId(); | ||
|
||
if (StringUtils.isEmpty(placementId)) { | ||
final String resolvedPlacementId = getStoredRequestImpId(imp); | ||
final UnicornImpExt updatedExt = unicornImpExt.toBuilder() | ||
.bidder(extImpBidder.toBuilder().placementId(resolvedPlacementId).build()) | ||
.build(); | ||
impBuilder | ||
.tagid(resolvedPlacementId) | ||
.ext(mapper.mapper().convertValue(updatedExt, ObjectNode.class)); | ||
} else { | ||
impBuilder | ||
.tagid(placementId) | ||
.ext(mapper.mapper().convertValue(unicornImpExt, ObjectNode.class)); | ||
} | ||
|
||
modifiedImps.add(impBuilder.build()); | ||
|
||
} | ||
return modifiedImps; | ||
} | ||
|
||
private UnicornImpExt parseImpExt(Imp imp) { | ||
try { | ||
return mapper.mapper().convertValue(imp.getExt(), UnicornImpExt.class); | ||
} catch (IllegalArgumentException e) { | ||
throw new PreBidException(String.format( | ||
"Error while decoding ext of imp with id: %s, error: %s ", imp.getId(), e.getMessage())); | ||
} | ||
} | ||
|
||
private String getStoredRequestImpId(Imp imp) { | ||
final JsonNode extPrebid = imp.getExt().get("prebid"); | ||
final JsonNode storedRequestNode = isNotEmptyNode(extPrebid) ? extPrebid.get("storedrequest") : null; | ||
final JsonNode storedRequestIdNode = isNotEmptyNode(storedRequestNode) ? storedRequestNode.get("id") : null; | ||
final String storedRequestId = storedRequestIdNode != null && storedRequestIdNode.isTextual() | ||
? storedRequestIdNode.textValue() | ||
: null; | ||
if (StringUtils.isNotEmpty(storedRequestId)) { | ||
return storedRequestId; | ||
} else { | ||
throw new PreBidException(String.format("stored request id not found in imp: %s", imp.getId())); | ||
} | ||
} | ||
|
||
private static boolean isNotEmptyNode(JsonNode node) { | ||
return node != null && !node.isEmpty(); | ||
} | ||
|
||
private Source updateSource(Source source) { | ||
return source != null | ||
? source.toBuilder().ext(createExtSource()).build() | ||
: Source.builder().ext(createExtSource()).build(); | ||
} | ||
|
||
final ExtSource createExtSource() { | ||
final ExtSource extSource = ExtSource.of(null); | ||
extSource.addProperty("stype", new TextNode("prebid_server_uncn")); | ||
extSource.addProperty("bidder", new TextNode("unicorn")); | ||
|
||
return extSource; | ||
} | ||
|
||
private ExtImpUnicorn parseImpExtBidder(Imp imp) { | ||
try { | ||
return mapper.mapper().convertValue(imp.getExt(), UNICORN_EXT_TYPE_REFERENCE).getBidder(); | ||
} catch (IllegalArgumentException e) { | ||
throw new PreBidException(e.getMessage()); | ||
} | ||
} | ||
|
||
private ExtRequest modifyExtRequest(ExtRequest extRequest, Integer accountId) { | ||
final ExtRequest modifiedRequest = extRequest != null | ||
? ExtRequest.of(extRequest.getPrebid()) | ||
: ExtRequest.of(null); | ||
final int resolvedAccountId = accountId == null ? 0 : accountId; | ||
modifiedRequest.addProperty("accountId", new IntNode(resolvedAccountId)); | ||
|
||
return modifiedRequest; | ||
} | ||
|
||
private HttpRequest<BidRequest> createRequest(BidRequest request, | ||
List<Imp> imps, Source source, | ||
ExtRequest extRequest) { | ||
final BidRequest outgoingRequest = request.toBuilder() | ||
.imp(imps) | ||
.source(source) | ||
.ext(extRequest) | ||
.build(); | ||
|
||
return HttpRequest.<BidRequest>builder() | ||
.method(HttpMethod.POST) | ||
.uri(endpointUrl) | ||
.headers(resolveHeaders(request.getDevice())) | ||
.payload(outgoingRequest) | ||
.body(mapper.encode(outgoingRequest)) | ||
.build(); | ||
} | ||
|
||
private static MultiMap resolveHeaders(Device device) { | ||
final MultiMap headers = HttpUtil.headers(); | ||
HttpUtil.addHeaderIfValueIsNotEmpty(headers, 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 final Result<List<BidderBid>> makeBids(HttpCall<BidRequest> httpCall, BidRequest bidRequest) { | ||
try { | ||
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); | ||
return Result.of(extractBids(bidResponse), Collections.emptyList()); | ||
} catch (DecodeException | PreBidException e) { | ||
return Result.withError(BidderError.badServerResponse(e.getMessage())); | ||
} | ||
} | ||
|
||
private static List<BidderBid> extractBids(BidResponse bidResponse) { | ||
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { | ||
return Collections.emptyList(); | ||
} | ||
return bidsFromResponse(bidResponse); | ||
} | ||
|
||
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse) { | ||
return bidResponse.getSeatbid().stream() | ||
.filter(Objects::nonNull) | ||
.map(SeatBid::getBid) | ||
.filter(Objects::nonNull) | ||
.flatMap(Collection::stream) | ||
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur())) | ||
.collect(Collectors.toList()); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/org/prebid/server/bidder/unicorn/model/UnicornImpExt.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.prebid.server.bidder.unicorn.model; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Value; | ||
import org.prebid.server.proto.openrtb.ext.request.unicorn.ExtImpUnicorn; | ||
|
||
@AllArgsConstructor(staticName = "of") | ||
@Value | ||
@Builder(toBuilder = true) | ||
public class UnicornImpExt { | ||
|
||
UnicornImpExtContext context; | ||
|
||
ExtImpUnicorn bidder; | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/org/prebid/server/bidder/unicorn/model/UnicornImpExtContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.prebid.server.bidder.unicorn.model; | ||
|
||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Value; | ||
|
||
@AllArgsConstructor(staticName = "of") | ||
@Value | ||
public class UnicornImpExtContext { | ||
|
||
ObjectNode data; | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/org/prebid/server/proto/openrtb/ext/request/unicorn/ExtImpUnicorn.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package org.prebid.server.proto.openrtb.ext.request.unicorn; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Value; | ||
|
||
@AllArgsConstructor(staticName = "of") | ||
@Value | ||
@Builder(toBuilder = true) | ||
public class ExtImpUnicorn { | ||
|
||
@JsonProperty("placementId") | ||
String placementId; | ||
|
||
@JsonProperty("publisherId") | ||
Integer publisherId; | ||
|
||
@JsonProperty("mediaId") | ||
String mediaId; | ||
|
||
@JsonProperty("accountId") | ||
Integer accountId; | ||
} |
52 changes: 52 additions & 0 deletions
52
src/main/java/org/prebid/server/spring/config/bidder/UnicornConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package org.prebid.server.spring.config.bidder; | ||
|
||
import org.prebid.server.bidder.BidderDeps; | ||
import org.prebid.server.bidder.unicorn.UnicornBidder; | ||
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/unicorn.yaml", factory = YamlPropertySourceFactory.class) | ||
public class UnicornConfiguration { | ||
|
||
private static final String BIDDER_NAME = "unicorn"; | ||
|
||
@Value("${external-url}") | ||
@NotBlank | ||
private String externalUrl; | ||
|
||
@Autowired | ||
private JacksonMapper mapper; | ||
|
||
@Autowired | ||
@Qualifier("unicornConfigurationProperties") | ||
private BidderConfigurationProperties configProperties; | ||
|
||
@Bean("unicornConfigurationProperties") | ||
@ConfigurationProperties("adapters.unicorn") | ||
BidderConfigurationProperties configurationProperties() { | ||
return new BidderConfigurationProperties(); | ||
} | ||
|
||
@Bean | ||
BidderDeps unicornBidderDeps() { | ||
return BidderDepsAssembler.forBidder(BIDDER_NAME) | ||
.withConfig(configProperties) | ||
.usersyncerCreator(UsersyncerCreator.create(externalUrl)) | ||
.bidderCreator(config -> new UnicornBidder(config.getEndpoint(), mapper)) | ||
.assemble(); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
adapters: | ||
unicorn: | ||
enabled: false | ||
endpoint: https://ds.uncn.jp/pb/0/bid.json | ||
pbs-enforces-gdpr: true | ||
pbs-enforces-ccpa: true | ||
modifying-vast-xml-allowed: true | ||
deprecated-names: | ||
aliases: {} | ||
meta-info: | ||
maintainer-email: prebid@unicorn.inc | ||
app-media-types: | ||
- banner | ||
site-media-types: | ||
supported-vendors: | ||
vendor-id: 0 | ||
usersync: | ||
url: | ||
redirect-url: | ||
cookie-family-name: unicorn | ||
type: iframe | ||
support-cors: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-04/schema#", | ||
"title": "UNICORN Adapter Params", | ||
"description": "A schema which validates params accepted by the UNICORN adapter", | ||
"type": "object", | ||
"properties": { | ||
"placementId": { | ||
"type": "string", | ||
"description": "In Application, if placementId is empty, prebid server configuration id will be used as placementId." | ||
}, | ||
"publisherId": { | ||
"type": "integer", | ||
"description": "Account specific publisher id" | ||
}, | ||
"mediaId": { | ||
"type": "string", | ||
"description": "Publisher specific media id" | ||
}, | ||
"accountId": { | ||
"type": "integer", | ||
"description": "Account ID for charge request" | ||
} | ||
}, | ||
"required" : ["mediaId", "accountId"] | ||
} |
Oops, something went wrong.