Skip to content

Commit f5fcad1

Browse files
zxPhoenixRitesh Ghodrao
authored andcommitted
Ogury: port new adapter from Go (prebid#3788)
1 parent 5056a17 commit f5fcad1

11 files changed

Lines changed: 1063 additions & 0 deletions

File tree

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package org.prebid.server.bidder.ogury;
2+
3+
import com.fasterxml.jackson.databind.node.ObjectNode;
4+
import com.iab.openrtb.request.BidRequest;
5+
import com.iab.openrtb.request.Device;
6+
import com.iab.openrtb.request.Imp;
7+
import com.iab.openrtb.request.Publisher;
8+
import com.iab.openrtb.request.Site;
9+
import com.iab.openrtb.response.Bid;
10+
import com.iab.openrtb.response.BidResponse;
11+
import com.iab.openrtb.response.SeatBid;
12+
import io.vertx.core.MultiMap;
13+
import org.apache.commons.collections4.CollectionUtils;
14+
import org.prebid.server.bidder.Bidder;
15+
import org.prebid.server.bidder.model.BidderBid;
16+
import org.prebid.server.bidder.model.BidderCall;
17+
import org.prebid.server.bidder.model.BidderError;
18+
import org.prebid.server.bidder.model.HttpRequest;
19+
import org.prebid.server.bidder.model.Price;
20+
import org.prebid.server.bidder.model.Result;
21+
import org.prebid.server.currency.CurrencyConversionService;
22+
import org.prebid.server.exception.PreBidException;
23+
import org.prebid.server.json.JacksonMapper;
24+
import org.prebid.server.proto.openrtb.ext.response.BidType;
25+
import org.prebid.server.util.BidderUtil;
26+
import org.prebid.server.util.HttpUtil;
27+
28+
import java.math.BigDecimal;
29+
import java.util.ArrayList;
30+
import java.util.Collection;
31+
import java.util.Collections;
32+
import java.util.List;
33+
import java.util.Objects;
34+
import java.util.Optional;
35+
36+
public class OguryBidder implements Bidder<BidRequest> {
37+
38+
private static final String EXT_FIELD_BIDDER = "bidder";
39+
private static final String BIDDER_CURRENCY = "USD";
40+
private static final String PREBID_FIELD_ASSET_KEY = "assetKey";
41+
private static final String PREBID_FIELD_ADUNIT_ID = "adUnitId";
42+
43+
private final String endpointUrl;
44+
private final CurrencyConversionService currencyConversionService;
45+
private final JacksonMapper mapper;
46+
47+
public OguryBidder(String endpointUrl, CurrencyConversionService currencyConversionService, JacksonMapper mapper) {
48+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
49+
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
50+
this.mapper = Objects.requireNonNull(mapper);
51+
}
52+
53+
@Override
54+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
55+
final List<BidderError> errors = new ArrayList<>();
56+
57+
final List<Imp> modifiedImps = new ArrayList<>();
58+
final List<Imp> impsWithOguryParams = new ArrayList<>();
59+
60+
for (Imp imp : bidRequest.getImp()) {
61+
try {
62+
final Imp modifiedImp = modifyImp(imp, bidRequest);
63+
64+
modifiedImps.add(modifiedImp);
65+
if (hasOguryParams(imp)) {
66+
impsWithOguryParams.add(modifiedImp);
67+
}
68+
} catch (PreBidException e) {
69+
errors.add(BidderError.badInput(e.getMessage()));
70+
}
71+
}
72+
73+
if (!isValidRequestKeys(bidRequest, impsWithOguryParams)) {
74+
errors.add(BidderError.badInput(
75+
"Invalid request. assetKey/adUnitId or request.site.publisher.id required"));
76+
return Result.withErrors(errors);
77+
}
78+
79+
final BidRequest modifiedBidRequest = bidRequest.toBuilder()
80+
.imp(CollectionUtils.isNotEmpty(impsWithOguryParams) ? impsWithOguryParams : modifiedImps)
81+
.build();
82+
83+
final MultiMap headers = resolveHeaders(modifiedBidRequest.getDevice());
84+
final List<HttpRequest<BidRequest>> httpRequests = Collections.singletonList(
85+
BidderUtil.defaultRequest(modifiedBidRequest, headers, endpointUrl, mapper));
86+
87+
return Result.of(httpRequests, errors);
88+
}
89+
90+
private ObjectNode resolveImpExtBidderHoist(Imp imp) {
91+
return (ObjectNode) imp.getExt().get(EXT_FIELD_BIDDER);
92+
}
93+
94+
private Imp modifyImp(Imp imp, BidRequest bidRequest) {
95+
final Price price = resolvePrice(imp, bidRequest);
96+
return imp.toBuilder()
97+
.tagid(imp.getId())
98+
.bidfloor(price.getValue())
99+
.bidfloorcur(price.getCurrency())
100+
.ext(modifyExt(imp))
101+
.build();
102+
}
103+
104+
private Price resolvePrice(Imp imp, BidRequest bidRequest) {
105+
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
106+
return BidderUtil.shouldConvertBidFloor(initialBidFloorPrice, BIDDER_CURRENCY)
107+
? convertBidFloor(initialBidFloorPrice, bidRequest)
108+
: initialBidFloorPrice;
109+
}
110+
111+
private Price convertBidFloor(Price bidFloorPrice, BidRequest bidRequest) {
112+
final BigDecimal convertedPrice = currencyConversionService.convertCurrency(
113+
bidFloorPrice.getValue(),
114+
bidRequest,
115+
bidFloorPrice.getCurrency(),
116+
BIDDER_CURRENCY);
117+
118+
return Price.of(BIDDER_CURRENCY, convertedPrice);
119+
}
120+
121+
private ObjectNode modifyExt(Imp imp) {
122+
final ObjectNode impExt = imp.getExt();
123+
final ObjectNode impExtBidderHoist = resolveImpExtBidderHoist(imp);
124+
125+
final ObjectNode modifiedImpExt = impExt.deepCopy();
126+
modifiedImpExt.setAll(impExtBidderHoist);
127+
modifiedImpExt.remove(EXT_FIELD_BIDDER);
128+
129+
return modifiedImpExt;
130+
}
131+
132+
private boolean hasOguryParams(Imp imp) {
133+
final ObjectNode impExtBidderHoist = resolveImpExtBidderHoist(imp);
134+
135+
return impExtBidderHoist != null
136+
&& impExtBidderHoist.has(PREBID_FIELD_ASSET_KEY)
137+
&& impExtBidderHoist.has(PREBID_FIELD_ADUNIT_ID);
138+
}
139+
140+
private boolean isValidRequestKeys(BidRequest request, List<Imp> impsWithOguryParams) {
141+
return !CollectionUtils.isEmpty(impsWithOguryParams) || Optional.ofNullable(request.getSite())
142+
.map(Site::getPublisher)
143+
.map(Publisher::getId)
144+
.isPresent();
145+
}
146+
147+
private MultiMap resolveHeaders(Device device) {
148+
final MultiMap headers = HttpUtil.headers();
149+
150+
if (device != null) {
151+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa());
152+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ACCEPT_LANGUAGE_HEADER, device.getLanguage());
153+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
154+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
155+
}
156+
157+
return headers;
158+
}
159+
160+
@Override
161+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
162+
try {
163+
final String body = httpCall.getResponse().getBody();
164+
165+
final BidResponse bidResponse = mapper.decodeValue(body, BidResponse.class);
166+
167+
final List<BidderError> errors = new ArrayList<>();
168+
final List<BidderBid> bidderBids = extractBids(bidResponse, errors);
169+
170+
return Result.of(bidderBids, errors);
171+
} catch (Exception e) {
172+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
173+
}
174+
}
175+
176+
private List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
177+
return Optional.ofNullable(bidResponse)
178+
.map(BidResponse::getSeatbid)
179+
.stream()
180+
.flatMap(Collection::stream)
181+
.filter(Objects::nonNull)
182+
.map(SeatBid::getBid)
183+
.filter(Objects::nonNull)
184+
.flatMap(Collection::stream)
185+
.filter(Objects::nonNull)
186+
.map(bid -> createBidderBid(bid, bidResponse, errors))
187+
.filter(Objects::nonNull)
188+
.toList();
189+
}
190+
191+
private BidderBid createBidderBid(Bid bid, BidResponse bidResponse, List<BidderError> errors) {
192+
try {
193+
return BidderBid.of(bid, getBidType(bid), bidResponse.getCur());
194+
} catch (PreBidException e) {
195+
errors.add(BidderError.badServerResponse(e.getMessage()));
196+
return null;
197+
}
198+
}
199+
200+
private static BidType getBidType(Bid bid) {
201+
final Integer markupType = bid.getMtype();
202+
if (markupType == null) {
203+
throw new PreBidException("Missing MType for impression: `%s`".formatted(bid.getImpid()));
204+
}
205+
206+
return switch (markupType) {
207+
case 1 -> BidType.banner;
208+
case 2 -> BidType.video;
209+
case 3 -> BidType.audio;
210+
case 4 -> BidType.xNative;
211+
default -> throw new PreBidException(
212+
"Unsupported MType '%d', for impression '%s'".formatted(markupType, bid.getImpid()));
213+
};
214+
}
215+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.ogury.OguryBidder;
5+
import org.prebid.server.currency.CurrencyConversionService;
6+
import org.prebid.server.json.JacksonMapper;
7+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
8+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
9+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
10+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.boot.context.properties.ConfigurationProperties;
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.springframework.context.annotation.PropertySource;
16+
17+
import jakarta.validation.constraints.NotBlank;
18+
19+
@Configuration
20+
@PropertySource(value = "classpath:/bidder-config/ogury.yaml", factory = YamlPropertySourceFactory.class)
21+
public class OguryConfiguration {
22+
23+
private static final String BIDDER_NAME = "ogury";
24+
25+
@Bean("oguryConfigurationProperties")
26+
@ConfigurationProperties("adapters.ogury")
27+
BidderConfigurationProperties configurationProperties() {
28+
return new BidderConfigurationProperties();
29+
}
30+
31+
@Bean
32+
BidderDeps oguryBidderDeps(BidderConfigurationProperties oguryConfigurationProperties,
33+
@NotBlank @Value("${external-url}") String externalUrl,
34+
CurrencyConversionService currencyConversionService,
35+
JacksonMapper mapper) {
36+
37+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
38+
.withConfig(oguryConfigurationProperties)
39+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
40+
.bidderCreator(config -> new OguryBidder(config.getEndpoint(), currencyConversionService, mapper))
41+
.assemble();
42+
}
43+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
adapters:
2+
ogury:
3+
endpoint: "https://prebids2s.presage.io/api/header-bidding-request"
4+
endpointCompression: gzip
5+
geoscope:
6+
- global
7+
meta-info:
8+
maintainer-email: deliveryservices@ogury.co
9+
site-media-types:
10+
- banner
11+
vendor-id: 31
12+
usersync:
13+
cookie-family-name: ogury
14+
iframe:
15+
url: "https://ms-cookie-sync.presage.io/user-sync.html?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}&source=prebids2s"
16+
uid-macro: "{{OGURY_UID}}"
17+
support-cors: false
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Ogury Adapter Params",
4+
"description": "A schema which validates params accepted by the Ogury adapter",
5+
"type": "object",
6+
"properties": {
7+
"assetKey": {
8+
"type": [
9+
"string"
10+
],
11+
"description": "The asset key provided by Ogury"
12+
},
13+
"adUnitId": {
14+
"type": [
15+
"string"
16+
],
17+
"description": "Ad unit id configured with Ogury"
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)