Skip to content

Commit f88ce6f

Browse files
committed
Missena Adapter: Add formats and settings params
1 parent 2f1571f commit f88ce6f

10 files changed

Lines changed: 390 additions & 128 deletions

File tree

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,59 @@
11
package org.prebid.server.bidder.missena;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.iab.openrtb.request.Eid;
5+
import com.iab.openrtb.request.SupplyChain;
36
import lombok.Builder;
47
import lombok.Value;
58

6-
@Builder(toBuilder = true)
9+
import java.math.BigDecimal;
10+
import java.util.List;
11+
712
@Value
13+
@Builder(toBuilder = true)
814
public class MissenaAdRequest {
915

10-
String requestId;
16+
@JsonProperty("adunit")
17+
String adUnit;
18+
19+
@JsonProperty("buyeruid")
20+
String buyerUid;
21+
22+
Integer coppa;
23+
24+
String currency;
25+
26+
@JsonProperty("userEids")
27+
List<Eid> userEids;
1128

12-
int timeout;
29+
BigDecimal floor;
30+
31+
String floorCurrency;
32+
33+
@JsonProperty("consent_required")
34+
Boolean gdpr;
35+
36+
@JsonProperty("consent_string")
37+
String gdprConsent;
38+
39+
@JsonProperty("ik")
40+
String idempotencyKey;
1341

1442
String referer;
1543

1644
String refererCanonical;
1745

18-
String consentString;
46+
String requestId;
47+
48+
SupplyChain schain;
49+
50+
Long timeout;
51+
52+
String url;
1953

20-
boolean consentRequired;
54+
MissenaUserParams params;
2155

22-
String placement;
56+
String usPrivacy;
2357

24-
String test;
58+
String version;
2559
}

src/main/java/org/prebid/server/bidder/missena/MissenaBidder.java

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.iab.openrtb.request.Imp;
77
import com.iab.openrtb.request.Regs;
88
import com.iab.openrtb.request.Site;
9+
import com.iab.openrtb.request.Source;
910
import com.iab.openrtb.request.User;
1011
import com.iab.openrtb.response.Bid;
1112
import io.vertx.core.MultiMap;
@@ -16,7 +17,9 @@
1617
import org.prebid.server.bidder.model.BidderCall;
1718
import org.prebid.server.bidder.model.BidderError;
1819
import org.prebid.server.bidder.model.HttpRequest;
20+
import org.prebid.server.bidder.model.Price;
1921
import org.prebid.server.bidder.model.Result;
22+
import org.prebid.server.currency.CurrencyConversionService;
2023
import org.prebid.server.exception.PreBidException;
2124
import org.prebid.server.json.DecodeException;
2225
import org.prebid.server.json.JacksonMapper;
@@ -25,8 +28,13 @@
2528
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
2629
import org.prebid.server.proto.openrtb.ext.request.missena.ExtImpMissena;
2730
import org.prebid.server.proto.openrtb.ext.response.BidType;
31+
import org.prebid.server.util.BidderUtil;
2832
import org.prebid.server.util.HttpUtil;
33+
import org.prebid.server.version.PrebidVersionProvider;
2934

