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
Original file line number Diff line number Diff line change
@@ -1,25 +1,59 @@
package org.prebid.server.bidder.missena;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.iab.openrtb.request.Eid;
import com.iab.openrtb.request.SupplyChain;
import lombok.Builder;
import lombok.Value;

@Builder(toBuilder = true)
import java.math.BigDecimal;
import java.util.List;

@Value
@Builder(toBuilder = true)
public class MissenaAdRequest {

String requestId;
@JsonProperty("adunit")
String adUnit;

@JsonProperty("buyeruid")
String buyerUid;

Integer coppa;

String currency;

@JsonProperty("userEids")
List<Eid> userEids;

int timeout;
BigDecimal floor;

String floorCurrency;

@JsonProperty("consent_required")
Boolean gdpr;

@JsonProperty("consent_string")
String gdprConsent;

@JsonProperty("ik")
String idempotencyKey;

String referer;

String refererCanonical;

String consentString;
String requestId;

SupplyChain schain;

Long timeout;

String url;

boolean consentRequired;
MissenaUserParams params;

String placement;
String usPrivacy;

String test;
String version;
}
128 changes: 106 additions & 22 deletions src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Regs;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.Source;
import com.iab.openrtb.request.User;
import com.iab.openrtb.response.Bid;
import io.vertx.core.MultiMap;
Expand All @@ -16,7 +17,9 @@
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;
Expand All @@ -25,8 +28,13 @@
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
import org.prebid.server.proto.openrtb.ext.request.missena.ExtImpMissena;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.BidderUtil;
import org.prebid.server.util.HttpUtil;
import org.prebid.server.version.PrebidVersionProvider;

import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand All @@ -37,31 +45,40 @@ public class MissenaBidder implements Bidder<MissenaAdRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpMissena>> TYPE_REFERENCE = new TypeReference<>() {
};
private static final int AD_REQUEST_DEFAULT_TIMEOUT = 2000;
private static final String USD_CURRENCY = "USD";
private static final String EUR_CURRENCY = "EUR";
private static final String PUBLISHER_ID_MACRO = "{{PublisherID}}";

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

public MissenaBidder(String endpointUrl,
JacksonMapper mapper,
CurrencyConversionService currencyConversionService,
PrebidVersionProvider prebidVersionProvider) {

public MissenaBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
this.mapper = Objects.requireNonNull(mapper);
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider);
}

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

for (Imp imp : request.getImp()) {
try {
final ExtImpMissena extImp = parseImpExt(imp);
requests.add(makeHttpRequest(request, imp.getId(), extImp));
final HttpRequest<MissenaAdRequest> httpRequest = makeHttpRequest(request, imp, extImp);
return Result.of(Collections.singletonList(httpRequest), errors);
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}

return Result.of(requests, errors);
return Result.withErrors(errors);
}

private ExtImpMissena parseImpExt(Imp imp) {
Expand All @@ -72,30 +89,90 @@ private ExtImpMissena parseImpExt(Imp imp) {
}
}

private HttpRequest<MissenaAdRequest> makeHttpRequest(BidRequest request, String impId, ExtImpMissena extImp) {
private HttpRequest<MissenaAdRequest> makeHttpRequest(BidRequest request, Imp imp, ExtImpMissena extImp) {
final Site site = request.getSite();
final User user = request.getUser();
final Regs regs = request.getRegs();
final Device device = request.getDevice();
final Source source = request.getSource();

final String requestCurrency = resolveCurrency(request.getCur());
final Price floorInfo = resolveBidFloor(imp, request, requestCurrency);

final MissenaUserParams userParams = MissenaUserParams.builder()
.formats(extImp.getFormats())
.placement(extImp.getPlacement())
.testMode(extImp.getTestMode())
.settings(extImp.getSettings())
.build();

final MissenaAdRequest missenaAdRequest = MissenaAdRequest.builder()
.adUnit(imp.getId())
.buyerUid(user != null ? user.getBuyeruid() : null)
.coppa(regs != null ? regs.getCoppa() : null)
.currency(requestCurrency)
.userEids(user != null ? user.getEids() : null)
.floor(floorInfo.getValue())
.floorCurrency(floorInfo.getCurrency())
.gdpr(isGdpr(regs))
.gdprConsent(getUserConsent(user))
.idempotencyKey(request.getId())
.referer(site != null ? site.getPage() : null)
.refererCanonical(site != null ? site.getDomain() : null)
.requestId(request.getId())
.timeout(AD_REQUEST_DEFAULT_TIMEOUT)
.referer(site == null ? null : site.getPage())
.refererCanonical(site == null ? null : site.getDomain())
.consentString(getUserConsent(request.getUser()))
.consentRequired(isGdpr(request.getRegs()))
.placement(extImp.getPlacement())
.test(extImp.getTestMode())
.schain(source != null ? source.getSchain() : null)
.timeout(request.getTmax())
.params(userParams)
.version(prebidVersionProvider.getNameVersionRecord())
.build();

return HttpRequest.<MissenaAdRequest>builder()
.method(HttpMethod.POST)
.uri(makeUrl(extImp.getApiKey()))
.headers(makeHeaders(request.getDevice(), site))
.impIds(Collections.singleton(impId))
.uri(resolveEndpointUrl(extImp.getApiKey()))
.headers(makeHeaders(device, site))
.impIds(Collections.singleton(imp.getId()))
.body(mapper.encodeToBytes(missenaAdRequest))
.payload(missenaAdRequest)
.build();
}

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

private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest, String targetCurrency) {
final String bidFloorCur = bidFloorPrice.getCurrency();

try {
final BigDecimal convertedPrice = currencyConversionService
.convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, targetCurrency);

return Price.of(targetCurrency, convertedPrice);
} catch (PreBidException e) {
throw new PreBidException("Unable to convert provided bid floor currency from %s to %s for imp `%s`"
.formatted(bidFloorCur, targetCurrency, impId));
}
}

