RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
common/bid_request.cc
00001 /* bid_request.cc
00002    Jeremy Barnes, 1 February 2012
00003    Copyright (c) 2012 Datacratic.  All rights reserved.
00004 
00005 */
00006 
00007 #include "rtbkit/common/bid_request.h"
00008 #include "jml/arch/exception.h"
00009 #include "jml/arch/format.h"
00010 #include "jml/arch/spinlock.h"
00011 
00012 #include <dlfcn.h>
00013 #include <boost/thread/locks.hpp>
00014 #include <boost/algorithm/string.hpp>
00015 #include <unordered_map>
00016 
00017 #include "jml/db/persistent.h"
00018 #include "openrtb/openrtb_parsing.h"
00019 #include "soa/types/json_printing.h"
00020 #include "soa/service/json_codec.h"
00021 
00022 
00023 using namespace std;
00024 using namespace ML;
00025 using namespace ML::DB;
00026 
00027 namespace Datacratic {
00028 
00029 using namespace RTBKIT;
00030 
00031 DefaultDescription<Location>::
00032 DefaultDescription()
00033 {
00034     this->nullAccepted = true;
00035     addField("countryCode", &Location::countryCode,
00036              "Country code of location");
00037     addField("regionCode", &Location::regionCode,
00038              "Region code of location");
00039     addField("cityName", &Location::cityName,
00040              "City name of location");
00041     addField("postalCode", &Location::postalCode,
00042              "Postal code of location");
00043     addField("dma", &Location::dma,
00044              "DMA code of location");
00045     addField("timezoneOffsetMinutes", &Location::timezoneOffsetMinutes,
00046              "Timezone offset of location in minutes");
00047 }
00048 
00049 DefaultDescription<Format>::
00050 DefaultDescription()
00051 {
00052 }
00053 
00054 struct HistoricalPositionDescriptor
00055     : public ValueDescriptionT<OpenRTB::AdPosition> {
00056     
00057     virtual void parseJsonTyped(OpenRTB::AdPosition * val,
00058                                 JsonParsingContext & context) const
00059     {
00060         if (context.isString()) {
00061             string s = context.expectStringAscii();
00062             if (s == "NONE" || s == "none") {
00063                 val->val = OpenRTB::AdPosition::UNKNOWN;
00064             }
00065             else if (s == "ABOVE_FOLD" || s == "above") {
00066                 val->val = OpenRTB::AdPosition::ABOVE;
00067             }
00068             else if (s == "BELOW_FOLD" || s == "below") {
00069                 val->val = OpenRTB::AdPosition::BELOW;
00070             }
00071             else throw ML::Exception("invalid ad position " + s);
00072         }
00073         else if (context.isNumber()) {
00074             int i = context.expectInt();
00075             val->val = i;
00076         }
00077         else throw ML::Exception("can't parse historical ad position " 
00078                                  + context.expectJson().toString());
00079     }
00080 
00081     virtual void printJsonTyped(const OpenRTB::AdPosition * val,
00082                                 JsonPrintingContext & context) const
00083     {
00084         context.writeInt(val->val);
00085     }
00086 
00087     virtual bool isDefaultTyped(const OpenRTB::AdPosition * val) const
00088     {
00089         return val->val == -1;
00090     }
00091 };
00092 
00093 DefaultDescription<AdSpot>::
00094 DefaultDescription()
00095 {
00096     addParent<OpenRTB::Impression>();
00097         
00098     addField("formats", &AdSpot::formats, "Impression formats");
00099     addField<OpenRTB::AdPosition>("position", &AdSpot::position, "Impression fold position",
00100                                   new HistoricalPositionDescriptor());
00101     addField("reservePrice", &AdSpot::reservePrice, "Impression reserve price");
00102 
00103     //throw ML::Exception("Need to sync formats with the bid request");
00104 }
00105 
00106 DefaultDescription<BidRequest>::
00107 DefaultDescription()
00108 {
00109     onUnknownField = [=] (BidRequest * br, JsonParsingContext & context)
00110         {
00111             //context.skip();
00112             if(context.printPath().find("!!CV") != std::string::npos)
00113             {
00114                context.skip();
00115             }
00116             else
00117             {
00118                cerr << "(default description)got unknown field " << context.printPath() << endl;
00119             std::function<Json::Value & (int, Json::Value &)> getEntry
00120             = [&] (int n, Json::Value & curr) -> Json::Value &
00121             {
00122                 if (n == context.path.size())
00123                     return curr;
00124                 else if (context.path[n].index != -1)
00125                     return getEntry(n + 1, curr[context.path[n].index]);
00126                 else return getEntry(n + 1, curr[context.path[n].key]);
00127             };
00128 
00129             getEntry(0, br->unparseable)
00130             = context.expectJson();
00131             }
00132         };
00133     addField("id", &BidRequest::auctionId, "Exchange auction ID");
00134     addField("timestamp", &BidRequest::timestamp, "Bid request timestamp");
00135     addField("isTest", &BidRequest::isTest, "Is bid request a test?");
00136     addField("url", &BidRequest::url, "Site URL");
00137     addField("ipAddress", &BidRequest::ipAddress, "IP address of user");
00138     addField("userAgent", &BidRequest::userAgent, "User agent of device");
00139     addField("language", &BidRequest::language, "User language code");
00140     addField("protocolVersion", &BidRequest::protocolVersion,
00141              "bid request protocol version");
00142     addField("exchange", &BidRequest::exchange, "Original bid request exchagne");
00143     addField("provider", &BidRequest::provider, "Bid request provider");
00144     addField("winSurcharges", &BidRequest::winSurcharges,
00145              "extra amounts paid on win");
00146     addField("meta", &BidRequest::meta,
00147              "extra metadata about the bid request");
00148     addField("location", &BidRequest::location,
00149              "location of user");
00150     addField("segments", &BidRequest::segments,
00151              "segments active for user");
00152     addField("restrictions", &BidRequest::restrictions,
00153              "restrictions active for bid request");
00154     addField("userIds", &BidRequest::userIds, "User IDs for this user");
00155     addField("imp", &BidRequest::imp, "Ad imp in this request");
00156     addField("spots", &BidRequest::imp, "Ad imp in this request");
00157     addField("site", &BidRequest::site, "OpenRTB site object");
00158     addField("app", &BidRequest::app, "OpenRTB app object");
00159     addField("device", &BidRequest::device, "OpenRTB device object");
00160     addField("user", &BidRequest::user, "OpenRTB user object");
00161     addField("unparseable", &BidRequest::unparseable, "Unparseable fields are stored here");
00162     addField("bidCurrency", &BidRequest::bidCurrency, "Currency we're bidding in");
00163 }
00164 
00165 } // namespace Datacratic
00166 
00167 
00168 namespace RTBKIT {
00169 
00170 using Datacratic::jsonDecode;
00171 using Datacratic::jsonEncode;
00172 
00173 /*****************************************************************************/
00174 /* FORMAT                                                                    */
00175 /*****************************************************************************/
00176 
00177 void
00178 Format::
00179 fromJson(const Json::Value & val)
00180 {
00181     if (val.isString()) {
00182         string s = val.asString();
00183         fromString(s);
00184 #if 0
00185         int width, height;
00186         int nchars = -1;
00187         int res = sscanf(s.c_str(), "%dx%d%n", &width, &height, &nchars);
00188         if ((res != 2 && res != 3) || nchars != s.length())
00189             throw ML::Exception("couldn't parse format string "
00190                                 + s);
00191         this->width = width;
00192         this->height = height;
00193 #endif
00194     } else if (val.isArray()) {
00195         throw ML::Exception("array format parsing not done yet");
00196     }
00197     else if (val.isObject()) {
00198         width = val["width"].asInt();
00199         height = val["height"].asInt();
00200     }
00201     else throw ML::Exception("couldn't parse format string " + val.toString());
00202 }
00203 
00204 void
00205 Format::
00206 fromString(const string &s)
00207 {
00208     int width, height;
00209     int nchars = -1;
00210     int res = sscanf(s.c_str(), "%dx%d%n", &width, &height, &nchars);
00211     if ((res != 2 && res != 3) || nchars != s.length())
00212         throw ML::Exception("couldn't parse format string "
00213                             + s);
00214     this->width = width;
00215     this->height = height;
00216 }
00217 
00218 Json::Value
00219 Format::
00220 toJson() const
00221 {
00222     return print();
00223 }
00224 
00225 std::string
00226 Format::
00227 toJsonStr() const
00228 {
00229     return print();
00230 }
00231 
00232 std::string
00233 Format::
00234 print() const
00235 {
00236     return ML::format("%dx%d", width, height);
00237 }
00238 
00239 void
00240 Format::
00241 serialize(ML::DB::Store_Writer & store) const
00242 {
00243     store << compact_size_t(width) << compact_size_t(height);
00244 }
00245 
00246 void
00247 Format::
00248 reconstitute(ML::DB::Store_Reader & store)
00249 {
00250     width = compact_size_t(store);
00251     height = compact_size_t(store);
00252 }
00253 
00254 
00255 /*****************************************************************************/
00256 /* FORMAT SET                                                                */
00257 /*****************************************************************************/
00258 
00259 void
00260 FormatSet::
00261 fromJson(const Json::Value & val)
00262 {
00263     clear();
00264     if (val.isString()) {
00265         Format f;
00266         f.fromJson(val);
00267         push_back(f);
00268         return;
00269     }
00270     else if (val.isArray()) {
00271         for (unsigned i = 0;  i < val.size();  ++i) {
00272             Format f;
00273             f.fromJson(val[i]);
00274             push_back(f);
00275         }
00276     }
00277     else throw ML::Exception("couldn't parse format set from JSON "
00278                              + val.toString());
00279 }
00280 
00281 Json::Value
00282 FormatSet::
00283 toJson() const
00284 {
00285     Json::Value result;
00286     if (empty()) return result;
00287     //if (size() == 1)
00288     //    return result = at(0).toJson();
00289     for (unsigned i = 0;  i < size();  ++i)
00290         result[i] = at(i).toJson();
00291     return result;
00292 }
00293 
00294 std::string
00295 FormatSet::
00296 toJsonStr() const
00297 {
00298     return boost::trim_copy(toJson().toString());
00299 }
00300 
00301 std::string
00302 FormatSet::
00303 print() const
00304 {
00305     if (empty()) return "[]";
00306     else if (size() == 1)
00307         return at(0).print();
00308     else {
00309         string result = "[";
00310         for (unsigned i = 0;  i < size();  ++i) {
00311             if (i != 0) result += ", ";
00312             result += at(i).print();
00313         }
00314         result += ']';
00315         return result;
00316     }
00317 }
00318 
00319 void
00320 FormatSet::
00321 sort()
00322 {
00323     std::sort(begin(), end());
00324 }
00325 
00326 bool isEmpty(const std::string & str)
00327 {
00328     return str.empty();
00329 }
00330 
00331 bool isEmpty(const Json::Value & val)
00332 {
00333     return val.isNull();
00334 }
00335 
00336 bool isEmpty(const Utf8String &str)
00337 {
00338     return (str.rawLength() == 0) ;
00339 }
00340 
00341 template<typename T>
00342 void addIfNotEmpty(Json::Value & obj, const std::string & key,
00343                    const T & val)
00344 {
00345     if (isEmpty(val)) return;
00346     obj[key] = val;
00347 }
00348 
00349 template<typename T>
00350 void addIfNotEmpty(Json::Value & obj, const std::string & key,
00351                    const T & val, const T & emptyVal)
00352 {
00353     if (val == emptyVal) return;
00354     obj[key] = val;
00355 }
00356 
00357 inline void addIfNotEmpty(Json::Value & obj, const std::string & key,
00358                           const Url & url)
00359 {
00360     if (url.empty()) return;
00361     obj[key] = url.toString();
00362 }
00363 
00364 inline void addIfNotEmpty(Json::Value & obj, const std::string & key,
00365                           const std::vector<CurrencyCode> & curr)
00366 {
00367     if (curr.empty())
00368         return;
00369 
00370     for (unsigned i = 0;  i < curr.size();  ++i)
00371         obj[key][i] = Amount::getCurrencyStr(curr[i]);
00372 }
00373 
00374 void
00375 FormatSet::
00376 serialize(ML::DB::Store_Writer & store) const
00377 {
00378     store << ML::compact_vector<Format, 3, uint16_t>(*this);
00379 }
00380 
00381 void
00382 FormatSet::
00383 reconstitute(ML::DB::Store_Reader & store)
00384 {
00385     ML::compact_vector<Format, 3, uint16_t> & v = *this;
00386     store >> v;
00387 }
00388 
00389 struct FormatSetDescription
00390     : public ValueDescriptionT<FormatSet> {
00391 
00392     virtual void parseJsonTyped(FormatSet * val,
00393                                 JsonParsingContext & context) const
00394     {
00395         val->fromJson(context.expectJson());
00396     }
00397 
00398     virtual void printJsonTyped(const FormatSet * val,
00399                                 JsonPrintingContext & context) const
00400     {
00401         context.writeJson(val->toJson());
00402     }
00403 
00404     virtual bool isDefaultTyped(const FormatSet * val) const
00405     {
00406         return val->empty();
00407     }
00408 };
00409 
00410 ValueDescriptionT<RTBKIT::FormatSet> *
00411 getDefaultDescription(RTBKIT::FormatSet *)
00412 {
00413     return new FormatSetDescription();
00414 }
00415 
00416 
00417 
00418 /*****************************************************************************/
00419 /* LOCATION                                                                  */
00420 /*****************************************************************************/
00421 
00422 Utf8String
00423 Location::
00424 fullLocationString() const
00425 {
00426     Utf8String result(countryCode + ":" + regionCode + ":") ;
00427     result += cityName ;
00428     result += (":" + postalCode + ":" + boost::lexical_cast<string>(dma));
00429     return result;
00430     //Utf8String result(countryCode +":"+ regionCode +":" +
00431  //   return ML::format("%s:%s:%s:%s:%d",
00432  //                     countryCode.c_str(), regionCode.c_str(),
00433  //                     cityName.c_str(), postalCode.c_str(), dma);
00434 }
00435 
00436 Json::Value
00437 Location::
00438 toJson() const
00439 {
00440     Json::Value result;
00441     addIfNotEmpty(result, "countryCode",  countryCode);
00442     addIfNotEmpty(result, "regionCode",   regionCode);
00443     addIfNotEmpty(result, "cityName",     std::string(cityName.rawData(),cityName.rawLength()));
00444     addIfNotEmpty(result, "postalCode",   postalCode);
00445     addIfNotEmpty(result, "dma",          dma, -1);
00446     addIfNotEmpty(result, "timezoneOffsetMinutes", timezoneOffsetMinutes, -1);
00447     return result;
00448 }
00449 
00450 std::string
00451 Location::
00452 toJsonStr() const
00453 {
00454     return boost::trim_copy(toJson().toString());
00455 }
00456 
00457 Location
00458 Location::
00459 createFromJson(const Json::Value & json)
00460 {
00461     Location result;
00462 
00463     for (auto it = json.begin(), end = json.end(); it != end;  ++it) {
00464         if (it.memberName() == "countryCode")
00465             result.countryCode = it->asString();
00466         else if (it.memberName() == "regionCode")
00467             result.regionCode = it->asString();
00468         else if (it.memberName() == "cityName")
00469             result.cityName = it->asString();
00470         else if (it.memberName() == "postalCode")
00471             result.postalCode = it->asString();
00472         else if (it.memberName() == "dma")
00473             result.dma = it->asInt();
00474         else if (it.memberName() == "timezoneOffsetMinutes")
00475             result.timezoneOffsetMinutes = it->asInt();
00476         else throw ML::Exception("unknown location field " + it.memberName());
00477     }
00478     return result;
00479 }
00480 
00481 void
00482 Location::
00483 serialize(ML::DB::Store_Writer & store) const
00484 {
00485     unsigned char version = 0;
00486     store << version << countryCode << regionCode << cityName << postalCode
00487           << compact_size_t(dma) << compact_size_t(timezoneOffsetMinutes);
00488 }
00489 
00490 void
00491 Location::
00492 reconstitute(ML::DB::Store_Reader & store)
00493 {
00494     unsigned char version;
00495     store >> version;
00496     if (version != 0)
00497         throw ML::Exception("invalid Location version");
00498     store >> countryCode >> regionCode >> cityName >> postalCode;
00499     dma = compact_size_t(store);
00500     timezoneOffsetMinutes = compact_size_t(store);
00501 }
00502 
00503 
00504 /*****************************************************************************/
00505 /* AD SPOT                                                                   */
00506 /*****************************************************************************/
00507 
00508 namespace {
00509 
00510 SmallIntVector getDims(const Json::Value & val)
00511 {
00512     SmallIntVector result;
00513 
00514     if (val.isArray()) {
00515         for (unsigned i = 0;  i < val.size();  ++i)
00516             result.push_back(val[i].asInt());
00517     }
00518     else result.push_back(val.asInt());
00519 
00520     return result;
00521 }
00522 
00523 } // file scope
00524 
00525 #if 0
00526 AdSpot::Position
00527 AdSpot::stringToPosition(const std::string &pos)
00528 {
00529     if (pos == "None" || pos == "NONE" || pos == "none")
00530         return AdSpot::Position::NONE;
00531     else if (pos == "ABOVE_FOLD" || pos == "above")
00532         return AdSpot::Position::ABOVE_FOLD;
00533     else if (pos == "BELOW_FOLD" || pos == "below")
00534         return AdSpot::Position::BELOW_FOLD;
00535     else
00536         throw ML::Exception(" Unknown value for AdSpot::Position ==>" + pos);
00537 }
00538 #endif
00539 
00540 void
00541 AdSpot::
00542 fromJson(const Json::Value & val)
00543 {
00544     *this = AdSpot();
00545 
00546     // Parse openrtb
00547     static DefaultDescription<AdSpot> desc;
00548     StructuredJsonParsingContext context(val);
00549 
00550     // Rather than barf on unknown fields, for forwards compatibility we put them
00551     // in the unparseable array via this function
00552     auto onUnknownField = [&] ()
00553         {
00554             cerr << "(adspot)got unknown field " << context.printPath()
00555             << context.expectJson() << endl;
00556             
00557 #if 0
00558             std::function<Json::Value & (int, Json::Value &)> getEntry
00559             = [&] (int n, Json::Value & curr) -> Json::Value &
00560             {
00561                 if (n == context.path.size())
00562                     return curr;
00563                 else if (context.path[n].index != -1)
00564                     return getEntry(n + 1, curr[context.path[n].index]);
00565                 else return getEntry(n + 1, curr[context.path[n].key]);
00566             };
00567 
00568             getEntry(0, unparseable[key]) = context.expectJson();
00569 #endif
00570         };
00571 
00572     context.onUnknownFieldHandlers.push_back(onUnknownField);
00573 
00574     desc.parseJsonTyped(this, context);
00575     
00576     return;
00577 
00578     try {
00579         id = Id(val["id"].asString());
00580         if (val.isMember("formats")) {
00581             formats.fromJson(val["formats"]);
00582         }
00583         else {
00584             SmallIntVector widths = getDims(val["width"]);
00585             SmallIntVector heights = getDims(val["height"]);
00586             formats.clear();
00587             if (widths.size() != heights.size())
00588                 throw ML::Exception("widths and heights must have same size");
00589             for (unsigned i = 0;  i < widths.size();  ++i)
00590                 formats.push_back(Format(widths[i], heights[i]));
00591         }
00592         const Json::Value & rpj = val["reservePrice"];
00593         if (rpj.isNumeric()) {
00594             reservePrice = USD_CPM(rpj.asDouble());
00595         }
00596         else if (!rpj.isNull()) {
00597             reservePrice = Amount::fromJson(rpj);
00598         }
00599         if (val.isMember("position")) {
00600             auto & pj = val["position"];
00601             if (pj.isString()) {
00602                 string s = pj.asString();
00603                 if (s == "NONE" || s == "none") {
00604                     position.val = OpenRTB::AdPosition::UNKNOWN;
00605                 }
00606                 else if (s == "ABOVE_FOLD" || s == "above") {
00607                     position.val = OpenRTB::AdPosition::ABOVE;
00608                 }
00609                 else if (s == "BELOW_FOLD" || s == "below") {
00610                     position.val = OpenRTB::AdPosition::BELOW;
00611                 }
00612             }
00613             else if (pj.isIntegral()) {
00614                 position.val = pj.asInt();
00615             }
00616             else throw ML::Exception("can't parse position " + val["position"].toString());
00617             //position = stringToPosition(val["position"].asString());
00618         }
00619         else
00620             position.val = -1;//OpenRTB::AdPosition::UNKNOWN;
00621     } catch (const std::exception & exc) {
00622         cerr << "parsing AdSpot " << val << ": " << exc.what() << endl;
00623         throw;
00624     }
00625 }
00626 
00627 Json::Value
00628 AdSpot::
00629 toJson() const
00630 {
00631     static DefaultDescription<AdSpot> desc;
00632     StructuredJsonPrintingContext context;
00633     desc.printJsonTyped(this, context);
00634     return std::move(context.output);
00635 }
00636 
00637 std::string formatDims(const SmallIntVector & dims)
00638 {
00639     if (dims.size() == 1)
00640         return ML::format("%d", (int)dims[0]);
00641 
00642     string result = "[";
00643     for (unsigned i = 0;  i < dims.size();  ++i) {
00644         result += ML::format("%d", (int)dims[i]);
00645         if (i != dims.size() - 1)
00646             result += ',';
00647     }
00648     return result + "]";
00649 }
00650 
00651 std::string
00652 AdSpot::
00653 format() const
00654 {
00655     return formats.print();
00656 }
00657 
00658 std::string
00659 AdSpot::
00660 firstFormat() const
00661 {
00662     return formats[0].print();
00663 }
00664 
00665 AdSpot
00666 AdSpot::
00667 createFromJson(const Json::Value & json)
00668 {
00669     AdSpot result;
00670     result.fromJson(json);
00671     return result;
00672 }
00673 
00674 void
00675 AdSpot::
00676 serialize(ML::DB::Store_Writer & store) const
00677 {
00678     unsigned char version = 2;
00679     store << version << toJson().toString();
00680 }
00681 
00682 void
00683 AdSpot::
00684 reconstitute(ML::DB::Store_Reader & store)
00685 {
00686     unsigned char version;
00687     store >> version;
00688     if (version != 2)
00689         throw ML::Exception("unknown AdSpot serialization version");
00690     string s;
00691     store >> s;
00692     fromJson(Json::parse(s));
00693 }
00694 
00695 
00696 /*****************************************************************************/
00697 /* USER IDS                                                                  */
00698 /*****************************************************************************/
00699 
00700 void
00701 UserIds::
00702 add(const Id & id, IdDomain domain)
00703 {
00704     if (!insert(make_pair(domainToString(domain), id)).second)
00705         throw ML::Exception("attempt to double add id %s for %s",
00706                             id.toString().c_str(), domainToString(domain));
00707     setStatic(id, domain);
00708 }
00709 
00710 void
00711 UserIds::
00712 add(const Id & id, const std::string & domain1, IdDomain domain2)
00713 {
00714     add(id, domain1);
00715     add(id, domain2);
00716 }
00717 
00718 void
00719 UserIds::
00720 add(const Id & id, const std::string & domain)
00721 {
00722     if (!insert(make_pair(domain, id)).second)
00723         throw ML::Exception("attempt to double add id " + id.toString() +" for " + domain);
00724     setStatic(id, domain);
00725 }
00726 
00727 const char *
00728 UserIds::
00729 domainToString(IdDomain domain)
00730 {
00731     switch (domain) {
00732     case ID_PROVIDER:   return "prov";
00733     case ID_EXCHANGE:   return "xchg";
00734     default:            return "<<<UNKNOWN>>>";
00735     }
00736 }
00737 
00738 void
00739 UserIds::
00740 setStatic(const Id & id, const std::string & domain)
00741 {
00742     if (domain == "prov")
00743         providerId = id;
00744     else if (domain == "xchg")
00745         exchangeId = id;
00746 }
00747 
00748 void
00749 UserIds::
00750 setStatic(const Id & id, IdDomain domain)
00751 {
00752     switch (domain) {
00753     case ID_PROVIDER:   providerId = id;  break;
00754     case ID_EXCHANGE:   exchangeId = id;  break;
00755     default: break;
00756     }
00757 }
00758 
00759 void
00760 UserIds::
00761 set(const Id & id, const std::string & domain)
00762 {
00763     (*this)[domain] = id;
00764 }
00765 
00766 Json::Value
00767 UserIds::
00768 toJson() const
00769 {
00770     Json::Value result;
00771     for (auto it = begin(), end = this->end();  it != end;  ++it)
00772         result[it->first] = it->second.toString();
00773     return result;
00774 }
00775 
00776 std::string
00777 UserIds::
00778 toJsonStr() const
00779 {
00780     return boost::trim_copy(toJson().toString());
00781 }
00782 
00783 UserIds
00784 UserIds::
00785 createFromJson(const Json::Value & json)
00786 {
00787     UserIds result;
00788 
00789     for (auto it = json.begin(), end = json.end(); it != end;  ++it) {
00790         Id id(it->asString());
00791         result.add(id, it.memberName());
00792     }
00793 
00794     return result;
00795 }
00796 
00797 std::string
00798 UserIds::
00799 serializeToString() const
00800 {
00801     // TODO: do a better job...
00802     return toJsonStr();
00803 }
00804 
00805 UserIds
00806 UserIds::
00807 createFromString(const std::string & str)
00808 {
00809     // TODO: do a better job...
00810     return createFromJson(Json::parse(str));
00811 }
00812 
00813 void
00814 UserIds::
00815 serialize(ML::DB::Store_Writer & store) const
00816 {
00817     unsigned char version = 0;
00818     store << version << (map<std::string, Id> &)(*this);
00819 }
00820 
00821 void
00822 UserIds::
00823 reconstitute(ML::DB::Store_Reader & store)
00824 {
00825     unsigned char version;
00826     store >> version;
00827     if (version != 0)
00828         throw ML::Exception("invalid UserIds version");
00829     store >> (map<std::string, Id> &)*this;
00830 }
00831 
00832 struct UserIdsDescription
00833     : public ValueDescriptionT<UserIds> {
00834 
00835     virtual void parseJsonTyped(UserIds * val,
00836                                 JsonParsingContext & context) const
00837     {
00838         auto onMember = [&] ()
00839             {
00840                 string key = context.path.fieldName();
00841                 Id value(context.expectStringAscii());
00842                 val->add(value, key);
00843             };
00844         
00845         context.forEachMember(onMember);
00846     }
00847 
00848     virtual void printJsonTyped(const UserIds * val,
00849                                 JsonPrintingContext & context) const
00850     {
00851         context.startObject();
00852 
00853         for (auto & id: *val) {
00854             context.startMember(id.first);
00855             context.writeString(id.second.toString());
00856         }
00857 
00858         context.endObject();
00859     }
00860 
00861     virtual bool isDefaultTyped(const UserIds * val) const
00862     {
00863         return val->empty();
00864     }
00865 
00866 };
00867 
00868 ValueDescriptionT<RTBKIT::UserIds> *
00869 getDefaultDescription(RTBKIT::UserIds *)
00870 {
00871     return new UserIdsDescription();
00872 }
00873 
00874 
00875 /*****************************************************************************/
00876 /* BID REQUEST                                                               */
00877 /*****************************************************************************/
00878 
00879 void
00880 BidRequest::
00881 sortAll()
00882 {
00883     restrictions.sortAll();
00884     segments.sortAll();
00885 }
00886 
00887 template<typename T>
00888 void toJsonValue(Json::Value & v, const T & val)
00889 {
00890     static DefaultDescription<T> desc;
00891     StructuredJsonPrintingContext printContext;
00892     desc.printJson(&val, printContext);
00893     v = std::move(printContext.output);
00894 }
00895 
00896 Json::Value
00897 BidRequest::
00898 toJson() const
00899 {
00900     Json::Value result;
00901     result["!!CV"] = "RTBKIT-JSON-1.0";
00902     result["id"] = auctionId.toString();
00903     result["timestamp"] = timestamp.secondsSinceEpoch();
00904     addIfNotEmpty(result, "isTest", isTest, false);
00905     addIfNotEmpty(result, "url", url);
00906     addIfNotEmpty(result, "ipAddress", ipAddress);
00907     addIfNotEmpty(result, "userAgent", userAgent);
00908     addIfNotEmpty(result, "language", language);
00909     addIfNotEmpty(result, "protocolVersion", protocolVersion);
00910     addIfNotEmpty(result, "exchange", exchange);
00911     addIfNotEmpty(result, "provider", provider);
00912     addIfNotEmpty(result, "meta", meta);
00913     addIfNotEmpty(result, "unparseable", unparseable);
00914     if (!bidCurrency.empty())
00915         result["bidCurrency"] = jsonEncode(bidCurrency);
00916    
00917     if (site) {
00918         toJsonValue(result["site"], *site);
00919     }
00920     if (app)
00921         toJsonValue(result["app"], *app);
00922     if (device)
00923         toJsonValue(result["device"], *device);
00924     if (user)
00925         toJsonValue(result["user"], *user);
00926 
00927     if (!winSurcharges.empty())
00928         result["winSurcharges"] = winSurcharges.toJson();
00929 
00930     result["location"] = location.toJson();
00931 
00932     if (!imp.empty()) {
00933         for (unsigned i = 0;  i < imp.size();  ++i) {
00934             result["imp"][i] = imp[i].toJson();
00935             result["spots"][i] = imp[i].toJson();
00936         }
00937     }
00938 
00939     if (!segments.empty())
00940         result["segments"] = segments.toJson();
00941     if (!restrictions.empty())
00942         result["restrictions"] = restrictions.toJson();
00943     if (!userIds.empty())
00944         result["userIds"] = userIds.toJson();
00945 
00946     return result;
00947 }
00948 
00949 std::string
00950 BidRequest::
00951 toJsonStr() const
00952 {
00953     static const DefaultDescription<BidRequest> BidRequestDesc;
00954     
00955     std::ostringstream stream;
00956     StreamJsonPrintingContext context(stream);
00957     BidRequestDesc.printJson(this, context);
00958     return stream.str();
00959 
00960     //return boost::trim_copy(toJson().toString());
00961 }
00962 
00963 template<typename T>
00964 void fromJsonOptional(const Json::Value & val,
00965                       std::unique_ptr<T> & ptr,
00966                       Json::Value & unparseable,
00967                       std::string key)
00968 {
00969     static DefaultDescription<T> desc;
00970     StructuredJsonParsingContext context(val);
00971     std::unique_ptr<T> res(new T());
00972 
00973     // Rather than barf on unknown fields, for forwards compatibility we put them
00974     // in the unparseable array via this function
00975     auto onUnknownField = [&] ()
00976         {
00977             std::function<Json::Value & (int, Json::Value &)> getEntry
00978             = [&] (int n, Json::Value & curr) -> Json::Value &
00979             {
00980                 if (n == context.path.size())
00981                     return curr;
00982                 else if (context.path[n].index != -1)
00983                     return getEntry(n + 1, curr[context.path[n].index]);
00984                 else return getEntry(n + 1, curr[context.path[n].key]);
00985             };
00986 
00987             getEntry(0, unparseable[key]) = context.expectJson();
00988         };
00989 
00990     context.onUnknownFieldHandlers.push_back(onUnknownField);
00991 
00992     desc.parseJson(res.get(), context);
00993 
00994     ptr.swap(res);
00995 }
00996 
00997 BidRequest
00998 BidRequest::
00999 createFromJson(const Json::Value & json)
01000 {
01001     BidRequest result;
01002 
01003     string canonicalVersion;
01004 
01005     //cerr << "parsing " << json << endl;
01006 
01007     for (auto it = json.begin(), end = json.end(); it != end;  ++it) {
01008 
01009         //cerr << "got member " << it.memberName() << endl;
01010 
01011         if (it.memberName() == "!!CV") {
01012             canonicalVersion = it->asString();
01013             if (canonicalVersion != "0.1" && canonicalVersion != "RTBKIT-JSON-1.0")
01014                 throw ML::Exception("can't parse BidRequest with CV "
01015                                     + canonicalVersion);
01016         }
01017         else if (it.memberName() == "id")
01018             result.auctionId.parse(it->asString());
01019         else if (it.memberName() == "timestamp")
01020             result.timestamp = Date::fromSecondsSinceEpoch(it->asDouble());
01021         else if (it.memberName() == "isTest")
01022             result.isTest = it->asBool();
01023         else if (it.memberName() == "url")
01024             result.url = Url(it->asString());
01025         else if (it.memberName() == "ipAddress")
01026             result.ipAddress = it->asString();
01027         else if (it.memberName() == "userAgent")
01028             result.userAgent = it->asString();
01029         else if (it.memberName() == "language")
01030             result.language = it->asString();
01031         else if (it.memberName() == "protocolVersion")
01032             result.protocolVersion = it->asString();
01033         else if (it.memberName() == "exchange")
01034             result.exchange = it->asString();
01035         else if (it.memberName() == "provider")
01036             result.provider = it->asString();
01037         else if (it.memberName() == "winSurchageMicros")
01038             result.winSurcharges["surcharge"] += MicroUSD(it->asInt());
01039         else if (it.memberName() == "winSurcharges")
01040             result.winSurcharges += LineItems::fromJson(*it);
01041         else if (it.memberName() == "meta")
01042             result.meta = *it;
01043         else if (it.memberName() == "creative")
01044             result.meta["creative"] = *it;
01045         else if (it.memberName() == "location")
01046             result.location = Location::createFromJson(*it);
01047         else if (it.memberName() == "segments")
01048             result.segments = SegmentsBySource::createFromJson(*it);
01049         else if (it.memberName() == "restrictions")
01050             result.restrictions = SegmentsBySource::createFromJson(*it);
01051         else if (it.memberName() == "userIds")
01052             result.userIds = UserIds::createFromJson(*it);
01053         else if (it.memberName() == "imp" || it.memberName() == "spots") {
01054             const Json::Value & json = *it;
01055             if (!json.empty() && !json.isArray())
01056                 throw ML::Exception("imp is not an array");
01057             result.imp.clear();
01058             for (unsigned i = 0;  i < json.size();  ++i) {
01059                 result.imp.push_back(AdSpot::createFromJson(json[i]));
01060             }
01061         }
01062         else if (it.memberName() == "site") {
01063             fromJsonOptional(*it, result.site, result.unparseable, "site");
01064         }
01065         else if (it.memberName() == "app") {
01066             fromJsonOptional(*it, result.app, result.unparseable, "app");
01067         }
01068         else if (it.memberName() == "device") {
01069             fromJsonOptional(*it, result.device, result.unparseable, "device");
01070         }
01071         else if (it.memberName() == "user") {
01072             fromJsonOptional(*it, result.user, result.unparseable, "user");
01073         }
01074         else if (it.memberName() == "unparseable")
01075             result.unparseable = *it;
01076         else if (it.memberName() == "bidCurrency")
01077             jsonDecode(*it, result.bidCurrency);
01078         else throw ML::Exception("unknown canonical bid request field "
01079                                  + it.memberName());
01080     }
01081 
01082     return result;
01083 }
01084 
01085 namespace {
01086 typedef std::unordered_map<std::string, BidRequest::Parser> Parsers;
01087 static Parsers parsers;
01088 typedef boost::lock_guard<ML::Spinlock> Guard;
01089 static ML::Spinlock lock;
01090 
01091 BidRequest::Parser getParser(std::string const & source) {
01092     // see if it's already existing
01093     {
01094         Guard guard(lock);
01095         auto i = parsers.find(source);
01096         if (i != parsers.end()) return i->second;
01097     }
01098 
01099     // else, try to load the parser library
01100     std::string path = "lib" + source + "_bid_request.so";
01101     void * handle = dlopen(path.c_str(), RTLD_NOW);
01102     if (!handle) {
01103         throw ML::Exception("couldn't find bid request parser library " + path);
01104     }
01105 
01106     // if it went well, it should be registered now
01107     Guard guard(lock);
01108     auto i = parsers.find(source);
01109     if (i != parsers.end()) return i->second;
01110 
01111     throw ML::Exception("couldn't find bid request parser for source " + source);
01112 }
01113 
01114 } // file scope
01115 
01116 void
01117 BidRequest::
01118 registerParser(const std::string & source, Parser parser)
01119 {
01120     Guard guard(lock);
01121     if (!parsers.insert(make_pair(source, parser)).second)
01122         throw ML::Exception("already had a bid request parser registered");
01123 }
01124 
01125 namespace {
01126 
01127 static const DefaultDescription<BidRequest> BidRequestDesc;
01128 
01129 struct CanonicalParser {
01130     
01131     static BidRequest * parse(const std::string & str)
01132     {
01133 #if 0 // old and slow
01134         auto j = Json::parse(str);
01135         return new BidRequest(BidRequest::createFromJson(j));
01136 #endif
01137 
01138         //cerr << "parsing " << str << endl;
01139         StreamingJsonParsingContext context;
01140         context.init("bid request", str.c_str(), str.size());
01141         auto_ptr<BidRequest> result(new BidRequest());
01142         BidRequestDesc.parseJsonTyped(result.get(), context);
01143 
01144         //cerr << "result->url = " << result->url << endl;
01145         //cerr << "result->userAgent = " << result->userAgent << endl;
01146 
01147         return result.release();
01148     }
01149 };
01150 
01151 struct AtInit {
01152     AtInit()
01153     {
01154         BidRequest::registerParser("recoset", CanonicalParser::parse);
01155         BidRequest::registerParser("datacratic", CanonicalParser::parse);
01156         BidRequest::registerParser("rtbkit", CanonicalParser::parse);
01157     }
01158 } atInit;
01159 } // file scope
01160 
01161 BidRequest *
01162 BidRequest::
01163 parse(const std::string & source, const std::string & bidRequest)
01164 {
01165     if (source.empty()) {
01166         throw ML::Exception("'source' parameter cannot be empty");
01167     }
01168 
01169     if (source == "datacratic" || strncmp(bidRequest.c_str(), "{\"!!CV\":", 8) == 0)
01170     {
01171         return CanonicalParser::parse(bidRequest);
01172     }
01173     Parser parser = getParser(source);
01174 
01175     //cerr << "got parser for source " << source << endl;
01176 
01177     auto result = parser(bidRequest);
01178 
01179     if (false) {
01180         cerr << bidRequest << endl;
01181         StreamJsonPrintingContext context(cerr);
01182         BidRequestDesc.printJsonTyped(result, context);
01183     }
01184 
01185     return result;
01186 }
01187 
01188 BidRequest *
01189 BidRequest::
01190 parse(const std::string & source, const Utf8String & bidRequest)
01191 {
01192     return BidRequest::parse(source, string(bidRequest.rawData(), bidRequest.rawLength()));
01193 }
01194 
01195 SegmentResult
01196 BidRequest::
01197 segmentPresent(const std::string & source,
01198                const std::string & segment) const
01199 {
01200     auto it = segments.find(source);
01201     if (it == segments.end())
01202         return SEG_MISSING;
01203     return (it->second->contains(segment)
01204             ? SEG_PRESENT : SEG_NOT_PRESENT);
01205 }
01206 
01207 SegmentResult
01208 BidRequest::
01209 segmentPresent(const std::string & source, int segment) const
01210 {
01211     auto it = segments.find(source);
01212     if (it == segments.end())
01213         return SEG_MISSING;
01214     return (it->second->contains(segment)
01215             ? SEG_PRESENT : SEG_NOT_PRESENT);
01216 }
01217 
01218 Id
01219 BidRequest::
01220 getUserId(IdDomain domain) const
01221 {
01222     switch (domain) {
01223     case ID_PROVIDER:   return userIds.providerId;
01224     case ID_EXCHANGE:   return userIds.exchangeId;
01225     default:            throw ML::Exception("unknown ID for getUserId");
01226     }
01227 }
01228 
01229 Id
01230 BidRequest::
01231 getUserId(const std::string & domain) const
01232 {
01233     auto it = userIds.find(domain);
01234     if (it == userIds.end())
01235         return Id();
01236     return it->second;
01237 }
01238 
01239 std::string
01240 BidRequest::
01241 serializeToString() const
01242 {
01243     ostringstream stream;
01244     DB::Store_Writer store(stream);
01245     serialize(store);
01246     return stream.str();
01247 }
01248 
01249 BidRequest
01250 BidRequest::
01251 createFromString(const std::string & str)
01252 {
01253     DB::Store_Reader store(str.c_str(), str.size());
01254     BidRequest result;
01255     result.reconstitute(store);
01256     return result;
01257 }
01258 
01259 inline ML::DB::Store_Writer &
01260 operator << (ML::DB::Store_Writer & store, const Json::Value & val)
01261 {
01262     return store << val.toString();
01263 }
01264 
01265 inline ML::DB::Store_Reader &
01266 operator >> (ML::DB::Store_Reader & store, Json::Value & val)
01267 {
01268     string s;
01269     store >> s;
01270     val = Json::parse(s);
01271     return store;
01272 }
01273 
01274 
01275 void
01276 BidRequest::
01277 serialize(ML::DB::Store_Writer & store) const
01278 {
01279     using namespace ML::DB;
01280     unsigned char version = 2;
01281     store << version << auctionId << language << protocolVersion
01282           << exchange << provider << timestamp << isTest
01283           << location << userIds << imp << url << ipAddress << userAgent
01284           << restrictions << segments << meta
01285           << winSurcharges;
01286 }
01287 
01288 void
01289 BidRequest::
01290 reconstitute(ML::DB::Store_Reader & store)
01291 {
01292     using namespace ML::DB;
01293 
01294     unsigned char version;
01295 
01296     store >> version;
01297 
01298     if (version != 2)
01299         throw ML::Exception("problem reconstituting BidRequest: "
01300                             "invalid version");
01301 
01302     store >> auctionId >> language >> protocolVersion
01303           >> exchange >> provider >> timestamp >> isTest
01304           >> location >> userIds >> imp >> url >> ipAddress >> userAgent
01305           >> restrictions >> segments >> meta >> winSurcharges;
01306 }
01307 
01308 } // namespace RTBKIT
01309 
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator