Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/application-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ There are two ways to configure application settings: database and file. This do
- `auction.truncate-target-attr` - Maximum targeting attributes size. Values between 1 and 255.
- `auction.default-integration` - Default integration to assume.
- `auction.debug-allow` - enables debug output in the auction response. Default `true`.
- `auction.impression-limit` - a max number of impressions allowed for the auction, impressions that exceed this limit will be dropped, 0 means no limit.
- `auction.bid-validations.banner-creative-max-size` - Overrides creative max size validation for banners. Valid values
are:
- "skip": don't do anything about creative max size for this publisher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,10 @@ private Future<BidRequest> updateBidRequest(AuctionContext auctionContext) {
.map(bidRequest -> overrideParameters(bidRequest, httpRequest, auctionContext.getPrebidErrors()))
.map(bidRequest -> paramsResolver.resolve(bidRequest, auctionContext, ENDPOINT, true))
.map(bidRequest -> ortb2RequestFactory.removeEmptyEids(bidRequest, auctionContext.getDebugWarnings()))
.compose(resolvedBidRequest -> ortb2RequestFactory.limitImpressions(
account,
resolvedBidRequest,
auctionContext.getDebugWarnings()))
.compose(resolvedBidRequest -> ortb2RequestFactory.validateRequest(
account,
resolvedBidRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ private Future<BidRequest> updateAndValidateBidRequest(AuctionContext auctionCon

return storedRequestProcessor.processAuctionRequest(account.getId(), auctionContext.getBidRequest())
.compose(auctionStoredResult -> updateBidRequest(auctionStoredResult, auctionContext))
.compose(bidRequest -> ortb2RequestFactory.limitImpressions(account, bidRequest, debugWarnings))
.compose(bidRequest -> ortb2RequestFactory.validateRequest(
account, bidRequest, httpRequest, auctionContext.getDebugContext(), debugWarnings))
.map(interstitialProcessor::process);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Eid;
import com.iab.openrtb.request.Geo;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Publisher;
import com.iab.openrtb.request.Regs;
import com.iab.openrtb.request.Site;
Expand Down Expand Up @@ -192,6 +193,23 @@ public Future<ActivityInfrastructure> activityInfrastructureFrom(AuctionContext
auctionContext.getDebugContext().getTraceLevel()));
}

public Future<BidRequest> limitImpressions(Account account, BidRequest bidRequest, List<String> warnings) {
final List<Imp> imps = bidRequest.getImp();
final int impsLimit = Optional.ofNullable(account)
.map(Account::getAuction)
.map(AccountAuctionConfig::getImpressionLimit)
.orElse(0);

if (impsLimit > 0 && imps.size() > impsLimit) {
metrics.updateImpsDroppedMetric(imps.size() - impsLimit);
warnings.add(("Only first %d impressions were kept due to the limit, "
+ "all the subsequent impressions have been dropped for the auction").formatted(impsLimit));
return Future.succeededFuture(bidRequest.toBuilder().imp(imps.subList(0, impsLimit)).build());
}

return Future.succeededFuture(bidRequest);
}

public Future<BidRequest> validateRequest(Account account,
BidRequest bidRequest,
HttpRequestContext httpRequestContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ public Future<WithPodErrors<AuctionContext>> fromRequest(RoutingContext routingC

.map(auctionContext -> auctionContext.with(debugResolver.debugContextFrom(auctionContext)))

.compose(auctionContext -> ortb2RequestFactory.limitImpressions(
auctionContext.getAccount(),
auctionContext.getBidRequest(),
auctionContext.getDebugWarnings())
.map(auctionContext::with))

.compose(auctionContext -> ortb2RequestFactory.validateRequest(
auctionContext.getAccount(),
auctionContext.getBidRequest(),
Expand Down
22 changes: 20 additions & 2 deletions src/main/java/org/prebid/server/bidder/Usersyncer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import lombok.Value;
import org.prebid.server.spring.config.bidder.model.usersync.CookieFamilySource;

import java.util.List;

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

Expand All @@ -16,7 +18,23 @@ public class Usersyncer {

UsersyncMethod redirect;

public static Usersyncer of(String cookieFamilyName, UsersyncMethod iframe, UsersyncMethod redirect) {
return of(true, cookieFamilyName, CookieFamilySource.ROOT, iframe, redirect);
boolean skipWhenInGdprScope;

List<Integer> gppSidToSkip;

public static Usersyncer of(String cookieFamilyName,
UsersyncMethod iframe,
UsersyncMethod redirect,
boolean skipWhenInGdprScope,
List<Integer> gppSidToSkip) {

return of(
true,
cookieFamilyName,
CookieFamilySource.ROOT,
iframe,
redirect,
skipWhenInGdprScope,
gppSidToSkip);
}
}
180 changes: 180 additions & 0 deletions src/main/java/org/prebid/server/bidder/akcelo/AkceloBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package org.prebid.server.bidder.akcelo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Publisher;
import com.iab.openrtb.request.Site;
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.prebid.server.bidder.Bidder;
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.ExtPublisher;
import org.prebid.server.proto.openrtb.ext.request.ExtPublisherPrebid;
import org.prebid.server.proto.openrtb.ext.request.akcelo.ExtImpAkcelo;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid;
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;
import java.util.Optional;

public class AkceloBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpAkcelo>> AKCELO_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};
private static final String BIDDER_NAME = "akcelo";

private final String endpointUrl;
private final JacksonMapper mapper;

public AkceloBidder(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> imps = request.getImp();
final List<Imp> modifiedImps = new ArrayList<>();

final ExtImpAkcelo firstExtImp;
try {
firstExtImp = parseImpExt(imps.getFirst());
} catch (PreBidException e) {
return Result.withError(BidderError.badInput(e.getMessage()));
}

for (final Imp imp : imps) {
modifiedImps.add(modifyImp(imp));
}

final BidRequest outgoingRequest = modifyRequest(request, modifiedImps, firstExtImp.getSiteId());
return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper));
}

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

private Imp modifyImp(Imp imp) {
return imp.toBuilder()
.ext(mapper.mapper().createObjectNode().set(BIDDER_NAME, imp.getExt().get("bidder")))
.build();
}

private BidRequest modifyRequest(BidRequest request, List<Imp> imps, String siteId) {
return request.toBuilder()
.imp(imps)
.site(modifySite(request.getSite(), siteId))
.build();
}

private Site modifySite(Site site, String siteId) {
final Publisher publisher = Optional.ofNullable(site)
.map(Site::getPublisher)
.map(Publisher::toBuilder)
.orElseGet(Publisher::builder)
.ext(ExtPublisher.of(ExtPublisherPrebid.of(siteId)))
.build();

return Optional.ofNullable(site)
.map(Site::toBuilder)
.orElseGet(Site::builder)
.publisher(publisher)
.build();
}

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

private List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
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 -> makeBid(bid, bidResponse.getCur(), errors))
.filter(Objects::nonNull)
.toList();
}

private BidderBid makeBid(Bid bid, String currency, List<BidderError> errors) {
final BidType bidType = getBidType(bid, errors);
return bidType == null ? null : BidderBid.of(bid, bidType, currency);
}

private BidType getBidType(Bid bid, List<BidderError> errors) {
final Integer mType = bid.getMtype();
if (mType != null) {
return switch (mType) {
case 1 -> BidType.banner;
case 2 -> BidType.video;
case 4 -> BidType.xNative;
default -> {
errors.add(BidderError.badServerResponse("unable to get media type " + mType));
yield null;
}
};
}

return getExtBidPrebidType(bid, errors);
}

private BidType getExtBidPrebidType(Bid bid, List<BidderError> errors) {
return Optional.ofNullable(bid.getExt())
.map(ext -> ext.get("prebid"))
.filter(JsonNode::isObject)
.map(ObjectNode.class::cast)
.map(this::parseExtBidPrebid)
.map(ExtBidPrebid::getType)
.orElseGet(() -> {
errors.add(BidderError.badServerResponse("missing media type for bid " + bid.getId()));
return null;
});
}

private ExtBidPrebid parseExtBidPrebid(ObjectNode prebid) {
try {
return mapper.mapper().treeToValue(prebid, ExtBidPrebid.class);
} catch (JsonProcessingException e) {
return null;
}
}
}
Loading
Loading