35+
import java.math.BigDecimal;
36+
import java.net.MalformedURLException;
37+
import java.net.URL;
3038
import java.util.ArrayList;
3139
import java.util.Collections;
3240
import java.util.List;
@@ -37,31 +45,40 @@ public class MissenaBidder implements Bidder<MissenaAdRequest> {
3745

3846
private static final TypeReference<ExtPrebid<?, ExtImpMissena>> TYPE_REFERENCE = new TypeReference<>() {
3947
};
40-
private static final int AD_REQUEST_DEFAULT_TIMEOUT = 2000;
48+
private static final String USD_CURRENCY = "USD";
49+
private static final String EUR_CURRENCY = "EUR";
50+
private static final String PUBLISHER_ID_MACRO = "{{PublisherID}}";
4151

4252
private final String endpointUrl;
4353
private final JacksonMapper mapper;
54+
private final CurrencyConversionService currencyConversionService;
55+
private final PrebidVersionProvider prebidVersionProvider;
56+
57+
public MissenaBidder(String endpointUrl,
58+
JacksonMapper mapper,
59+
CurrencyConversionService currencyConversionService,
60+
PrebidVersionProvider prebidVersionProvider) {
4461

45-
public MissenaBidder(String endpointUrl, JacksonMapper mapper) {
4662
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
4763
this.mapper = Objects.requireNonNull(mapper);
64+
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
65+
this.prebidVersionProvider = Objects.requireNonNull(prebidVersionProvider);
4866
}
4967

5068
@Override
5169
public Result<List<HttpRequest<MissenaAdRequest>>> makeHttpRequests(BidRequest request) {
52-
final List<HttpRequest<MissenaAdRequest>> requests = new ArrayList<>();
5370
final List<BidderError> errors = new ArrayList<>();
5471

5572
for (Imp imp : request.getImp()) {
5673
try {
5774
final ExtImpMissena extImp = parseImpExt(imp);
58-
requests.add(makeHttpRequest(request, imp.getId(), extImp));
75+
final HttpRequest<MissenaAdRequest> httpRequest = makeHttpRequest(request, imp, extImp);
76+
return Result.of(Collections.singletonList(httpRequest), errors);
5977
} catch (PreBidException e) {
6078
errors.add(BidderError.badInput(e.getMessage()));
6179
}
6280
}
63-
64-
return Result.of(requests, errors);
81+
return Result.withErrors(errors);
6582
}
6683

6784
private ExtImpMissena parseImpExt(Imp imp) {
@@ -72,30 +89,95 @@ private ExtImpMissena parseImpExt(Imp imp) {
7289
}
7390
}
7491

75-
private HttpRequest<MissenaAdRequest> makeHttpRequest(BidRequest request, String impId, ExtImpMissena extImp) {
92+
private HttpRequest<MissenaAdRequest> makeHttpRequest(BidRequest request, Imp imp, ExtImpMissena extImp) {
7693
final Site site = request.getSite();
94+
final String pageUrl = site != null ? site.getPage() : null;
95+
final User user = request.getUser();
96+
final Regs regs = request.getRegs();
97+
final Device device = request.getDevice();
98+
final Source source = request.getSource();
99+
100+
final String requestCurrency = resolveCurrency(request.getCur());
101+
final Price floorInfo = resolveBidFloor(imp, request, requestCurrency);
102+
103+
final MissenaUserParams userParams = MissenaUserParams.builder()
104+
.formats(extImp.getFormats())
105+
.placement(extImp.getPlacement())
106+
.testMode(extImp.getTestMode())
107+
.settings(extImp.getSettings())
108+
.build();
77109

78110
final MissenaAdRequest missenaAdRequest = MissenaAdRequest.builder()
111+
.adUnit(imp.getId())
112+
.buyerUid(user != null ? user.getBuyeruid() : null)
113+
.coppa(regs != null ? regs.getCoppa() : null)
114+
.currency(requestCurrency)
115+
.userEids(user != null ? user.getEids() : null)
116+
.floor(floorInfo.getValue())
117+
.floorCurrency(floorInfo.getCurrency())
118+
.gdpr(isGdpr(regs))
119+
.gdprConsent(getUserConsent(user))
120+
.idempotencyKey(request.getId())
121+
.referer(pageUrl)
122+
.refererCanonical(site != null ? site.getDomain() : null)
79123
.requestId(request.getId())
80-
.timeout(AD_REQUEST_DEFAULT_TIMEOUT)
81-
.referer(site == null ? null : site.getPage())
82-
.refererCanonical(site == null ? null : site.getDomain())
83-
.consentString(getUserConsent(request.getUser()))
84-
.consentRequired(isGdpr(request.getRegs()))
85-
.placement(extImp.getPlacement())
86-
.test(extImp.getTestMode())
124+
.schain(source != null ? source.getSchain() : null)
125+
.timeout(request.getTmax())
126+
.params(userParams)
127+
.version(prebidVersionProvider.getNameVersionRecord())
87128
.build();
88129

89130
return HttpRequest.<MissenaAdRequest>builder()
90131
.method(HttpMethod.POST)
91-
.uri(makeUrl(extImp.getApiKey()))
92-
.headers(makeHeaders(request.getDevice(), site))
93-
.impIds(Collections.singleton(impId))
132+
.uri(resolveEndpointUrl(extImp.getApiKey()))
133+
.headers(makeHeaders(device, site))
134+
.impIds(Collections.singleton(imp.getId()))
94135
.body(mapper.encodeToBytes(missenaAdRequest))
95136
.payload(missenaAdRequest)
96137
.build();
97138
}
98139

140+
private Price resolveBidFloor(Imp imp, BidRequest bidRequest, String targetCurrency) {
141+
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
142+
return BidderUtil.isValidPrice(initialBidFloorPrice)
143+
? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest, targetCurrency)
144+
: initialBidFloorPrice;
145+
}
146+
147+
private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest, String targetCurrency) {
148+
final String bidFloorCur = bidFloorPrice.getCurrency();
149+
150+
if (targetCurrency.equalsIgnoreCase(bidFloorCur)) {
151+
return bidFloorPrice;
152+
}
153+
154+
try {
155+
final BigDecimal convertedPrice = currencyConversionService
156+
.convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, targetCurrency);
157+
158+
return Price.of(targetCurrency, convertedPrice);
159+
} catch (PreBidException e) {
160+
throw new PreBidException("Unable to convert provided bid floor currency from %s to %s for imp `%s`"
161+
.formatted(bidFloorCur, targetCurrency, impId));
162+
}
163+
}
164+
165+
private String resolveCurrency(List<String> requestCurrencies) {
166+
for (String currency : requestCurrencies) {
167+
if (USD_CURRENCY.equalsIgnoreCase(currency)) {
168+
return USD_CURRENCY;
169+
}
170+
}
171+
172+
for (String currency : requestCurrencies) {
173+
if (EUR_CURRENCY.equalsIgnoreCase(currency)) {
174+
return EUR_CURRENCY;
175+
}
176+
}
177+
178+
return USD_CURRENCY;
179+
}
180+
99181
private MultiMap makeHeaders(Device device, Site site) {
100182
final MultiMap headers = HttpUtil.headers();
101183

@@ -105,15 +187,21 @@ private MultiMap makeHeaders(Device device, Site site) {
105187
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
106188
}
107189

108-
if (site != null) {
190+
if (site != null && StringUtils.isNotBlank(site.getPage())) {
109191
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage());
192+
try {
193+
final URL url = new URL(site.getPage());
194+
final String origin = url.getProtocol() + "://" + url.getHost();
195+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ORIGIN_HEADER, origin);
196+
} catch (MalformedURLException e) {
197+
// do nothing
198+
}
110199
}
111-
112200
return headers;
113201
}
114202

