Skip to content

Commit 6e977ec

Browse files
New AdupTech Adapter (#4024)
1 parent 50a74bb commit 6e977ec

File tree

12 files changed

+817
-0
lines changed

12 files changed

+817
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package org.prebid.server.bidder.aduptech;
2+
3+
import com.iab.openrtb.request.BidRequest;
4+
import com.iab.openrtb.request.Imp;
5+
import com.iab.openrtb.response.Bid;
6+
import com.iab.openrtb.response.BidResponse;
7+
import com.iab.openrtb.response.SeatBid;
8+
import io.vertx.core.MultiMap;
9+
import org.apache.commons.collections4.CollectionUtils;
10+
import org.prebid.server.bidder.Bidder;
11+
import org.prebid.server.bidder.model.BidderBid;
12+
import org.prebid.server.bidder.model.BidderCall;
13+
import org.prebid.server.bidder.model.BidderError;
14+
import org.prebid.server.bidder.model.HttpRequest;
15+
import org.prebid.server.bidder.model.Price;
16+
import org.prebid.server.bidder.model.Result;
17+
import org.prebid.server.currency.CurrencyConversionService;
18+
import org.prebid.server.exception.PreBidException;
19+
import org.prebid.server.json.DecodeException;
20+
import org.prebid.server.json.JacksonMapper;
21+
import org.prebid.server.proto.openrtb.ext.response.BidType;
22+
import org.prebid.server.util.BidderUtil;
23+
import org.prebid.server.util.HttpUtil;
24+
25+
import java.math.BigDecimal;
26+
import java.util.ArrayList;
27+
import java.util.Collection;
28+
import java.util.Collections;
29+
import java.util.Currency;
30+
import java.util.List;
31+
import java.util.Objects;
32+
import java.util.stream.Collectors;
33+
34+
public class AduptechBidder implements Bidder<BidRequest> {
35+
36+
private static final String COMPONENT_ID_HEADER = "Componentid";
37+
private static final String COMPONENT_ID_HEADER_VALUE = "prebid-java";
38+
private static final String DEFAULT_BID_CURRENCY = "USD";
39+
40+
private final String endpointUrl;
41+
private final JacksonMapper mapper;
42+
private final CurrencyConversionService currencyConversionService;
43+
private final String targetCurrency;
44+
45+
public AduptechBidder(String endpointUrl,
46+
JacksonMapper mapper,
47+
CurrencyConversionService currencyConversionService,
48+
String targetCurrency) {
49+
50+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
51+
this.mapper = Objects.requireNonNull(mapper);
52+
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
53+
this.targetCurrency = validateCurrency(targetCurrency);
54+
}
55+
56+
private static String validateCurrency(String code) {
57+
try {
58+
Currency.getInstance(code);
59+
} catch (IllegalArgumentException e) {
60+
throw new IllegalArgumentException("invalid extra info: invalid TargetCurrency %s".formatted(code));
61+
}
62+
return code.toUpperCase();
63+
}
64+
65+
@Override
66+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
67+
final List<Imp> modifiedImps = new ArrayList<>(request.getImp().size());
68+
for (Imp imp : request.getImp()) {
69+
try {
70+
modifiedImps.add(modifyImp(imp, request));
71+
} catch (PreBidException e) {
72+
return Result.withError(BidderError.badInput(e.getMessage()));
73+
}
74+
}
75+
76+
final BidRequest outgoingRequest = request.toBuilder().imp(modifiedImps).build();
77+
final HttpRequest<BidRequest> httpRequest = BidderUtil.defaultRequest(
78+
outgoingRequest,
79+
makeHeaders(),
80+
endpointUrl,
81+
mapper);
82+
83+
return Result.withValue(httpRequest);
84+
}
85+
86+
private Imp modifyImp(Imp imp, BidRequest bidRequest) {
87+
Price impFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
88+
impFloorPrice = BidderUtil.isValidPrice(impFloorPrice)
89+
&& !targetCurrency.equalsIgnoreCase(impFloorPrice.getCurrency())
90+
? convertBidFloor(impFloorPrice, bidRequest)
91+
: impFloorPrice;
92+
93+
return imp.toBuilder()
94+
.bidfloor(impFloorPrice.getValue())
95+
.bidfloorcur(impFloorPrice.getCurrency())
96+
.build();
97+
}
98+
99+
private Price convertBidFloor(Price impFloorPrice, BidRequest bidRequest) {
100+
try {
101+
return convertToTargetCurrency(impFloorPrice.getValue(), bidRequest, impFloorPrice.getCurrency());
102+
} catch (PreBidException e) {
103+
final BigDecimal defaultCurrencyBidFloor = currencyConversionService.convertCurrency(
104+
impFloorPrice.getValue(),
105+
bidRequest,
106+
impFloorPrice.getCurrency(),
107+
DEFAULT_BID_CURRENCY);
108+
return convertToTargetCurrency(defaultCurrencyBidFloor, bidRequest, DEFAULT_BID_CURRENCY);
109+
}
110+
}
111+
112+
private Price convertToTargetCurrency(BigDecimal impFloorPrice, BidRequest bidRequest, String fromCurrency) {
113+
final BigDecimal convertedFloor = currencyConversionService.convertCurrency(
114+
impFloorPrice,
115+
bidRequest,
116+
fromCurrency,
117+
targetCurrency);
118+
119+
return Price.of(targetCurrency, convertedFloor);
120+
}
121+
122+
private static MultiMap makeHeaders() {
123+
return HttpUtil.headers().add(COMPONENT_ID_HEADER, COMPONENT_ID_HEADER_VALUE);
124+
}
125+
126+
@Override
127+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
128+
try {
129+
final List<BidderError> errors = new ArrayList<>();
130+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
131+
return Result.of(extractBids(bidResponse, errors), errors);
132+
} catch (DecodeException e) {
133+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
134+
}
135+
}
136+
137+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
138+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
139+
return Collections.emptyList();
140+
}
141+
return bidsFromResponse(bidResponse, errors);
142+
}
143+
144+
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
145+
return bidResponse.getSeatbid().stream()
146+
.filter(Objects::nonNull)
147+
.map(SeatBid::getBid)
148+
.filter(Objects::nonNull)
149+
.flatMap(Collection::stream)
150+
.filter(Objects::nonNull)
151+
.map(bid -> makeBid(bid, bidResponse.getCur(), errors))
152+
.filter(Objects::nonNull)
153+
.collect(Collectors.toList());
154+
}
155+
156+
private static BidderBid makeBid(Bid bid, String currency, List<BidderError> errors) {
157+
try {
158+
return BidderBid.of(bid, getBidType(bid.getMtype()), currency);
159+
} catch (PreBidException e) {
160+
errors.add(BidderError.badServerResponse(e.getMessage()));
161+
return null;
162+
}
163+
}
164+
165+
private static BidType getBidType(Integer markupType) {
166+
return switch (markupType) {
167+
case 1 -> BidType.banner;
168+
case 4 -> BidType.xNative;
169+
case null, default -> throw new PreBidException("Unknown markup type: " + markupType);
170+
};
171+
}
172+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.prebid.server.proto.openrtb.ext.request.aduptech;
2+
3+
import com.fasterxml.jackson.databind.node.ObjectNode;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpAduptech {
8+
9+
String publisher;
10+
11+
String placement;
12+
13+
String query;
14+
15+
Boolean adtest;
16+
17+
Boolean debug;
18+
19+
ObjectNode ext;
20+
}
21+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import lombok.Data;
4+
import lombok.EqualsAndHashCode;
5+
import lombok.NoArgsConstructor;
6+
import org.prebid.server.bidder.BidderDeps;
7+
import org.prebid.server.bidder.aduptech.AduptechBidder;
8+
import org.prebid.server.currency.CurrencyConversionService;
9+
import org.prebid.server.json.JacksonMapper;
10+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
11+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
12+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
13+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
14+
import org.springframework.beans.factory.annotation.Value;
15+
import org.springframework.boot.context.properties.ConfigurationProperties;
16+
import org.springframework.context.annotation.Bean;
17+
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.context.annotation.PropertySource;
19+
20+
import jakarta.validation.constraints.NotBlank;
21+
import jakarta.validation.constraints.NotNull;
22+
23+
@Configuration
24+
@PropertySource(value = "classpath:/bidder-config/aduptech.yaml", factory = YamlPropertySourceFactory.class)
25+
public class AduptechConfiguration {
26+
27+
private static final String BIDDER_NAME = "aduptech";
28+
29+
@Bean("aduptechConfigurationProperties")
30+
@ConfigurationProperties("adapters.aduptech")
31+
AduptechConfigurationProperties configurationProperties() {
32+
return new AduptechConfigurationProperties();
33+
}
34+
35+
@Bean
36+
BidderDeps aduptechBidderDeps(AduptechConfigurationProperties aduptechConfigurationProperties,
37+
@NotBlank @Value("${external-url}") String externalUrl,
38+
CurrencyConversionService currencyConversionService,
39+
JacksonMapper mapper) {
40+
41+
return BidderDepsAssembler.<AduptechConfigurationProperties>forBidder(BIDDER_NAME)
42+
.withConfig(aduptechConfigurationProperties)
43+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
44+
.bidderCreator(config -> new AduptechBidder(
45+
config.getEndpoint(),
46+
mapper,
47+
currencyConversionService,
48+
config.getTargetCurrency()))
49+
.assemble();
50+
}
51+
52+
@Data
53+
@EqualsAndHashCode(callSuper = true)
54+
@NoArgsConstructor
55+
private static class AduptechConfigurationProperties extends BidderConfigurationProperties {
56+
57+
@NotNull
58+
private String targetCurrency;
59+
}
60+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
adapters:
2+
aduptech:
3+
endpoint: https://rtb.d.adup-tech.com/rtb/bid
4+
ortb-version: "2.6"
5+
meta-info:
6+
maintainer-email: [email protected]
7+
app-media-types:
8+
- banner
9+
- native
10+
site-media-types:
11+
- banner
12+
- native
13+
supported-vendors:
14+
vendor-id: 647
15+
usersync:
16+
cookie-family-name: aduptech
17+
iframe:
18+
url: https://rtb.d.adup-tech.com/service/sync?iframe=1&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
19+
support-cors: false
20+
uid-macro: '[UID]'
21+
redirect:
22+
url: https://rtb.d.adup-tech.com/service/sync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
23+
support-cors: false
24+
uid-macro: '[UID]'
25+
target-currency: "EUR"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "AdUp Tech adapter params",
4+
"description": "A schema which validates params accepted by the AdUp Tech adapter",
5+
"type": "object",
6+
"properties": {
7+
"publisher": {
8+
"type": "string",
9+
"minLength": 1,
10+
"description": "Unique publisher identifier."
11+
},
12+
"placement": {
13+
"type": "string",
14+
"minLength": 1,
15+
"description": "Unique placement identifier per publisher."
16+
},
17+
"query": {
18+
"type": "string",
19+
"description": "Semicolon separated list of keywords."
20+
},
21+
"adtest": {
22+
"type": "boolean",
23+
"description": "Deactivates tracking of impressions and clicks. **Should only be used for testing purposes!**"
24+
},
25+
"debug": {
26+
"type": "boolean",
27+
"description": "Enables debug mode. **Should only be used for testing purposes!**"
28+
},
29+
"ext": {
30+
"type": "object",
31+
"description": "Additional parameters to be included in the request."
32+
}
33+
},
34+
"required": [
35+
"publisher",
36+
"placement"
37+
]
38+
}

0 commit comments

Comments
 (0)