private String resolveCurrency(List<String> requestCurrencies) {
String currency = USD_CURRENCY;

for (String requestCurrency : requestCurrencies) {
if (USD_CURRENCY.equalsIgnoreCase(requestCurrency)) {
return USD_CURRENCY;
}

if (EUR_CURRENCY.equalsIgnoreCase(requestCurrency)) {
currency = EUR_CURRENCY;
}
}

return currency;
}

private MultiMap makeHeaders(Device device, Site site) {
final MultiMap headers = HttpUtil.headers();

Expand All @@ -105,15 +182,21 @@ private MultiMap makeHeaders(Device device, Site site) {
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
}

if (site != null) {
if (site != null && StringUtils.isNotBlank(site.getPage())) {
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage());
try {
final URL url = new URL(site.getPage());
final String origin = url.getProtocol() + "://" + url.getHost();
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ORIGIN_HEADER, origin);
} catch (MalformedURLException e) {
// do nothing
}
}

return headers;
}

private String makeUrl(String apiKey) {
return endpointUrl + "?t=%s".formatted(apiKey);
private String resolveEndpointUrl(String apiKey) {
return endpointUrl.replace(PUBLISHER_ID_MACRO, HttpUtil.encodeUrl(apiKey));
}

private static boolean isGdpr(Regs regs) {
Expand All @@ -128,7 +211,8 @@ private static String getUserConsent(User user) {
return Optional.ofNullable(user)
.map(User::getExt)
.map(ExtUser::getConsent)
.orElse(StringUtils.EMPTY);
.filter(StringUtils::isNotBlank)
.orElse(null);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.prebid.server.bidder.missena;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode; // Changed import
import lombok.Builder;
import lombok.Value;

import java.util.List;

@Builder
@Value
public class MissenaUserParams {

List<String> formats;

String placement;

@JsonProperty("test")
String testMode;

ObjectNode settings;
}

Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package org.prebid.server.proto.openrtb.ext.request.missena;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Builder;
import lombok.Value;

@Value(staticConstructor = "of")
import java.util.List;

@Value
@Builder(toBuilder = true)
public class ExtImpMissena {

@JsonProperty("apiKey")
String apiKey;

List<String> formats;

String placement;

@JsonProperty("test")
String testMode;

ObjectNode settings;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.missena.MissenaBidder;
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.prebid.server.version.PrebidVersionProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
Expand All @@ -30,12 +32,15 @@ BidderConfigurationProperties configurationProperties() {
@Bean
BidderDeps missenaBidderDeps(BidderConfigurationProperties missenaConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
CurrencyConversionService currencyConversionService,
PrebidVersionProvider prebidVersionProvider,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(missenaConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new MissenaBidder(config.getEndpoint(), mapper))
.bidderCreator(config -> new MissenaBidder(
config.getEndpoint(), mapper, currencyConversionService, prebidVersionProvider))
.assemble();
}
}
2 changes: 1 addition & 1 deletion src/main/resources/bidder-config/missena.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
adapters:
missena:
endpoint: https://bid.missena.io/
endpoint: https://bid.missena.io/?t={{PublisherID}}
meta-info:
maintainer-email: [email protected]
modifying-vast-xml-allowed: true
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/static/bidder-params/missena.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@
"test": {
"type": "string",
"description": "Test Mode"
},
"formats": {
"type": "array",
"description": "An array of formats to request (banner, native, or video)",
"items": {
"type": "string"
}
},
"settings": {
"type": "object",
"description": "An object containing extra settings for the Missena adapter"
}
},
"required": [
Expand Down
Loading
Loading