44import com .fasterxml .jackson .core .type .TypeReference ;
55import com .fasterxml .jackson .databind .JsonNode ;
66import com .fasterxml .jackson .databind .node .ObjectNode ;
7+ import com .iab .openrtb .request .App ;
78import com .iab .openrtb .request .BidRequest ;
89import com .iab .openrtb .request .Imp ;
910import com .iab .openrtb .request .Publisher ;
1213import com .iab .openrtb .response .BidResponse ;
1314import com .iab .openrtb .response .SeatBid ;
1415import org .apache .commons .collections4 .CollectionUtils ;
16+ import org .apache .commons .lang3 .StringUtils ;
1517import org .prebid .server .bidder .Bidder ;
1618import org .prebid .server .bidder .model .BidderBid ;
1719import org .prebid .server .bidder .model .BidderCall ;
2931import org .prebid .server .util .BidderUtil ;
3032import org .prebid .server .util .HttpUtil ;
3133
34+ import java .net .URI ;
35+ import java .net .URISyntaxException ;
3236import java .util .ArrayList ;
3337import java .util .Collection ;
3438import java .util .Collections ;
3842
3943public class SparteoBidder implements Bidder <BidRequest > {
4044
45+ private static final String NETWORK_ID_MACRO = "{{NetworkId}}" ;
46+ private static final String SITE_DOMAIN_QUERY_MACRO = "{{SiteDomainQuery}}" ;
47+ private static final String APP_DOMAIN_QUERY_MACRO = "{{AppDomainQuery}}" ;
48+ private static final String BUNDLE_QUERY_MACRO = "{{BundleQuery}}" ;
49+ private static final String UNKNOWN_VALUE = "unknown" ;
50+
4151 private static final TypeReference <ExtPrebid <?, ExtImpSparteo >> TYPE_REFERENCE =
4252 new TypeReference <>() { };
4353
4454 private final String endpointUrl ;
4555 private final JacksonMapper mapper ;
4656
4757 public SparteoBidder (String endpointUrl , JacksonMapper mapper ) {
48- this .endpointUrl = HttpUtil .validateUrl (Objects .requireNonNull (endpointUrl ));
58+ this .endpointUrl = HttpUtil .validateUrlSyntax (Objects .requireNonNull (endpointUrl ));
4959 this .mapper = Objects .requireNonNull (mapper );
5060 }
5161
5262 @ Override
5363 public Result <List <HttpRequest <BidRequest >>> makeHttpRequests (BidRequest request ) {
5464 final List <BidderError > errors = new ArrayList <>();
5565 final List <Imp > modifiedImps = new ArrayList <>();
56- String siteNetworkId = null ;
66+ String networkId = null ;
5767
5868 for (Imp imp : request .getImp ()) {
59- if (siteNetworkId == null ) {
69+ if (networkId == null ) {
6070 try {
61- siteNetworkId = parseExtImp (imp ).getNetworkId ();
71+ networkId = parseExtImp (imp ).getNetworkId ();
6272 } catch (PreBidException e ) {
6373 errors .add (BidderError .badInput (
6474 "ignoring imp id=%s, error processing ext: %s" .formatted (
6575 imp .getId (), e .getMessage ())));
6676 }
6777 }
68-
6978 final ObjectNode modifiedExt = modifyImpExt (imp );
7079 modifiedImps .add (imp .toBuilder ().ext (modifiedExt ).build ());
7180 }
@@ -74,12 +83,21 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
7483 return Result .withErrors (errors );
7584 }
7685
77- final BidRequest outgoingRequest = request .toBuilder ()
78- .imp (modifiedImps )
79- .site (modifySite (request .getSite (), siteNetworkId , mapper ))
80- .build ();
86+ final BidRequest .BidRequestBuilder builder = request .toBuilder ().imp (modifiedImps );
8187
82- final HttpRequest <BidRequest > call = BidderUtil .defaultRequest (outgoingRequest , endpointUrl , mapper );
88+ final Site site = request .getSite ();
89+ final App app = request .getApp ();
90+
91+ if (site != null ) {
92+ builder .site (modifySite (site , networkId , mapper ));
93+ } else if (app != null ) {
94+ builder .app (modifyApp (app , networkId , mapper ));
95+ }
96+
97+ final BidRequest outgoingRequest = builder .build ();
98+
99+ final String finalEndpointUrl = replaceMacros (site , app , networkId , errors );
100+ final HttpRequest <BidRequest > call = BidderUtil .defaultRequest (outgoingRequest , finalEndpointUrl , mapper );
83101
84102 return Result .of (Collections .singletonList (call ), errors );
85103 }
@@ -92,23 +110,24 @@ private ExtImpSparteo parseExtImp(Imp imp) {
92110 }
93111 }
94112
95- private static ObjectNode modifyImpExt (Imp imp ) {
113+ private ObjectNode modifyImpExt (Imp imp ) {
96114 final ObjectNode modifiedImpExt = imp .getExt ().deepCopy ();
97115 final ObjectNode sparteoNode = modifiedImpExt .putObject ("sparteo" );
98116 final JsonNode bidderJsonNode = modifiedImpExt .remove ("bidder" );
99117 sparteoNode .set ("params" , bidderJsonNode );
100-
101118 return modifiedImpExt ;
102119 }
103120
104- private Site modifySite (Site site , String siteNetworkId , JacksonMapper mapper ) {
105- if (site == null || site . getPublisher () == null || siteNetworkId == null ) {
121+ private Site modifySite (Site site , String networkId , JacksonMapper mapper ) {
122+ if (site == null ) {
106123 return site ;
107124 }
108125
109- final Publisher originalPublisher = site .getPublisher ();
110- final ExtPublisher originalExt = originalPublisher .getExt ();
126+ final Publisher originalPublisher = site .getPublisher () != null
127+ ? site .getPublisher ()
128+ : Publisher .builder ().build ();
111129
130+ final ExtPublisher originalExt = originalPublisher .getExt ();
112131 final ExtPublisher modifiedExt = originalExt != null
113132 ? ExtPublisher .of (originalExt .getPrebid ())
114133 : ExtPublisher .empty ();
@@ -117,25 +136,164 @@ private Site modifySite(Site site, String siteNetworkId, JacksonMapper mapper) {
117136 mapper .fillExtension (modifiedExt , originalExt );
118137 }
119138
120- final JsonNode paramsProperty = modifiedExt . getProperty ( "params" );
121- final ObjectNode paramsNode ;
139+ final ObjectNode paramsNode = ensureParamsNode ( modifiedExt );
140+ paramsNode . put ( "networkId" , networkId ) ;
122141
123- if (paramsProperty != null && paramsProperty .isObject ()) {
124- paramsNode = (ObjectNode ) paramsProperty ;
125- } else {
126- paramsNode = mapper .mapper ().createObjectNode ();
127- modifiedExt .addProperty ("params" , paramsNode );
142+ final Publisher modifiedPublisher = originalPublisher .toBuilder ()
143+ .ext (modifiedExt )
144+ .build ();
145+
146+ return site .toBuilder ().publisher (modifiedPublisher ).build ();
147+ }
148+
149+ private App modifyApp (App app , String networkId , JacksonMapper mapper ) {
150+ if (app == null ) {
151+ return app ;
128152 }
129153
130- paramsNode .put ("networkId" , siteNetworkId );
154+ final Publisher originalPublisher = app .getPublisher () != null
155+ ? app .getPublisher ()
156+ : Publisher .builder ().build ();
157+
158+ final ExtPublisher originalExt = originalPublisher .getExt ();
159+ final ExtPublisher modifiedExt = originalExt != null
160+ ? ExtPublisher .of (originalExt .getPrebid ())
161+ : ExtPublisher .empty ();
162+
163+ if (originalExt != null ) {
164+ mapper .fillExtension (modifiedExt , originalExt );
165+ }
166+
167+ final ObjectNode paramsNode = ensureParamsNode (modifiedExt );
168+ paramsNode .put ("networkId" , networkId );
131169
132170 final Publisher modifiedPublisher = originalPublisher .toBuilder ()
133171 .ext (modifiedExt )
134172 .build ();
135173
136- return site .toBuilder ()
137- .publisher (modifiedPublisher )
138- .build ();
174+ return app .toBuilder ().publisher (modifiedPublisher ).build ();
175+ }
176+
177+ private ObjectNode ensureParamsNode (ExtPublisher extPublisher ) {
178+ final JsonNode paramsProperty = extPublisher .getProperty ("params" );
179+ if (paramsProperty != null && paramsProperty .isObject ()) {
180+ return (ObjectNode ) paramsProperty ;
181+ }
182+ final ObjectNode paramsNode = mapper .mapper ().createObjectNode ();
183+ extPublisher .addProperty ("params" , paramsNode );
184+ return paramsNode ;
185+ }
186+
187+ private String replaceMacros (Site site , App app , String networkId , List <BidderError > errors ) {
188+ final String siteDomain = resolveSiteDomain (site );
189+ final String appDomain = resolveAppDomain (app );
190+ final String bundle = resolveBundle (app );
191+
192+ if (site != null ) {
193+ if (UNKNOWN_VALUE .equals (siteDomain )) {
194+ errors .add (BidderError .badInput (
195+ "Domain not found. Missing the site.domain or the site.page field" ));
196+ }
197+ } else if (app != null ) {
198+ if (UNKNOWN_VALUE .equals (appDomain )) {
199+ errors .add (BidderError .badInput ("Domain not found. Missing the app.domain field." ));
200+ }
201+ }
202+
203+ if (UNKNOWN_VALUE .equals (bundle )) {
204+ errors .add (BidderError .badInput (
205+ "Bundle not found. Missing the app.bundle field." ));
206+ }
207+ return resolveEndpoint (siteDomain , appDomain , networkId , bundle );
208+ }
209+
210+ private static String normalizeHostname (String host ) {
211+ String h = StringUtils .trimToEmpty (host );
212+ if (h .isEmpty ()) {
213+ return "" ;
214+ }
215+
216+ String hostname = null ;
217+ try {
218+ hostname = new URI (h ).getHost ();
219+ } catch (URISyntaxException e ) {
220+ }
221+
222+ if (StringUtils .isNotEmpty (hostname )) {
223+ h = hostname ;
224+ } else {
225+ if (h .contains (":" )) {
226+ h = StringUtils .substringBefore (h , ":" );
227+ } else {
228+ h = StringUtils .substringBefore (h , "/" );
229+ }
230+ }
231+
232+ h = h .toLowerCase ();
233+ h = StringUtils .removeStart (h , "www." );
234+ h = StringUtils .removeEnd (h , "." );
235+
236+ return "null" .equals (h ) ? "" : h ;
237+ }
238+
239+ private String resolveSiteDomain (Site site ) {
240+ if (site != null ) {
241+ final String siteDomain = normalizeHostname (site .getDomain ());
242+ if (siteDomain != null && !siteDomain .isEmpty ()) {
243+ return siteDomain ;
244+ } else {
245+ final String fromPage = normalizeHostname (site .getPage ());
246+ if (fromPage != null && !fromPage .isEmpty ()) {
247+ return fromPage ;
248+ }
249+ }
250+ return UNKNOWN_VALUE ;
251+ }
252+
253+ return null ;
254+ }
255+
256+ private String resolveAppDomain (App app ) {
257+ if (app != null ) {
258+ final String appDomain = normalizeHostname (app .getDomain ());
259+ if (appDomain != null && !appDomain .isEmpty ()) {
260+ return appDomain ;
261+ }
262+ return UNKNOWN_VALUE ;
263+ }
264+
265+ return null ;
266+ }
267+
268+ private String resolveBundle (App app ) {
269+ if (app != null ) {
270+ final String appBundle = normalizeHostname (app .getBundle ());
271+ if (appBundle != null && !appBundle .isEmpty ()) {
272+ return appBundle ;
273+ }
274+ return UNKNOWN_VALUE ;
275+ }
276+
277+ return null ;
278+ }
279+
280+ private String resolveEndpoint (String siteDomain , String appDomain , String networkId , String bundle ) {
281+
282+ final String siteDomainQuery = StringUtils .isNotBlank (siteDomain )
283+ ? "&site_domain=" + HttpUtil .encodeUrl (siteDomain )
284+ : "" ;
285+ final String appDomainQuery = StringUtils .isNotBlank (appDomain )
286+ ? "&app_domain=" + HttpUtil .encodeUrl (appDomain )
287+ : "" ;
288+ final String bundleQuery = StringUtils .isNotBlank (bundle )
289+ ? "&bundle=" + HttpUtil .encodeUrl (bundle )
290+ : "" ;
291+
292+ return endpointUrl
293+ .replace (NETWORK_ID_MACRO , StringUtils .defaultString (networkId ))
294+ .replace (BUNDLE_QUERY_MACRO , bundleQuery )
295+ .replace (SITE_DOMAIN_QUERY_MACRO , siteDomainQuery )
296+ .replace (APP_DOMAIN_QUERY_MACRO , appDomainQuery );
139297 }
140298
141299 @ Override
0 commit comments