115-
private String makeUrl(String apiKey) {
116-
return endpointUrl + "?t=%s".formatted(apiKey);
203+
private String resolveEndpointUrl(String apiKey) {
204+
return endpointUrl.replace(PUBLISHER_ID_MACRO, HttpUtil.encodeUrl(apiKey));
117205
}
118206

119207
private static boolean isGdpr(Regs regs) {
@@ -128,7 +216,8 @@ private static String getUserConsent(User user) {
128216
return Optional.ofNullable(user)
129217
.map(User::getExt)
130218
.map(ExtUser::getConsent)
131-
.orElse(StringUtils.EMPTY);
219+
.filter(StringUtils::isNotBlank)
220+
.orElse(null);
132221
}
133222

134223
@Override
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.prebid.server.bidder.missena;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.databind.node.ObjectNode; // Changed import
5+
import lombok.Builder;
6+
import lombok.Value;
7+
8+
import java.util.List;
9+
10+
@Builder
11+
@Value
12+
public class MissenaUserParams {
13+
14+
List<String> formats;
15+
16+
String placement;
17+
18+
@JsonProperty("test")
19+
String testMode;
20+
21+
ObjectNode settings;
22+
23+
}
24+
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
package org.prebid.server.proto.openrtb.ext.request.missena;
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
5+
import lombok.Builder;
46
import lombok.Value;
57

6-
@Value(staticConstructor = "of")
8+
import java.util.List;
9+
10+
@Value
11+
@Builder(toBuilder = true)
712
public class ExtImpMissena {
813

914
@JsonProperty("apiKey")
1015
String apiKey;
1116

17+
List<String> formats;
18+
1219
String placement;
1320

1421
@JsonProperty("test")
1522
String testMode;
23+
24+
ObjectNode settings;
1625
}

src/main/java/org/prebid/server/spring/config/bidder/MissenaConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import org.prebid.server.bidder.BidderDeps;
44
import org.prebid.server.bidder.missena.MissenaBidder;
5+
import org.prebid.server.currency.CurrencyConversionService;
56
import org.prebid.server.json.JacksonMapper;
67
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
78
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
89
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
910
import org.prebid.server.spring.env.YamlPropertySourceFactory;
11+
import org.prebid.server.version.PrebidVersionProvider;
1012
import org.springframework.beans.factory.annotation.Value;
1113
import org.springframework.boot.context.properties.ConfigurationProperties;
1214
import org.springframework.context.annotation.Bean;
@@ -30,12 +32,15 @@ BidderConfigurationProperties configurationProperties() {
3032
@Bean
3133
BidderDeps missenaBidderDeps(BidderConfigurationProperties missenaConfigurationProperties,
3234
@NotBlank @Value("${external-url}") String externalUrl,
35+
CurrencyConversionService currencyConversionService,
36+
PrebidVersionProvider prebidVersionProvider,
3337
JacksonMapper mapper) {
3438

3539
return BidderDepsAssembler.forBidder(BIDDER_NAME)
3640
.withConfig(missenaConfigurationProperties)
3741
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38-
.bidderCreator(config -> new MissenaBidder(config.getEndpoint(), mapper))
42+
.bidderCreator(config -> new MissenaBidder(
43+
config.getEndpoint(), mapper, currencyConversionService, prebidVersionProvider))
3944
.assemble();
4045
}
4146
}

src/main/resources/bidder-config/missena.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
adapters:
22
missena:
3-
endpoint: https://bid.missena.io/
3+
endpoint: https://bid.missena.io/?t={{PublisherID}}
44
meta-info:
55
maintainer-email: prebid@missena.com
66
modifying-vast-xml-allowed: true

src/main/resources/static/bidder-params/missena.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
"test": {
1717
"type": "string",
1818
"description": "Test Mode"
19+
},
20+
"formats": {
21+
"type": "array",
22+
"description": "An array of formats to request (banner, native, or video)",
23+
"items": {
24+
"type": "string"
25+
}
26+
},
27+
"settings": {
28+
"type": "object",
29+
"description": "An object containing extra settings for the Missena adapter"
1930
}
2031
},
2132
"required": [

0 commit comments

Comments
 (0)