Skip to content

Commit

Permalink
Kobler: New adapter (#3667)
Browse files Browse the repository at this point in the history
  • Loading branch information
przemkaczmarek committed Jan 17, 2025
1 parent b96c137 commit 8ea1419
Show file tree
Hide file tree
Showing 13 changed files with 531 additions and 4 deletions.
154 changes: 154 additions & 0 deletions src/main/java/org/prebid/server/bidder/kobler/KoblerBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package org.prebid.server.bidder.kobler;

import com.fasterxml.jackson.core.type.TypeReference;
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.ObjectUtils;
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.Price;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.currency.CurrencyConversionService;
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.kobler.ExtImpKobler;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class KoblerBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpKobler>> KOBLER_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};
private static final String DEFAULT_BID_CURRENCY = "USD";

private final String endpointUrl;
private final CurrencyConversionService currencyConversionService;
private final JacksonMapper mapper;

public KoblerBidder(String endpointUrl,
CurrencyConversionService currencyConversionService,
JacksonMapper mapper) {

this.endpointUrl = HttpUtil.validateUrl(endpointUrl);
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
this.mapper = Objects.requireNonNull(mapper);
}


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

for (Imp imp : bidRequest.getImp()) {
try {
final ExtImpKobler impExt = parseImpExt(imp);
final Imp modifiedImp = modifyImp(imp, impExt, bidRequest);
requests.add(makeHttpRequest(bidRequest, modifiedImp));
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}

return Result.of(requests, errors);
}

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

private Imp modifyImp(Imp imp, ExtImpKobler extImpKobler, BidRequest bidRequest) {
final Price resolvedBidFloor = resolveBidFloor(imp, bidRequest);

return imp.toBuilder()
.bidfloor(resolvedBidFloor.getValue())
.bidfloorcur(resolvedBidFloor.getCurrency())
.ext(mapper.mapper().valueToTree(extImpKobler))
.build();
}

private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, DEFAULT_BID_CURRENCY)
? convertBidFloor(initialBidFloorPrice, bidRequest)
: initialBidFloorPrice;
}

private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) {
final BigDecimal convertedPrice = currencyConversionService.convertCurrency(
bidFloorPrice.getValue(),
bidRequest,
bidFloorPrice.getCurrency(),
DEFAULT_BID_CURRENCY);

return Price.of(DEFAULT_BID_CURRENCY, convertedPrice);
}

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

@Override
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final List<BidderError> errors = new ArrayList<>();
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
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 bidsFromResponse(bidResponse, errors);
}

private List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.map(bid -> BidderBid.of(bid, getBidType(bid), bidResponse.getCur()))
.filter(Objects::nonNull)
.toList();
}

private BidType getBidType(Bid bid) {
final Integer markupType = ObjectUtils.defaultIfNull(bid.getMtype(), 0);

return switch (markupType) {
case 1 -> BidType.banner;
default -> throw new PreBidException(
"could not define media type for impression: " + bid.getImpid());
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.prebid.server.proto.openrtb.ext.request.kobler;

import lombok.Value;

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

Boolean test;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.prebid.server.spring.config.bidder;

import jakarta.validation.constraints.NotBlank;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.kobler.KoblerBidder;
import org.prebid.server.currency.CurrencyConversionService;
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;

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

private static final String BIDDER_NAME = "kobler";

@Bean("koblerConfigurationProperties")
@ConfigurationProperties("adapters.kobler")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

BidderDeps koblerBidderDeps(BidderConfigurationProperties koblerConfigurationProperies,
CurrencyConversionService currencyConversionService,
@NotBlank @Value("#{external-url}") String externalUrl,
JacksonMapper mapper) {
return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(koblerConfigurationProperies)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new KoblerBidder(config.getEndpoint(),currencyConversionService, mapper))
.assemble();
}
}
14 changes: 14 additions & 0 deletions src/main/resources/bidder-config/kobler.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
adapters:
kobler:
endpoint: "https://bid.essrtb.com/bid/prebid_server_rtb_call"
endpointCompression: gzip
maintainer:
email: bidding-support@kobler.no
geoscope:
- NOR
- SWE
- DNK
capabilities:
site:
mediaTypes:
- banner
13 changes: 13 additions & 0 deletions src/main/resources/static/bidder-params/kobler.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Kobler Adapter Params",
"description": "A schema which validates params accepted by the Kobler adapter",
"type": "object",

"properties": {
"test": {
"type": "boolean",
"description": "Whether the request is for testing only. When multiple ad units are submitted together, it is enough to set this parameter on the first one."
}
}
}
Loading

0 comments on commit 8ea1419

Please sign in to comment.