Skip to content

Commit

Permalink
Copper6Ssp: New adapter (#3428)
Browse files Browse the repository at this point in the history
  • Loading branch information
Compile-Ninja committed Sep 9, 2024
1 parent 6454683 commit 411bd33
Show file tree
Hide file tree
Showing 13 changed files with 781 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.prebid.server.bidder.copper6ssp;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
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 org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.copper6ssp.proto.Copper6SspImpExtBidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderError;
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.copper6ssp.ImpExtCopper6Ssp;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
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;

public class Copper6SspBidder implements Bidder<BidRequest> {

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

private final String endpointUrl;
private final JacksonMapper mapper;

public Copper6SspBidder(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>> outgoingRequests = new ArrayList<>();
final List<BidderError> errors = new ArrayList<>();

for (Imp imp : request.getImp()) {
final ImpExtCopper6Ssp extImp;
try {
extImp = parseImpExt(imp);
outgoingRequests.add(makeRequest(modifyImp(imp, extImp), request));
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}

return CollectionUtils.isEmpty(outgoingRequests)
? Result.withErrors(errors)
: Result.of(outgoingRequests, errors);
}

private ImpExtCopper6Ssp parseImpExt(Imp imp) {
try {
return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
throw new PreBidException(e.getMessage());
}
}

private Imp modifyImp(Imp imp, ImpExtCopper6Ssp extImp) {
final Copper6SspImpExtBidder impExtBidder = getImpExtWithType(extImp);
final ObjectNode modifiedImpExtBidder = mapper.mapper().createObjectNode();

modifiedImpExtBidder.set("bidder", mapper.mapper().valueToTree(impExtBidder));

return imp.toBuilder().ext(modifiedImpExtBidder).build();
}

private Copper6SspImpExtBidder getImpExtWithType(ImpExtCopper6Ssp impExtCopper6Ssp) {
final boolean hasPlacementId = StringUtils.isNotBlank(impExtCopper6Ssp.getPlacementId());
final boolean hasEndpointId = StringUtils.isNotBlank(impExtCopper6Ssp.getEndpointId());

return Copper6SspImpExtBidder.builder()
.type(hasPlacementId ? "publisher" : hasEndpointId ? "network" : null)
.placementId(hasPlacementId ? impExtCopper6Ssp.getPlacementId() : null)
.endpointId(hasEndpointId ? impExtCopper6Ssp.getEndpointId() : null)
.build();
}

private HttpRequest<BidRequest> makeRequest(Imp imp, BidRequest request) {
final BidRequest outgoingRequest = request.toBuilder().imp(Collections.singletonList(imp)).build();
return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper);
}

@Override
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.withValues(extractBids(bidResponse));
} 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 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), bidResponse.getCur()))
.toList();
}

private static BidType getBidType(Bid bid) {
final Integer markupType = bid.getMtype();
if (markupType == null) {
throw new PreBidException("Missing MType for bid: " + bid.getId());
}

return switch (markupType) {
case 1 -> BidType.banner;
case 2 -> BidType.video;
case 4 -> BidType.xNative;
default -> throw new PreBidException("Unable to fetch mediaType in multi-format: %s"
.formatted(bid.getImpid()));
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.prebid.server.bidder.copper6ssp.proto;

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

@Builder
@Value
public class Copper6SspImpExtBidder {

String type;

@JsonProperty("placementId")
String placementId;

@JsonProperty("endpointId")
String endpointId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.prebid.server.proto.openrtb.ext.request.copper6ssp;

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

@Value(staticConstructor = "of")
public class ImpExtCopper6Ssp {

@JsonProperty("placementId")
String placementId;

@JsonProperty("endpointId")
String endpointId;
}

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.copper6ssp.Copper6SspBidder;
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 jakarta.validation.constraints.NotBlank;

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

private static final String BIDDER_NAME = "copper6ssp";

@Bean("copper6sspConfigurationProperties")
@ConfigurationProperties("adapters.copper6ssp")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

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

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(copper6sspConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new Copper6SspBidder(config.getEndpoint(), mapper))
.assemble();
}
}
25 changes: 25 additions & 0 deletions src/main/resources/bidder-config/copper6ssp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
adapters:
copper6ssp:
endpoint: https://endpoint.copper6.com/
meta-info:
maintainer-email: info@copper6.com
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors:
vendor-id: 0
usersync:
cookie-family-name: copper6ssp
redirect:
support-cors: false
url: https://csync.copper6.com/pbserver?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&ccpa={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
uid-macro: '[UID]'
iframe:
support-cors: false
url: https://csync.copper6.com/pbserverIframe?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&ccpa={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&pbserverUrl={{redirect_url}}
uid-macro: '[UID]'
30 changes: 30 additions & 0 deletions src/main/resources/static/bidder-params/copper6ssp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Copper6SSPs Adapter Params",
"description": "A schema which validates params accepted by the Copper6SSP adapter",
"type": "object",
"properties": {
"placementId": {
"type": "string",
"minLength": 1,
"description": "Placement ID"
},
"endpointId": {
"type": "string",
"minLength": 1,
"description": "Endpoint ID"
}
},
"oneOf": [
{
"required": [
"placementId"
]
},
{
"required": [
"endpointId"
]
}
]
}
Loading

0 comments on commit 411bd33

Please sign in to comment.