Skip to content

Commit fe98db2

Browse files
authored
ZMaticoo: Add new bidder (prebid#3055)
1 parent 3b8bee3 commit fe98db2

12 files changed

Lines changed: 674 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package org.prebid.server.bidder.zmaticoo;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.core.type.TypeReference;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.iab.openrtb.request.BidRequest;
7+
import com.iab.openrtb.request.Imp;
8+
import com.iab.openrtb.request.Native;
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.http.HttpMethod;
13+
import org.apache.commons.collections4.CollectionUtils;
14+
import org.apache.commons.lang3.ObjectUtils;
15+
import org.apache.commons.lang3.StringUtils;
16+
import org.prebid.server.bidder.Bidder;
17+
import org.prebid.server.bidder.model.BidderBid;
18+
import org.prebid.server.bidder.model.BidderCall;
19+
import org.prebid.server.bidder.model.BidderError;
20+
import org.prebid.server.bidder.model.HttpRequest;
21+
import org.prebid.server.bidder.model.Result;
22+
import org.prebid.server.exception.PreBidException;
23+
import org.prebid.server.json.DecodeException;
24+
import org.prebid.server.json.JacksonMapper;
25+
import org.prebid.server.model.UpdateResult;
26+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
27+
import org.prebid.server.proto.openrtb.ext.request.zmaticoo.ExtImpZMaticoo;
28+
import org.prebid.server.proto.openrtb.ext.response.BidType;
29+
import org.prebid.server.util.BidderUtil;
30+
import org.prebid.server.util.HttpUtil;
31+
32+
import java.util.ArrayList;
33+
import java.util.Collection;
34+
import java.util.Collections;
35+
import java.util.List;
36+
import java.util.Objects;
37+
38+
public class ZMaticooBidder implements Bidder<BidRequest> {
39+
40+
private static final TypeReference<ExtPrebid<?, ExtImpZMaticoo>> ZMATICOO_EXT_TYPE_REFERENCE =
41+
new TypeReference<>() {
42+
};
43+
44+
private final String endpointUrl;
45+
private final JacksonMapper mapper;
46+
47+
public ZMaticooBidder(String endpointUrl, JacksonMapper mapper) {
48+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
49+
this.mapper = Objects.requireNonNull(mapper);
50+
}
51+
52+
@Override
53+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
54+
final List<BidderError> bidderErrors = new ArrayList<>();
55+
final List<Imp> modifiedImps = new ArrayList<>();
56+
57+
for (Imp imp : request.getImp()) {
58+
try {
59+
validateImpExt(imp);
60+
modifiedImps.add(modifyImp(imp));
61+
} catch (PreBidException e) {
62+
bidderErrors.add(BidderError.badInput(e.getMessage()));
63+
}
64+
}
65+
66+
if (CollectionUtils.isNotEmpty(bidderErrors)) {
67+
return Result.withErrors(bidderErrors);
68+
}
69+
70+
final BidRequest modifiedRequest = request.toBuilder().imp(modifiedImps).build();
71+
return Result.withValue(makeHttpRequest(modifiedRequest));
72+
}
73+
74+
private void validateImpExt(Imp imp) {
75+
final ExtImpZMaticoo extImpZMaticoo;
76+
try {
77+
extImpZMaticoo = mapper.mapper().convertValue(imp.getExt(), ZMATICOO_EXT_TYPE_REFERENCE).getBidder();
78+
} catch (IllegalArgumentException e) {
79+
throw new PreBidException(e.getMessage());
80+
}
81+
if (StringUtils.isBlank(extImpZMaticoo.getPubId()) || StringUtils.isBlank(extImpZMaticoo.getZoneId())) {
82+
throw new PreBidException("imp.ext.pubId or imp.ext.zoneId required");
83+
}
84+
}
85+
86+
private Imp modifyImp(Imp imp) {
87+
final Native xNative = imp.getXNative();
88+
if (xNative == null) {
89+
return imp;
90+
}
91+
92+
final UpdateResult<String> nativeRequest = resolveNativeRequest(xNative.getRequest());
93+
return nativeRequest.isUpdated()
94+
? imp.toBuilder()
95+
.xNative(xNative.toBuilder()
96+
.request(nativeRequest.getValue())
97+
.build())
98+
.build()
99+
: imp;
100+
}
101+
102+
private UpdateResult<String> resolveNativeRequest(String nativeRequest) {
103+
final JsonNode nativeRequestNode;
104+
try {
105+
nativeRequestNode = StringUtils.isNotBlank(nativeRequest)
106+
? mapper.mapper().readTree(nativeRequest)
107+
: mapper.mapper().createObjectNode();
108+
} catch (JsonProcessingException e) {
109+
throw new PreBidException(e.getMessage());
110+
}
111+
112+
if (nativeRequestNode.has("native")) {
113+
return UpdateResult.unaltered(nativeRequest);
114+
}
115+
116+
final String updatedNativeRequest = mapper.mapper().createObjectNode()
117+
.putPOJO("native", nativeRequestNode)
118+
.toString();
119+
120+
return UpdateResult.updated(updatedNativeRequest);
121+
}
122+
123+
private HttpRequest<BidRequest> makeHttpRequest(BidRequest modifiedRequest) {
124+
return HttpRequest.<BidRequest>builder()
125+
.method(HttpMethod.POST)
126+
.uri(endpointUrl)
127+
.headers(HttpUtil.headers())
128+
.impIds(BidderUtil.impIds(modifiedRequest))
129+
.payload(modifiedRequest)
130+
.body(mapper.encodeToBytes(modifiedRequest))
131+
.build();
132+
}
133+
134+
@Override
135+
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
136+
try {
137+
final List<BidderError> errors = new ArrayList<>();
138+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
139+
return Result.of(extractBids(bidResponse, errors), errors);
140+
} catch (DecodeException e) {
141+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
142+
}
143+
}
144+
145+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
146+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
147+
return Collections.emptyList();
148+
}
149+
150+
return bidResponse.getSeatbid().stream()
151+
.filter(Objects::nonNull)
152+
.map(SeatBid::getBid)
153+
.filter(Objects::nonNull)
154+
.flatMap(Collection::stream)
155+
.filter(Objects::nonNull)
156+
.map(bid -> makeBidderBid(bid, bidResponse.getCur(), errors))
157+
.filter(Objects::nonNull)
158+
.toList();
159+
}
160+
161+
private static BidderBid makeBidderBid(Bid bid, String currency, List<BidderError> errors) {
162+
try {
163+
final BidType bidType = getBidMediaType(bid);
164+
return BidderBid.of(bid, bidType, currency);
165+
} catch (PreBidException e) {
166+
errors.add(BidderError.badServerResponse(e.getMessage()));
167+
return null;
168+
}
169+
}
170+
171+
private static BidType getBidMediaType(Bid bid) {
172+
final int markupType = ObjectUtils.defaultIfNull(bid.getMtype(), 0);
173+
return switch (markupType) {
174+
case 1 -> BidType.banner;
175+
case 2 -> BidType.video;
176+
case 4 -> BidType.xNative;
177+
default -> throw new PreBidException(
178+
"unrecognized bid type in response from zmaticoo for bid " + bid.getImpid());
179+
};
180+
}
181+
182+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.prebid.server.proto.openrtb.ext.request.zmaticoo;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpZMaticoo {
8+
9+
@JsonProperty("pubId")
10+
String pubId;
11+
12+
@JsonProperty("zoneId")
13+
String zoneId;
14+
}
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.zmaticoo.ZMaticooBidder;
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 javax.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/zmaticoo.yaml", factory = YamlPropertySourceFactory.class)
20+
public class ZMaticooBidderConfiguration {
21+
22+
private static final String BIDDER_NAME = "zmaticoo";
23+
24+
@Bean("zmaticooConfigurationProperties")
25+
@ConfigurationProperties("adapters.zmaticoo")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps zmaticooBidderDeps(BidderConfigurationProperties zmaticooConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(zmaticooConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new ZMaticooBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
adapters:
2+
zmaticoo:
3+
endpoint: https://bid.zmaticoo.com/prebid/bid
4+
meta-info:
5+
maintainer-email: adam.li@eclicktech.com.cn
6+
app-media-types:
7+
- banner
8+
- video
9+
- native
10+
site-media-types:
11+
supported-vendors:
12+
vendor-id: 803
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "zMaticoo Adapter Params",
4+
"description": "A schema which validates params accepted by the zMaticoo adapter",
5+
"type": "object",
6+
"properties": {
7+
"pubId": {
8+
"type": "string",
9+
"description": "Publisher ID",
10+
"minLength": 1
11+
},
12+
"zoneId": {
13+
"type": "string",
14+
"description": "Zone Id",
15+
"minLength": 1
16+
}
17+
},
18+
"required": [
19+
"pubId",
20+
"zoneId"
21+
]
22+
}

0 commit comments

Comments
 (0)