Skip to content

Commit 49310ff

Browse files
AntoxaAntoxicRitesh Ghodrao
authored andcommitted
New Madsense Adapter (prebid#3972)
1 parent 9e8ea0f commit 49310ff

12 files changed

Lines changed: 806 additions & 0 deletions

File tree

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package org.prebid.server.bidder.madsense;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
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.Site;
8+
import com.iab.openrtb.response.Bid;
9+
import com.iab.openrtb.response.BidResponse;
10+
import com.iab.openrtb.response.SeatBid;
11+
import io.vertx.core.MultiMap;
12+
import org.apache.commons.collections4.CollectionUtils;
13+
import org.prebid.server.bidder.Bidder;
14+
import org.prebid.server.bidder.model.BidderBid;
15+
import org.prebid.server.bidder.model.BidderCall;
16+
import org.prebid.server.bidder.model.BidderError;
17+
import org.prebid.server.bidder.model.HttpRequest;
18+
import org.prebid.server.bidder.model.Result;
19+
import org.prebid.server.exception.PreBidException;
20+
import org.prebid.server.json.DecodeException;
21+
import org.prebid.server.json.JacksonMapper;
22+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
23+
import org.prebid.server.proto.openrtb.ext.request.madsense.ExtImpMadsense;
24+
import org.prebid.server.proto.openrtb.ext.response.BidType;
25+
import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo;
26+
import org.prebid.server.util.BidderUtil;
27+
import org.prebid.server.util.HttpUtil;
28+
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+
35+
public class MadsenseBidder implements Bidder<BidRequest> {
36+
37+
private static final String X_OPENRTB_VERSION_HEADER_VALUE = "2.6";
38+
private static final TypeReference<ExtPrebid<?, ExtImpMadsense>> TYPE_REFERENCE = new TypeReference<>() {
39+
};
40+
41+
private final String endpointUrl;
42+
private final JacksonMapper mapper;
43+
44+
public MadsenseBidder(String endpointUrl, JacksonMapper mapper) {
45+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
46+
this.mapper = Objects.requireNonNull(mapper);
47+
}
48+
49+
@Override
50+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
51+
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();
52+
final List<BidderError> errors = new ArrayList<>();
53+
final List<Imp> videoImps = new ArrayList<>();
54+
55+
for (Imp imp : request.getImp()) {
56+
if (imp.getBanner() != null) {
57+
try {
58+
httpRequests.add(makeHttpRequest(request, Collections.singletonList(imp)));
59+
} catch (PreBidException e) {
60+
errors.add(BidderError.badInput(e.getMessage()));
61+
}
62+
} else if (imp.getVideo() != null) {
63+
videoImps.add(imp);
64+
}
65+
}
66+
67+
if (CollectionUtils.isNotEmpty(videoImps)) {
68+
try {
69+
httpRequests.add(makeHttpRequest(request, videoImps));
70+
} catch (PreBidException e) {
71+
errors.add(BidderError.badInput(e.getMessage()));
72+
}
73+
}
74+
75+
return Result.of(httpRequests, errors);
76+
}
77+
78+
private ExtImpMadsense parseImpExt(Imp imp) {
79+
try {
80+
return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder();
81+
} catch (IllegalArgumentException e) {
82+
throw new PreBidException("Error parsing imp.ext parameters");
83+
}
84+
}
85+
86+
private HttpRequest<BidRequest> makeHttpRequest(BidRequest request, List<Imp> imps) {
87+
final Imp firstImp = request.getImp().getFirst();
88+
final ExtImpMadsense extImp = parseImpExt(firstImp);
89+
final String companyId = Objects.equals(request.getTest(), 1) ? "test" : extImp.getCompanyId();
90+
return BidderUtil.defaultRequest(
91+
request.toBuilder().imp(imps).build(),
92+
makeHeaders(request),
93+
makeEndpoint(companyId),
94+
mapper);
95+
}
96+
97+
private static MultiMap makeHeaders(BidRequest request) {
98+
final MultiMap headers = HttpUtil.headers()
99+
.set(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPENRTB_VERSION_HEADER_VALUE);
100+
101+
final Device device = request.getDevice();
102+
if (device != null) {
103+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa());
104+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
105+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
106+
}
107+
108+
final Site site = request.getSite();
109+
if (site != null) {
110+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.ORIGIN_HEADER, site.getDomain());
111+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getRef());
112+
}
113+
114+
return headers;
115+
}
116+
117+
private String makeEndpoint(String companyId) {
118+
return endpointUrl + "?company_id=" + HttpUtil.encodeUrl(companyId);
119+
}
120+
121+
@Override
122+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
123+
final List<BidderError> errors = new ArrayList<>();
124+
try {
125+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
126+
return Result.of(extractBids(bidResponse, errors), errors);
127+
} catch (DecodeException e) {
128+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
129+
}
130+
}
131+
132+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
133+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
134+
return Collections.emptyList();
135+
}
136+
return bidsFromResponse(bidResponse, errors);
137+
}
138+
139+
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
140+
return bidResponse.getSeatbid().stream()
141+
.filter(Objects::nonNull)
142+
.map(SeatBid::getBid)
143+
.filter(Objects::nonNull)
144+
.flatMap(Collection::stream)
145+
.filter(Objects::nonNull)
146+
.map(bid -> makeBidderBid(bid, bidResponse.getCur(), errors))
147+
.filter(Objects::nonNull)
148+
.toList();
149+
}
150+
151+
private static BidderBid makeBidderBid(Bid bid, String currency, List<BidderError> errors) {
152+
try {
153+
final BidType bidType = getBidType(bid);
154+
return BidderBid.builder()
155+
.bid(bid)
156+
.bidCurrency(currency)
157+
.videoInfo(bidType == BidType.video
158+
? ExtBidPrebidVideo.of(resolveDuration(bid), resolveCategory(bid))
159+
: null)
160+
.type(bidType)
161+
.build();
162+
} catch (PreBidException e) {
163+
errors.add(BidderError.badServerResponse(e.getMessage()));
164+
return null;
165+
}
166+
}
167+
168+
private static BidType getBidType(Bid bid) {
169+
return switch (bid.getMtype()) {
170+
case 1 -> BidType.banner;
171+
case 2 -> BidType.video;
172+
case null, default -> throw new PreBidException(
173+
"Unsupported bid mediaType: %s for impression: %s".formatted(bid.getMtype(), bid.getImpid()));
174+
};
175+
}
176+
177+
private static String resolveCategory(Bid bid) {
178+
final List<String> categories = bid.getCat();
179+
return CollectionUtils.isEmpty(categories) ? null : categories.getFirst();
180+
}
181+
182+
private static Integer resolveDuration(Bid bid) {
183+
final Integer duration = bid.getDur();
184+
return duration != null && duration > 0 ? duration : null;
185+
}
186+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.prebid.server.proto.openrtb.ext.request.madsense;
2+
3+
import lombok.Value;
4+
5+
@Value(staticConstructor = "of")
6+
public class ExtImpMadsense {
7+
8+
String companyId;
9+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.madsense.MadsenseBidder;
5+
import org.prebid.server.json.JacksonMapper;
6+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
7+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
8+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
9+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.context.annotation.PropertySource;
15+
16+
import jakarta.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/madsense.yaml", factory = YamlPropertySourceFactory.class)
20+
public class MadsenseConfiguration {
21+
22+
private static final String BIDDER_NAME = "madsense";
23+
24+
@Bean("madsenseConfigurationProperties")
25+
@ConfigurationProperties("adapters.madsense")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps madsenseBidderDeps(BidderConfigurationProperties madsenseConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(madsenseConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new MadsenseBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
adapters:
2+
madsense:
3+
endpoint: https://ads.madsense.io/pbs
4+
meta-info:
5+
maintainer-email: prebid@madsense.io
6+
app-media-types:
7+
- banner
8+
- video
9+
site-media-types:
10+
- banner
11+
- video
12+
supported-vendors:
13+
vendor-id: 0
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "madSense Adapter Params",
4+
"description": "A schema which validates params accepted by the madSense adapter",
5+
"type": "object",
6+
"properties": {
7+
"company_id": {
8+
"type": "string",
9+
"description": "An id used to identify madSense company",
10+
"minLength": 1
11+
}
12+
},
13+
"required": [
14+
"company_id"
15+
]
16+
}

0 commit comments

Comments
 (0)