From f40ec3c8a7a96457b401f795b1417394ac943e8a Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 10:38:16 +0100 Subject: [PATCH 01/20] WURFL-devicedetection module implementation (compatible with PBS Java 3.18+) --- extra/bundle/pom.xml | 7 + extra/modules/WURFL-devicedetection/README.md | 247 +++++++++++++++ extra/modules/WURFL-devicedetection/pom.xml | 29 ++ .../sample/request_data.json | 119 ++++++++ .../WURFLDeviceDetectionConfigProperties.java | 51 ++++ .../WURFLDeviceDetectionConfiguration.java | 37 +++ .../WURFLModuleConfigurationException.java | 8 + .../model/AuctionRequestHeadersContext.java | 31 ++ .../model/WURFLEngineInitializer.java | 112 +++++++ .../resolver/HeadersResolver.java | 132 ++++++++ .../resolver/PlatformNameVersion.java | 33 ++ .../devicedetection/v1/AccountValidator.java | 31 ++ .../devicedetection/v1/ExtWURFLMapper.java | 65 ++++ .../devicedetection/v1/OrtbDeviceUpdater.java | 259 ++++++++++++++++ .../WURFLDeviceDetectionEntrypointHook.java | 37 +++ .../v1/WURFLDeviceDetectionModule.java | 29 ++ ...LDeviceDetectionRawAuctionRequestHook.java | 143 +++++++++ .../module-config/WURFL-devicedetection.yaml | 46 +++ ...FLDeviceDetectionConfigPropertiesTest.java | 46 +++ .../devicedetection/mock/WURFLDeviceMock.java | 282 ++++++++++++++++++ .../AuctionRequestHeadersContextTest.java | 65 ++++ .../model/WURFLEngineInitializerTest.java | 125 ++++++++ .../resolver/HeadersResolverTest.java | 199 ++++++++++++ .../resolver/PlatformNameVersionTest.java | 69 +++++ .../v1/AccountValidatorTest.java | 110 +++++++ .../v1/ExtWURFLMapperTest.java | 131 ++++++++ .../v1/OrtbDeviceUpdaterTest.java | 243 +++++++++++++++ ...URFLDeviceDetectionEntrypointHookTest.java | 81 +++++ .../v1/WURFLDeviceDetectionModuleTest.java | 40 +++ ...iceDetectionRawAuctionRequestHookTest.java | 124 ++++++++ extra/modules/pom.xml | 1 + sample/configs/prebid-config-with-wurfl.yaml | 80 +++++ 32 files changed, 3012 insertions(+) create mode 100644 extra/modules/WURFL-devicedetection/README.md create mode 100644 extra/modules/WURFL-devicedetection/pom.xml create mode 100644 extra/modules/WURFL-devicedetection/sample/request_data.json create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java create mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java create mode 100644 sample/configs/prebid-config-with-wurfl.yaml diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 9eb4d2aaf33..4a5ca3144ed 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -55,6 +55,13 @@ pb-request-correction ${project.version} + diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md new file mode 100644 index 00000000000..86eb9cd8f6b --- /dev/null +++ b/extra/modules/WURFL-devicedetection/README.md @@ -0,0 +1,247 @@ +## WURFL-devicedetection module + +The **WURFL Device Enrichment Module** for Prebid Server enhances the OpenRTB 2.x payload +with comprehensive device detection data powered by **ScientiaMobile**’s WURFL device detection framework. +Thanks to WURFL's device database, the module provides accurate and comprehensive device-related information, +enabling bidders to make better-informed targeting and optimization decisions. + +### Key features + +#### Device Field Enrichment: + +The module populates missing or empty fields in ortb2.device with the following data: + - **make**: Manufacturer of the device (e.g., "Apple", "Samsung"). + - **model**: Device model (e.g., "iPhone 14", "Galaxy S22"). + - **os**: Operating system (e.g., "iOS", "Android"). + - **osv**: Operating system version (e.g., "16.0", "12.0"). + - **h**: Screen height in pixels. + - **w**: Screen width in pixels. + - **ppi**: Screen pixels per inch (PPI). + - **pixelratio**: Screen pixel density ratio. + - **devicetype**: Device type (e.g., mobile, tablet, desktop). + - **Note**: If these fields are already populated in the bid request, the module will not overwrite them. +#### Publisher-Specific Enrichment: + +Device enrichment is selectively enabled for publishers based on their account ID. +The module identifies publishers through the `getAccount()` method in the `AuctionContext` class. + + +### Build prerequisites + +To build the WURFL device detection module, you need to download the WURFL Onsite Java API (both JAR and POM files) +from the Scientiamobile private repository and install it in your local Maven repository. +Access to the WURFL Onsite Java API repository requires a valid Scientiamobile WURFL license. +For more details, visit: [Scientiamobile WURFL Onsite API for Java](https://www.scientiamobile.com/secondary-products/wurfl-onsite-api-for-java/). + +Run the following command to install the WURFL API: + +```bash +mvn install:install-file \ + -Dfile= \ + -DgroupId=com.scientiamobile.wurfl \ + -DartifactId=wurfl \ + -Dversion= \ + -Dpackaging=jar \ + -DpomFile= +``` + +### Activating the WURFL Device Detection Module + +The WURFL device detection module is disabled by default. Building the Prebid Server Java with the default bundle option +does not include the WURFL module in the server's JAR file. + +To include the WURFL device detection module in the Prebid Server Java bundle, follow these steps: + +1. Uncomment the WURFL Java API dependency in `extra/modules/WURFL-devicedetection/pom.xml`. +2. Uncomment the WURFL module dependency in `extra/bundle/pom.xml`. +3. Uncomment the WURFL module name in the module list in `extra/modules/pom.xml`. + +After making these changes, you can build the Prebid Server Java bundle with the WURFL module using the following command: + +```bash +mvn clean package --file extra/pom.xml +``` + +### Configuring the WURFL Device Detection Module + +Below is a sample configuration for the WURFL device detection module: + +```yaml +hooks: + wurfl-devicedetection: + enabled: true + host-execution-plan: > + { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "entrypoint": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "wurfl-devicedetection", + "hook_impl_code": "wurfl-devicedetection-entrypoint-hook" + } + ] + } + ] + }, + "raw_auction_request": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "wurfl-devicedetection", + "hook_impl_code": "wurfl-devicedetection-raw-auction-request" + } + ] + } + ] + } + } + } + } + } + modules: + wurfl-devicedetection: + wurfl-file-dir-path: + wurfl-snapshot-url: https://data.scientiamobile.com//wurfl.zip + cache-size: 200000 + wurfl-run-updater: true + allowed-publisher-ids: 1 + ext-caps: false +``` + +### Configuration Options + +- **`wurfl-file-dir-path`** (Mandatory): Path to the directory where the WURFL file is downloaded. Directory must exist and be writable. +- **`wurfl-file-name`** (Mandatory): Name of the WURFL file, typically `wurfl.zip`. +- **`wurfl-file-url`** (Mandatory): URL to the licensed WURFL file to be downloaded when Prebid Server Java starts. +- **`cache-size`** (Optional): Maximum number of devices stored in the WURFL cache. Defaults to the WURFL cache's standard size. +- **`ext_caps`** (Optional): If `true`, the module adds all licensed capabilities to the `device.ext` object. +- **`wurfl-updater-frequency`** (Optional): Frequency for updating the WURFL file. Defaults to no updates. +- **`allowed_publisher_ids`** (Optional): List of publisher IDs permitted to use the module. Defaults to all publishers. + +A valid WURFL license must include all the required capabilities for device enrichment. + +### Launching Prebid Server Java with the WURFL Module + +After configuring the module and successfully building the Prebid Server bundle, start the server with the following command: + +```bash +java -jar target/prebid-server-bundle.jar --spring.config.additional-location=sample/configs/prebid-config-with-wurfl.yaml +``` + +This sample configuration contains the module hook basic configuration. All the other module configuration options +are located in the `WURFL-devicedetection.yaml` inside the module. + +When the server starts, it downloads the WURFL file from the `wurfl-file-url` and loads it into the module. + +Sample request data for testing is available in the module's `sample` directory. Using the `auction` endpoint, +you can observe WURFL-enriched device data in the response. + +### Sample Response + +Using the sample request data via `curl` when the module is configured with `ext_caps` set to `false` (or no value) + +```bash +curl http://localhost:8080/openrtb2/auction --data @extra/modules/WURFL-devicedetection/sample/request_data.json +``` + +the device object in the response will include WURFL device detection data: + +```json +"device": { + "ua": "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL Build/AP3A.241005.015;) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64", + "devicetype": 1, + "make": "Google", + "model": "Pixel 9 Pro XL", + "os": "Android", + "osv": "15", + "h": 2992, + "w": 1344, + "ppi": 481, + "pxratio": 2.55, + "js": 1, + "ext": { + "wurfl": { + "wurfl_id": "google_pixel_9_pro_xl_ver1_suban150" + } + } +} +``` + +When `ext_caps` is set to `true`, the response will include all licensed capabilities: + +```json +"device":{ + "ua":"Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL Build/AP3A.241005.015; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64", + "devicetype":1, + "make":"Google", + "model":"Pixel 9 Pro XL", + "os":"Android", + "osv":"15", + "h":2992, + "w":1344, + "ppi":481, + "pxratio":2.55, + "js":1, + "ext":{ + "wurfl":{ + "wurfl_id":"google_pixel_9_pro_xl_ver1_suban150", + "mobile_browser_version":"", + "resolution_height":"2992", + "resolution_width":"1344", + "is_wireless_device":"true", + "is_tablet":"false", + "physical_form_factor":"phone_phablet", + "ajax_support_javascript":"true", + "preferred_markup":"html_web_4_0", + "brand_name":"Google", + "can_assign_phone_number":"true", + "xhtml_support_level":"4", + "ux_full_desktop":"false", + "device_os":"Android", + "physical_screen_width":"71", + "is_connected_tv":"false", + "is_smarttv":"false", + "physical_screen_height":"158", + "model_name":"Pixel 9 Pro XL", + "is_ott":"false", + "density_class":"2.55", + "marketing_name":"", + "device_os_version":"15.0", + "mobile_browser":"Chrome Mobile", + "pointing_method":"touchscreen", + "is_app_webview":"false", + "advertised_app_name":"Edge Browser", + "is_smartphone":"true", + "is_robot":"false", + "advertised_device_os":"Android", + "is_largescreen":"true", + "is_android":"true", + "is_xhtmlmp_preferred":"false", + "device_name":"Google Pixel 9 Pro XL", + "is_ios":"false", + "is_touchscreen":"true", + "is_wml_preferred":"false", + "is_app":"false", + "is_mobile":"true", + "is_phone":"true", + "is_full_desktop":"false", + "is_generic":"false", + "advertised_browser":"Edge", + "complete_device_name":"Google Pixel 9 Pro XL", + "advertised_browser_version":"124.0.2478.64", + "is_html_preferred":"true", + "is_windows_phone":"false", + "pixel_density":"481", + "form_factor":"Smartphone", + "advertised_device_os_version":"15" + } + } +} +``` diff --git a/extra/modules/WURFL-devicedetection/pom.xml b/extra/modules/WURFL-devicedetection/pom.xml new file mode 100644 index 00000000000..da087b44cca --- /dev/null +++ b/extra/modules/WURFL-devicedetection/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + org.prebid.server.hooks.modules + all-modules + 3.19.0-SNAPSHOT + + + wurfl-devicedetection + + wurfl-devicedetection + WURFL device detection and data enrichment module + + + 1.13.2.1 + + + + + + diff --git a/extra/modules/WURFL-devicedetection/sample/request_data.json b/extra/modules/WURFL-devicedetection/sample/request_data.json new file mode 100644 index 00000000000..5184f4ca6f3 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/sample/request_data.json @@ -0,0 +1,119 @@ +{ + "imp": [ + { + "ext": { + "data": { + "adserver": { + "name": "gam", + "adslot": "test" + }, + "pbadslot": "test", + "gpid": "test" + }, + "gpid": "test", + "prebid": { + "bidder": { + "appnexus": { + "placement_id": 1, + "use_pmt_rule": false + }, + "0test": { + "placement_id": 1 + } + }, + "adunitcode": "25e8ad9f-13a4-4404-ba74-f9eebff0e86c", + "floors": { + "floorMin": 0.01 + } + } + }, + "id": "2529eeea-813e-4da6-838f-f91c28d64867", + "banner": { + "topframe": 1, + "format": [ + { + "w": 728, + "h": 90 + } + ], + "pos": 1 + }, + "bidfloor": 0.01, + "bidfloorcur": "USD" + } + ], + "site": { + "domain": "test.com", + "publisher": { + "domain": "test.com", + "id": "3" + }, + "page": "https://www.test.com/" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL Build/AP3A.241005.015; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64" + }, + "id": "fc4670ce-4985-4316-a245-b43c885dc37a", + "test": 1, + "cur": [ + "USD" + ], + "source": { + "ext": { + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "sid": "1234", + "hp": 1 + } + ] + } + } + }, + "ext": { + "prebid": { + "cache": { + "bids": { + "returnCreative": true + }, + "vastxml": { + "returnCreative": true + } + }, + "auctiontimestamp": 1799310801804, + "targeting": { + "includewinners": true, + "includebidderkeys": false + }, + "schains": [ + { + "bidders": [ + "appnexus" + ], + "schain": { + "ver": "1.0", + "complete": 1, + "nodes": [ + { + "asi": "example.com", + "sid": "1234", + "hp": 1 + } + ] + } + } + ], + "floors": { + "enabled": false, + "floorMin": 0.01, + "floorMinCur": "USD" + }, + "createtids": false + } + }, + "user": {}, + "tmax": 2000 +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java new file mode 100644 index 00000000000..e9d60e6880f --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java @@ -0,0 +1,51 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config; + +import lombok.Data; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionModule; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.List; +import java.util.Set; + +@ConfigurationProperties(prefix = "hooks.modules." + WURFLDeviceDetectionModule.CODE) +@Data + +public class WURFLDeviceDetectionConfigProperties { + + public static final Set REQUIRED_STATIC_CAPS = Set.of( + "ajax_support_javascript", + "brand_name", + "density_class", + "is_connected_tv", + "is_ott", + "is_tablet", + "model_name", + "resolution_height", + "resolution_width", + "physical_form_factor" + ); + + public static final Set REQUIRED_VIRTUAL_CAPS = Set.of( + + "advertised_device_os", + "advertised_device_os_version", + "complete_device_name", + "is_full_desktop", + "is_mobile", + "is_phone", + "form_factor", + "pixel_density" + ); + + int cacheSize; + + String wurflFileDirPath; + + String wurflSnapshotUrl; + + boolean extCaps; + + boolean wurflRunUpdater = true; + + List allowedPublisherIds = List.of(); +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java new file mode 100644 index 00000000000..770ae1cd88f --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java @@ -0,0 +1,37 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config; + +import com.scientiamobile.wurfl.core.WURFLEngine; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.WURFLEngineInitializer; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionEntrypointHook; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionModule; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionRawAuctionRequestHook; +import org.prebid.server.spring.env.YamlPropertySourceFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +import java.util.List; + +@ConditionalOnProperty(prefix = "hooks." + WURFLDeviceDetectionModule.CODE, name = "enabled", havingValue = "true") +@Configuration +@PropertySource( + value = "classpath:/module-config/WURFL-devicedetection.yaml", + factory = YamlPropertySourceFactory.class) +@EnableConfigurationProperties(WURFLDeviceDetectionConfigProperties.class) +public class WURFLDeviceDetectionConfiguration { + + @Bean + public WURFLDeviceDetectionModule wurflDeviceDetectionModule(WURFLDeviceDetectionConfigProperties + configProperties) { + + final WURFLEngine wurflEngine = WURFLEngineInitializer.builder() + .configProperties(configProperties) + .build().initWURFLEngine(); + wurflEngine.load(); + + return new WURFLDeviceDetectionModule(List.of(new WURFLDeviceDetectionEntrypointHook(), + new WURFLDeviceDetectionRawAuctionRequestHook(wurflEngine, configProperties))); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java new file mode 100644 index 00000000000..d2c767c45c6 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java @@ -0,0 +1,8 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.exc; + +public class WURFLModuleConfigurationException extends RuntimeException { + + public WURFLModuleConfigurationException(String message) { + super(message); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java new file mode 100644 index 00000000000..95a32fbfabe --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java @@ -0,0 +1,31 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; + +import lombok.Getter; +import org.prebid.server.model.CaseInsensitiveMultiMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@Getter +public class AuctionRequestHeadersContext { + + Map headers; + + private AuctionRequestHeadersContext(Map headers) { + this.headers = headers; + } + + public static AuctionRequestHeadersContext from(final CaseInsensitiveMultiMap headers) { + final Map headersMap = new HashMap<>(); + if (Objects.isNull(headers)) { + return new AuctionRequestHeadersContext(headersMap); + } + + for (String headerName : headers.names()) { + headersMap.put(headerName, headers.getAll(headerName).getFirst()); + } + return new AuctionRequestHeadersContext(headersMap); + } + +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java new file mode 100644 index 00000000000..45e288dad33 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java @@ -0,0 +1,112 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; + +import com.scientiamobile.wurfl.core.GeneralWURFLEngine; +import com.scientiamobile.wurfl.core.WURFLEngine; +import com.scientiamobile.wurfl.core.cache.LRUMapCacheProvider; +import com.scientiamobile.wurfl.core.cache.NullCacheProvider; +import com.scientiamobile.wurfl.core.updater.Frequency; +import com.scientiamobile.wurfl.core.updater.WURFLUpdater; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.exc.WURFLModuleConfigurationException; + +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Builder +public class WURFLEngineInitializer { + + private WURFLDeviceDetectionConfigProperties configProperties; + + public WURFLEngine initWURFLEngine() { + downloadWurflFile(configProperties); + final WURFLEngine engine = initializeEngine(configProperties); + setupUpdater(configProperties, engine); + return engine; + } + + static void downloadWurflFile(WURFLDeviceDetectionConfigProperties configProperties) { + if (StringUtils.isNotBlank(configProperties.getWurflSnapshotUrl()) + && StringUtils.isNotBlank(configProperties.getWurflFileDirPath())) { + GeneralWURFLEngine.wurflDownload( + configProperties.getWurflSnapshotUrl(), + configProperties.getWurflFileDirPath()); + } + } + + static WURFLEngine initializeEngine(WURFLDeviceDetectionConfigProperties configProperties) { + + final String wurflFileName = extractWURFLFileName(configProperties.getWurflSnapshotUrl()); + + final Path wurflPath = Paths.get( + configProperties.getWurflFileDirPath(), + wurflFileName + ); + final WURFLEngine engine = new GeneralWURFLEngine(wurflPath.toString()); + verifyStaticCapabilitiesDefinition(engine); + + if (configProperties.getCacheSize() > 0) { + engine.setCacheProvider(new LRUMapCacheProvider(configProperties.getCacheSize())); + } else { + engine.setCacheProvider(new NullCacheProvider()); + } + return engine; + } + + private static String extractWURFLFileName(String wurflSnapshotUrl) { + + try { + final URI uri = new URI(wurflSnapshotUrl); + final String path = uri.getPath(); + return path.substring(path.lastIndexOf('/') + 1); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid WURFL snapshot URL: " + wurflSnapshotUrl, e); + } + } + + static void verifyStaticCapabilitiesDefinition(WURFLEngine engine) { + + final List unsupportedStaticCaps = new ArrayList<>(); + final Map allCaps = engine.getAllCapabilities().stream() + .collect(Collectors.toMap( + key -> key, + value -> true + )); + + for (String requiredCapName : WURFLDeviceDetectionConfigProperties.REQUIRED_STATIC_CAPS) { + if (!allCaps.containsKey(requiredCapName)) { + unsupportedStaticCaps.add(requiredCapName); + } + } + + if (!unsupportedStaticCaps.isEmpty()) { + Collections.sort(unsupportedStaticCaps); + final String failedCheckMessage = """ + Static capabilities %s needed for device enrichment are not defined in WURFL. + Please make sure that your license has the needed capabilities or upgrade it. + """.formatted(String.join(",", unsupportedStaticCaps)); + + throw new WURFLModuleConfigurationException(failedCheckMessage); + } + + } + + static void setupUpdater(WURFLDeviceDetectionConfigProperties configProperties, WURFLEngine engine) { + final boolean runUpdater = configProperties.isWurflRunUpdater(); + + if (runUpdater) { + final WURFLUpdater updater = new WURFLUpdater(engine, configProperties.getWurflSnapshotUrl()); + updater.setFrequency(Frequency.DAILY); + updater.performPeriodicUpdate(); + } + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java new file mode 100644 index 00000000000..5051b47a9dd --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java @@ -0,0 +1,132 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; + +import com.iab.openrtb.request.BrandVersion; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.UserAgent; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Slf4j +public class HeadersResolver { + + static final String SEC_CH_UA = "Sec-CH-UA"; + static final String SEC_CH_UA_PLATFORM = "Sec-CH-UA-Platform"; + static final String SEC_CH_UA_PLATFORM_VERSION = "Sec-CH-UA-Platform-Version"; + static final String SEC_CH_UA_MOBILE = "Sec-CH-UA-Mobile"; + static final String SEC_CH_UA_ARCH = "Sec-CH-UA-Arch"; + static final String SEC_CH_UA_MODEL = "Sec-CH-UA-Model"; + static final String SEC_CH_UA_FULL_VERSION_LIST = "Sec-CH-UA-Full-Version-List"; + static final String USER_AGENT = "User-Agent"; + + public Map resolve(final Device device, final Map headers) { + + if (Objects.isNull(device) && Objects.isNull(headers)) { + return new HashMap<>(); + } + + final Map resolvedHeaders = resolveFromOrtbDevice(device); + if (MapUtils.isEmpty(resolvedHeaders)) { + return headers; + } + + return resolvedHeaders; + } + + private Map resolveFromOrtbDevice(Device device) { + + final Map resolvedHeaders = new HashMap<>(); + + if (Objects.isNull(device)) { + log.warn("ORBT Device is null"); + return resolvedHeaders; + } + + if (Objects.nonNull(device.getUa())) { + resolvedHeaders.put(USER_AGENT, device.getUa()); + } + + resolvedHeaders.putAll(resolveFromSua(device.getSua())); + return resolvedHeaders; + } + + private Map resolveFromSua(UserAgent sua) { + + final Map headers = new HashMap<>(); + + if (Objects.isNull(sua)) { + log.warn("Sua is null, returning empty headers"); + return new HashMap<>(); + } + + // Browser brands and versions + final List brands = sua.getBrowsers(); + if (CollectionUtils.isEmpty(brands)) { + log.warn("No browser brands and versions found"); + return headers; + } + + final String brandList = brandListAsString(brands); + headers.put(SEC_CH_UA, brandList); + headers.put(SEC_CH_UA_FULL_VERSION_LIST, brandList); + + // Platform + final PlatformNameVersion platformNameVersion = PlatformNameVersion.from(sua.getPlatform()); + if (Objects.nonNull(platformNameVersion)) { + headers.put(SEC_CH_UA_PLATFORM, escape(platformNameVersion.getPlatformName())); + headers.put(SEC_CH_UA_PLATFORM_VERSION, escape(platformNameVersion.getPlatformVersion())); + } + + // Model + final String model = sua.getModel(); + if (Objects.nonNull(model) && !model.isEmpty()) { + headers.put(SEC_CH_UA_MODEL, escape(model)); + } + + // Architecture + final String arch = sua.getArchitecture(); + if (Objects.nonNull(arch) && !arch.isEmpty()) { + headers.put(SEC_CH_UA_ARCH, escape(arch)); + } + + // Mobile + final Integer mobile = sua.getMobile(); + if (Objects.nonNull(mobile)) { + headers.put(SEC_CH_UA_MOBILE, "?" + mobile); + } + return headers; + } + + private String brandListAsString(List versions) { + + final String brandNameString = versions.stream() + .filter(brandVersion -> brandVersion.getBrand() != null) + .map(brandVersion -> { + final String brandName = escape(brandVersion.getBrand()); + final String versionString = versionFromTokens(brandVersion.getVersion()); + return brandName + ";v=\"" + versionString + "\""; + }) + .collect(Collectors.joining(", ")); + return brandNameString; + } + + private static String escape(String value) { + return '"' + value.replace("\"", "\\\"") + '"'; + } + + public static String versionFromTokens(List tokens) { + if (tokens == null || tokens.isEmpty()) { + return ""; + } + + return tokens.stream() + .filter(token -> token != null && !token.isEmpty()) + .collect(Collectors.joining(".")); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java new file mode 100644 index 00000000000..dc01aa127b2 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java @@ -0,0 +1,33 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; + +import com.iab.openrtb.request.BrandVersion; +import lombok.Getter; + +import java.util.Objects; + +public class PlatformNameVersion { + + @Getter + private String platformName; + + private String platformVersion; + + public static PlatformNameVersion from(BrandVersion platform) { + if (Objects.isNull(platform)) { + return null; + } + final PlatformNameVersion platformNameVersion = new PlatformNameVersion(); + platformNameVersion.platformName = platform.getBrand(); + platformNameVersion.platformVersion = HeadersResolver.versionFromTokens(platform.getVersion()); + return platformNameVersion; + } + + public String getPlatformVersion() { + return platformVersion; + } + + public String toString() { + return platformName + " " + platformVersion; + } + +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java new file mode 100644 index 00000000000..0d930479fab --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java @@ -0,0 +1,31 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.settings.model.Account; +import lombok.Builder; + +import java.util.Objects; +import java.util.Optional; +import java.util.Map; + +@Slf4j +@Builder +public class AccountValidator { + + Map allowedPublisherIds; + AuctionContext auctionContext; + + public boolean isAccountValid() { + + return Optional.ofNullable(auctionContext) + .map(AuctionContext::getAccount) + .map(Account::getId) + .filter(StringUtils::isNotBlank) + .map(allowedPublisherIds::get) + .filter(Objects::nonNull) + .isPresent(); + } + +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java new file mode 100644 index 00000000000..33500ebfce6 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java @@ -0,0 +1,65 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Builder +@Slf4j +public class ExtWURFLMapper { + + private final List staticCaps; + private final List virtualCaps; + private boolean addExtCaps; + private final com.scientiamobile.wurfl.core.Device wurflDevice; + private static final String NULL_VALUE_TOKEN = "$null$"; + private static final String WURFL_ID_PROPERTY = "wurfl_id"; + + public JsonNode mapExtProperties() { + + final ObjectMapper objectMapper = new ObjectMapper(); + final ObjectNode wurflNode = objectMapper.createObjectNode(); + + try { + wurflNode.put(WURFL_ID_PROPERTY, wurflDevice.getId()); + + if (addExtCaps) { + + staticCaps.stream() + .map(sc -> { + try { + return Map.entry(sc, wurflDevice.getCapability(sc)); + } catch (Exception e) { + log.error("Error getting capability for {}: {}", sc, e.getMessage()); + return Map.entry(sc, NULL_VALUE_TOKEN); + } + }) + .filter(entry -> Objects.nonNull(entry.getValue()) + && !NULL_VALUE_TOKEN.equals(entry.getValue())) + .forEach(entry -> wurflNode.put(entry.getKey(), entry.getValue())); + + virtualCaps.stream() + .map(vc -> Map.entry(vc, wurflDevice.getVirtualCapability(vc))) + .filter(entry -> Objects.nonNull(entry.getValue())) + .forEach(entry -> wurflNode.put(entry.getKey(), entry.getValue())); + } + } catch (Exception e) { + log.error("Exception while updating EXT"); + } + + JsonNode node = null; + try { + node = objectMapper.readTree(wurflNode.toString()); + } catch (JsonProcessingException e) { + log.error("Error creating WURFL ext device JSON: {}", e.getMessage()); + } + return node; + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java new file mode 100644 index 00000000000..f8e9a9259c5 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java @@ -0,0 +1,259 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import com.iab.openrtb.request.Device; +import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.prebid.server.model.UpdateResult; +import org.prebid.server.proto.openrtb.ext.request.ExtDevice; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Slf4j +public class OrtbDeviceUpdater { + + private static final String WURFL_PROPERTY = "wurfl"; + private static final Map NAME_TO_IS_VIRTUAL_CAPABILITY = Map.of( + "brand_name", false, + "model_name", false, + "resolution_width", false, + "resolution_height", false, + "advertised_device_os", true, + "advertised_device_os_version", true, + "pixel_density", true, + "density_class", false, + "ajax_support_javascript", false + ); + + public Device update(Device ortbDevice, com.scientiamobile.wurfl.core.Device wurflDevice, + List staticCaps, List virtualCaps, boolean addExtCaps) { + + final Device.DeviceBuilder deviceBuilder = ortbDevice.toBuilder(); + + // make + final UpdateResult updatedMake = tryUpdateStringField(ortbDevice.getMake(), wurflDevice, + "brand_name"); + if (updatedMake.isUpdated()) { + deviceBuilder.make(updatedMake.getValue()); + } + + // model + final UpdateResult updatedModel = tryUpdateStringField(ortbDevice.getModel(), wurflDevice, + "model_name"); + if (updatedModel.isUpdated()) { + deviceBuilder.model(updatedModel.getValue()); + } + + // deviceType + final UpdateResult updatedDeviceType = tryUpdateDeviceTypeField(ortbDevice.getDevicetype(), + getOrtb2DeviceType(wurflDevice)); + if (updatedDeviceType.isUpdated()) { + deviceBuilder.devicetype(updatedDeviceType.getValue()); + } + + // os + final UpdateResult updatedOS = tryUpdateStringField(ortbDevice.getOs(), wurflDevice, + "advertised_device_os"); + if (updatedOS.isUpdated()) { + deviceBuilder.os(updatedOS.getValue()); + } + + // os version + final UpdateResult updatedOsv = tryUpdateStringField(ortbDevice.getOsv(), wurflDevice, + "advertised_device_os_version"); + if (updatedOS.isUpdated()) { + deviceBuilder.osv(updatedOsv.getValue()); + } + + // h (resolution height) + final UpdateResult updatedH = tryUpdateIntegerField(ortbDevice.getH(), wurflDevice, + "resolution_height", false); + if (updatedH.isUpdated()) { + deviceBuilder.h(updatedH.getValue()); + } + + // w (resolution height) + final UpdateResult updatedW = tryUpdateIntegerField(ortbDevice.getW(), wurflDevice, + "resolution_width", false); + if (updatedW.isUpdated()) { + deviceBuilder.w(updatedW.getValue()); + } + + // Pixels per inch + final UpdateResult updatedPpi = tryUpdateIntegerField(ortbDevice.getPpi(), wurflDevice, + "pixel_density", false); + if (updatedPpi.isUpdated()) { + deviceBuilder.ppi(updatedPpi.getValue()); + } + + // Pixel ratio + final UpdateResult updatedPxRatio = tryUpdateBigDecimalField(ortbDevice.getPxratio(), wurflDevice, + "density_class"); + if (updatedPxRatio.isUpdated()) { + deviceBuilder.pxratio(updatedPxRatio.getValue()); + } + + // Javascript support + final UpdateResult updatedJs = tryUpdateIntegerField(ortbDevice.getJs(), wurflDevice, + "ajax_support_javascript", true); + if (updatedJs.isUpdated()) { + deviceBuilder.js(updatedJs.getValue()); + } + + // Ext + final ExtWURFLMapper extMapper = ExtWURFLMapper.builder() + .wurflDevice(wurflDevice) + .staticCaps(staticCaps) + .virtualCaps(virtualCaps) + .addExtCaps(addExtCaps) + .build(); + final ExtDevice updatedExt = ExtDevice.empty(); + final ExtDevice ortbDeviceExt = ortbDevice.getExt(); + + if (Objects.nonNull(ortbDeviceExt)) { + updatedExt.addProperties(ortbDeviceExt.getProperties()); + if (!ortbDeviceExt.containsProperty(WURFL_PROPERTY)) { + updatedExt.addProperty("wurfl", extMapper.mapExtProperties()); + } + } else { + updatedExt.addProperty("wurfl", extMapper.mapExtProperties()); + } + deviceBuilder.ext(updatedExt); + return deviceBuilder.build(); + } + + private UpdateResult tryUpdateStringField(String fromOrtbDevice, + com.scientiamobile.wurfl.core.Device wurflDevice, + String capName) { + if (StringUtils.isNotBlank(fromOrtbDevice)) { + return UpdateResult.unaltered(fromOrtbDevice); + } + + final String fromWurfl = isVirtualCapability(capName) + ? wurflDevice.getVirtualCapability(capName) + : wurflDevice.getCapability(capName); + + if (Objects.nonNull(fromWurfl)) { + return UpdateResult.updated(fromWurfl); + } + + return UpdateResult.unaltered(fromOrtbDevice); + } + + private UpdateResult tryUpdateIntegerField(Integer fromOrtbDevice, + com.scientiamobile.wurfl.core.Device wurflDevice, + String capName, boolean convertFromBool) { + if (Objects.nonNull(fromOrtbDevice)) { + return UpdateResult.unaltered(fromOrtbDevice); + } + + final String fromWurfl = isVirtualCapability(capName) + ? wurflDevice.getVirtualCapability(capName) + : wurflDevice.getCapability(capName); + + if (StringUtils.isNotBlank(fromWurfl)) { + + if (convertFromBool) { + return fromWurfl.equalsIgnoreCase("true") + ? UpdateResult.updated(1) + : UpdateResult.updated(0); + } + + return UpdateResult.updated(Integer.parseInt(fromWurfl)); + } + return UpdateResult.unaltered(fromOrtbDevice); + } + + private UpdateResult tryUpdateBigDecimalField(BigDecimal fromOrtbDevice, + com.scientiamobile.wurfl.core.Device wurflDevice, + String capName) { + + if (Objects.nonNull(fromOrtbDevice)) { + return UpdateResult.unaltered(fromOrtbDevice); + } + + final String fromWurfl = isVirtualCapability(capName) + ? wurflDevice.getVirtualCapability(capName) + : wurflDevice.getCapability(capName); + + if (Objects.nonNull(fromWurfl)) { + + BigDecimal pxRatio = null; + try { + pxRatio = new BigDecimal(fromWurfl); + return UpdateResult.updated(pxRatio); + } catch (NullPointerException e) { + log.warn("Cannot convert WURFL device pixel density {} to ortb device pxratio", pxRatio); + } + } + + return UpdateResult.unaltered(fromOrtbDevice); + } + + private boolean isVirtualCapability(String capName) { + return NAME_TO_IS_VIRTUAL_CAPABILITY.get(capName); + } + + private UpdateResult tryUpdateDeviceTypeField(Integer fromOrtbDevice, Integer fromWurfl) { + final boolean isNotNullAndPositive = Objects.nonNull(fromOrtbDevice) && fromOrtbDevice > 0; + if (isNotNullAndPositive) { + return UpdateResult.unaltered(fromOrtbDevice); + } + + if (Objects.nonNull(fromWurfl)) { + return UpdateResult.updated(fromWurfl); + } + + return UpdateResult.unaltered(fromOrtbDevice); + } + + public static Integer getOrtb2DeviceType(final com.scientiamobile.wurfl.core.Device wurflDevice) { + + final boolean isPhone; + final boolean isTablet; + + if (wurflDevice.getVirtualCapabilityAsBool("is_mobile")) { + // if at least one if these capabilities is not defined the mobile device type is undefined + try { + isPhone = wurflDevice.getVirtualCapabilityAsBool("is_phone"); + isTablet = wurflDevice.getCapabilityAsBool("is_tablet"); + } catch (CapabilityNotDefinedException | VirtualCapabilityNotDefinedException e) { + return null; + } + + if (isPhone || isTablet) { + return 1; + } + return 6; + } + + // desktop device + if (wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")) { + return 2; + } + + // connected tv + if (wurflDevice.getCapabilityAsBool("is_connected_tv")) { + return 3; + } + + if (wurflDevice.getCapabilityAsBool("is_phone")) { + return 4; + } + + if (wurflDevice.getCapabilityAsBool("is_tablet")) { + return 5; + } + + if (wurflDevice.getCapabilityAsBool("is_ott")) { + return 7; + } + + return null; // Return null for undefined device type + } + +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java new file mode 100644 index 00000000000..ccca8c4038b --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java @@ -0,0 +1,37 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import lombok.extern.slf4j.Slf4j; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; +import org.prebid.server.hooks.v1.InvocationAction; +import org.prebid.server.hooks.v1.InvocationContext; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.v1.InvocationStatus; +import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; +import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; +import io.vertx.core.Future; + +@Slf4j +public class WURFLDeviceDetectionEntrypointHook implements EntrypointHook { + + private static final String CODE = "wurfl-devicedetection-entrypoint-hook"; + + @Override + public Future> call( + EntrypointPayload entrypointPayload, InvocationContext invocationContext) { + + final AuctionRequestHeadersContext bidRequestHeadersContext = AuctionRequestHeadersContext.from( + entrypointPayload.headers()); + return Future.succeededFuture( + InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.no_action) + .moduleContext(bidRequestHeadersContext) + .build()); + } + + @Override + public String code() { + return CODE; + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java new file mode 100644 index 00000000000..3bdaceff99f --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java @@ -0,0 +1,29 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import org.prebid.server.hooks.v1.Module; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationContext; + +import java.util.Collection; +import java.util.List; + +public class WURFLDeviceDetectionModule implements Module { + + public static final String CODE = "wurfl-devicedetection"; + private final List> hooks; + + public WURFLDeviceDetectionModule(List> hooks) { + this.hooks = hooks; + + } + + @Override + public String code() { + return CODE; + } + + @Override + public Collection> hooks() { + return this.hooks; + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java new file mode 100644 index 00000000000..1824b4e520c --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java @@ -0,0 +1,143 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.scientiamobile.wurfl.core.WURFLEngine; +import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.MapUtils; +import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver.HeadersResolver; +import org.prebid.server.hooks.v1.InvocationAction; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.execution.v1.InvocationResultImpl; +import org.prebid.server.hooks.v1.InvocationStatus; +import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; +import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; +import org.prebid.server.hooks.v1.auction.RawAuctionRequestHook; +import org.prebid.server.auction.model.AuctionContext; +import io.vertx.core.Future; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class WURFLDeviceDetectionRawAuctionRequestHook implements RawAuctionRequestHook { + + public static final String CODE = "wurfl-devicedetection-raw-auction-request"; + + private final WURFLEngine wurflEngine; + private final List staticCaps; + private final List virtualCaps; + private final OrtbDeviceUpdater ortbDeviceUpdater; + private final Map allowedPublisherIDs; + private final boolean addExtCaps; + + public WURFLDeviceDetectionRawAuctionRequestHook(WURFLEngine wurflEngine, + WURFLDeviceDetectionConfigProperties configProperties) { + this.wurflEngine = wurflEngine; + this.staticCaps = wurflEngine.getAllCapabilities().stream().toList(); + this.virtualCaps = safeGetVirtualCaps(wurflEngine); + this.ortbDeviceUpdater = new OrtbDeviceUpdater(); + this.addExtCaps = configProperties.isExtCaps(); + this.allowedPublisherIDs = configProperties.getAllowedPublisherIds().stream() + .collect(Collectors.toMap(item -> item, item -> item)); + } + + private List safeGetVirtualCaps(WURFLEngine wurflEngine) { + final List allVcaps = wurflEngine.getAllVirtualCapabilities().stream().toList(); + final List safeVcaps = new ArrayList<>(); + final var device = wurflEngine.getDeviceById("generic"); + allVcaps.forEach(vc -> { + try { + device.getVirtualCapability(vc); + safeVcaps.add(vc); + } catch (VirtualCapabilityNotDefinedException | CapabilityNotDefinedException ignored) { } + }); + return safeVcaps; + } + + @Override + public Future> call(AuctionRequestPayload auctionRequestPayload, + AuctionInvocationContext invocationContext) { + if (!shouldEnrichDevice(invocationContext)) { + return noUpdateResultFuture(); + } + + final BidRequest bidRequest = auctionRequestPayload.bidRequest(); + Device ortbDevice = null; + if (bidRequest == null) { + log.warn("BidRequest is null"); + return noUpdateResultFuture(); + } else { + ortbDevice = bidRequest.getDevice(); + if (ortbDevice == null) { + log.warn("Device is null"); + return noUpdateResultFuture(); + } + } + + final AuctionRequestHeadersContext headersContext; + Map requestHeaders = null; + if (invocationContext.moduleContext() instanceof AuctionRequestHeadersContext) { + headersContext = (AuctionRequestHeadersContext) invocationContext.moduleContext(); + if (headersContext != null) { + requestHeaders = headersContext.getHeaders(); + } + + final Map headers = new HeadersResolver().resolve(ortbDevice, requestHeaders); + final com.scientiamobile.wurfl.core.Device wurflDevice = wurflEngine.getDeviceForRequest(headers); + + try { + final Device updatedDevice = ortbDeviceUpdater.update(ortbDevice, wurflDevice, staticCaps, + virtualCaps, addExtCaps); + return Future.succeededFuture( + InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.update) + .payloadUpdate(payload -> + AuctionRequestPayloadImpl.of(bidRequest.toBuilder() + .device(updatedDevice) + .build())) + .build() + ); + } catch (Exception e) { + log.error("Exception " + e.getMessage()); + } + + } + + return noUpdateResultFuture(); + } + + private static Future> noUpdateResultFuture() { + return Future.succeededFuture( + InvocationResultImpl.builder() + .status(InvocationStatus.success) + .action(InvocationAction.no_action) + .build()); + } + + private boolean shouldEnrichDevice(AuctionInvocationContext invocationContext) { + if (MapUtils.isEmpty(allowedPublisherIDs)) { + return true; + } + + final AuctionContext auctionContext = invocationContext.auctionContext(); + return AccountValidator.builder().allowedPublisherIds(allowedPublisherIDs) + .auctionContext(auctionContext) + .build() + .isAccountValid(); + } + + @Override + public String code() { + return CODE; + } + +} diff --git a/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml b/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml new file mode 100644 index 00000000000..e8c4f2a5229 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml @@ -0,0 +1,46 @@ +hooks: + wurfl-devicedetection: + enabled: true + host-execution-plan: > + { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "entrypoint": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "wurfl-devicedetection", + "hook_impl_code": "wurfl-devicedetection-entrypoint-hook" + } + ] + } + ] + }, + "raw_auction_request": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "wurfl-devicedetection", + "hook_impl_code": "wurfl-devicedetection-raw-auction-request" + } + ] + } + ] + } + } + } + } + } + modules: + wurfl-devicedetection: + wurfl-file-dir-path: + wurfl-snapshot-url: https://data.scientiamobile.com//wurfl.zip + cache-size: 200000 + wurfl-run-updater: true + allowed-publisher-ids: 1 + ext-caps: false diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java new file mode 100644 index 00000000000..dcce5123e34 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java @@ -0,0 +1,46 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class WURFLDeviceDetectionConfigPropertiesTest { + + @Test + void shouldInitializeWithEmptyValues() { + // given + final WURFLDeviceDetectionConfigProperties properties = new WURFLDeviceDetectionConfigProperties(); + + // then + assertThat(properties.getCacheSize()).isEqualTo(0); + assertThat(properties.getWurflFileDirPath()).isNull(); + assertThat(properties.getWurflSnapshotUrl()).isNull(); + assertThat(properties.isExtCaps()).isFalse(); + assertThat(properties.isWurflRunUpdater()).isTrue(); + } + + @Test + void shouldSetAndGetProperties() { + // given + final WURFLDeviceDetectionConfigProperties properties = new WURFLDeviceDetectionConfigProperties(); + + // when + properties.setCacheSize(1000); + properties.setWurflFileDirPath("/path/to/file"); + + properties.setWurflSnapshotUrl("https://example-scientiamobile.com/wurfl.zip"); + properties.setWurflRunUpdater(false); + properties.setAllowedPublisherIds(List.of("1", "3")); + properties.setExtCaps(true); + + // then + assertThat(properties.getCacheSize()).isEqualTo(1000); + assertThat(properties.getWurflFileDirPath()).isEqualTo("/path/to/file"); + assertThat(properties.getWurflSnapshotUrl()).isEqualTo("https://example-scientiamobile.com/wurfl.zip"); + assertThat(properties.isWurflRunUpdater()).isEqualTo(false); + assertThat(properties.getAllowedPublisherIds()).isEqualTo(List.of("1", "3")); + assertThat(properties.isExtCaps()).isTrue(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java new file mode 100644 index 00000000000..a3280fe7d42 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java @@ -0,0 +1,282 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock; + +import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.matchers.MatchType; +import lombok.Builder; + +import java.util.Map; + +@Builder +public class WURFLDeviceMock implements com.scientiamobile.wurfl.core.Device { + + private Map capabilities; + private String id; + private Map virtualCapabilities; + + @Override + public MatchType getMatchType() { + return MatchType.conclusive; + } + + @Override + public String getVirtualCapability(String vcapName) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException { + + if (!virtualCapabilities.containsKey(vcapName)) { + throw new VirtualCapabilityNotDefinedException(vcapName); + } + + return virtualCapabilities.get(vcapName); + } + + @Override + public int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException, NumberFormatException { + return 0; + } + + @Override + public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException, NumberFormatException { + + if (vcapName.equals("is_phone") || vcapName.equals("is_full_desktop") || vcapName.equals("is_connected_tv") + || vcapName.equals("is_mobile") || vcapName.equals("is_tablet")) { + return Boolean.parseBoolean(getVirtualCapability(vcapName)); + } + + return false; + } + + @Override + public Map getVirtualCapabilities() { + return Map.of(); + } + + @Override + public String getId() { + return id; + } + + @Override + public String getWURFLUserAgent() { + return ""; + } + + @Override + public String getCapability(String capName) throws CapabilityNotDefinedException { + + if (!capabilities.containsKey(capName)) { + throw new CapabilityNotDefinedException(capName); + } + + return capabilities.get(capName); + + } + + @Override + public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException { + return switch (capName) { + case "resolution_height", "resolution_width" -> Integer.parseInt(capabilities.get(capName)); + default -> 0; + }; + } + + @Override + public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException { + return switch (capName) { + case "ajax_support_javascript", "is_connected_tv", "is_ott", "is_tablet", "is_mobile" -> + Boolean.parseBoolean(getCapability(capName)); + default -> false; + }; + } + + @Override + public Map getCapabilities() { + return Map.of(); + } + + @Override + public boolean isActualDeviceRoot() { + return true; + } + + @Override + public String getDeviceRootId() { + return ""; + } + + public static class WURFLDeviceMockFactory { + + public static com.scientiamobile.wurfl.core.Device mockIPhone() { + + return builder().capabilities(Map.of( + "brand_name", "Apple", + "model_name", "iPhone", + "ajax_support_javascript", "true", + "density_class", "1.0", + "is_connected_tv", "false", + "is_ott", "false", + "is_tablet", "false", + "resolution_height", "1440", + "resolution_width", "3200" + )).virtualCapabilities( + Map.of("advertised_device_os", "iOS", + "advertised_device_os_version", "17.1", + "complete_device_name", "Apple iPhone", + "is_full_desktop", "false", + "is_mobile", "true", + "is_phone", "true", + "form_factor", "Smartphone", + "pixel_density", "515")) + .id("apple_iphone_ver1") + .build(); + } + + public static com.scientiamobile.wurfl.core.Device mockOttDevice() { + + return builder().capabilities(Map.of( + "brand_name", "Diyomate", + "model_name", "A6", + "ajax_support_javascript", "true", + "density_class", "1.5", + "is_connected_tv", "false", + "is_ott", "true", + "is_tablet", "false", + "resolution_height", "1080", + "resolution_width", "1920" + )).virtualCapabilities( + Map.of("advertised_device_os", "Android", + "advertised_device_os_version", "4.0", + "complete_device_name", "Diyomate A6", + "is_full_desktop", "false", + "is_mobile", "false", + "is_phone", "false", + "form_factor", "Smart-TV", + "pixel_density", "69")) + .id("diyomate_a6_ver1") + .build(); + } + + public static com.scientiamobile.wurfl.core.Device mockMobileUndefinedDevice() { + + return builder().capabilities(Map.of( + "brand_name", "TestUnd", + "model_name", "U1", + "ajax_support_javascript", "false", + "density_class", "1.0", + "is_connected_tv", "false", + "is_ott", "false", + "is_tablet", "false", + "resolution_height", "128", + "resolution_width", "128" + )).virtualCapabilities( + Map.of("advertised_device_os", "TestOS", + "advertised_device_os_version", "1.0", + "complete_device_name", "TestUnd U1", + "is_full_desktop", "false", + "is_mobile", "true", + "is_phone", "false", + "form_factor", "Test-non-phone", + "pixel_density", "69")) + .build(); + } + + public static com.scientiamobile.wurfl.core.Device mockUnknownDevice() { + + return builder().capabilities(Map.of( + "brand_name", "TestUnd", + "model_name", "U1", + "ajax_support_javascript", "false", + "density_class", "1.0", + "is_connected_tv", "false", + "is_ott", "false", + "is_tablet", "false", + "resolution_height", "128", + "resolution_width", "128" + )).virtualCapabilities( + Map.of("advertised_device_os", "TestOS", + "advertised_device_os_version", "1.0", + "complete_device_name", "TestUnd U1", + "is_full_desktop", "false", + "is_mobile", "false", + "is_phone", "false", + "form_factor", "Test-unknown", + "pixel_density", "69")) + .build(); + } + + public static com.scientiamobile.wurfl.core.Device mockDesktop() { + + return builder().capabilities(Map.of( + "brand_name", "TestDesktop", + "model_name", "D1", + "ajax_support_javascript", "true", + "density_class", "1.5", + "is_connected_tv", "false", + "is_ott", "false", + "is_tablet", "false", + "resolution_height", "1080", + "resolution_width", "1920" + )).virtualCapabilities( + Map.of("advertised_device_os", "Windows", + "advertised_device_os_version", "10", + "complete_device_name", "TestDesktop D1", + "is_full_desktop", "true", + "is_mobile", "false", + "is_phone", "false", + "form_factor", "Desktop", + "pixel_density", "300")) + .build(); + } + + public static com.scientiamobile.wurfl.core.Device mockConnectedTv() { + + return builder().capabilities(Map.of( + "brand_name", "TestConnectedTv", + "model_name", "C1", + "ajax_support_javascript", "true", + "density_class", "1.5", + "is_connected_tv", "true", + "is_ott", "false", + "is_tablet", "false", + "resolution_height", "1080", + "resolution_width", "1920" + )).virtualCapabilities( + Map.of("advertised_device_os", "WebOS", + "advertised_device_os_version", "4", + "complete_device_name", "TestConnectedTV C1", + "is_full_desktop", "false", + "is_mobile", "false", + "is_phone", "false", + "form_factor", "Smart-TV", + "pixel_density", "200")) + .build(); + } + + public static com.scientiamobile.wurfl.core.Device mockTablet() { + + return builder().capabilities(Map.of( + "brand_name", "Samsung", + "model_name", "Galaxy Tab S9+", + "ajax_support_javascript", "true", + "density_class", "1.5", + "is_connected_tv", "false", + "is_ott", "false", + "is_tablet", "true", + "resolution_height", "1752", + "resolution_width", "2800" + )).virtualCapabilities( + Map.of("advertised_device_os", "Android", + "advertised_device_os_version", "13", + "complete_device_name", "Samsung Galaxy Tab S9+", + "is_full_desktop", "false", + "is_mobile", "false", + "is_phone", "false", + "form_factor", "Tablet", + "pixel_density", "274")) + .build(); + } + + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java new file mode 100644 index 00000000000..15a89de4ecf --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java @@ -0,0 +1,65 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; + +import org.junit.jupiter.api.Test; +import org.prebid.server.model.CaseInsensitiveMultiMap; + +import static org.assertj.core.api.Assertions.assertThat; + +class AuctionRequestHeadersContextTest { + + @Test + void fromShouldHandleNullHeaders() { + // when + final AuctionRequestHeadersContext result = AuctionRequestHeadersContext.from(null); + + // then + assertThat(result.headers).isEmpty(); + } + + @Test + void fromShouldConvertCaseInsensitiveMultiMapToHeaders() { + // given + final CaseInsensitiveMultiMap multiMap = CaseInsensitiveMultiMap.builder() + .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") + .add("Header2", "value2") + .build(); + + // when + final AuctionRequestHeadersContext target = AuctionRequestHeadersContext.from(multiMap); + + // then + assertThat(target.headers) + .hasSize(2) + .containsEntry("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") + .containsEntry("Header2", "value2"); + } + + @Test + void fromShouldTakeFirstValueForDuplicateHeaders() { + // given + final CaseInsensitiveMultiMap multiMap = CaseInsensitiveMultiMap.builder() + .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") + .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test2") + .build(); + + // when + final AuctionRequestHeadersContext target = AuctionRequestHeadersContext.from(multiMap); + + // then + assertThat(target.headers) + .hasSize(1) + .containsEntry("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test"); + } + + @Test + void fromShouldHandleEmptyMultiMap() { + // given + final CaseInsensitiveMultiMap emptyMultiMap = CaseInsensitiveMultiMap.empty(); + + // when + final AuctionRequestHeadersContext target = AuctionRequestHeadersContext.from(emptyMultiMap); + + // then + assertThat(target.headers).isEmpty(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java new file mode 100644 index 00000000000..76d27053e75 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java @@ -0,0 +1,125 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; + +import com.scientiamobile.wurfl.core.GeneralWURFLEngine; +import com.scientiamobile.wurfl.core.WURFLEngine; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.exc.WURFLModuleConfigurationException; +import org.junit.jupiter.api.function.Executable; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.Mockito.mockStatic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class WURFLEngineInitializerTest { + + @Mock(strictness = LENIENT) + private WURFLDeviceDetectionConfigProperties configProperties; + + @Mock(strictness = LENIENT) + private WURFLEngine wurflEngine; + + @BeforeEach + void setUp() { + when(configProperties.getWurflSnapshotUrl()).thenReturn("http://test.url/wurfl.zip"); + when(configProperties.getWurflFileDirPath()).thenReturn("/test/path"); + } + + @Test + void downloadWurflFileIfNeededShouldDownloadWhenUrlAndPathArePresent() { + try (MockedStatic mockedStatic = mockStatic(GeneralWURFLEngine.class)) { + // when + WURFLEngineInitializer.downloadWurflFile(configProperties); + + // then + mockedStatic.verify(() -> + GeneralWURFLEngine.wurflDownload("http://test.url/wurfl.zip", "/test/path")); + } + } + + @Test + void verifyStaticCapabilitiesDefinitionShouldThrowExceptionWhenCapabilitiesAreNotDefined() { + // given + when(wurflEngine.getAllCapabilities()).thenReturn(Set.of( + "brand_name", + "density_class", + "is_connected_tv", + "is_ott", + "is_tablet", + "model_name")); + + final String expFailedCheckMessage = """ + Static capabilities %s needed for device enrichment are not defined in WURFL. + Please make sure that your license has the needed capabilities or upgrade it. + """.formatted(String.join(",", List.of( + "ajax_support_javascript", + "physical_form_factor", + "resolution_height", + "resolution_width" + ))); + + // when + final Executable exceptionSource = () -> WURFLEngineInitializer.verifyStaticCapabilitiesDefinition(wurflEngine); + + // then + final Exception exception = assertThrows(WURFLModuleConfigurationException.class, exceptionSource); + assertThat(exception.getMessage()).isEqualTo(expFailedCheckMessage); + } + + @Test + void verifyStaticCapabilitiesDefinitionShouldCompleteSuccessfullyWhenCapabilitiesAreDefined() { + // given + when(wurflEngine.getAllCapabilities()).thenReturn(Set.of( + "brand_name", + "density_class", + "is_connected_tv", + "is_ott", + "is_tablet", + "model_name", + "resolution_width", + "resolution_height", + "physical_form_factor", + "ajax_support_javascript" + )); + + // when + var excOccurred = false; + try { + WURFLEngineInitializer.verifyStaticCapabilitiesDefinition(wurflEngine); + } catch (Exception e) { + excOccurred = true; + } + + // then + assertThat(excOccurred).isFalse(); + } + + @Test + void builderShouldCreateWURFLEngineInitializerBuilderFromProperties() { + // given + when(configProperties.getWurflSnapshotUrl()).thenReturn("http://test.url/wurfl.zip"); + when(configProperties.getWurflFileDirPath()).thenReturn("/test/path"); + when(configProperties.getCacheSize()).thenReturn(1000); + when(configProperties.isWurflRunUpdater()).thenReturn(true); + + // when + final var builder = WURFLEngineInitializer.builder() + .configProperties(configProperties); + + // then + assertThat(builder).isNotNull(); + assertThat(builder.build()).isNotNull(); + assertThat(builder.toString()).isNotEmpty(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java new file mode 100644 index 00000000000..f3d3be006fa --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java @@ -0,0 +1,199 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; + +import com.iab.openrtb.request.BrandVersion; +import com.iab.openrtb.request.Device; +import com.iab.openrtb.request.UserAgent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class HeadersResolverTest { + + private HeadersResolver target; + + private static final String TEST_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; + + @BeforeEach + void setUp() { + target = new HeadersResolver(); + } + + @Test + void resolveWithNullDeviceShouldReturnOriginalHeaders() { + // given + final Map headers = new HashMap<>(); + headers.put("test", "value"); + + // when + final Map result = target.resolve(null, headers); + + // then + assertThat(result).isEqualTo(headers); + } + + @Test + void resolveWithDeviceUaShouldReturnUserAgentHeader() { + // given + final Device device = Device.builder() + .ua("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") + .build(); + + // when + final Map result = target.resolve(device, new HashMap<>()); + + // then + assertThat(result).containsEntry("User-Agent", TEST_USER_AGENT); + } + + @Test + void resolveWithFullSuaShouldReturnAllHeaders() { + // given + final BrandVersion brandVersion = new BrandVersion( + "Chrome", + Arrays.asList("100", "0", "0"), + null); + + final BrandVersion winBrandVersion = new BrandVersion( + "Windows", + Arrays.asList("10", "0", "0"), + null); + final UserAgent sua = UserAgent.builder() + .browsers(List.of(brandVersion)) + .platform(winBrandVersion) + .model("Test Model") + .architecture("x86") + .mobile(0) + .build(); + + final Device device = Device.builder() + .sua(sua) + .build(); + + // when + final Map result = target.resolve(device, new HashMap<>()); + + // then + assertThat(result) + .containsEntry("Sec-CH-UA", "\"Chrome\";v=\"100.0.0\"") + .containsEntry("Sec-CH-UA-Full-Version-List", "\"Chrome\";v=\"100.0.0\"") + .containsEntry("Sec-CH-UA-Platform", "\"Windows\"") + .containsEntry("Sec-CH-UA-Platform-Version", "\"10.0.0\"") + .containsEntry("Sec-CH-UA-Model", "\"Test Model\"") + .containsEntry("Sec-CH-UA-Arch", "\"x86\"") + .containsEntry("Sec-CH-UA-Mobile", "?0"); + } + + @Test + void resolveWithFullDeviceAndHeadersShouldPrioritizeDevice() { + // given + final BrandVersion brandVersion = new BrandVersion( + "Chrome", + Arrays.asList("100", "0", "0"), + null); + + final BrandVersion winBrandVersion = new BrandVersion( + "Windows", + Arrays.asList("10", "0", "0"), + null); + final UserAgent sua = UserAgent.builder() + .browsers(List.of(brandVersion)) + .platform(winBrandVersion) + .model("Test Model") + .architecture("x86") + .mobile(0) + .build(); + + final Device device = Device.builder() + .sua(sua) + .ua(TEST_USER_AGENT) + .build(); + + final Map headers = new HashMap<>(); + headers.put("Sec-CH-UA", "Test UA-CH"); + headers.put("Sec-CH-UA-Full-Version-List", "Test-UA-Full-Version-List"); + headers.put("Sec-CH-UA-Platform", "Test-UA-Platform"); + headers.put("Sec-CH-UA-Platform-Version", "Test-UA-Platform-Version"); + headers.put("Sec-CH-UA-Model", "Test-UA-Model"); + headers.put("Sec-CH-UA-Arch", "Test-UA-Arch"); + headers.put("Sec-CH-UA-Mobile", "Test-UA-Mobile"); + headers.put("User-Agent", "Mozilla/5.0 (Test OS; 10) like Gecko"); + // when + final Map result = target.resolve(device, headers); + + // then + assertThat(result) + .containsEntry("Sec-CH-UA", "\"Chrome\";v=\"100.0.0\"") + .containsEntry("Sec-CH-UA-Full-Version-List", "\"Chrome\";v=\"100.0.0\"") + .containsEntry("Sec-CH-UA-Platform", "\"Windows\"") + .containsEntry("Sec-CH-UA-Platform-Version", "\"10.0.0\"") + .containsEntry("Sec-CH-UA-Model", "\"Test Model\"") + .containsEntry("Sec-CH-UA-Arch", "\"x86\"") + .containsEntry("Sec-CH-UA-Mobile", "?0"); + } + + @Test + void versionFromTokensShouldHandleNullAndEmptyInput() { + // when & then + assertThat(HeadersResolver.versionFromTokens(null)).isEmpty(); + assertThat(HeadersResolver.versionFromTokens(List.of())).isEmpty(); + } + + @Test + void versionFromTokensShouldJoinValidTokens() { + // given + final List tokens = Arrays.asList("100", "0", "1234"); + + // when + final String result = HeadersResolver.versionFromTokens(tokens); + + // then + assertThat(result).isEqualTo("100.0.1234"); + } + + @Test + void resolveWithMultipleBrandVersionsShouldFormatCorrectly() { + // given + final BrandVersion chrome = new BrandVersion("Chrome", + Arrays.asList("100", "0"), + null); + final BrandVersion chromium = new BrandVersion("Chromium", + Arrays.asList("100", "0"), + null); + + final BrandVersion notABrand = new BrandVersion("Not\\A;Brand", + Arrays.asList("99", "0"), + null); + + final UserAgent sua = UserAgent.builder() + .browsers(Arrays.asList(chrome, chromium, notABrand)) + .build(); + + final Device device = Device.builder() + .sua(sua) + .build(); + + // when + final Map result = target.resolve(device, new HashMap<>()); + + // then + final String expectedFormat = "\"Chrome\";v=\"100.0\", \"Chromium\";v=\"100.0\", \"Not\\A;Brand\";v=\"99.0\""; + assertThat(result) + .containsEntry("Sec-CH-UA", expectedFormat) + .containsEntry("Sec-CH-UA-Full-Version-List", expectedFormat); + } + + @Test + void resolveWithNullDeviceAndNullHeadersShouldReturnEmptyMap() { + // when + final Map result = target.resolve(null, null); + + // then + assertThat(result).isEmpty(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java new file mode 100644 index 00000000000..666e4571908 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java @@ -0,0 +1,69 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; + +import com.iab.openrtb.request.BrandVersion; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlatformNameVersionTest { + + @Test + void fromShouldReturnNullWhenPlatformIsNull() { + // when + final PlatformNameVersion target = PlatformNameVersion.from(null); + + // then + assertThat(target).isNull(); + } + + @Test + void fromShouldCreatePlatformNameVersionWithValidInput() { + // given + final BrandVersion platform = new BrandVersion("Windows", + Arrays.asList("10", "0", "0"), + null); + + // when + final PlatformNameVersion target = PlatformNameVersion.from(platform); + + // then + assertThat(target).isNotNull(); + assertThat(target.getPlatformName()).isEqualTo("Windows"); + assertThat(target.getPlatformVersion()).isEqualTo("10.0.0"); + } + + @Test + void toStringShouldReturnFormattedString() { + // given + final BrandVersion platform = new BrandVersion("macOS", + Arrays.asList("13", "1"), + null); + final PlatformNameVersion target = PlatformNameVersion.from(platform); + + // when + final String result = target.toString(); + + // then + assertThat(result).isEqualTo("macOS 13.1"); + } + + @Test + void fromShouldHandleEmptyVersionList() { + // given + final BrandVersion platform = new BrandVersion("Linux", + List.of(), + null); + + // when + final PlatformNameVersion target = PlatformNameVersion.from(platform); + + // then + assertThat(target).isNotNull(); + assertThat(target.getPlatformName()).isEqualTo("Linux"); + assertThat(target.getPlatformVersion()).isEmpty(); + assertThat(target.toString()).isEqualTo("Linux "); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java new file mode 100644 index 00000000000..a21e2fca77b --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java @@ -0,0 +1,110 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.auction.model.AuctionContext; +import org.prebid.server.settings.model.Account; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AccountValidatorTest { + + private AuctionContext auctionContext; + + @Mock + private Account account; + + private AccountValidator validator; + + @BeforeEach + void setUp() { + auctionContext = AuctionContext.builder().account(account).build(); + } + + @Test + void isAccountValidShouldReturnTrueWhenPublisherIdIsAllowed() { + // given + when(account.getId()).thenReturn("allowed-publisher"); + final var accountValidatorBuiler = AccountValidator.builder() + .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) + .auctionContext(auctionContext); + assertThat(accountValidatorBuiler.toString()).isNotNull(); + validator = accountValidatorBuiler.build(); + + // when + final boolean result = validator.isAccountValid(); + + // then + assertThat(result).isTrue(); + } + + @Test + void isAccountValidShouldReturnFalseWhenPublisherIdIsNotAllowed() { + // given + when(account.getId()).thenReturn("unknown-publisher"); + validator = AccountValidator.builder() + .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) + .auctionContext(auctionContext) + .build(); + + // when + final boolean result = validator.isAccountValid(); + + // then + assertThat(result).isFalse(); + } + + @Test + void isAccountValidShouldReturnFalseWhenAuctionContextIsNull() { + // given + validator = AccountValidator.builder() + .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) + .auctionContext(null) + .build(); + + // when + final boolean result = validator.isAccountValid(); + + // then + assertThat(result).isFalse(); + } + + @Test + void isAccountValidShouldReturnFalseWhenPublisherIdIsEmpty() { + // given + when(account.getId()).thenReturn(""); + validator = AccountValidator.builder() + .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) + .auctionContext(auctionContext) + .build(); + + // when + final boolean result = validator.isAccountValid(); + + // then + assertThat(result).isFalse(); + } + + @Test + void isAccountValidShouldReturnFalseWhenAccountIsNull() { + // given + when(auctionContext.getAccount()).thenReturn(null); + validator = AccountValidator.builder() + .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) + .auctionContext(auctionContext) + .build(); + + // when + final boolean result = validator.isAccountValid(); + + // then + assertThat(result).isFalse(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java new file mode 100644 index 00000000000..8e717fcf1ce --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java @@ -0,0 +1,131 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import com.fasterxml.jackson.databind.JsonNode; +import com.iab.openrtb.request.Device; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class ExtWURFLMapperTest { + + @Mock + private com.scientiamobile.wurfl.core.Device wurflDevice; + + @Mock + private Device device; + + private ExtWURFLMapper target; + private List staticCaps; + private List virtualCaps; + + @BeforeEach + public void setUp() { + staticCaps = Arrays.asList("brand_name", "model_name"); + virtualCaps = Arrays.asList("is_mobile", "form_factor"); + + target = ExtWURFLMapper.builder() + .staticCaps(staticCaps) + .virtualCaps(virtualCaps) + .wurflDevice(wurflDevice) + .addExtCaps(true) + .build(); + } + + @Test + public void shouldMapStaticCapabilities() { + // given + when(wurflDevice.getCapability("brand_name")).thenReturn("Apple"); + when(wurflDevice.getCapability("model_name")).thenReturn("iPhone"); + + // when + final JsonNode result = target.mapExtProperties(); + + // then + assertThat(result.get("brand_name").asText()).isEqualTo("Apple"); + assertThat(result.get("model_name").asText()).isEqualTo("iPhone"); + } + + @Test + public void shouldMapVirtualCapabilities() { + // given + when(wurflDevice.getVirtualCapability("is_mobile")).thenReturn("true"); + when(wurflDevice.getVirtualCapability("form_factor")).thenReturn("smartphone"); + + // when + final JsonNode result = target.mapExtProperties(); + + // then + assertThat(result.get("is_mobile").asText()).isEqualTo("true"); + assertThat(result.get("form_factor").asText()).isEqualTo("smartphone"); + } + + @Test + public void shouldMapWURFLId() { + // given + when(wurflDevice.getId()).thenReturn("test_wurfl_id"); + + // when + final JsonNode result = target.mapExtProperties(); + + // then + assertThat(result.get("wurfl_id").asText()).isEqualTo("test_wurfl_id"); + } + + @Test + public void shouldSkipNullCapabilities() { + // given + when(wurflDevice.getCapability("brand_name")).thenReturn(null); + when(wurflDevice.getCapability("model_name")).thenReturn("iPhone"); + when(wurflDevice.getVirtualCapability("is_mobile")).thenReturn(null); + + // when + final JsonNode result = target.mapExtProperties(); + + // then + assertThat(result.has("brand_name")).isFalse(); + assertThat(result.get("model_name").asText()).isEqualTo("iPhone"); + assertThat(result.has("is_mobile")).isFalse(); + } + + @Test + public void shouldHandleExceptionsGracefully() { + // given + when(wurflDevice.getCapability("brand_name")).thenThrow(new RuntimeException("Test exception")); + when(wurflDevice.getCapability("model_name")).thenReturn("iPhone"); + + // when + final JsonNode result = target.mapExtProperties(); + + // then + assertThat(result.has("brand_name")).isFalse(); + assertThat(result.get("model_name")).isNotNull(); + assertThat(result.get("model_name").asText()).isEqualTo("iPhone"); + } + + @Test + public void shouldNotAddExtCapsIfDisabled() { + // given + target = ExtWURFLMapper.builder() + .staticCaps(staticCaps) + .virtualCaps(virtualCaps) + .wurflDevice(wurflDevice) + .addExtCaps(false) + .build(); + + // when + final JsonNode result = target.mapExtProperties(); + + // then + assertThat(result.has("brand_name")).isFalse(); + assertThat(result.has("model_name")).isFalse(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java new file mode 100644 index 00000000000..f0f60f43383 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java @@ -0,0 +1,243 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import com.fasterxml.jackson.databind.node.TextNode; +import com.iab.openrtb.request.Device; +import org.prebid.server.proto.openrtb.ext.request.ExtDevice; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockConnectedTv; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockDesktop; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockIPhone; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockMobileUndefinedDevice; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockOttDevice; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockTablet; +import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockUnknownDevice; + +@Slf4j +@ExtendWith(MockitoExtension.class) +class OrtbDeviceUpdaterTest { + + private OrtbDeviceUpdater target; + private List staticCaps; + private List virtualCaps; + + @BeforeEach + void setUp() { + target = new OrtbDeviceUpdater(); + staticCaps = Arrays.asList("ajax_support_javascript", "brand_name", "density_class", + "is_connected_tv", "is_ott", "is_tablet", "model_name", "resolution_height", "resolution_width"); + virtualCaps = Arrays.asList("advertised_device_os", "advertised_device_os_version", + "is_full_desktop", "pixel_density"); + } + + @Test + void updateShouldUpdateDeviceMakeWhenOriginalIsEmpty() { + // given + final var wurflDevice = mockIPhone(); + final Device device = Device.builder().build(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getMake()).isEqualTo("Apple"); + assertThat(result.getDevicetype()).isEqualTo(1); + } + + @Test + void updateShouldNotUpdateDeviceMakeWhenOriginalExists() { + // given + final Device device = Device.builder().make("Samsung").build(); + final var wurflDevice = mockIPhone(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getMake()).isEqualTo("Samsung"); + } + + @Test + void updateShouldNotUpdateDeviceMakeWhenOriginalBigIntegerExists() { + // given + final Device device = Device.builder().make("Apple").pxratio(new BigDecimal("1.0")).build(); + final var wurflDevice = mockIPhone(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getMake()).isEqualTo("Apple"); + assertThat(result.getPxratio()).isEqualTo("1.0"); + } + + @Test + void updateShouldUpdateDeviceModelWhenOriginalIsEmpty() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockIPhone(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getModel()).isEqualTo("iPhone"); + } + + @Test + void updateShouldUpdateDeviceOsWhenOriginalIsEmpty() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockIPhone(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getOs()).isEqualTo("iOS"); + } + + @Test + void updateShouldUpdateResolutionWhenOriginalIsEmpty() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockIPhone(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getW()).isEqualTo(3200); + assertThat(result.getH()).isEqualTo(1440); + } + + @Test + void updateShouldHandleJavascriptSupport() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockIPhone(); + + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getJs()).isEqualTo(1); + } + + @Test + void updateShouldHandleOttDeviceType() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockOttDevice(); + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + assertThat(result.getDevicetype()).isEqualTo(7); + } + + @Test + void updateShouldReturnDeviceOtherMobileWhenMobileIsNotPhoneOrTablet() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockMobileUndefinedDevice(); + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + // then + assertThat(result.getDevicetype()).isEqualTo(6); + } + + @Test + void updateShouldReturnNullWhenMobileTypeIsUnknown() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockUnknownDevice(); + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + // then + assertThat(result.getDevicetype()).isNull(); + } + + @Test + void updateShouldHandlePersonalComputerDeviceType() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockDesktop(); + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + // then + assertThat(result.getDevicetype()).isEqualTo(2); + } + + @Test + void updateShouldHandleConnectedTvDeviceType() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockConnectedTv(); + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + // then + assertThat(result.getDevicetype()).isEqualTo(3); + } + + @Test + void updateShouldNotUpdateDeviceTypeWhenSet() { + // given + final Device device = Device.builder() + .devicetype(3) + .build(); + final var wurflDevice = mockDesktop(); // device type 2 + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + // then + assertThat(result.getDevicetype()).isEqualTo(3); // unchanged + } + + @Test + void updateShouldHandleTabletDeviceType() { + // given + final Device device = Device.builder().build(); + final var wurflDevice = mockTablet(); + // when + final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); + // then + assertThat(result.getDevicetype()).isEqualTo(5); + } + + @Test + void updateShouldAddWurflPropertyToExtIfMissingAndPreserveExistingProperties() { + // given + final ExtDevice existingExt = ExtDevice.empty(); + existingExt.addProperty("someProperty", TextNode.valueOf("value")); + final Device device = Device.builder() + .ext(existingExt) + .build(); + + final var wurflDevice = WURFLDeviceMock.WURFLDeviceMockFactory.mockIPhone(); + final List staticCaps = List.of("brand_name"); + final List virtualCaps = List.of("advertised_device_os"); + + final OrtbDeviceUpdater updater = new OrtbDeviceUpdater(); + + // when + final Device result = updater.update(device, wurflDevice, staticCaps, virtualCaps, true); + + // then + final ExtDevice resultExt = result.getExt(); + assertThat(resultExt).isNotNull(); + assertThat(resultExt.getProperty("someProperty").textValue()).isEqualTo("value"); + assertThat(resultExt.getProperty("wurfl")).isNotNull(); + + } + +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java new file mode 100644 index 00000000000..b648015637e --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java @@ -0,0 +1,81 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import io.vertx.core.Future; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; +import org.prebid.server.hooks.v1.InvocationAction; +import org.prebid.server.hooks.v1.InvocationContext; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.v1.InvocationStatus; +import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; +import org.prebid.server.model.CaseInsensitiveMultiMap; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class WURFLDeviceDetectionEntrypointHookTest { + + private EntrypointPayload payload; + private InvocationContext context; + + @BeforeEach + void setUp() { + payload = mock(EntrypointPayload.class); + context = mock(InvocationContext.class); + } + + @Test + void codeShouldReturnCorrectHookCode() { + + // given + final WURFLDeviceDetectionEntrypointHook target = new WURFLDeviceDetectionEntrypointHook(); + + // when + final String result = target.code(); + + // then + assertThat(result).isEqualTo("wurfl-devicedetection-entrypoint-hook"); + } + + @Test + void callShouldReturnSuccessWithNoAction() { + // given + final WURFLDeviceDetectionEntrypointHook target = new WURFLDeviceDetectionEntrypointHook(); + final CaseInsensitiveMultiMap headers = CaseInsensitiveMultiMap.builder() + .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") + .build(); + when(payload.headers()).thenReturn(headers); + + // when + final Future> result = target.call(payload, context); + + // then + assertThat(result).isNotNull(); + assertThat(result.succeeded()).isTrue(); + final InvocationResult invocationResult = result.result(); + assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success); + assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action); + assertThat(invocationResult.moduleContext()).isNotNull(); + } + + @Test + void callShouldHandleNullHeaders() { + // given + final WURFLDeviceDetectionEntrypointHook target = new WURFLDeviceDetectionEntrypointHook(); + + // when + when(payload.headers()).thenReturn(null); + final Future> result = target.call(payload, context); + + // then + assertThat(result).isNotNull(); + assertThat(result.succeeded()).isTrue(); + final InvocationResult invocationResult = result.result(); + assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success); + assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action); + assertThat(invocationResult.moduleContext()).isNotNull(); + assertThat(invocationResult.moduleContext() instanceof AuctionRequestHeadersContext).isTrue(); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java new file mode 100644 index 00000000000..b2d36390f52 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java @@ -0,0 +1,40 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import org.junit.jupiter.api.Test; +import org.prebid.server.hooks.v1.Hook; +import org.prebid.server.hooks.v1.InvocationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +class WURFLDeviceDetectionModuleTest { + + @Test + void codeShouldReturnCorrectModuleCode() { + // given + final List> hooks = new ArrayList<>(); + final WURFLDeviceDetectionModule target = new WURFLDeviceDetectionModule(hooks); + + // when + final String result = target.code(); + + // then + assertThat(result).isEqualTo("wurfl-devicedetection"); + } + + @Test + void hooksShouldReturnProvidedHooks() { + // given + final List> hooks = new ArrayList<>(); + final WURFLDeviceDetectionModule target = new WURFLDeviceDetectionModule(hooks); + + // when + final Collection> result = target.hooks(); + + // then + assertThat(result).isSameAs(hooks); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java new file mode 100644 index 00000000000..5b5a1d01f74 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java @@ -0,0 +1,124 @@ +package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; + +import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; +import com.scientiamobile.wurfl.core.WURFLEngine; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock; +import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; +import org.prebid.server.hooks.v1.InvocationAction; +import org.prebid.server.hooks.v1.InvocationResult; +import org.prebid.server.hooks.v1.InvocationStatus; +import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; +import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; +import org.prebid.server.model.CaseInsensitiveMultiMap; + +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class WURFLDeviceDetectionRawAuctionRequestHookTest { + + @Mock + private WURFLEngine wurflEngine; + + @Mock + private WURFLDeviceDetectionConfigProperties configProperties; + + @Mock + private AuctionRequestPayload payload; + + @Mock + private AuctionInvocationContext context; + + private WURFLDeviceDetectionRawAuctionRequestHook target; + + @BeforeEach + void setUp() { + + target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflEngine, configProperties); + } + + @Test + void codeShouldReturnCorrectHookCode() { + // when + final String result = target.code(); + + // then + assertThat(result).isEqualTo("wurfl-devicedetection-raw-auction-request"); + } + + @Test + void callShouldReturnNoActionWhenBidRequestIsNull() { + // given + when(payload.bidRequest()).thenReturn(null); + + // when + final InvocationResult result = target.call(payload, context).result(); + + // then + assertThat(result.status()).isEqualTo(InvocationStatus.success); + assertThat(result.action()).isEqualTo(InvocationAction.no_action); + } + + @Test + void callShouldReturnNoActionWhenDeviceIsNull() { + // given + final BidRequest bidRequest = BidRequest.builder().build(); + when(payload.bidRequest()).thenReturn(bidRequest); + + // when + final InvocationResult result = target.call(payload, context).result(); + + // then + assertThat(result.status()).isEqualTo(InvocationStatus.success); + assertThat(result.action()).isEqualTo(InvocationAction.no_action); + } + + @Test + void callShouldUpdateDeviceWhenWurflDeviceIsDetected() { + // given + final String ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_2) Version/17.4.1 Mobile/15E148 Safari/604.1"; + final Device device = Device.builder().ua(ua).build(); + final BidRequest bidRequest = BidRequest.builder().device(device).build(); + when(payload.bidRequest()).thenReturn(bidRequest); + + final CaseInsensitiveMultiMap headers = CaseInsensitiveMultiMap.builder() + .add("User-Agent", ua) + .build(); + final AuctionRequestHeadersContext headersContext = AuctionRequestHeadersContext.from(headers); + + // when + when(context.moduleContext()).thenReturn(headersContext); + final var wurflDevice = WURFLDeviceMock.WURFLDeviceMockFactory.mockIPhone(); + when(wurflEngine.getDeviceForRequest(any(Map.class))).thenReturn(wurflDevice); + + final InvocationResult result = target.call(payload, context).result(); + + // then + assertThat(result.status()).isEqualTo(InvocationStatus.success); + assertThat(result.action()).isEqualTo(InvocationAction.update); + } + + @Test + void shouldEnrichDeviceWhenAllowedPublisherIdsIsEmpty() { + // given + when(configProperties.getAllowedPublisherIds()).thenReturn(Collections.emptyList()); + target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflEngine, configProperties); + + // when + final InvocationResult result = target.call(payload, context).result(); + + // then + assertThat(result.status()).isEqualTo(InvocationStatus.success); + } +} diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index d6bdcf20c34..c16e5072d26 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -24,6 +24,7 @@ pb-response-correction greenbids-real-time-data pb-request-correction + diff --git a/sample/configs/prebid-config-with-wurfl.yaml b/sample/configs/prebid-config-with-wurfl.yaml new file mode 100644 index 00000000000..c9f6b1d63d2 --- /dev/null +++ b/sample/configs/prebid-config-with-wurfl.yaml @@ -0,0 +1,80 @@ +status-response: "ok" +adapters: + appnexus: + enabled: true + ix: + enabled: true + openx: + enabled: true + pubmatic: + enabled: true + rubicon: + enabled: true +metrics: + prefix: prebid +cache: + scheme: http + host: localhost + path: /cache + query: uuid= +settings: + enforce-valid-account: false + generate-storedrequest-bidrequest-id: true + filesystem: + settings-filename: sample/configs/sample-app-settings.yaml + stored-requests-dir: sample + stored-imps-dir: sample + stored-responses-dir: sample + categories-dir: +gdpr: + default-value: 1 + vendorlist: + v2: + cache-dir: /var/tmp/vendor2 + v3: + cache-dir: /var/tmp/vendor3 +admin-endpoints: + logging-changelevel: + enabled: true + path: /logging/changelevel + on-application-port: true + protected: false +hooks: + wurfl-devicedetection: + enabled: true + host-execution-plan: > + { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "entrypoint": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "wurfl-devicedetection", + "hook_impl_code": "wurfl-devicedetection-entrypoint-hook" + } + ] + } + ] + }, + "raw_auction_request": { + "groups": [ + { + "timeout": 10, + "hook_sequence": [ + { + "module_code": "wurfl-devicedetection", + "hook_impl_code": "wurfl-devicedetection-raw-auction-request" + } + ] + } + ] + } + } + } + } + } + From eef4efc3b8692cfcb01796415bf4f792763d67a1 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 10:48:39 +0100 Subject: [PATCH 02/20] updated README.md --- extra/modules/WURFL-devicedetection/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index 86eb9cd8f6b..f25218e6c7e 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -1,5 +1,7 @@ ## WURFL-devicedetection module +### Overview + The **WURFL Device Enrichment Module** for Prebid Server enhances the OpenRTB 2.x payload with comprehensive device detection data powered by **ScientiaMobile**’s WURFL device detection framework. Thanks to WURFL's device database, the module provides accurate and comprehensive device-related information, From 12eb97cc759244d49dc6c0ff8b7b4de89d91df8d Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 10:58:59 +0100 Subject: [PATCH 03/20] updated README.md title --- extra/modules/WURFL-devicedetection/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index f25218e6c7e..cf9a9123fed 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -1,4 +1,4 @@ -## WURFL-devicedetection module +## WURFL Device Enrichment Module ### Overview From da8fd51c5433461936e2db3605716727d1dc1a63 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 11:40:38 +0100 Subject: [PATCH 04/20] some README.md wording improvements --- extra/modules/WURFL-devicedetection/README.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index cf9a9123fed..108fd495963 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -11,7 +11,7 @@ enabling bidders to make better-informed targeting and optimization decisions. #### Device Field Enrichment: -The module populates missing or empty fields in ortb2.device with the following data: +The WURFL module populates missing or empty fields in ortb2.device with the following data: - **make**: Manufacturer of the device (e.g., "Apple", "Samsung"). - **model**: Device model (e.g., "iPhone 14", "Galaxy S22"). - **os**: Operating system (e.g., "iOS", "Android"). @@ -30,10 +30,10 @@ The module identifies publishers through the `getAccount()` method in the `Aucti ### Build prerequisites -To build the WURFL device detection module, you need to download the WURFL Onsite Java API (both JAR and POM files) -from the Scientiamobile private repository and install it in your local Maven repository. -Access to the WURFL Onsite Java API repository requires a valid Scientiamobile WURFL license. -For more details, visit: [Scientiamobile WURFL Onsite API for Java](https://www.scientiamobile.com/secondary-products/wurfl-onsite-api-for-java/). +To build the WURFL module, you need to download the WURFL Onsite Java API (both JAR and POM files) +from the ScientiaMobile private repository and install it in your local Maven repository. +Access to the WURFL Onsite Java API repository requires a valid ScientiaMobile WURFL license. +For more details, visit: [ScientiaMobile WURFL Onsite API for Java](https://www.scientiamobile.com/secondary-products/wurfl-onsite-api-for-java/). Run the following command to install the WURFL API: @@ -47,12 +47,12 @@ mvn install:install-file \ -DpomFile= ``` -### Activating the WURFL Device Detection Module +### Activating the WURFL Module -The WURFL device detection module is disabled by default. Building the Prebid Server Java with the default bundle option +The WURFL module is disabled by default. Building the Prebid Server Java with the default bundle option does not include the WURFL module in the server's JAR file. -To include the WURFL device detection module in the Prebid Server Java bundle, follow these steps: +To include the WURFL module in the Prebid Server Java bundle, follow these steps: 1. Uncomment the WURFL Java API dependency in `extra/modules/WURFL-devicedetection/pom.xml`. 2. Uncomment the WURFL module dependency in `extra/bundle/pom.xml`. @@ -64,9 +64,9 @@ After making these changes, you can build the Prebid Server Java bundle with the mvn clean package --file extra/pom.xml ``` -### Configuring the WURFL Device Detection Module +### Configuring the WURFL Module -Below is a sample configuration for the WURFL device detection module: +Below is a sample configuration for the WURFL module: ```yaml hooks: From c0dd07765e686ed73f3bf7492b0256ff0ff99939 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 11:56:09 +0100 Subject: [PATCH 05/20] used markdown table for config params. --- extra/modules/WURFL-devicedetection/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index 108fd495963..c50ee88595f 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -119,13 +119,15 @@ hooks: ### Configuration Options -- **`wurfl-file-dir-path`** (Mandatory): Path to the directory where the WURFL file is downloaded. Directory must exist and be writable. -- **`wurfl-file-name`** (Mandatory): Name of the WURFL file, typically `wurfl.zip`. -- **`wurfl-file-url`** (Mandatory): URL to the licensed WURFL file to be downloaded when Prebid Server Java starts. -- **`cache-size`** (Optional): Maximum number of devices stored in the WURFL cache. Defaults to the WURFL cache's standard size. -- **`ext_caps`** (Optional): If `true`, the module adds all licensed capabilities to the `device.ext` object. -- **`wurfl-updater-frequency`** (Optional): Frequency for updating the WURFL file. Defaults to no updates. -- **`allowed_publisher_ids`** (Optional): List of publisher IDs permitted to use the module. Defaults to all publishers. +| Parameter | Requirement | Description | +|---------------------------|-------------|-------------------------------------------------------------------------------------------------------| +| **`wurfl-file-dir-path`** | Mandatory | Path to the directory where the WURFL file is downloaded. Directory must exist and be writable. | +| **`wurfl-snapshot-url`** | Mandatory | URL of the licensed WURFL snapshot file to be downloaded when Prebid Server Java starts. | +| **`cache-size`** | Optional | Maximum number of devices stored in the WURFL cache. Defaults to the WURFL cache's standard size. | +| **`ext-caps`** | Optional | If `true`, the module adds all licensed capabilities to the `device.ext` object. | +| **`wurfl-run-updater`** | Optional | Enables the WURFL updater. Defaults to no updates. | +| **`allowed-publisher-ids`** | Optional | List of publisher IDs permitted to use the module. Defaults to all publishers. | + A valid WURFL license must include all the required capabilities for device enrichment. From 39bf6d710dd598473ea757b3b55119f34c999bf8 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 12:01:48 +0100 Subject: [PATCH 06/20] added maintainer email address --- extra/modules/WURFL-devicedetection/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index c50ee88595f..545cdf04ced 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -249,3 +249,7 @@ When `ext_caps` is set to `true`, the response will include all licensed capabil } } ``` + +## Maintainer + +prebid@scientiamobile.com From 3aff0372b5858974fbdcfaf64fa1d1f12bcdef81 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 12:35:25 +0100 Subject: [PATCH 07/20] modified sample request data --- extra/modules/WURFL-devicedetection/sample/request_data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/modules/WURFL-devicedetection/sample/request_data.json b/extra/modules/WURFL-devicedetection/sample/request_data.json index 5184f4ca6f3..42691bbc74d 100644 --- a/extra/modules/WURFL-devicedetection/sample/request_data.json +++ b/extra/modules/WURFL-devicedetection/sample/request_data.json @@ -46,7 +46,7 @@ "domain": "test.com", "publisher": { "domain": "test.com", - "id": "3" + "id": "1" }, "page": "https://www.test.com/" }, From eedc3e0412b95f630be2f191e8c2c18d855cca02 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 13:16:17 +0100 Subject: [PATCH 08/20] fixed typos --- extra/modules/WURFL-devicedetection/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index 545cdf04ced..68f0fcb59ca 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -142,14 +142,14 @@ java -jar target/prebid-server-bundle.jar --spring.config.additional-location=sa This sample configuration contains the module hook basic configuration. All the other module configuration options are located in the `WURFL-devicedetection.yaml` inside the module. -When the server starts, it downloads the WURFL file from the `wurfl-file-url` and loads it into the module. +When the server starts, it downloads the WURFL file from the `wurfl-snapshot-url` and loads it into the module. Sample request data for testing is available in the module's `sample` directory. Using the `auction` endpoint, you can observe WURFL-enriched device data in the response. ### Sample Response -Using the sample request data via `curl` when the module is configured with `ext_caps` set to `false` (or no value) +Using the sample request data via `curl` when the module is configured with `ext-caps` set to `false` (or no value) ```bash curl http://localhost:8080/openrtb2/auction --data @extra/modules/WURFL-devicedetection/sample/request_data.json From 0e93e641b29b93096c098d8ba489b6ff17ea79ef Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 14:40:41 +0100 Subject: [PATCH 09/20] improved wording --- extra/modules/WURFL-devicedetection/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index 68f0fcb59ca..ce1ea054243 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -4,7 +4,7 @@ The **WURFL Device Enrichment Module** for Prebid Server enhances the OpenRTB 2.x payload with comprehensive device detection data powered by **ScientiaMobile**’s WURFL device detection framework. -Thanks to WURFL's device database, the module provides accurate and comprehensive device-related information, +Thanks to WURFL's device knowledge, the module provides accurate and comprehensive device-related information, enabling bidders to make better-informed targeting and optimization decisions. ### Key features From 89cb0dd0e00bcd291033af5df43136e915e9a01f Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 15:20:07 +0100 Subject: [PATCH 10/20] improved published specific enrichment paragraph --- extra/modules/WURFL-devicedetection/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index ce1ea054243..27a901dace3 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -27,6 +27,14 @@ The WURFL module populates missing or empty fields in ortb2.device with the foll Device enrichment is selectively enabled for publishers based on their account ID. The module identifies publishers through the `getAccount()` method in the `AuctionContext` class. +Device enrichment is selectively enabled for publishers based on their account ID. The module identifies publishers through the following fields: + +```json +site.publisher.id (for web environments). +app.publisher.id (for mobile app environments). +dooh.publisher.id (for digital out-of-home environments). +``` + ### Build prerequisites From 7a55ba27d435c5af26005e8cac4d1692f724c452 Mon Sep 17 00:00:00 2001 From: andrea Date: Fri, 17 Jan 2025 15:24:02 +0100 Subject: [PATCH 11/20] better doc formatting --- extra/modules/WURFL-devicedetection/README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index 27a901dace3..eeb319eecf8 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -25,14 +25,11 @@ The WURFL module populates missing or empty fields in ortb2.device with the foll #### Publisher-Specific Enrichment: Device enrichment is selectively enabled for publishers based on their account ID. -The module identifies publishers through the `getAccount()` method in the `AuctionContext` class. +The module identifies publishers through the following fields: -Device enrichment is selectively enabled for publishers based on their account ID. The module identifies publishers through the following fields: - -```json -site.publisher.id (for web environments). -app.publisher.id (for mobile app environments). -dooh.publisher.id (for digital out-of-home environments). +`site.publisher.id` (for web environments). +`app.publisher.id` (for mobile app environments). +`dooh.publisher.id` (for digital out-of-home environments). ``` From 93f31822ce43f3726433697d1acfbe1f4c65a81a Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 24 Feb 2025 14:17:30 +0100 Subject: [PATCH 12/20] Updated WURFL API version, uncommented module add into modules pom. Modified Device mock used in tests. --- extra/bundle/pom.xml | 2 -- extra/modules/WURFL-devicedetection/pom.xml | 4 ++-- .../module-config/WURFL-devicedetection.yaml | 2 +- .../devicedetection/mock/WURFLDeviceMock.java | 17 ++++++++--------- extra/modules/pom.xml | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 4aa2d3ab359..822ad30ea5d 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -55,13 +55,11 @@ pb-request-correction ${project.version} - diff --git a/extra/modules/WURFL-devicedetection/pom.xml b/extra/modules/WURFL-devicedetection/pom.xml index da087b44cca..5ffc9e50899 100644 --- a/extra/modules/WURFL-devicedetection/pom.xml +++ b/extra/modules/WURFL-devicedetection/pom.xml @@ -5,7 +5,7 @@ org.prebid.server.hooks.modules all-modules - 3.19.0-SNAPSHOT + 3.22.0-SNAPSHOT wurfl-devicedetection @@ -14,7 +14,7 @@ WURFL device detection and data enrichment module - 1.13.2.1 + 1.13.3.0 diff --git a/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml b/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml index e8c4f2a5229..73fe089d34c 100644 --- a/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml +++ b/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml @@ -39,7 +39,7 @@ hooks: modules: wurfl-devicedetection: wurfl-file-dir-path: - wurfl-snapshot-url: https://data.scientiamobile.com//wurfl.zip + wurfl-snapshot-url: https://data.scientiamobile.com/your_wurfl_snapshot_url/wurfl.zip cache-size: 200000 wurfl-run-updater: true allowed-publisher-ids: 1 diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java index a3280fe7d42..a6c85e4197f 100644 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java @@ -48,17 +48,12 @@ public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabil return false; } - @Override - public Map getVirtualCapabilities() { - return Map.of(); - } - @Override public String getId() { return id; } - @Override + public String getWURFLUserAgent() { return ""; } @@ -91,17 +86,21 @@ public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedEx }; } - @Override + public Map getCapabilities() { return Map.of(); } - @Override + public Map getVirtualCapabilities() { + return Map.of(); + } + + public boolean isActualDeviceRoot() { return true; } - @Override + public String getDeviceRootId() { return ""; } diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index fc67f358805..04d49f64886 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -24,7 +24,7 @@ pb-response-correction greenbids-real-time-data pb-request-correction - + WURFL-devicedetection From 96d91e452d4c5f0ff1d4d111c012388e926699ad Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 25 Feb 2025 09:07:15 +0100 Subject: [PATCH 13/20] Added WURFL API mock classes --- .../com/scientiamobile/wurfl/core/Device.java | 29 ++++ .../wurfl/core/GeneralWURFLEngine.java | 146 ++++++++++++++++++ .../wurfl/core/WURFLEngine.java | 15 ++ .../wurfl/core/cache/CacheProvider.java | 4 + .../wurfl/core/cache/LRUMapCacheProvider.java | 8 + .../wurfl/core/cache/NullCacheProvider.java | 7 + .../exc/CapabilityNotDefinedException.java | 12 ++ .../VirtualCapabilityNotDefinedException.java | 12 ++ .../wurfl/core/exc/WURFLRuntimeException.java | 12 ++ .../wurfl/core/matchers/MatchType.java | 6 + .../wurfl/core/updater/Frequency.java | 6 + .../wurfl/core/updater/WURFLUpdater.java | 11 ++ 12 files changed, 268 insertions(+) create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java create mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java new file mode 100644 index 00000000000..36096103978 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java @@ -0,0 +1,29 @@ +package com.scientiamobile.wurfl.core; + +import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.matchers.MatchType; + +public interface Device { + + String getId(); + + MatchType getMatchType(); + + String getCapability(String name) throws CapabilityNotDefinedException; + + String getVirtualCapability(String name) + throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException; + + int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException, NumberFormatException; + + boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException, NumberFormatException; + + String getWURFLUserAgent(); + + public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException; + + boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException; +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java new file mode 100644 index 00000000000..5173be11480 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java @@ -0,0 +1,146 @@ +package com.scientiamobile.wurfl.core; + +import com.scientiamobile.wurfl.core.cache.CacheProvider; +import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; +import com.scientiamobile.wurfl.core.matchers.MatchType; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class GeneralWURFLEngine implements WURFLEngine { + + public GeneralWURFLEngine(String wurflPath){} + + public static void wurflDownload(String wurflUrl, String dest) { + } + + static final Map capabilities = Map.ofEntries( + Map.entry("brand_name", "Google"), + Map.entry("model_name", "Pixel 9 Pro XL"), + Map.entry("device_os", "Android"), + Map.entry("device_os_version", "15.0"), + Map.entry("pointing_method", "touchscreen"), + Map.entry("is_wireless_device", "true"), + Map.entry("is_smarttv", "false"), + Map.entry("density_class", "2.55"), + Map.entry("resolution_width", "1344"), + Map.entry("resolution_height", "2992"), + Map.entry("ux_full_desktop", "false"), + Map.entry("marketing_name", ""), + Map.entry("mobile_browser", "Chrome Mobile"), + Map.entry("mobile_browser_version", ""), + Map.entry("preferred_markup", "html_web_4_0"), + Map.entry("is_connected_tv", "false"), + Map.entry("physical_screen_height", "158"), + Map.entry("ajax_support_javascript", "true"), + Map.entry("can_assign_phone_number", "true"), + Map.entry("is_ott", "false"), + Map.entry("is_tablet", "false"), + Map.entry("physical_form_factor", "phone_phablet"), + Map.entry("xhtml_support_level", "4") + ); + static final Map virtualCapabilities = Map.of( + "advertised_device_os", "Android", + "advertised_device_os_version", "15", + "pixel_density", "481", + "is_phone", "true", + "is_mobile", "true", + "is_full_desktop", "false", + "form_factor", "Smartphone", + "is_android", "true", + "is_ios", "false", + "complete_device_name", "Google Pixel 9 Pro XL" + ); + + final static Set capabilitiesKeys = new HashSet<>(capabilities.keySet()); + final static Set virtualCapabilitiesKeys = new HashSet<>(virtualCapabilities.keySet()); + + @Override + public Set getAllCapabilities() { + return capabilitiesKeys; + } + + @Override + public Set getAllVirtualCapabilities() { + return virtualCapabilitiesKeys; + } + + @Override + public void load() { + } + + @Override + public void setCacheProvider(CacheProvider cacheProvider) { + } + + @Override + public Device getDeviceById(String deviceId) { + return mockDevice(); + } + + @Override + public Device getDeviceForRequest(Map headers) { + return mockDevice(); + } + + private Device mockDevice() { + return new Device() { + @Override + public String getId() { + return "google_pixel_9_pro_xl_ver1_suban150"; + } + + @Override + public MatchType getMatchType() { + return MatchType.conclusive; + } + + @Override + public String getCapability(String name) throws CapabilityNotDefinedException { + if(capabilities.containsKey(name)) { + return capabilities.get(name); + } else { + throw new CapabilityNotDefinedException( + "Capability: " + name + " is not defined in WURFL"); + } + } + + @Override + public String getVirtualCapability(String name) throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException { + if(virtualCapabilities.containsKey(name)) { + return virtualCapabilities.get(name); + } else { + throw new VirtualCapabilityNotDefinedException( + "Virtual Capability: " + name + " is not defined in WURFL"); + } + } + + @Override + public int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException, NumberFormatException { + return 0; + } + + @Override + public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException, NumberFormatException { + return Boolean.parseBoolean( getVirtualCapability(vcapName)); + } + + @Override + public String getWURFLUserAgent() { + return ""; + } + + @Override + public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException { + return 0; + } + + @Override + public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException { + return Boolean.parseBoolean( getCapability(capName)); + } + }; + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java new file mode 100644 index 00000000000..0b240536c17 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java @@ -0,0 +1,15 @@ +package com.scientiamobile.wurfl.core; + +import com.scientiamobile.wurfl.core.cache.CacheProvider; + +import java.util.Map; +import java.util.Set; + +public interface WURFLEngine { + Set getAllCapabilities(); + Set getAllVirtualCapabilities(); + void load(); + void setCacheProvider(CacheProvider cacheProvider); + Device getDeviceById(String deviceId); + Device getDeviceForRequest(Map headers); +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java new file mode 100644 index 00000000000..7e190e37491 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java @@ -0,0 +1,4 @@ +package com.scientiamobile.wurfl.core.cache; + +public interface CacheProvider { +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java new file mode 100644 index 00000000000..00de199dece --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java @@ -0,0 +1,8 @@ +package com.scientiamobile.wurfl.core.cache; + +public class LRUMapCacheProvider implements CacheProvider { + + public LRUMapCacheProvider(int maxSize) { + + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java new file mode 100644 index 00000000000..768617aab7f --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java @@ -0,0 +1,7 @@ +package com.scientiamobile.wurfl.core.cache; + +public class NullCacheProvider implements CacheProvider { + + public NullCacheProvider() { + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java new file mode 100644 index 00000000000..64c6ca4393d --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java @@ -0,0 +1,12 @@ +package com.scientiamobile.wurfl.core.exc; + +public class CapabilityNotDefinedException extends WURFLRuntimeException { + + public CapabilityNotDefinedException(String message) { + super(message); + } + + public CapabilityNotDefinedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java new file mode 100644 index 00000000000..0dd80b37bb7 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java @@ -0,0 +1,12 @@ +package com.scientiamobile.wurfl.core.exc; + +public class VirtualCapabilityNotDefinedException extends WURFLRuntimeException { + + public VirtualCapabilityNotDefinedException(String message) { + super(message); + } + + public VirtualCapabilityNotDefinedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java new file mode 100644 index 00000000000..6e20e217a1d --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java @@ -0,0 +1,12 @@ +package com.scientiamobile.wurfl.core.exc; + +public class WURFLRuntimeException extends RuntimeException { + + public WURFLRuntimeException(String message) { + super(message); + } + + public WURFLRuntimeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java new file mode 100644 index 00000000000..a3c5c3dfda4 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java @@ -0,0 +1,6 @@ +package com.scientiamobile.wurfl.core.matchers; + +public enum MatchType { + conclusive, + recovery +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java new file mode 100644 index 00000000000..57d1f5a8a9b --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java @@ -0,0 +1,6 @@ +package com.scientiamobile.wurfl.core.updater; + +public enum Frequency { + DAILY, + WEEKLY +} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java new file mode 100644 index 00000000000..2fe046f1ed3 --- /dev/null +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java @@ -0,0 +1,11 @@ +package com.scientiamobile.wurfl.core.updater; + +import com.scientiamobile.wurfl.core.WURFLEngine; + +public class WURFLUpdater { + + public WURFLUpdater(WURFLEngine engine, String wurflFileUrl){} + + public void setFrequency(Frequency frequency){ } + public void performPeriodicUpdate(){} +} From 9b8b10e9c6f7a03d5881c94eeb9e5ddadad94cc4 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 25 Feb 2025 09:33:56 +0100 Subject: [PATCH 14/20] checkstyle compliance --- .../com/scientiamobile/wurfl/core/Device.java | 2 +- .../wurfl/core/GeneralWURFLEngine.java | 38 ++++++++++--------- .../wurfl/core/WURFLEngine.java | 8 +++- .../wurfl/core/matchers/MatchType.java | 5 ++- .../wurfl/core/updater/Frequency.java | 1 + 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java index 36096103978..ee61c0a29ef 100644 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java @@ -23,7 +23,7 @@ boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotD String getWURFLUserAgent(); - public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException; + int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException; boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException; } diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java index 5173be11480..a349bb082f3 100644 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java @@ -11,12 +11,12 @@ public class GeneralWURFLEngine implements WURFLEngine { - public GeneralWURFLEngine(String wurflPath){} + public GeneralWURFLEngine(String wurflPath) { } public static void wurflDownload(String wurflUrl, String dest) { } - static final Map capabilities = Map.ofEntries( + static final Map CAPABILITIES = Map.ofEntries( Map.entry("brand_name", "Google"), Map.entry("model_name", "Pixel 9 Pro XL"), Map.entry("device_os", "Android"), @@ -41,7 +41,7 @@ public static void wurflDownload(String wurflUrl, String dest) { Map.entry("physical_form_factor", "phone_phablet"), Map.entry("xhtml_support_level", "4") ); - static final Map virtualCapabilities = Map.of( + static final Map VIRTUAL_CAPABILITIES = Map.of( "advertised_device_os", "Android", "advertised_device_os_version", "15", "pixel_density", "481", @@ -54,17 +54,17 @@ public static void wurflDownload(String wurflUrl, String dest) { "complete_device_name", "Google Pixel 9 Pro XL" ); - final static Set capabilitiesKeys = new HashSet<>(capabilities.keySet()); - final static Set virtualCapabilitiesKeys = new HashSet<>(virtualCapabilities.keySet()); + static final Set CAPABILITIES_KEYS = new HashSet<>(CAPABILITIES.keySet()); + static final Set VIRTUAL_CAPABILITIES_KEYS = new HashSet<>(VIRTUAL_CAPABILITIES.keySet()); @Override public Set getAllCapabilities() { - return capabilitiesKeys; + return CAPABILITIES_KEYS; } @Override public Set getAllVirtualCapabilities() { - return virtualCapabilitiesKeys; + return VIRTUAL_CAPABILITIES_KEYS; } @Override @@ -99,8 +99,8 @@ public MatchType getMatchType() { @Override public String getCapability(String name) throws CapabilityNotDefinedException { - if(capabilities.containsKey(name)) { - return capabilities.get(name); + if (CAPABILITIES.containsKey(name)) { + return CAPABILITIES.get(name); } else { throw new CapabilityNotDefinedException( "Capability: " + name + " is not defined in WURFL"); @@ -108,9 +108,10 @@ public String getCapability(String name) throws CapabilityNotDefinedException { } @Override - public String getVirtualCapability(String name) throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException { - if(virtualCapabilities.containsKey(name)) { - return virtualCapabilities.get(name); + public String getVirtualCapability(String name) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException { + if (VIRTUAL_CAPABILITIES.containsKey(name)) { + return VIRTUAL_CAPABILITIES.get(name); } else { throw new VirtualCapabilityNotDefinedException( "Virtual Capability: " + name + " is not defined in WURFL"); @@ -118,13 +119,15 @@ public String getVirtualCapability(String name) throws VirtualCapabilityNotDefin } @Override - public int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException, NumberFormatException { + public int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException, NumberFormatException { return 0; } @Override - public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException, NumberFormatException { - return Boolean.parseBoolean( getVirtualCapability(vcapName)); + public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, + CapabilityNotDefinedException, NumberFormatException { + return Boolean.parseBoolean(getVirtualCapability(vcapName)); } @Override @@ -138,8 +141,9 @@ public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedExcepti } @Override - public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException { - return Boolean.parseBoolean( getCapability(capName)); + public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, + NumberFormatException { + return Boolean.parseBoolean(getCapability(capName)); } }; } diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java index 0b240536c17..6905947faa0 100644 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java @@ -6,10 +6,16 @@ import java.util.Set; public interface WURFLEngine { + Set getAllCapabilities(); + Set getAllVirtualCapabilities(); + void load(); + void setCacheProvider(CacheProvider cacheProvider); + Device getDeviceById(String deviceId); - Device getDeviceForRequest(Map headers); + + Device getDeviceForRequest(Map headers); } diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java index a3c5c3dfda4..dcb55fc2020 100644 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java @@ -1,6 +1,7 @@ package com.scientiamobile.wurfl.core.matchers; public enum MatchType { - conclusive, - recovery + + conclusive, + recovery } diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java index 57d1f5a8a9b..1d088d2832d 100644 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java +++ b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java @@ -1,6 +1,7 @@ package com.scientiamobile.wurfl.core.updater; public enum Frequency { + DAILY, WEEKLY } From 1bd2865edce65e92c29e832f9cf578907b1132a7 Mon Sep 17 00:00:00 2001 From: andrea Date: Tue, 25 Feb 2025 10:57:40 +0100 Subject: [PATCH 15/20] Updated README. More checkstyle compliance --- extra/modules/WURFL-devicedetection/README.md | 19 +++++++++++-------- .../devicedetection/mock/WURFLDeviceMock.java | 4 ---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index eeb319eecf8..dc6182318ba 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -22,6 +22,7 @@ The WURFL module populates missing or empty fields in ortb2.device with the foll - **pixelratio**: Screen pixel density ratio. - **devicetype**: Device type (e.g., mobile, tablet, desktop). - **Note**: If these fields are already populated in the bid request, the module will not overwrite them. + #### Publisher-Specific Enrichment: Device enrichment is selectively enabled for publishers based on their account ID. @@ -30,7 +31,6 @@ The module identifies publishers through the following fields: `site.publisher.id` (for web environments). `app.publisher.id` (for mobile app environments). `dooh.publisher.id` (for digital out-of-home environments). -``` ### Build prerequisites @@ -54,14 +54,17 @@ mvn install:install-file \ ### Activating the WURFL Module -The WURFL module is disabled by default. Building the Prebid Server Java with the default bundle option -does not include the WURFL module in the server's JAR file. +In order to use the WURFL module, you must add the WURFL Onsite Java API dependency in the WURFL-devicedetection module's `pom.xml`: -To include the WURFL module in the Prebid Server Java bundle, follow these steps: - -1. Uncomment the WURFL Java API dependency in `extra/modules/WURFL-devicedetection/pom.xml`. -2. Uncomment the WURFL module dependency in `extra/bundle/pom.xml`. -3. Uncomment the WURFL module name in the module list in `extra/modules/pom.xml`. +```xml + + com.scientiamobile.wurfl + wurfl + ${wurfl.version} + +``` +If the WURFL API dependency is not added, the module will compile a demo version that returns sample data, allowing basic testing without an WURFL Onsite Java API license. +**Note: **Before compiling the Prebid Server Java bundle with the WURFL module, you must also remove the `com` directory under `src/main/java` to avoid classloader issues. After making these changes, you can build the Prebid Server Java bundle with the WURFL module using the following command: diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java index a6c85e4197f..fd2a0bc820e 100644 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java +++ b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java @@ -53,7 +53,6 @@ public String getId() { return id; } - public String getWURFLUserAgent() { return ""; } @@ -86,7 +85,6 @@ public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedEx }; } - public Map getCapabilities() { return Map.of(); } @@ -95,12 +93,10 @@ public Map getVirtualCapabilities() { return Map.of(); } - public boolean isActualDeviceRoot() { return true; } - public String getDeviceRootId() { return ""; } From cee250a4bfc2a2bc86c72c8e301ab35edbac0899 Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 27 Feb 2025 17:42:10 +0100 Subject: [PATCH 16/20] Removed commented dependency --- extra/modules/WURFL-devicedetection/pom.xml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/pom.xml b/extra/modules/WURFL-devicedetection/pom.xml index 5ffc9e50899..b1f353eed99 100644 --- a/extra/modules/WURFL-devicedetection/pom.xml +++ b/extra/modules/WURFL-devicedetection/pom.xml @@ -17,13 +17,5 @@ 1.13.3.0 - - - + From 59fe1a6105996b1779624f122ddfa58d0f67b1e8 Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 3 Mar 2025 12:32:13 +0100 Subject: [PATCH 17/20] Improved WURFL module README.md --- extra/modules/WURFL-devicedetection/README.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md index dc6182318ba..54a36a5cff2 100644 --- a/extra/modules/WURFL-devicedetection/README.md +++ b/extra/modules/WURFL-devicedetection/README.md @@ -19,8 +19,9 @@ The WURFL module populates missing or empty fields in ortb2.device with the foll - **h**: Screen height in pixels. - **w**: Screen width in pixels. - **ppi**: Screen pixels per inch (PPI). - - **pixelratio**: Screen pixel density ratio. + - **pxratio**: Screen pixel density ratio. - **devicetype**: Device type (e.g., mobile, tablet, desktop). + - **js**: Support for JavaScript, where 0 = no, 1 = yes - **Note**: If these fields are already populated in the bid request, the module will not overwrite them. #### Publisher-Specific Enrichment: @@ -33,14 +34,12 @@ The module identifies publishers through the following fields: `dooh.publisher.id` (for digital out-of-home environments). -### Build prerequisites +### Building WURFL Module with a licensed WURFL Onsite Java API -To build the WURFL module, you need to download the WURFL Onsite Java API (both JAR and POM files) -from the ScientiaMobile private repository and install it in your local Maven repository. -Access to the WURFL Onsite Java API repository requires a valid ScientiaMobile WURFL license. -For more details, visit: [ScientiaMobile WURFL Onsite API for Java](https://www.scientiamobile.com/secondary-products/wurfl-onsite-api-for-java/). +In order to compile the WURFL module in the PBS Java server bundle, you must follow these steps: -Run the following command to install the WURFL API: +1 - Download the WURFL Onsite Java API (both JAR and POM files) +from the ScientiaMobile private repository and install it in your local Maven repository ```bash mvn install:install-file \ @@ -52,9 +51,7 @@ mvn install:install-file \ -DpomFile= ``` -### Activating the WURFL Module - -In order to use the WURFL module, you must add the WURFL Onsite Java API dependency in the WURFL-devicedetection module's `pom.xml`: +2 - add the WURFL Onsite Java API dependency in the WURFL-devicedetection module's `pom.xml`: ```xml @@ -64,13 +61,16 @@ In order to use the WURFL module, you must add the WURFL Onsite Java API depende ``` If the WURFL API dependency is not added, the module will compile a demo version that returns sample data, allowing basic testing without an WURFL Onsite Java API license. -**Note: **Before compiling the Prebid Server Java bundle with the WURFL module, you must also remove the `com` directory under `src/main/java` to avoid classloader issues. -After making these changes, you can build the Prebid Server Java bundle with the WURFL module using the following command: +3 - Remove the `com` directory under `src/main/java` to avoid classloader issues. + +4 - Build the Prebid Server Java bundle with the WURFL module using the following command: ```bash mvn clean package --file extra/pom.xml ``` +**NOTE** - For further automation of WURFL API dependency usage, please check the paragraph +"Configuring your Builds to work with ScientiaMobile's Private Maven Repository" [on this page](https://docs.scientiamobile.com/documentation/onsite/onsite-java-api). ### Configuring the WURFL Module From f5fa9ff79a9628cca80acd454bd25cba1fb05b8b Mon Sep 17 00:00:00 2001 From: andrea Date: Mon, 3 Mar 2025 14:03:38 +0100 Subject: [PATCH 18/20] Aligned dev branch to upstream repo master branch --- extra/bundle/pom.xml | 5 - extra/modules/WURFL-devicedetection/README.md | 263 ----------------- extra/modules/WURFL-devicedetection/pom.xml | 21 -- .../sample/request_data.json | 119 -------- .../com/scientiamobile/wurfl/core/Device.java | 29 -- .../wurfl/core/GeneralWURFLEngine.java | 150 ---------- .../wurfl/core/WURFLEngine.java | 21 -- .../wurfl/core/cache/CacheProvider.java | 4 - .../wurfl/core/cache/LRUMapCacheProvider.java | 8 - .../wurfl/core/cache/NullCacheProvider.java | 7 - .../exc/CapabilityNotDefinedException.java | 12 - .../VirtualCapabilityNotDefinedException.java | 12 - .../wurfl/core/exc/WURFLRuntimeException.java | 12 - .../wurfl/core/matchers/MatchType.java | 7 - .../wurfl/core/updater/Frequency.java | 7 - .../wurfl/core/updater/WURFLUpdater.java | 11 - .../WURFLDeviceDetectionConfigProperties.java | 51 ---- .../WURFLDeviceDetectionConfiguration.java | 37 --- .../WURFLModuleConfigurationException.java | 8 - .../model/AuctionRequestHeadersContext.java | 31 -- .../model/WURFLEngineInitializer.java | 112 ------- .../resolver/HeadersResolver.java | 132 --------- .../resolver/PlatformNameVersion.java | 33 --- .../devicedetection/v1/AccountValidator.java | 31 -- .../devicedetection/v1/ExtWURFLMapper.java | 65 ---- .../devicedetection/v1/OrtbDeviceUpdater.java | 259 ---------------- .../WURFLDeviceDetectionEntrypointHook.java | 37 --- .../v1/WURFLDeviceDetectionModule.java | 29 -- ...LDeviceDetectionRawAuctionRequestHook.java | 143 --------- .../module-config/WURFL-devicedetection.yaml | 46 --- ...FLDeviceDetectionConfigPropertiesTest.java | 46 --- .../devicedetection/mock/WURFLDeviceMock.java | 277 ------------------ .../AuctionRequestHeadersContextTest.java | 65 ---- .../model/WURFLEngineInitializerTest.java | 125 -------- .../resolver/HeadersResolverTest.java | 199 ------------- .../resolver/PlatformNameVersionTest.java | 69 ----- .../v1/AccountValidatorTest.java | 110 ------- .../v1/ExtWURFLMapperTest.java | 131 --------- .../v1/OrtbDeviceUpdaterTest.java | 243 --------------- ...URFLDeviceDetectionEntrypointHookTest.java | 81 ----- .../v1/WURFLDeviceDetectionModuleTest.java | 40 --- ...iceDetectionRawAuctionRequestHookTest.java | 124 -------- extra/modules/pom.xml | 1 - sample/configs/prebid-config-with-wurfl.yaml | 80 ----- 44 files changed, 3293 deletions(-) delete mode 100644 extra/modules/WURFL-devicedetection/README.md delete mode 100644 extra/modules/WURFL-devicedetection/pom.xml delete mode 100644 extra/modules/WURFL-devicedetection/sample/request_data.json delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java delete mode 100644 extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java delete mode 100644 extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java delete mode 100644 sample/configs/prebid-config-with-wurfl.yaml diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml index 822ad30ea5d..783f2e26c1d 100644 --- a/extra/bundle/pom.xml +++ b/extra/bundle/pom.xml @@ -55,11 +55,6 @@ pb-request-correction ${project.version} - - org.prebid.server.hooks.modules - wurfl-devicedetection - ${project.version} - diff --git a/extra/modules/WURFL-devicedetection/README.md b/extra/modules/WURFL-devicedetection/README.md deleted file mode 100644 index 54a36a5cff2..00000000000 --- a/extra/modules/WURFL-devicedetection/README.md +++ /dev/null @@ -1,263 +0,0 @@ -## WURFL Device Enrichment Module - -### Overview - -The **WURFL Device Enrichment Module** for Prebid Server enhances the OpenRTB 2.x payload -with comprehensive device detection data powered by **ScientiaMobile**’s WURFL device detection framework. -Thanks to WURFL's device knowledge, the module provides accurate and comprehensive device-related information, -enabling bidders to make better-informed targeting and optimization decisions. - -### Key features - -#### Device Field Enrichment: - -The WURFL module populates missing or empty fields in ortb2.device with the following data: - - **make**: Manufacturer of the device (e.g., "Apple", "Samsung"). - - **model**: Device model (e.g., "iPhone 14", "Galaxy S22"). - - **os**: Operating system (e.g., "iOS", "Android"). - - **osv**: Operating system version (e.g., "16.0", "12.0"). - - **h**: Screen height in pixels. - - **w**: Screen width in pixels. - - **ppi**: Screen pixels per inch (PPI). - - **pxratio**: Screen pixel density ratio. - - **devicetype**: Device type (e.g., mobile, tablet, desktop). - - **js**: Support for JavaScript, where 0 = no, 1 = yes - - **Note**: If these fields are already populated in the bid request, the module will not overwrite them. - -#### Publisher-Specific Enrichment: - -Device enrichment is selectively enabled for publishers based on their account ID. -The module identifies publishers through the following fields: - -`site.publisher.id` (for web environments). -`app.publisher.id` (for mobile app environments). -`dooh.publisher.id` (for digital out-of-home environments). - - -### Building WURFL Module with a licensed WURFL Onsite Java API - -In order to compile the WURFL module in the PBS Java server bundle, you must follow these steps: - -1 - Download the WURFL Onsite Java API (both JAR and POM files) -from the ScientiaMobile private repository and install it in your local Maven repository - -```bash -mvn install:install-file \ - -Dfile= \ - -DgroupId=com.scientiamobile.wurfl \ - -DartifactId=wurfl \ - -Dversion= \ - -Dpackaging=jar \ - -DpomFile= -``` - -2 - add the WURFL Onsite Java API dependency in the WURFL-devicedetection module's `pom.xml`: - -```xml - - com.scientiamobile.wurfl - wurfl - ${wurfl.version} - -``` -If the WURFL API dependency is not added, the module will compile a demo version that returns sample data, allowing basic testing without an WURFL Onsite Java API license. - -3 - Remove the `com` directory under `src/main/java` to avoid classloader issues. - -4 - Build the Prebid Server Java bundle with the WURFL module using the following command: - -```bash -mvn clean package --file extra/pom.xml -``` -**NOTE** - For further automation of WURFL API dependency usage, please check the paragraph -"Configuring your Builds to work with ScientiaMobile's Private Maven Repository" [on this page](https://docs.scientiamobile.com/documentation/onsite/onsite-java-api). - -### Configuring the WURFL Module - -Below is a sample configuration for the WURFL module: - -```yaml -hooks: - wurfl-devicedetection: - enabled: true - host-execution-plan: > - { - "endpoints": { - "/openrtb2/auction": { - "stages": { - "entrypoint": { - "groups": [ - { - "timeout": 10, - "hook_sequence": [ - { - "module_code": "wurfl-devicedetection", - "hook_impl_code": "wurfl-devicedetection-entrypoint-hook" - } - ] - } - ] - }, - "raw_auction_request": { - "groups": [ - { - "timeout": 10, - "hook_sequence": [ - { - "module_code": "wurfl-devicedetection", - "hook_impl_code": "wurfl-devicedetection-raw-auction-request" - } - ] - } - ] - } - } - } - } - } - modules: - wurfl-devicedetection: - wurfl-file-dir-path: - wurfl-snapshot-url: https://data.scientiamobile.com//wurfl.zip - cache-size: 200000 - wurfl-run-updater: true - allowed-publisher-ids: 1 - ext-caps: false -``` - -### Configuration Options - -| Parameter | Requirement | Description | -|---------------------------|-------------|-------------------------------------------------------------------------------------------------------| -| **`wurfl-file-dir-path`** | Mandatory | Path to the directory where the WURFL file is downloaded. Directory must exist and be writable. | -| **`wurfl-snapshot-url`** | Mandatory | URL of the licensed WURFL snapshot file to be downloaded when Prebid Server Java starts. | -| **`cache-size`** | Optional | Maximum number of devices stored in the WURFL cache. Defaults to the WURFL cache's standard size. | -| **`ext-caps`** | Optional | If `true`, the module adds all licensed capabilities to the `device.ext` object. | -| **`wurfl-run-updater`** | Optional | Enables the WURFL updater. Defaults to no updates. | -| **`allowed-publisher-ids`** | Optional | List of publisher IDs permitted to use the module. Defaults to all publishers. | - - -A valid WURFL license must include all the required capabilities for device enrichment. - -### Launching Prebid Server Java with the WURFL Module - -After configuring the module and successfully building the Prebid Server bundle, start the server with the following command: - -```bash -java -jar target/prebid-server-bundle.jar --spring.config.additional-location=sample/configs/prebid-config-with-wurfl.yaml -``` - -This sample configuration contains the module hook basic configuration. All the other module configuration options -are located in the `WURFL-devicedetection.yaml` inside the module. - -When the server starts, it downloads the WURFL file from the `wurfl-snapshot-url` and loads it into the module. - -Sample request data for testing is available in the module's `sample` directory. Using the `auction` endpoint, -you can observe WURFL-enriched device data in the response. - -### Sample Response - -Using the sample request data via `curl` when the module is configured with `ext-caps` set to `false` (or no value) - -```bash -curl http://localhost:8080/openrtb2/auction --data @extra/modules/WURFL-devicedetection/sample/request_data.json -``` - -the device object in the response will include WURFL device detection data: - -```json -"device": { - "ua": "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL Build/AP3A.241005.015;) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64", - "devicetype": 1, - "make": "Google", - "model": "Pixel 9 Pro XL", - "os": "Android", - "osv": "15", - "h": 2992, - "w": 1344, - "ppi": 481, - "pxratio": 2.55, - "js": 1, - "ext": { - "wurfl": { - "wurfl_id": "google_pixel_9_pro_xl_ver1_suban150" - } - } -} -``` - -When `ext_caps` is set to `true`, the response will include all licensed capabilities: - -```json -"device":{ - "ua":"Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL Build/AP3A.241005.015; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64", - "devicetype":1, - "make":"Google", - "model":"Pixel 9 Pro XL", - "os":"Android", - "osv":"15", - "h":2992, - "w":1344, - "ppi":481, - "pxratio":2.55, - "js":1, - "ext":{ - "wurfl":{ - "wurfl_id":"google_pixel_9_pro_xl_ver1_suban150", - "mobile_browser_version":"", - "resolution_height":"2992", - "resolution_width":"1344", - "is_wireless_device":"true", - "is_tablet":"false", - "physical_form_factor":"phone_phablet", - "ajax_support_javascript":"true", - "preferred_markup":"html_web_4_0", - "brand_name":"Google", - "can_assign_phone_number":"true", - "xhtml_support_level":"4", - "ux_full_desktop":"false", - "device_os":"Android", - "physical_screen_width":"71", - "is_connected_tv":"false", - "is_smarttv":"false", - "physical_screen_height":"158", - "model_name":"Pixel 9 Pro XL", - "is_ott":"false", - "density_class":"2.55", - "marketing_name":"", - "device_os_version":"15.0", - "mobile_browser":"Chrome Mobile", - "pointing_method":"touchscreen", - "is_app_webview":"false", - "advertised_app_name":"Edge Browser", - "is_smartphone":"true", - "is_robot":"false", - "advertised_device_os":"Android", - "is_largescreen":"true", - "is_android":"true", - "is_xhtmlmp_preferred":"false", - "device_name":"Google Pixel 9 Pro XL", - "is_ios":"false", - "is_touchscreen":"true", - "is_wml_preferred":"false", - "is_app":"false", - "is_mobile":"true", - "is_phone":"true", - "is_full_desktop":"false", - "is_generic":"false", - "advertised_browser":"Edge", - "complete_device_name":"Google Pixel 9 Pro XL", - "advertised_browser_version":"124.0.2478.64", - "is_html_preferred":"true", - "is_windows_phone":"false", - "pixel_density":"481", - "form_factor":"Smartphone", - "advertised_device_os_version":"15" - } - } -} -``` - -## Maintainer - -prebid@scientiamobile.com diff --git a/extra/modules/WURFL-devicedetection/pom.xml b/extra/modules/WURFL-devicedetection/pom.xml deleted file mode 100644 index b1f353eed99..00000000000 --- a/extra/modules/WURFL-devicedetection/pom.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - 4.0.0 - - - org.prebid.server.hooks.modules - all-modules - 3.22.0-SNAPSHOT - - - wurfl-devicedetection - - wurfl-devicedetection - WURFL device detection and data enrichment module - - - 1.13.3.0 - - - - diff --git a/extra/modules/WURFL-devicedetection/sample/request_data.json b/extra/modules/WURFL-devicedetection/sample/request_data.json deleted file mode 100644 index 42691bbc74d..00000000000 --- a/extra/modules/WURFL-devicedetection/sample/request_data.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "imp": [ - { - "ext": { - "data": { - "adserver": { - "name": "gam", - "adslot": "test" - }, - "pbadslot": "test", - "gpid": "test" - }, - "gpid": "test", - "prebid": { - "bidder": { - "appnexus": { - "placement_id": 1, - "use_pmt_rule": false - }, - "0test": { - "placement_id": 1 - } - }, - "adunitcode": "25e8ad9f-13a4-4404-ba74-f9eebff0e86c", - "floors": { - "floorMin": 0.01 - } - } - }, - "id": "2529eeea-813e-4da6-838f-f91c28d64867", - "banner": { - "topframe": 1, - "format": [ - { - "w": 728, - "h": 90 - } - ], - "pos": 1 - }, - "bidfloor": 0.01, - "bidfloorcur": "USD" - } - ], - "site": { - "domain": "test.com", - "publisher": { - "domain": "test.com", - "id": "1" - }, - "page": "https://www.test.com/" - }, - "device": { - "ua": "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL Build/AP3A.241005.015; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 EdgA/124.0.2478.64" - }, - "id": "fc4670ce-4985-4316-a245-b43c885dc37a", - "test": 1, - "cur": [ - "USD" - ], - "source": { - "ext": { - "schain": { - "ver": "1.0", - "complete": 1, - "nodes": [ - { - "asi": "example.com", - "sid": "1234", - "hp": 1 - } - ] - } - } - }, - "ext": { - "prebid": { - "cache": { - "bids": { - "returnCreative": true - }, - "vastxml": { - "returnCreative": true - } - }, - "auctiontimestamp": 1799310801804, - "targeting": { - "includewinners": true, - "includebidderkeys": false - }, - "schains": [ - { - "bidders": [ - "appnexus" - ], - "schain": { - "ver": "1.0", - "complete": 1, - "nodes": [ - { - "asi": "example.com", - "sid": "1234", - "hp": 1 - } - ] - } - } - ], - "floors": { - "enabled": false, - "floorMin": 0.01, - "floorMinCur": "USD" - }, - "createtids": false - } - }, - "user": {}, - "tmax": 2000 -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java deleted file mode 100644 index ee61c0a29ef..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/Device.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.scientiamobile.wurfl.core; - -import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.matchers.MatchType; - -public interface Device { - - String getId(); - - MatchType getMatchType(); - - String getCapability(String name) throws CapabilityNotDefinedException; - - String getVirtualCapability(String name) - throws VirtualCapabilityNotDefinedException, CapabilityNotDefinedException; - - int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException, NumberFormatException; - - boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException, NumberFormatException; - - String getWURFLUserAgent(); - - int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException; - - boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException; -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java deleted file mode 100644 index a349bb082f3..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/GeneralWURFLEngine.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.scientiamobile.wurfl.core; - -import com.scientiamobile.wurfl.core.cache.CacheProvider; -import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.matchers.MatchType; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class GeneralWURFLEngine implements WURFLEngine { - - public GeneralWURFLEngine(String wurflPath) { } - - public static void wurflDownload(String wurflUrl, String dest) { - } - - static final Map CAPABILITIES = Map.ofEntries( - Map.entry("brand_name", "Google"), - Map.entry("model_name", "Pixel 9 Pro XL"), - Map.entry("device_os", "Android"), - Map.entry("device_os_version", "15.0"), - Map.entry("pointing_method", "touchscreen"), - Map.entry("is_wireless_device", "true"), - Map.entry("is_smarttv", "false"), - Map.entry("density_class", "2.55"), - Map.entry("resolution_width", "1344"), - Map.entry("resolution_height", "2992"), - Map.entry("ux_full_desktop", "false"), - Map.entry("marketing_name", ""), - Map.entry("mobile_browser", "Chrome Mobile"), - Map.entry("mobile_browser_version", ""), - Map.entry("preferred_markup", "html_web_4_0"), - Map.entry("is_connected_tv", "false"), - Map.entry("physical_screen_height", "158"), - Map.entry("ajax_support_javascript", "true"), - Map.entry("can_assign_phone_number", "true"), - Map.entry("is_ott", "false"), - Map.entry("is_tablet", "false"), - Map.entry("physical_form_factor", "phone_phablet"), - Map.entry("xhtml_support_level", "4") - ); - static final Map VIRTUAL_CAPABILITIES = Map.of( - "advertised_device_os", "Android", - "advertised_device_os_version", "15", - "pixel_density", "481", - "is_phone", "true", - "is_mobile", "true", - "is_full_desktop", "false", - "form_factor", "Smartphone", - "is_android", "true", - "is_ios", "false", - "complete_device_name", "Google Pixel 9 Pro XL" - ); - - static final Set CAPABILITIES_KEYS = new HashSet<>(CAPABILITIES.keySet()); - static final Set VIRTUAL_CAPABILITIES_KEYS = new HashSet<>(VIRTUAL_CAPABILITIES.keySet()); - - @Override - public Set getAllCapabilities() { - return CAPABILITIES_KEYS; - } - - @Override - public Set getAllVirtualCapabilities() { - return VIRTUAL_CAPABILITIES_KEYS; - } - - @Override - public void load() { - } - - @Override - public void setCacheProvider(CacheProvider cacheProvider) { - } - - @Override - public Device getDeviceById(String deviceId) { - return mockDevice(); - } - - @Override - public Device getDeviceForRequest(Map headers) { - return mockDevice(); - } - - private Device mockDevice() { - return new Device() { - @Override - public String getId() { - return "google_pixel_9_pro_xl_ver1_suban150"; - } - - @Override - public MatchType getMatchType() { - return MatchType.conclusive; - } - - @Override - public String getCapability(String name) throws CapabilityNotDefinedException { - if (CAPABILITIES.containsKey(name)) { - return CAPABILITIES.get(name); - } else { - throw new CapabilityNotDefinedException( - "Capability: " + name + " is not defined in WURFL"); - } - } - - @Override - public String getVirtualCapability(String name) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException { - if (VIRTUAL_CAPABILITIES.containsKey(name)) { - return VIRTUAL_CAPABILITIES.get(name); - } else { - throw new VirtualCapabilityNotDefinedException( - "Virtual Capability: " + name + " is not defined in WURFL"); - } - } - - @Override - public int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException, NumberFormatException { - return 0; - } - - @Override - public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException, NumberFormatException { - return Boolean.parseBoolean(getVirtualCapability(vcapName)); - } - - @Override - public String getWURFLUserAgent() { - return ""; - } - - @Override - public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException { - return 0; - } - - @Override - public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, - NumberFormatException { - return Boolean.parseBoolean(getCapability(capName)); - } - }; - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java deleted file mode 100644 index 6905947faa0..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/WURFLEngine.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.scientiamobile.wurfl.core; - -import com.scientiamobile.wurfl.core.cache.CacheProvider; - -import java.util.Map; -import java.util.Set; - -public interface WURFLEngine { - - Set getAllCapabilities(); - - Set getAllVirtualCapabilities(); - - void load(); - - void setCacheProvider(CacheProvider cacheProvider); - - Device getDeviceById(String deviceId); - - Device getDeviceForRequest(Map headers); -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java deleted file mode 100644 index 7e190e37491..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/CacheProvider.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.scientiamobile.wurfl.core.cache; - -public interface CacheProvider { -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java deleted file mode 100644 index 00de199dece..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/LRUMapCacheProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.scientiamobile.wurfl.core.cache; - -public class LRUMapCacheProvider implements CacheProvider { - - public LRUMapCacheProvider(int maxSize) { - - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java deleted file mode 100644 index 768617aab7f..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/cache/NullCacheProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.scientiamobile.wurfl.core.cache; - -public class NullCacheProvider implements CacheProvider { - - public NullCacheProvider() { - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java deleted file mode 100644 index 64c6ca4393d..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/CapabilityNotDefinedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.scientiamobile.wurfl.core.exc; - -public class CapabilityNotDefinedException extends WURFLRuntimeException { - - public CapabilityNotDefinedException(String message) { - super(message); - } - - public CapabilityNotDefinedException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java deleted file mode 100644 index 0dd80b37bb7..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/VirtualCapabilityNotDefinedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.scientiamobile.wurfl.core.exc; - -public class VirtualCapabilityNotDefinedException extends WURFLRuntimeException { - - public VirtualCapabilityNotDefinedException(String message) { - super(message); - } - - public VirtualCapabilityNotDefinedException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java deleted file mode 100644 index 6e20e217a1d..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/exc/WURFLRuntimeException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.scientiamobile.wurfl.core.exc; - -public class WURFLRuntimeException extends RuntimeException { - - public WURFLRuntimeException(String message) { - super(message); - } - - public WURFLRuntimeException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java deleted file mode 100644 index dcb55fc2020..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/matchers/MatchType.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.scientiamobile.wurfl.core.matchers; - -public enum MatchType { - - conclusive, - recovery -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java deleted file mode 100644 index 1d088d2832d..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/Frequency.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.scientiamobile.wurfl.core.updater; - -public enum Frequency { - - DAILY, - WEEKLY -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java b/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java deleted file mode 100644 index 2fe046f1ed3..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/com/scientiamobile/wurfl/core/updater/WURFLUpdater.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.scientiamobile.wurfl.core.updater; - -import com.scientiamobile.wurfl.core.WURFLEngine; - -public class WURFLUpdater { - - public WURFLUpdater(WURFLEngine engine, String wurflFileUrl){} - - public void setFrequency(Frequency frequency){ } - public void performPeriodicUpdate(){} -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java deleted file mode 100644 index e9d60e6880f..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigProperties.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config; - -import lombok.Data; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionModule; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.List; -import java.util.Set; - -@ConfigurationProperties(prefix = "hooks.modules." + WURFLDeviceDetectionModule.CODE) -@Data - -public class WURFLDeviceDetectionConfigProperties { - - public static final Set REQUIRED_STATIC_CAPS = Set.of( - "ajax_support_javascript", - "brand_name", - "density_class", - "is_connected_tv", - "is_ott", - "is_tablet", - "model_name", - "resolution_height", - "resolution_width", - "physical_form_factor" - ); - - public static final Set REQUIRED_VIRTUAL_CAPS = Set.of( - - "advertised_device_os", - "advertised_device_os_version", - "complete_device_name", - "is_full_desktop", - "is_mobile", - "is_phone", - "form_factor", - "pixel_density" - ); - - int cacheSize; - - String wurflFileDirPath; - - String wurflSnapshotUrl; - - boolean extCaps; - - boolean wurflRunUpdater = true; - - List allowedPublisherIds = List.of(); -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java deleted file mode 100644 index 770ae1cd88f..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config; - -import com.scientiamobile.wurfl.core.WURFLEngine; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.WURFLEngineInitializer; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionEntrypointHook; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionModule; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1.WURFLDeviceDetectionRawAuctionRequestHook; -import org.prebid.server.spring.env.YamlPropertySourceFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -import java.util.List; - -@ConditionalOnProperty(prefix = "hooks." + WURFLDeviceDetectionModule.CODE, name = "enabled", havingValue = "true") -@Configuration -@PropertySource( - value = "classpath:/module-config/WURFL-devicedetection.yaml", - factory = YamlPropertySourceFactory.class) -@EnableConfigurationProperties(WURFLDeviceDetectionConfigProperties.class) -public class WURFLDeviceDetectionConfiguration { - - @Bean - public WURFLDeviceDetectionModule wurflDeviceDetectionModule(WURFLDeviceDetectionConfigProperties - configProperties) { - - final WURFLEngine wurflEngine = WURFLEngineInitializer.builder() - .configProperties(configProperties) - .build().initWURFLEngine(); - wurflEngine.load(); - - return new WURFLDeviceDetectionModule(List.of(new WURFLDeviceDetectionEntrypointHook(), - new WURFLDeviceDetectionRawAuctionRequestHook(wurflEngine, configProperties))); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java deleted file mode 100644 index d2c767c45c6..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/exc/WURFLModuleConfigurationException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.exc; - -public class WURFLModuleConfigurationException extends RuntimeException { - - public WURFLModuleConfigurationException(String message) { - super(message); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java deleted file mode 100644 index 95a32fbfabe..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContext.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; - -import lombok.Getter; -import org.prebid.server.model.CaseInsensitiveMultiMap; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -@Getter -public class AuctionRequestHeadersContext { - - Map headers; - - private AuctionRequestHeadersContext(Map headers) { - this.headers = headers; - } - - public static AuctionRequestHeadersContext from(final CaseInsensitiveMultiMap headers) { - final Map headersMap = new HashMap<>(); - if (Objects.isNull(headers)) { - return new AuctionRequestHeadersContext(headersMap); - } - - for (String headerName : headers.names()) { - headersMap.put(headerName, headers.getAll(headerName).getFirst()); - } - return new AuctionRequestHeadersContext(headersMap); - } - -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java deleted file mode 100644 index 45e288dad33..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializer.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; - -import com.scientiamobile.wurfl.core.GeneralWURFLEngine; -import com.scientiamobile.wurfl.core.WURFLEngine; -import com.scientiamobile.wurfl.core.cache.LRUMapCacheProvider; -import com.scientiamobile.wurfl.core.cache.NullCacheProvider; -import com.scientiamobile.wurfl.core.updater.Frequency; -import com.scientiamobile.wurfl.core.updater.WURFLUpdater; -import lombok.Builder; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.exc.WURFLModuleConfigurationException; - -import java.net.URI; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Slf4j -@Builder -public class WURFLEngineInitializer { - - private WURFLDeviceDetectionConfigProperties configProperties; - - public WURFLEngine initWURFLEngine() { - downloadWurflFile(configProperties); - final WURFLEngine engine = initializeEngine(configProperties); - setupUpdater(configProperties, engine); - return engine; - } - - static void downloadWurflFile(WURFLDeviceDetectionConfigProperties configProperties) { - if (StringUtils.isNotBlank(configProperties.getWurflSnapshotUrl()) - && StringUtils.isNotBlank(configProperties.getWurflFileDirPath())) { - GeneralWURFLEngine.wurflDownload( - configProperties.getWurflSnapshotUrl(), - configProperties.getWurflFileDirPath()); - } - } - - static WURFLEngine initializeEngine(WURFLDeviceDetectionConfigProperties configProperties) { - - final String wurflFileName = extractWURFLFileName(configProperties.getWurflSnapshotUrl()); - - final Path wurflPath = Paths.get( - configProperties.getWurflFileDirPath(), - wurflFileName - ); - final WURFLEngine engine = new GeneralWURFLEngine(wurflPath.toString()); - verifyStaticCapabilitiesDefinition(engine); - - if (configProperties.getCacheSize() > 0) { - engine.setCacheProvider(new LRUMapCacheProvider(configProperties.getCacheSize())); - } else { - engine.setCacheProvider(new NullCacheProvider()); - } - return engine; - } - - private static String extractWURFLFileName(String wurflSnapshotUrl) { - - try { - final URI uri = new URI(wurflSnapshotUrl); - final String path = uri.getPath(); - return path.substring(path.lastIndexOf('/') + 1); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid WURFL snapshot URL: " + wurflSnapshotUrl, e); - } - } - - static void verifyStaticCapabilitiesDefinition(WURFLEngine engine) { - - final List unsupportedStaticCaps = new ArrayList<>(); - final Map allCaps = engine.getAllCapabilities().stream() - .collect(Collectors.toMap( - key -> key, - value -> true - )); - - for (String requiredCapName : WURFLDeviceDetectionConfigProperties.REQUIRED_STATIC_CAPS) { - if (!allCaps.containsKey(requiredCapName)) { - unsupportedStaticCaps.add(requiredCapName); - } - } - - if (!unsupportedStaticCaps.isEmpty()) { - Collections.sort(unsupportedStaticCaps); - final String failedCheckMessage = """ - Static capabilities %s needed for device enrichment are not defined in WURFL. - Please make sure that your license has the needed capabilities or upgrade it. - """.formatted(String.join(",", unsupportedStaticCaps)); - - throw new WURFLModuleConfigurationException(failedCheckMessage); - } - - } - - static void setupUpdater(WURFLDeviceDetectionConfigProperties configProperties, WURFLEngine engine) { - final boolean runUpdater = configProperties.isWurflRunUpdater(); - - if (runUpdater) { - final WURFLUpdater updater = new WURFLUpdater(engine, configProperties.getWurflSnapshotUrl()); - updater.setFrequency(Frequency.DAILY); - updater.performPeriodicUpdate(); - } - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java deleted file mode 100644 index 5051b47a9dd..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolver.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; - -import com.iab.openrtb.request.BrandVersion; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.UserAgent; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.MapUtils; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; - -@Slf4j -public class HeadersResolver { - - static final String SEC_CH_UA = "Sec-CH-UA"; - static final String SEC_CH_UA_PLATFORM = "Sec-CH-UA-Platform"; - static final String SEC_CH_UA_PLATFORM_VERSION = "Sec-CH-UA-Platform-Version"; - static final String SEC_CH_UA_MOBILE = "Sec-CH-UA-Mobile"; - static final String SEC_CH_UA_ARCH = "Sec-CH-UA-Arch"; - static final String SEC_CH_UA_MODEL = "Sec-CH-UA-Model"; - static final String SEC_CH_UA_FULL_VERSION_LIST = "Sec-CH-UA-Full-Version-List"; - static final String USER_AGENT = "User-Agent"; - - public Map resolve(final Device device, final Map headers) { - - if (Objects.isNull(device) && Objects.isNull(headers)) { - return new HashMap<>(); - } - - final Map resolvedHeaders = resolveFromOrtbDevice(device); - if (MapUtils.isEmpty(resolvedHeaders)) { - return headers; - } - - return resolvedHeaders; - } - - private Map resolveFromOrtbDevice(Device device) { - - final Map resolvedHeaders = new HashMap<>(); - - if (Objects.isNull(device)) { - log.warn("ORBT Device is null"); - return resolvedHeaders; - } - - if (Objects.nonNull(device.getUa())) { - resolvedHeaders.put(USER_AGENT, device.getUa()); - } - - resolvedHeaders.putAll(resolveFromSua(device.getSua())); - return resolvedHeaders; - } - - private Map resolveFromSua(UserAgent sua) { - - final Map headers = new HashMap<>(); - - if (Objects.isNull(sua)) { - log.warn("Sua is null, returning empty headers"); - return new HashMap<>(); - } - - // Browser brands and versions - final List brands = sua.getBrowsers(); - if (CollectionUtils.isEmpty(brands)) { - log.warn("No browser brands and versions found"); - return headers; - } - - final String brandList = brandListAsString(brands); - headers.put(SEC_CH_UA, brandList); - headers.put(SEC_CH_UA_FULL_VERSION_LIST, brandList); - - // Platform - final PlatformNameVersion platformNameVersion = PlatformNameVersion.from(sua.getPlatform()); - if (Objects.nonNull(platformNameVersion)) { - headers.put(SEC_CH_UA_PLATFORM, escape(platformNameVersion.getPlatformName())); - headers.put(SEC_CH_UA_PLATFORM_VERSION, escape(platformNameVersion.getPlatformVersion())); - } - - // Model - final String model = sua.getModel(); - if (Objects.nonNull(model) && !model.isEmpty()) { - headers.put(SEC_CH_UA_MODEL, escape(model)); - } - - // Architecture - final String arch = sua.getArchitecture(); - if (Objects.nonNull(arch) && !arch.isEmpty()) { - headers.put(SEC_CH_UA_ARCH, escape(arch)); - } - - // Mobile - final Integer mobile = sua.getMobile(); - if (Objects.nonNull(mobile)) { - headers.put(SEC_CH_UA_MOBILE, "?" + mobile); - } - return headers; - } - - private String brandListAsString(List versions) { - - final String brandNameString = versions.stream() - .filter(brandVersion -> brandVersion.getBrand() != null) - .map(brandVersion -> { - final String brandName = escape(brandVersion.getBrand()); - final String versionString = versionFromTokens(brandVersion.getVersion()); - return brandName + ";v=\"" + versionString + "\""; - }) - .collect(Collectors.joining(", ")); - return brandNameString; - } - - private static String escape(String value) { - return '"' + value.replace("\"", "\\\"") + '"'; - } - - public static String versionFromTokens(List tokens) { - if (tokens == null || tokens.isEmpty()) { - return ""; - } - - return tokens.stream() - .filter(token -> token != null && !token.isEmpty()) - .collect(Collectors.joining(".")); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java deleted file mode 100644 index dc01aa127b2..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersion.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; - -import com.iab.openrtb.request.BrandVersion; -import lombok.Getter; - -import java.util.Objects; - -public class PlatformNameVersion { - - @Getter - private String platformName; - - private String platformVersion; - - public static PlatformNameVersion from(BrandVersion platform) { - if (Objects.isNull(platform)) { - return null; - } - final PlatformNameVersion platformNameVersion = new PlatformNameVersion(); - platformNameVersion.platformName = platform.getBrand(); - platformNameVersion.platformVersion = HeadersResolver.versionFromTokens(platform.getVersion()); - return platformNameVersion; - } - - public String getPlatformVersion() { - return platformVersion; - } - - public String toString() { - return platformName + " " + platformVersion; - } - -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java deleted file mode 100644 index 0d930479fab..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidator.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.settings.model.Account; -import lombok.Builder; - -import java.util.Objects; -import java.util.Optional; -import java.util.Map; - -@Slf4j -@Builder -public class AccountValidator { - - Map allowedPublisherIds; - AuctionContext auctionContext; - - public boolean isAccountValid() { - - return Optional.ofNullable(auctionContext) - .map(AuctionContext::getAccount) - .map(Account::getId) - .filter(StringUtils::isNotBlank) - .map(allowedPublisherIds::get) - .filter(Objects::nonNull) - .isPresent(); - } - -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java deleted file mode 100644 index 33500ebfce6..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapper.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.Builder; -import lombok.extern.slf4j.Slf4j; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@Builder -@Slf4j -public class ExtWURFLMapper { - - private final List staticCaps; - private final List virtualCaps; - private boolean addExtCaps; - private final com.scientiamobile.wurfl.core.Device wurflDevice; - private static final String NULL_VALUE_TOKEN = "$null$"; - private static final String WURFL_ID_PROPERTY = "wurfl_id"; - - public JsonNode mapExtProperties() { - - final ObjectMapper objectMapper = new ObjectMapper(); - final ObjectNode wurflNode = objectMapper.createObjectNode(); - - try { - wurflNode.put(WURFL_ID_PROPERTY, wurflDevice.getId()); - - if (addExtCaps) { - - staticCaps.stream() - .map(sc -> { - try { - return Map.entry(sc, wurflDevice.getCapability(sc)); - } catch (Exception e) { - log.error("Error getting capability for {}: {}", sc, e.getMessage()); - return Map.entry(sc, NULL_VALUE_TOKEN); - } - }) - .filter(entry -> Objects.nonNull(entry.getValue()) - && !NULL_VALUE_TOKEN.equals(entry.getValue())) - .forEach(entry -> wurflNode.put(entry.getKey(), entry.getValue())); - - virtualCaps.stream() - .map(vc -> Map.entry(vc, wurflDevice.getVirtualCapability(vc))) - .filter(entry -> Objects.nonNull(entry.getValue())) - .forEach(entry -> wurflNode.put(entry.getKey(), entry.getValue())); - } - } catch (Exception e) { - log.error("Exception while updating EXT"); - } - - JsonNode node = null; - try { - node = objectMapper.readTree(wurflNode.toString()); - } catch (JsonProcessingException e) { - log.error("Error creating WURFL ext device JSON: {}", e.getMessage()); - } - return node; - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java deleted file mode 100644 index f8e9a9259c5..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdater.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import com.iab.openrtb.request.Device; -import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.prebid.server.model.UpdateResult; -import org.prebid.server.proto.openrtb.ext.request.ExtDevice; - -import java.math.BigDecimal; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@Slf4j -public class OrtbDeviceUpdater { - - private static final String WURFL_PROPERTY = "wurfl"; - private static final Map NAME_TO_IS_VIRTUAL_CAPABILITY = Map.of( - "brand_name", false, - "model_name", false, - "resolution_width", false, - "resolution_height", false, - "advertised_device_os", true, - "advertised_device_os_version", true, - "pixel_density", true, - "density_class", false, - "ajax_support_javascript", false - ); - - public Device update(Device ortbDevice, com.scientiamobile.wurfl.core.Device wurflDevice, - List staticCaps, List virtualCaps, boolean addExtCaps) { - - final Device.DeviceBuilder deviceBuilder = ortbDevice.toBuilder(); - - // make - final UpdateResult updatedMake = tryUpdateStringField(ortbDevice.getMake(), wurflDevice, - "brand_name"); - if (updatedMake.isUpdated()) { - deviceBuilder.make(updatedMake.getValue()); - } - - // model - final UpdateResult updatedModel = tryUpdateStringField(ortbDevice.getModel(), wurflDevice, - "model_name"); - if (updatedModel.isUpdated()) { - deviceBuilder.model(updatedModel.getValue()); - } - - // deviceType - final UpdateResult updatedDeviceType = tryUpdateDeviceTypeField(ortbDevice.getDevicetype(), - getOrtb2DeviceType(wurflDevice)); - if (updatedDeviceType.isUpdated()) { - deviceBuilder.devicetype(updatedDeviceType.getValue()); - } - - // os - final UpdateResult updatedOS = tryUpdateStringField(ortbDevice.getOs(), wurflDevice, - "advertised_device_os"); - if (updatedOS.isUpdated()) { - deviceBuilder.os(updatedOS.getValue()); - } - - // os version - final UpdateResult updatedOsv = tryUpdateStringField(ortbDevice.getOsv(), wurflDevice, - "advertised_device_os_version"); - if (updatedOS.isUpdated()) { - deviceBuilder.osv(updatedOsv.getValue()); - } - - // h (resolution height) - final UpdateResult updatedH = tryUpdateIntegerField(ortbDevice.getH(), wurflDevice, - "resolution_height", false); - if (updatedH.isUpdated()) { - deviceBuilder.h(updatedH.getValue()); - } - - // w (resolution height) - final UpdateResult updatedW = tryUpdateIntegerField(ortbDevice.getW(), wurflDevice, - "resolution_width", false); - if (updatedW.isUpdated()) { - deviceBuilder.w(updatedW.getValue()); - } - - // Pixels per inch - final UpdateResult updatedPpi = tryUpdateIntegerField(ortbDevice.getPpi(), wurflDevice, - "pixel_density", false); - if (updatedPpi.isUpdated()) { - deviceBuilder.ppi(updatedPpi.getValue()); - } - - // Pixel ratio - final UpdateResult updatedPxRatio = tryUpdateBigDecimalField(ortbDevice.getPxratio(), wurflDevice, - "density_class"); - if (updatedPxRatio.isUpdated()) { - deviceBuilder.pxratio(updatedPxRatio.getValue()); - } - - // Javascript support - final UpdateResult updatedJs = tryUpdateIntegerField(ortbDevice.getJs(), wurflDevice, - "ajax_support_javascript", true); - if (updatedJs.isUpdated()) { - deviceBuilder.js(updatedJs.getValue()); - } - - // Ext - final ExtWURFLMapper extMapper = ExtWURFLMapper.builder() - .wurflDevice(wurflDevice) - .staticCaps(staticCaps) - .virtualCaps(virtualCaps) - .addExtCaps(addExtCaps) - .build(); - final ExtDevice updatedExt = ExtDevice.empty(); - final ExtDevice ortbDeviceExt = ortbDevice.getExt(); - - if (Objects.nonNull(ortbDeviceExt)) { - updatedExt.addProperties(ortbDeviceExt.getProperties()); - if (!ortbDeviceExt.containsProperty(WURFL_PROPERTY)) { - updatedExt.addProperty("wurfl", extMapper.mapExtProperties()); - } - } else { - updatedExt.addProperty("wurfl", extMapper.mapExtProperties()); - } - deviceBuilder.ext(updatedExt); - return deviceBuilder.build(); - } - - private UpdateResult tryUpdateStringField(String fromOrtbDevice, - com.scientiamobile.wurfl.core.Device wurflDevice, - String capName) { - if (StringUtils.isNotBlank(fromOrtbDevice)) { - return UpdateResult.unaltered(fromOrtbDevice); - } - - final String fromWurfl = isVirtualCapability(capName) - ? wurflDevice.getVirtualCapability(capName) - : wurflDevice.getCapability(capName); - - if (Objects.nonNull(fromWurfl)) { - return UpdateResult.updated(fromWurfl); - } - - return UpdateResult.unaltered(fromOrtbDevice); - } - - private UpdateResult tryUpdateIntegerField(Integer fromOrtbDevice, - com.scientiamobile.wurfl.core.Device wurflDevice, - String capName, boolean convertFromBool) { - if (Objects.nonNull(fromOrtbDevice)) { - return UpdateResult.unaltered(fromOrtbDevice); - } - - final String fromWurfl = isVirtualCapability(capName) - ? wurflDevice.getVirtualCapability(capName) - : wurflDevice.getCapability(capName); - - if (StringUtils.isNotBlank(fromWurfl)) { - - if (convertFromBool) { - return fromWurfl.equalsIgnoreCase("true") - ? UpdateResult.updated(1) - : UpdateResult.updated(0); - } - - return UpdateResult.updated(Integer.parseInt(fromWurfl)); - } - return UpdateResult.unaltered(fromOrtbDevice); - } - - private UpdateResult tryUpdateBigDecimalField(BigDecimal fromOrtbDevice, - com.scientiamobile.wurfl.core.Device wurflDevice, - String capName) { - - if (Objects.nonNull(fromOrtbDevice)) { - return UpdateResult.unaltered(fromOrtbDevice); - } - - final String fromWurfl = isVirtualCapability(capName) - ? wurflDevice.getVirtualCapability(capName) - : wurflDevice.getCapability(capName); - - if (Objects.nonNull(fromWurfl)) { - - BigDecimal pxRatio = null; - try { - pxRatio = new BigDecimal(fromWurfl); - return UpdateResult.updated(pxRatio); - } catch (NullPointerException e) { - log.warn("Cannot convert WURFL device pixel density {} to ortb device pxratio", pxRatio); - } - } - - return UpdateResult.unaltered(fromOrtbDevice); - } - - private boolean isVirtualCapability(String capName) { - return NAME_TO_IS_VIRTUAL_CAPABILITY.get(capName); - } - - private UpdateResult tryUpdateDeviceTypeField(Integer fromOrtbDevice, Integer fromWurfl) { - final boolean isNotNullAndPositive = Objects.nonNull(fromOrtbDevice) && fromOrtbDevice > 0; - if (isNotNullAndPositive) { - return UpdateResult.unaltered(fromOrtbDevice); - } - - if (Objects.nonNull(fromWurfl)) { - return UpdateResult.updated(fromWurfl); - } - - return UpdateResult.unaltered(fromOrtbDevice); - } - - public static Integer getOrtb2DeviceType(final com.scientiamobile.wurfl.core.Device wurflDevice) { - - final boolean isPhone; - final boolean isTablet; - - if (wurflDevice.getVirtualCapabilityAsBool("is_mobile")) { - // if at least one if these capabilities is not defined the mobile device type is undefined - try { - isPhone = wurflDevice.getVirtualCapabilityAsBool("is_phone"); - isTablet = wurflDevice.getCapabilityAsBool("is_tablet"); - } catch (CapabilityNotDefinedException | VirtualCapabilityNotDefinedException e) { - return null; - } - - if (isPhone || isTablet) { - return 1; - } - return 6; - } - - // desktop device - if (wurflDevice.getVirtualCapabilityAsBool("is_full_desktop")) { - return 2; - } - - // connected tv - if (wurflDevice.getCapabilityAsBool("is_connected_tv")) { - return 3; - } - - if (wurflDevice.getCapabilityAsBool("is_phone")) { - return 4; - } - - if (wurflDevice.getCapabilityAsBool("is_tablet")) { - return 5; - } - - if (wurflDevice.getCapabilityAsBool("is_ott")) { - return 7; - } - - return null; // Return null for undefined device type - } - -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java deleted file mode 100644 index ccca8c4038b..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHook.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import lombok.extern.slf4j.Slf4j; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; -import org.prebid.server.hooks.v1.InvocationAction; -import org.prebid.server.hooks.v1.InvocationContext; -import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationStatus; -import org.prebid.server.hooks.v1.entrypoint.EntrypointHook; -import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; -import org.prebid.server.hooks.execution.v1.InvocationResultImpl; -import io.vertx.core.Future; - -@Slf4j -public class WURFLDeviceDetectionEntrypointHook implements EntrypointHook { - - private static final String CODE = "wurfl-devicedetection-entrypoint-hook"; - - @Override - public Future> call( - EntrypointPayload entrypointPayload, InvocationContext invocationContext) { - - final AuctionRequestHeadersContext bidRequestHeadersContext = AuctionRequestHeadersContext.from( - entrypointPayload.headers()); - return Future.succeededFuture( - InvocationResultImpl.builder() - .status(InvocationStatus.success) - .action(InvocationAction.no_action) - .moduleContext(bidRequestHeadersContext) - .build()); - } - - @Override - public String code() { - return CODE; - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java deleted file mode 100644 index 3bdaceff99f..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModule.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import org.prebid.server.hooks.v1.Module; -import org.prebid.server.hooks.v1.Hook; -import org.prebid.server.hooks.v1.InvocationContext; - -import java.util.Collection; -import java.util.List; - -public class WURFLDeviceDetectionModule implements Module { - - public static final String CODE = "wurfl-devicedetection"; - private final List> hooks; - - public WURFLDeviceDetectionModule(List> hooks) { - this.hooks = hooks; - - } - - @Override - public String code() { - return CODE; - } - - @Override - public Collection> hooks() { - return this.hooks; - } -} diff --git a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java b/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java deleted file mode 100644 index 1824b4e520c..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHook.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; -import com.scientiamobile.wurfl.core.WURFLEngine; -import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.MapUtils; -import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver.HeadersResolver; -import org.prebid.server.hooks.v1.InvocationAction; -import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.execution.v1.InvocationResultImpl; -import org.prebid.server.hooks.v1.InvocationStatus; -import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; -import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; -import org.prebid.server.hooks.v1.auction.RawAuctionRequestHook; -import org.prebid.server.auction.model.AuctionContext; -import io.vertx.core.Future; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Slf4j -public class WURFLDeviceDetectionRawAuctionRequestHook implements RawAuctionRequestHook { - - public static final String CODE = "wurfl-devicedetection-raw-auction-request"; - - private final WURFLEngine wurflEngine; - private final List staticCaps; - private final List virtualCaps; - private final OrtbDeviceUpdater ortbDeviceUpdater; - private final Map allowedPublisherIDs; - private final boolean addExtCaps; - - public WURFLDeviceDetectionRawAuctionRequestHook(WURFLEngine wurflEngine, - WURFLDeviceDetectionConfigProperties configProperties) { - this.wurflEngine = wurflEngine; - this.staticCaps = wurflEngine.getAllCapabilities().stream().toList(); - this.virtualCaps = safeGetVirtualCaps(wurflEngine); - this.ortbDeviceUpdater = new OrtbDeviceUpdater(); - this.addExtCaps = configProperties.isExtCaps(); - this.allowedPublisherIDs = configProperties.getAllowedPublisherIds().stream() - .collect(Collectors.toMap(item -> item, item -> item)); - } - - private List safeGetVirtualCaps(WURFLEngine wurflEngine) { - final List allVcaps = wurflEngine.getAllVirtualCapabilities().stream().toList(); - final List safeVcaps = new ArrayList<>(); - final var device = wurflEngine.getDeviceById("generic"); - allVcaps.forEach(vc -> { - try { - device.getVirtualCapability(vc); - safeVcaps.add(vc); - } catch (VirtualCapabilityNotDefinedException | CapabilityNotDefinedException ignored) { } - }); - return safeVcaps; - } - - @Override - public Future> call(AuctionRequestPayload auctionRequestPayload, - AuctionInvocationContext invocationContext) { - if (!shouldEnrichDevice(invocationContext)) { - return noUpdateResultFuture(); - } - - final BidRequest bidRequest = auctionRequestPayload.bidRequest(); - Device ortbDevice = null; - if (bidRequest == null) { - log.warn("BidRequest is null"); - return noUpdateResultFuture(); - } else { - ortbDevice = bidRequest.getDevice(); - if (ortbDevice == null) { - log.warn("Device is null"); - return noUpdateResultFuture(); - } - } - - final AuctionRequestHeadersContext headersContext; - Map requestHeaders = null; - if (invocationContext.moduleContext() instanceof AuctionRequestHeadersContext) { - headersContext = (AuctionRequestHeadersContext) invocationContext.moduleContext(); - if (headersContext != null) { - requestHeaders = headersContext.getHeaders(); - } - - final Map headers = new HeadersResolver().resolve(ortbDevice, requestHeaders); - final com.scientiamobile.wurfl.core.Device wurflDevice = wurflEngine.getDeviceForRequest(headers); - - try { - final Device updatedDevice = ortbDeviceUpdater.update(ortbDevice, wurflDevice, staticCaps, - virtualCaps, addExtCaps); - return Future.succeededFuture( - InvocationResultImpl.builder() - .status(InvocationStatus.success) - .action(InvocationAction.update) - .payloadUpdate(payload -> - AuctionRequestPayloadImpl.of(bidRequest.toBuilder() - .device(updatedDevice) - .build())) - .build() - ); - } catch (Exception e) { - log.error("Exception " + e.getMessage()); - } - - } - - return noUpdateResultFuture(); - } - - private static Future> noUpdateResultFuture() { - return Future.succeededFuture( - InvocationResultImpl.builder() - .status(InvocationStatus.success) - .action(InvocationAction.no_action) - .build()); - } - - private boolean shouldEnrichDevice(AuctionInvocationContext invocationContext) { - if (MapUtils.isEmpty(allowedPublisherIDs)) { - return true; - } - - final AuctionContext auctionContext = invocationContext.auctionContext(); - return AccountValidator.builder().allowedPublisherIds(allowedPublisherIDs) - .auctionContext(auctionContext) - .build() - .isAccountValid(); - } - - @Override - public String code() { - return CODE; - } - -} diff --git a/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml b/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml deleted file mode 100644 index 73fe089d34c..00000000000 --- a/extra/modules/WURFL-devicedetection/src/main/resources/module-config/WURFL-devicedetection.yaml +++ /dev/null @@ -1,46 +0,0 @@ -hooks: - wurfl-devicedetection: - enabled: true - host-execution-plan: > - { - "endpoints": { - "/openrtb2/auction": { - "stages": { - "entrypoint": { - "groups": [ - { - "timeout": 10, - "hook_sequence": [ - { - "module_code": "wurfl-devicedetection", - "hook_impl_code": "wurfl-devicedetection-entrypoint-hook" - } - ] - } - ] - }, - "raw_auction_request": { - "groups": [ - { - "timeout": 10, - "hook_sequence": [ - { - "module_code": "wurfl-devicedetection", - "hook_impl_code": "wurfl-devicedetection-raw-auction-request" - } - ] - } - ] - } - } - } - } - } - modules: - wurfl-devicedetection: - wurfl-file-dir-path: - wurfl-snapshot-url: https://data.scientiamobile.com/your_wurfl_snapshot_url/wurfl.zip - cache-size: 200000 - wurfl-run-updater: true - allowed-publisher-ids: 1 - ext-caps: false diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java deleted file mode 100644 index dcce5123e34..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/config/WURFLDeviceDetectionConfigPropertiesTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config; - -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class WURFLDeviceDetectionConfigPropertiesTest { - - @Test - void shouldInitializeWithEmptyValues() { - // given - final WURFLDeviceDetectionConfigProperties properties = new WURFLDeviceDetectionConfigProperties(); - - // then - assertThat(properties.getCacheSize()).isEqualTo(0); - assertThat(properties.getWurflFileDirPath()).isNull(); - assertThat(properties.getWurflSnapshotUrl()).isNull(); - assertThat(properties.isExtCaps()).isFalse(); - assertThat(properties.isWurflRunUpdater()).isTrue(); - } - - @Test - void shouldSetAndGetProperties() { - // given - final WURFLDeviceDetectionConfigProperties properties = new WURFLDeviceDetectionConfigProperties(); - - // when - properties.setCacheSize(1000); - properties.setWurflFileDirPath("/path/to/file"); - - properties.setWurflSnapshotUrl("https://example-scientiamobile.com/wurfl.zip"); - properties.setWurflRunUpdater(false); - properties.setAllowedPublisherIds(List.of("1", "3")); - properties.setExtCaps(true); - - // then - assertThat(properties.getCacheSize()).isEqualTo(1000); - assertThat(properties.getWurflFileDirPath()).isEqualTo("/path/to/file"); - assertThat(properties.getWurflSnapshotUrl()).isEqualTo("https://example-scientiamobile.com/wurfl.zip"); - assertThat(properties.isWurflRunUpdater()).isEqualTo(false); - assertThat(properties.getAllowedPublisherIds()).isEqualTo(List.of("1", "3")); - assertThat(properties.isExtCaps()).isTrue(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java deleted file mode 100644 index fd2a0bc820e..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/mock/WURFLDeviceMock.java +++ /dev/null @@ -1,277 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock; - -import com.scientiamobile.wurfl.core.exc.CapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.exc.VirtualCapabilityNotDefinedException; -import com.scientiamobile.wurfl.core.matchers.MatchType; -import lombok.Builder; - -import java.util.Map; - -@Builder -public class WURFLDeviceMock implements com.scientiamobile.wurfl.core.Device { - - private Map capabilities; - private String id; - private Map virtualCapabilities; - - @Override - public MatchType getMatchType() { - return MatchType.conclusive; - } - - @Override - public String getVirtualCapability(String vcapName) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException { - - if (!virtualCapabilities.containsKey(vcapName)) { - throw new VirtualCapabilityNotDefinedException(vcapName); - } - - return virtualCapabilities.get(vcapName); - } - - @Override - public int getVirtualCapabilityAsInt(String s) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException, NumberFormatException { - return 0; - } - - @Override - public boolean getVirtualCapabilityAsBool(String vcapName) throws VirtualCapabilityNotDefinedException, - CapabilityNotDefinedException, NumberFormatException { - - if (vcapName.equals("is_phone") || vcapName.equals("is_full_desktop") || vcapName.equals("is_connected_tv") - || vcapName.equals("is_mobile") || vcapName.equals("is_tablet")) { - return Boolean.parseBoolean(getVirtualCapability(vcapName)); - } - - return false; - } - - @Override - public String getId() { - return id; - } - - public String getWURFLUserAgent() { - return ""; - } - - @Override - public String getCapability(String capName) throws CapabilityNotDefinedException { - - if (!capabilities.containsKey(capName)) { - throw new CapabilityNotDefinedException(capName); - } - - return capabilities.get(capName); - - } - - @Override - public int getCapabilityAsInt(String capName) throws CapabilityNotDefinedException, NumberFormatException { - return switch (capName) { - case "resolution_height", "resolution_width" -> Integer.parseInt(capabilities.get(capName)); - default -> 0; - }; - } - - @Override - public boolean getCapabilityAsBool(String capName) throws CapabilityNotDefinedException, NumberFormatException { - return switch (capName) { - case "ajax_support_javascript", "is_connected_tv", "is_ott", "is_tablet", "is_mobile" -> - Boolean.parseBoolean(getCapability(capName)); - default -> false; - }; - } - - public Map getCapabilities() { - return Map.of(); - } - - public Map getVirtualCapabilities() { - return Map.of(); - } - - public boolean isActualDeviceRoot() { - return true; - } - - public String getDeviceRootId() { - return ""; - } - - public static class WURFLDeviceMockFactory { - - public static com.scientiamobile.wurfl.core.Device mockIPhone() { - - return builder().capabilities(Map.of( - "brand_name", "Apple", - "model_name", "iPhone", - "ajax_support_javascript", "true", - "density_class", "1.0", - "is_connected_tv", "false", - "is_ott", "false", - "is_tablet", "false", - "resolution_height", "1440", - "resolution_width", "3200" - )).virtualCapabilities( - Map.of("advertised_device_os", "iOS", - "advertised_device_os_version", "17.1", - "complete_device_name", "Apple iPhone", - "is_full_desktop", "false", - "is_mobile", "true", - "is_phone", "true", - "form_factor", "Smartphone", - "pixel_density", "515")) - .id("apple_iphone_ver1") - .build(); - } - - public static com.scientiamobile.wurfl.core.Device mockOttDevice() { - - return builder().capabilities(Map.of( - "brand_name", "Diyomate", - "model_name", "A6", - "ajax_support_javascript", "true", - "density_class", "1.5", - "is_connected_tv", "false", - "is_ott", "true", - "is_tablet", "false", - "resolution_height", "1080", - "resolution_width", "1920" - )).virtualCapabilities( - Map.of("advertised_device_os", "Android", - "advertised_device_os_version", "4.0", - "complete_device_name", "Diyomate A6", - "is_full_desktop", "false", - "is_mobile", "false", - "is_phone", "false", - "form_factor", "Smart-TV", - "pixel_density", "69")) - .id("diyomate_a6_ver1") - .build(); - } - - public static com.scientiamobile.wurfl.core.Device mockMobileUndefinedDevice() { - - return builder().capabilities(Map.of( - "brand_name", "TestUnd", - "model_name", "U1", - "ajax_support_javascript", "false", - "density_class", "1.0", - "is_connected_tv", "false", - "is_ott", "false", - "is_tablet", "false", - "resolution_height", "128", - "resolution_width", "128" - )).virtualCapabilities( - Map.of("advertised_device_os", "TestOS", - "advertised_device_os_version", "1.0", - "complete_device_name", "TestUnd U1", - "is_full_desktop", "false", - "is_mobile", "true", - "is_phone", "false", - "form_factor", "Test-non-phone", - "pixel_density", "69")) - .build(); - } - - public static com.scientiamobile.wurfl.core.Device mockUnknownDevice() { - - return builder().capabilities(Map.of( - "brand_name", "TestUnd", - "model_name", "U1", - "ajax_support_javascript", "false", - "density_class", "1.0", - "is_connected_tv", "false", - "is_ott", "false", - "is_tablet", "false", - "resolution_height", "128", - "resolution_width", "128" - )).virtualCapabilities( - Map.of("advertised_device_os", "TestOS", - "advertised_device_os_version", "1.0", - "complete_device_name", "TestUnd U1", - "is_full_desktop", "false", - "is_mobile", "false", - "is_phone", "false", - "form_factor", "Test-unknown", - "pixel_density", "69")) - .build(); - } - - public static com.scientiamobile.wurfl.core.Device mockDesktop() { - - return builder().capabilities(Map.of( - "brand_name", "TestDesktop", - "model_name", "D1", - "ajax_support_javascript", "true", - "density_class", "1.5", - "is_connected_tv", "false", - "is_ott", "false", - "is_tablet", "false", - "resolution_height", "1080", - "resolution_width", "1920" - )).virtualCapabilities( - Map.of("advertised_device_os", "Windows", - "advertised_device_os_version", "10", - "complete_device_name", "TestDesktop D1", - "is_full_desktop", "true", - "is_mobile", "false", - "is_phone", "false", - "form_factor", "Desktop", - "pixel_density", "300")) - .build(); - } - - public static com.scientiamobile.wurfl.core.Device mockConnectedTv() { - - return builder().capabilities(Map.of( - "brand_name", "TestConnectedTv", - "model_name", "C1", - "ajax_support_javascript", "true", - "density_class", "1.5", - "is_connected_tv", "true", - "is_ott", "false", - "is_tablet", "false", - "resolution_height", "1080", - "resolution_width", "1920" - )).virtualCapabilities( - Map.of("advertised_device_os", "WebOS", - "advertised_device_os_version", "4", - "complete_device_name", "TestConnectedTV C1", - "is_full_desktop", "false", - "is_mobile", "false", - "is_phone", "false", - "form_factor", "Smart-TV", - "pixel_density", "200")) - .build(); - } - - public static com.scientiamobile.wurfl.core.Device mockTablet() { - - return builder().capabilities(Map.of( - "brand_name", "Samsung", - "model_name", "Galaxy Tab S9+", - "ajax_support_javascript", "true", - "density_class", "1.5", - "is_connected_tv", "false", - "is_ott", "false", - "is_tablet", "true", - "resolution_height", "1752", - "resolution_width", "2800" - )).virtualCapabilities( - Map.of("advertised_device_os", "Android", - "advertised_device_os_version", "13", - "complete_device_name", "Samsung Galaxy Tab S9+", - "is_full_desktop", "false", - "is_mobile", "false", - "is_phone", "false", - "form_factor", "Tablet", - "pixel_density", "274")) - .build(); - } - - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java deleted file mode 100644 index 15a89de4ecf..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/AuctionRequestHeadersContextTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; - -import org.junit.jupiter.api.Test; -import org.prebid.server.model.CaseInsensitiveMultiMap; - -import static org.assertj.core.api.Assertions.assertThat; - -class AuctionRequestHeadersContextTest { - - @Test - void fromShouldHandleNullHeaders() { - // when - final AuctionRequestHeadersContext result = AuctionRequestHeadersContext.from(null); - - // then - assertThat(result.headers).isEmpty(); - } - - @Test - void fromShouldConvertCaseInsensitiveMultiMapToHeaders() { - // given - final CaseInsensitiveMultiMap multiMap = CaseInsensitiveMultiMap.builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") - .add("Header2", "value2") - .build(); - - // when - final AuctionRequestHeadersContext target = AuctionRequestHeadersContext.from(multiMap); - - // then - assertThat(target.headers) - .hasSize(2) - .containsEntry("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") - .containsEntry("Header2", "value2"); - } - - @Test - void fromShouldTakeFirstValueForDuplicateHeaders() { - // given - final CaseInsensitiveMultiMap multiMap = CaseInsensitiveMultiMap.builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test2") - .build(); - - // when - final AuctionRequestHeadersContext target = AuctionRequestHeadersContext.from(multiMap); - - // then - assertThat(target.headers) - .hasSize(1) - .containsEntry("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test"); - } - - @Test - void fromShouldHandleEmptyMultiMap() { - // given - final CaseInsensitiveMultiMap emptyMultiMap = CaseInsensitiveMultiMap.empty(); - - // when - final AuctionRequestHeadersContext target = AuctionRequestHeadersContext.from(emptyMultiMap); - - // then - assertThat(target.headers).isEmpty(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java deleted file mode 100644 index 76d27053e75..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/model/WURFLEngineInitializerTest.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model; - -import com.scientiamobile.wurfl.core.GeneralWURFLEngine; -import com.scientiamobile.wurfl.core.WURFLEngine; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.exc.WURFLModuleConfigurationException; -import org.junit.jupiter.api.function.Executable; - -import java.util.List; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mock.Strictness.LENIENT; -import static org.mockito.Mockito.mockStatic; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class WURFLEngineInitializerTest { - - @Mock(strictness = LENIENT) - private WURFLDeviceDetectionConfigProperties configProperties; - - @Mock(strictness = LENIENT) - private WURFLEngine wurflEngine; - - @BeforeEach - void setUp() { - when(configProperties.getWurflSnapshotUrl()).thenReturn("http://test.url/wurfl.zip"); - when(configProperties.getWurflFileDirPath()).thenReturn("/test/path"); - } - - @Test - void downloadWurflFileIfNeededShouldDownloadWhenUrlAndPathArePresent() { - try (MockedStatic mockedStatic = mockStatic(GeneralWURFLEngine.class)) { - // when - WURFLEngineInitializer.downloadWurflFile(configProperties); - - // then - mockedStatic.verify(() -> - GeneralWURFLEngine.wurflDownload("http://test.url/wurfl.zip", "/test/path")); - } - } - - @Test - void verifyStaticCapabilitiesDefinitionShouldThrowExceptionWhenCapabilitiesAreNotDefined() { - // given - when(wurflEngine.getAllCapabilities()).thenReturn(Set.of( - "brand_name", - "density_class", - "is_connected_tv", - "is_ott", - "is_tablet", - "model_name")); - - final String expFailedCheckMessage = """ - Static capabilities %s needed for device enrichment are not defined in WURFL. - Please make sure that your license has the needed capabilities or upgrade it. - """.formatted(String.join(",", List.of( - "ajax_support_javascript", - "physical_form_factor", - "resolution_height", - "resolution_width" - ))); - - // when - final Executable exceptionSource = () -> WURFLEngineInitializer.verifyStaticCapabilitiesDefinition(wurflEngine); - - // then - final Exception exception = assertThrows(WURFLModuleConfigurationException.class, exceptionSource); - assertThat(exception.getMessage()).isEqualTo(expFailedCheckMessage); - } - - @Test - void verifyStaticCapabilitiesDefinitionShouldCompleteSuccessfullyWhenCapabilitiesAreDefined() { - // given - when(wurflEngine.getAllCapabilities()).thenReturn(Set.of( - "brand_name", - "density_class", - "is_connected_tv", - "is_ott", - "is_tablet", - "model_name", - "resolution_width", - "resolution_height", - "physical_form_factor", - "ajax_support_javascript" - )); - - // when - var excOccurred = false; - try { - WURFLEngineInitializer.verifyStaticCapabilitiesDefinition(wurflEngine); - } catch (Exception e) { - excOccurred = true; - } - - // then - assertThat(excOccurred).isFalse(); - } - - @Test - void builderShouldCreateWURFLEngineInitializerBuilderFromProperties() { - // given - when(configProperties.getWurflSnapshotUrl()).thenReturn("http://test.url/wurfl.zip"); - when(configProperties.getWurflFileDirPath()).thenReturn("/test/path"); - when(configProperties.getCacheSize()).thenReturn(1000); - when(configProperties.isWurflRunUpdater()).thenReturn(true); - - // when - final var builder = WURFLEngineInitializer.builder() - .configProperties(configProperties); - - // then - assertThat(builder).isNotNull(); - assertThat(builder.build()).isNotNull(); - assertThat(builder.toString()).isNotEmpty(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java deleted file mode 100644 index f3d3be006fa..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/HeadersResolverTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; - -import com.iab.openrtb.request.BrandVersion; -import com.iab.openrtb.request.Device; -import com.iab.openrtb.request.UserAgent; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class HeadersResolverTest { - - private HeadersResolver target; - - private static final String TEST_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"; - - @BeforeEach - void setUp() { - target = new HeadersResolver(); - } - - @Test - void resolveWithNullDeviceShouldReturnOriginalHeaders() { - // given - final Map headers = new HashMap<>(); - headers.put("test", "value"); - - // when - final Map result = target.resolve(null, headers); - - // then - assertThat(result).isEqualTo(headers); - } - - @Test - void resolveWithDeviceUaShouldReturnUserAgentHeader() { - // given - final Device device = Device.builder() - .ua("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") - .build(); - - // when - final Map result = target.resolve(device, new HashMap<>()); - - // then - assertThat(result).containsEntry("User-Agent", TEST_USER_AGENT); - } - - @Test - void resolveWithFullSuaShouldReturnAllHeaders() { - // given - final BrandVersion brandVersion = new BrandVersion( - "Chrome", - Arrays.asList("100", "0", "0"), - null); - - final BrandVersion winBrandVersion = new BrandVersion( - "Windows", - Arrays.asList("10", "0", "0"), - null); - final UserAgent sua = UserAgent.builder() - .browsers(List.of(brandVersion)) - .platform(winBrandVersion) - .model("Test Model") - .architecture("x86") - .mobile(0) - .build(); - - final Device device = Device.builder() - .sua(sua) - .build(); - - // when - final Map result = target.resolve(device, new HashMap<>()); - - // then - assertThat(result) - .containsEntry("Sec-CH-UA", "\"Chrome\";v=\"100.0.0\"") - .containsEntry("Sec-CH-UA-Full-Version-List", "\"Chrome\";v=\"100.0.0\"") - .containsEntry("Sec-CH-UA-Platform", "\"Windows\"") - .containsEntry("Sec-CH-UA-Platform-Version", "\"10.0.0\"") - .containsEntry("Sec-CH-UA-Model", "\"Test Model\"") - .containsEntry("Sec-CH-UA-Arch", "\"x86\"") - .containsEntry("Sec-CH-UA-Mobile", "?0"); - } - - @Test - void resolveWithFullDeviceAndHeadersShouldPrioritizeDevice() { - // given - final BrandVersion brandVersion = new BrandVersion( - "Chrome", - Arrays.asList("100", "0", "0"), - null); - - final BrandVersion winBrandVersion = new BrandVersion( - "Windows", - Arrays.asList("10", "0", "0"), - null); - final UserAgent sua = UserAgent.builder() - .browsers(List.of(brandVersion)) - .platform(winBrandVersion) - .model("Test Model") - .architecture("x86") - .mobile(0) - .build(); - - final Device device = Device.builder() - .sua(sua) - .ua(TEST_USER_AGENT) - .build(); - - final Map headers = new HashMap<>(); - headers.put("Sec-CH-UA", "Test UA-CH"); - headers.put("Sec-CH-UA-Full-Version-List", "Test-UA-Full-Version-List"); - headers.put("Sec-CH-UA-Platform", "Test-UA-Platform"); - headers.put("Sec-CH-UA-Platform-Version", "Test-UA-Platform-Version"); - headers.put("Sec-CH-UA-Model", "Test-UA-Model"); - headers.put("Sec-CH-UA-Arch", "Test-UA-Arch"); - headers.put("Sec-CH-UA-Mobile", "Test-UA-Mobile"); - headers.put("User-Agent", "Mozilla/5.0 (Test OS; 10) like Gecko"); - // when - final Map result = target.resolve(device, headers); - - // then - assertThat(result) - .containsEntry("Sec-CH-UA", "\"Chrome\";v=\"100.0.0\"") - .containsEntry("Sec-CH-UA-Full-Version-List", "\"Chrome\";v=\"100.0.0\"") - .containsEntry("Sec-CH-UA-Platform", "\"Windows\"") - .containsEntry("Sec-CH-UA-Platform-Version", "\"10.0.0\"") - .containsEntry("Sec-CH-UA-Model", "\"Test Model\"") - .containsEntry("Sec-CH-UA-Arch", "\"x86\"") - .containsEntry("Sec-CH-UA-Mobile", "?0"); - } - - @Test - void versionFromTokensShouldHandleNullAndEmptyInput() { - // when & then - assertThat(HeadersResolver.versionFromTokens(null)).isEmpty(); - assertThat(HeadersResolver.versionFromTokens(List.of())).isEmpty(); - } - - @Test - void versionFromTokensShouldJoinValidTokens() { - // given - final List tokens = Arrays.asList("100", "0", "1234"); - - // when - final String result = HeadersResolver.versionFromTokens(tokens); - - // then - assertThat(result).isEqualTo("100.0.1234"); - } - - @Test - void resolveWithMultipleBrandVersionsShouldFormatCorrectly() { - // given - final BrandVersion chrome = new BrandVersion("Chrome", - Arrays.asList("100", "0"), - null); - final BrandVersion chromium = new BrandVersion("Chromium", - Arrays.asList("100", "0"), - null); - - final BrandVersion notABrand = new BrandVersion("Not\\A;Brand", - Arrays.asList("99", "0"), - null); - - final UserAgent sua = UserAgent.builder() - .browsers(Arrays.asList(chrome, chromium, notABrand)) - .build(); - - final Device device = Device.builder() - .sua(sua) - .build(); - - // when - final Map result = target.resolve(device, new HashMap<>()); - - // then - final String expectedFormat = "\"Chrome\";v=\"100.0\", \"Chromium\";v=\"100.0\", \"Not\\A;Brand\";v=\"99.0\""; - assertThat(result) - .containsEntry("Sec-CH-UA", expectedFormat) - .containsEntry("Sec-CH-UA-Full-Version-List", expectedFormat); - } - - @Test - void resolveWithNullDeviceAndNullHeadersShouldReturnEmptyMap() { - // when - final Map result = target.resolve(null, null); - - // then - assertThat(result).isEmpty(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java deleted file mode 100644 index 666e4571908..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/resolver/PlatformNameVersionTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.resolver; - -import com.iab.openrtb.request.BrandVersion; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class PlatformNameVersionTest { - - @Test - void fromShouldReturnNullWhenPlatformIsNull() { - // when - final PlatformNameVersion target = PlatformNameVersion.from(null); - - // then - assertThat(target).isNull(); - } - - @Test - void fromShouldCreatePlatformNameVersionWithValidInput() { - // given - final BrandVersion platform = new BrandVersion("Windows", - Arrays.asList("10", "0", "0"), - null); - - // when - final PlatformNameVersion target = PlatformNameVersion.from(platform); - - // then - assertThat(target).isNotNull(); - assertThat(target.getPlatformName()).isEqualTo("Windows"); - assertThat(target.getPlatformVersion()).isEqualTo("10.0.0"); - } - - @Test - void toStringShouldReturnFormattedString() { - // given - final BrandVersion platform = new BrandVersion("macOS", - Arrays.asList("13", "1"), - null); - final PlatformNameVersion target = PlatformNameVersion.from(platform); - - // when - final String result = target.toString(); - - // then - assertThat(result).isEqualTo("macOS 13.1"); - } - - @Test - void fromShouldHandleEmptyVersionList() { - // given - final BrandVersion platform = new BrandVersion("Linux", - List.of(), - null); - - // when - final PlatformNameVersion target = PlatformNameVersion.from(platform); - - // then - assertThat(target).isNotNull(); - assertThat(target.getPlatformName()).isEqualTo("Linux"); - assertThat(target.getPlatformVersion()).isEmpty(); - assertThat(target.toString()).isEqualTo("Linux "); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java deleted file mode 100644 index a21e2fca77b..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/AccountValidatorTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.auction.model.AuctionContext; -import org.prebid.server.settings.model.Account; - -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class AccountValidatorTest { - - private AuctionContext auctionContext; - - @Mock - private Account account; - - private AccountValidator validator; - - @BeforeEach - void setUp() { - auctionContext = AuctionContext.builder().account(account).build(); - } - - @Test - void isAccountValidShouldReturnTrueWhenPublisherIdIsAllowed() { - // given - when(account.getId()).thenReturn("allowed-publisher"); - final var accountValidatorBuiler = AccountValidator.builder() - .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) - .auctionContext(auctionContext); - assertThat(accountValidatorBuiler.toString()).isNotNull(); - validator = accountValidatorBuiler.build(); - - // when - final boolean result = validator.isAccountValid(); - - // then - assertThat(result).isTrue(); - } - - @Test - void isAccountValidShouldReturnFalseWhenPublisherIdIsNotAllowed() { - // given - when(account.getId()).thenReturn("unknown-publisher"); - validator = AccountValidator.builder() - .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) - .auctionContext(auctionContext) - .build(); - - // when - final boolean result = validator.isAccountValid(); - - // then - assertThat(result).isFalse(); - } - - @Test - void isAccountValidShouldReturnFalseWhenAuctionContextIsNull() { - // given - validator = AccountValidator.builder() - .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) - .auctionContext(null) - .build(); - - // when - final boolean result = validator.isAccountValid(); - - // then - assertThat(result).isFalse(); - } - - @Test - void isAccountValidShouldReturnFalseWhenPublisherIdIsEmpty() { - // given - when(account.getId()).thenReturn(""); - validator = AccountValidator.builder() - .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) - .auctionContext(auctionContext) - .build(); - - // when - final boolean result = validator.isAccountValid(); - - // then - assertThat(result).isFalse(); - } - - @Test - void isAccountValidShouldReturnFalseWhenAccountIsNull() { - // given - when(auctionContext.getAccount()).thenReturn(null); - validator = AccountValidator.builder() - .allowedPublisherIds(Collections.singletonMap("allowed-publisher", "allowed-publisher")) - .auctionContext(auctionContext) - .build(); - - // when - final boolean result = validator.isAccountValid(); - - // then - assertThat(result).isFalse(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java deleted file mode 100644 index 8e717fcf1ce..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/ExtWURFLMapperTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import com.fasterxml.jackson.databind.JsonNode; -import com.iab.openrtb.request.Device; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class ExtWURFLMapperTest { - - @Mock - private com.scientiamobile.wurfl.core.Device wurflDevice; - - @Mock - private Device device; - - private ExtWURFLMapper target; - private List staticCaps; - private List virtualCaps; - - @BeforeEach - public void setUp() { - staticCaps = Arrays.asList("brand_name", "model_name"); - virtualCaps = Arrays.asList("is_mobile", "form_factor"); - - target = ExtWURFLMapper.builder() - .staticCaps(staticCaps) - .virtualCaps(virtualCaps) - .wurflDevice(wurflDevice) - .addExtCaps(true) - .build(); - } - - @Test - public void shouldMapStaticCapabilities() { - // given - when(wurflDevice.getCapability("brand_name")).thenReturn("Apple"); - when(wurflDevice.getCapability("model_name")).thenReturn("iPhone"); - - // when - final JsonNode result = target.mapExtProperties(); - - // then - assertThat(result.get("brand_name").asText()).isEqualTo("Apple"); - assertThat(result.get("model_name").asText()).isEqualTo("iPhone"); - } - - @Test - public void shouldMapVirtualCapabilities() { - // given - when(wurflDevice.getVirtualCapability("is_mobile")).thenReturn("true"); - when(wurflDevice.getVirtualCapability("form_factor")).thenReturn("smartphone"); - - // when - final JsonNode result = target.mapExtProperties(); - - // then - assertThat(result.get("is_mobile").asText()).isEqualTo("true"); - assertThat(result.get("form_factor").asText()).isEqualTo("smartphone"); - } - - @Test - public void shouldMapWURFLId() { - // given - when(wurflDevice.getId()).thenReturn("test_wurfl_id"); - - // when - final JsonNode result = target.mapExtProperties(); - - // then - assertThat(result.get("wurfl_id").asText()).isEqualTo("test_wurfl_id"); - } - - @Test - public void shouldSkipNullCapabilities() { - // given - when(wurflDevice.getCapability("brand_name")).thenReturn(null); - when(wurflDevice.getCapability("model_name")).thenReturn("iPhone"); - when(wurflDevice.getVirtualCapability("is_mobile")).thenReturn(null); - - // when - final JsonNode result = target.mapExtProperties(); - - // then - assertThat(result.has("brand_name")).isFalse(); - assertThat(result.get("model_name").asText()).isEqualTo("iPhone"); - assertThat(result.has("is_mobile")).isFalse(); - } - - @Test - public void shouldHandleExceptionsGracefully() { - // given - when(wurflDevice.getCapability("brand_name")).thenThrow(new RuntimeException("Test exception")); - when(wurflDevice.getCapability("model_name")).thenReturn("iPhone"); - - // when - final JsonNode result = target.mapExtProperties(); - - // then - assertThat(result.has("brand_name")).isFalse(); - assertThat(result.get("model_name")).isNotNull(); - assertThat(result.get("model_name").asText()).isEqualTo("iPhone"); - } - - @Test - public void shouldNotAddExtCapsIfDisabled() { - // given - target = ExtWURFLMapper.builder() - .staticCaps(staticCaps) - .virtualCaps(virtualCaps) - .wurflDevice(wurflDevice) - .addExtCaps(false) - .build(); - - // when - final JsonNode result = target.mapExtProperties(); - - // then - assertThat(result.has("brand_name")).isFalse(); - assertThat(result.has("model_name")).isFalse(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java deleted file mode 100644 index f0f60f43383..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/OrtbDeviceUpdaterTest.java +++ /dev/null @@ -1,243 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import com.fasterxml.jackson.databind.node.TextNode; -import com.iab.openrtb.request.Device; -import org.prebid.server.proto.openrtb.ext.request.ExtDevice; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock; - -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockConnectedTv; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockDesktop; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockIPhone; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockMobileUndefinedDevice; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockOttDevice; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockTablet; -import static org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock.WURFLDeviceMockFactory.mockUnknownDevice; - -@Slf4j -@ExtendWith(MockitoExtension.class) -class OrtbDeviceUpdaterTest { - - private OrtbDeviceUpdater target; - private List staticCaps; - private List virtualCaps; - - @BeforeEach - void setUp() { - target = new OrtbDeviceUpdater(); - staticCaps = Arrays.asList("ajax_support_javascript", "brand_name", "density_class", - "is_connected_tv", "is_ott", "is_tablet", "model_name", "resolution_height", "resolution_width"); - virtualCaps = Arrays.asList("advertised_device_os", "advertised_device_os_version", - "is_full_desktop", "pixel_density"); - } - - @Test - void updateShouldUpdateDeviceMakeWhenOriginalIsEmpty() { - // given - final var wurflDevice = mockIPhone(); - final Device device = Device.builder().build(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getMake()).isEqualTo("Apple"); - assertThat(result.getDevicetype()).isEqualTo(1); - } - - @Test - void updateShouldNotUpdateDeviceMakeWhenOriginalExists() { - // given - final Device device = Device.builder().make("Samsung").build(); - final var wurflDevice = mockIPhone(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getMake()).isEqualTo("Samsung"); - } - - @Test - void updateShouldNotUpdateDeviceMakeWhenOriginalBigIntegerExists() { - // given - final Device device = Device.builder().make("Apple").pxratio(new BigDecimal("1.0")).build(); - final var wurflDevice = mockIPhone(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getMake()).isEqualTo("Apple"); - assertThat(result.getPxratio()).isEqualTo("1.0"); - } - - @Test - void updateShouldUpdateDeviceModelWhenOriginalIsEmpty() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockIPhone(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getModel()).isEqualTo("iPhone"); - } - - @Test - void updateShouldUpdateDeviceOsWhenOriginalIsEmpty() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockIPhone(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getOs()).isEqualTo("iOS"); - } - - @Test - void updateShouldUpdateResolutionWhenOriginalIsEmpty() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockIPhone(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getW()).isEqualTo(3200); - assertThat(result.getH()).isEqualTo(1440); - } - - @Test - void updateShouldHandleJavascriptSupport() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockIPhone(); - - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getJs()).isEqualTo(1); - } - - @Test - void updateShouldHandleOttDeviceType() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockOttDevice(); - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - assertThat(result.getDevicetype()).isEqualTo(7); - } - - @Test - void updateShouldReturnDeviceOtherMobileWhenMobileIsNotPhoneOrTablet() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockMobileUndefinedDevice(); - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - // then - assertThat(result.getDevicetype()).isEqualTo(6); - } - - @Test - void updateShouldReturnNullWhenMobileTypeIsUnknown() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockUnknownDevice(); - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - // then - assertThat(result.getDevicetype()).isNull(); - } - - @Test - void updateShouldHandlePersonalComputerDeviceType() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockDesktop(); - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - // then - assertThat(result.getDevicetype()).isEqualTo(2); - } - - @Test - void updateShouldHandleConnectedTvDeviceType() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockConnectedTv(); - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - // then - assertThat(result.getDevicetype()).isEqualTo(3); - } - - @Test - void updateShouldNotUpdateDeviceTypeWhenSet() { - // given - final Device device = Device.builder() - .devicetype(3) - .build(); - final var wurflDevice = mockDesktop(); // device type 2 - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - // then - assertThat(result.getDevicetype()).isEqualTo(3); // unchanged - } - - @Test - void updateShouldHandleTabletDeviceType() { - // given - final Device device = Device.builder().build(); - final var wurflDevice = mockTablet(); - // when - final Device result = target.update(device, wurflDevice, staticCaps, virtualCaps, true); - // then - assertThat(result.getDevicetype()).isEqualTo(5); - } - - @Test - void updateShouldAddWurflPropertyToExtIfMissingAndPreserveExistingProperties() { - // given - final ExtDevice existingExt = ExtDevice.empty(); - existingExt.addProperty("someProperty", TextNode.valueOf("value")); - final Device device = Device.builder() - .ext(existingExt) - .build(); - - final var wurflDevice = WURFLDeviceMock.WURFLDeviceMockFactory.mockIPhone(); - final List staticCaps = List.of("brand_name"); - final List virtualCaps = List.of("advertised_device_os"); - - final OrtbDeviceUpdater updater = new OrtbDeviceUpdater(); - - // when - final Device result = updater.update(device, wurflDevice, staticCaps, virtualCaps, true); - - // then - final ExtDevice resultExt = result.getExt(); - assertThat(resultExt).isNotNull(); - assertThat(resultExt.getProperty("someProperty").textValue()).isEqualTo("value"); - assertThat(resultExt.getProperty("wurfl")).isNotNull(); - - } - -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java deleted file mode 100644 index b648015637e..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionEntrypointHookTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import io.vertx.core.Future; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; -import org.prebid.server.hooks.v1.InvocationAction; -import org.prebid.server.hooks.v1.InvocationContext; -import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationStatus; -import org.prebid.server.hooks.v1.entrypoint.EntrypointPayload; -import org.prebid.server.model.CaseInsensitiveMultiMap; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class WURFLDeviceDetectionEntrypointHookTest { - - private EntrypointPayload payload; - private InvocationContext context; - - @BeforeEach - void setUp() { - payload = mock(EntrypointPayload.class); - context = mock(InvocationContext.class); - } - - @Test - void codeShouldReturnCorrectHookCode() { - - // given - final WURFLDeviceDetectionEntrypointHook target = new WURFLDeviceDetectionEntrypointHook(); - - // when - final String result = target.code(); - - // then - assertThat(result).isEqualTo("wurfl-devicedetection-entrypoint-hook"); - } - - @Test - void callShouldReturnSuccessWithNoAction() { - // given - final WURFLDeviceDetectionEntrypointHook target = new WURFLDeviceDetectionEntrypointHook(); - final CaseInsensitiveMultiMap headers = CaseInsensitiveMultiMap.builder() - .add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Test") - .build(); - when(payload.headers()).thenReturn(headers); - - // when - final Future> result = target.call(payload, context); - - // then - assertThat(result).isNotNull(); - assertThat(result.succeeded()).isTrue(); - final InvocationResult invocationResult = result.result(); - assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success); - assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action); - assertThat(invocationResult.moduleContext()).isNotNull(); - } - - @Test - void callShouldHandleNullHeaders() { - // given - final WURFLDeviceDetectionEntrypointHook target = new WURFLDeviceDetectionEntrypointHook(); - - // when - when(payload.headers()).thenReturn(null); - final Future> result = target.call(payload, context); - - // then - assertThat(result).isNotNull(); - assertThat(result.succeeded()).isTrue(); - final InvocationResult invocationResult = result.result(); - assertThat(invocationResult.status()).isEqualTo(InvocationStatus.success); - assertThat(invocationResult.action()).isEqualTo(InvocationAction.no_action); - assertThat(invocationResult.moduleContext()).isNotNull(); - assertThat(invocationResult.moduleContext() instanceof AuctionRequestHeadersContext).isTrue(); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java deleted file mode 100644 index b2d36390f52..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionModuleTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import org.junit.jupiter.api.Test; -import org.prebid.server.hooks.v1.Hook; -import org.prebid.server.hooks.v1.InvocationContext; - -import java.util.ArrayList; -import java.util.List; -import java.util.Collection; - -import static org.assertj.core.api.Assertions.assertThat; - -class WURFLDeviceDetectionModuleTest { - - @Test - void codeShouldReturnCorrectModuleCode() { - // given - final List> hooks = new ArrayList<>(); - final WURFLDeviceDetectionModule target = new WURFLDeviceDetectionModule(hooks); - - // when - final String result = target.code(); - - // then - assertThat(result).isEqualTo("wurfl-devicedetection"); - } - - @Test - void hooksShouldReturnProvidedHooks() { - // given - final List> hooks = new ArrayList<>(); - final WURFLDeviceDetectionModule target = new WURFLDeviceDetectionModule(hooks); - - // when - final Collection> result = target.hooks(); - - // then - assertThat(result).isSameAs(hooks); - } -} diff --git a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java b/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java deleted file mode 100644 index 5b5a1d01f74..00000000000 --- a/extra/modules/WURFL-devicedetection/src/test/java/org/prebid/server/hooks/modules/com/scientiamobile/wurfl/devicedetection/v1/WURFLDeviceDetectionRawAuctionRequestHookTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.v1; - -import com.iab.openrtb.request.BidRequest; -import com.iab.openrtb.request.Device; -import com.scientiamobile.wurfl.core.WURFLEngine; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.config.WURFLDeviceDetectionConfigProperties; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.mock.WURFLDeviceMock; -import org.prebid.server.hooks.modules.com.scientiamobile.wurfl.devicedetection.model.AuctionRequestHeadersContext; -import org.prebid.server.hooks.v1.InvocationAction; -import org.prebid.server.hooks.v1.InvocationResult; -import org.prebid.server.hooks.v1.InvocationStatus; -import org.prebid.server.hooks.v1.auction.AuctionInvocationContext; -import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; -import org.prebid.server.model.CaseInsensitiveMultiMap; - -import java.util.Collections; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class WURFLDeviceDetectionRawAuctionRequestHookTest { - - @Mock - private WURFLEngine wurflEngine; - - @Mock - private WURFLDeviceDetectionConfigProperties configProperties; - - @Mock - private AuctionRequestPayload payload; - - @Mock - private AuctionInvocationContext context; - - private WURFLDeviceDetectionRawAuctionRequestHook target; - - @BeforeEach - void setUp() { - - target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflEngine, configProperties); - } - - @Test - void codeShouldReturnCorrectHookCode() { - // when - final String result = target.code(); - - // then - assertThat(result).isEqualTo("wurfl-devicedetection-raw-auction-request"); - } - - @Test - void callShouldReturnNoActionWhenBidRequestIsNull() { - // given - when(payload.bidRequest()).thenReturn(null); - - // when - final InvocationResult result = target.call(payload, context).result(); - - // then - assertThat(result.status()).isEqualTo(InvocationStatus.success); - assertThat(result.action()).isEqualTo(InvocationAction.no_action); - } - - @Test - void callShouldReturnNoActionWhenDeviceIsNull() { - // given - final BidRequest bidRequest = BidRequest.builder().build(); - when(payload.bidRequest()).thenReturn(bidRequest); - - // when - final InvocationResult result = target.call(payload, context).result(); - - // then - assertThat(result.status()).isEqualTo(InvocationStatus.success); - assertThat(result.action()).isEqualTo(InvocationAction.no_action); - } - - @Test - void callShouldUpdateDeviceWhenWurflDeviceIsDetected() { - // given - final String ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_2) Version/17.4.1 Mobile/15E148 Safari/604.1"; - final Device device = Device.builder().ua(ua).build(); - final BidRequest bidRequest = BidRequest.builder().device(device).build(); - when(payload.bidRequest()).thenReturn(bidRequest); - - final CaseInsensitiveMultiMap headers = CaseInsensitiveMultiMap.builder() - .add("User-Agent", ua) - .build(); - final AuctionRequestHeadersContext headersContext = AuctionRequestHeadersContext.from(headers); - - // when - when(context.moduleContext()).thenReturn(headersContext); - final var wurflDevice = WURFLDeviceMock.WURFLDeviceMockFactory.mockIPhone(); - when(wurflEngine.getDeviceForRequest(any(Map.class))).thenReturn(wurflDevice); - - final InvocationResult result = target.call(payload, context).result(); - - // then - assertThat(result.status()).isEqualTo(InvocationStatus.success); - assertThat(result.action()).isEqualTo(InvocationAction.update); - } - - @Test - void shouldEnrichDeviceWhenAllowedPublisherIdsIsEmpty() { - // given - when(configProperties.getAllowedPublisherIds()).thenReturn(Collections.emptyList()); - target = new WURFLDeviceDetectionRawAuctionRequestHook(wurflEngine, configProperties); - - // when - final InvocationResult result = target.call(payload, context).result(); - - // then - assertThat(result.status()).isEqualTo(InvocationStatus.success); - } -} diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml index 04d49f64886..456f4f67732 100644 --- a/extra/modules/pom.xml +++ b/extra/modules/pom.xml @@ -24,7 +24,6 @@ pb-response-correction greenbids-real-time-data pb-request-correction - WURFL-devicedetection diff --git a/sample/configs/prebid-config-with-wurfl.yaml b/sample/configs/prebid-config-with-wurfl.yaml deleted file mode 100644 index c9f6b1d63d2..00000000000 --- a/sample/configs/prebid-config-with-wurfl.yaml +++ /dev/null @@ -1,80 +0,0 @@ -status-response: "ok" -adapters: - appnexus: - enabled: true - ix: - enabled: true - openx: - enabled: true - pubmatic: - enabled: true - rubicon: - enabled: true -metrics: - prefix: prebid -cache: - scheme: http - host: localhost - path: /cache - query: uuid= -settings: - enforce-valid-account: false - generate-storedrequest-bidrequest-id: true - filesystem: - settings-filename: sample/configs/sample-app-settings.yaml - stored-requests-dir: sample - stored-imps-dir: sample - stored-responses-dir: sample - categories-dir: -gdpr: - default-value: 1 - vendorlist: - v2: - cache-dir: /var/tmp/vendor2 - v3: - cache-dir: /var/tmp/vendor3 -admin-endpoints: - logging-changelevel: - enabled: true - path: /logging/changelevel - on-application-port: true - protected: false -hooks: - wurfl-devicedetection: - enabled: true - host-execution-plan: > - { - "endpoints": { - "/openrtb2/auction": { - "stages": { - "entrypoint": { - "groups": [ - { - "timeout": 10, - "hook_sequence": [ - { - "module_code": "wurfl-devicedetection", - "hook_impl_code": "wurfl-devicedetection-entrypoint-hook" - } - ] - } - ] - }, - "raw_auction_request": { - "groups": [ - { - "timeout": 10, - "hook_sequence": [ - { - "module_code": "wurfl-devicedetection", - "hook_impl_code": "wurfl-devicedetection-raw-auction-request" - } - ] - } - ] - } - } - } - } - } - From b5d502e170f17794a21324969bb87e6bf54a611f Mon Sep 17 00:00:00 2001 From: andrea Date: Wed, 5 Mar 2025 16:36:13 +0100 Subject: [PATCH 19/20] PREB-38: initial unit test for Device in bidder config (test fails, no impl) --- .../prebid/server/auction/FpdResolver.java | 6 ++ .../ext/request/ExtBidderConfigOrtb.java | 5 ++ .../server/auction/ExchangeServiceTest.java | 65 ++++++++++++++++--- .../prebid/server/json/JsonMergerTest.java | 5 +- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/FpdResolver.java b/src/main/java/org/prebid/server/auction/FpdResolver.java index f0ade099ece..14ea841d35f 100644 --- a/src/main/java/org/prebid/server/auction/FpdResolver.java +++ b/src/main/java/org/prebid/server/auction/FpdResolver.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.NullNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; +import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Site; import com.iab.openrtb.request.User; @@ -150,4 +151,9 @@ private void removeOrReplace(ObjectNode impExt, String field, JsonNode jsonNode) impExt.set(field, jsonNode); } } + + public Object resolveDevice(Device originDevice, ObjectNode fpdDevice) { + return mergeFpd(originDevice, fpdDevice, Device.class); + + } } diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtBidderConfigOrtb.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtBidderConfigOrtb.java index 59a6ecc08f7..b264417fe31 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtBidderConfigOrtb.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtBidderConfigOrtb.java @@ -27,4 +27,9 @@ public class ExtBidderConfigOrtb { * Defines the contract for bidrequest.ext.prebid.bidderconfig.config.ortb2.user */ ObjectNode user; + + /** + * Defines the contract for bidrequest.ext.prebid.bidderconfig.config.ortb2.device + */ + ObjectNode device; } diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 2e8c9a5e2c1..8ee027fea26 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -2679,12 +2679,12 @@ public void shouldUseConcreteOverGeneralSiteWithExtPrebidBidderConfigIgnoringCas final ObjectNode siteWithPage = mapper.valueToTree(Site.builder().page("testPage").build()); final ExtBidderConfig extBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(siteWithPage, null, null, null)); + ExtBidderConfigOrtb.of(siteWithPage, null, null, null, null)); final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of( singletonList("SoMeBiDdEr"), extBidderConfig); final ObjectNode siteWithDomain = mapper.valueToTree(Site.builder().domain("notUsed").build()); final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(siteWithDomain, null, null, null)); + ExtBidderConfigOrtb.of(siteWithDomain, null, null, null, null)); final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"), allExtBidderConfig); @@ -2726,12 +2726,12 @@ public void shouldUseConcreteOverGeneralDoohWithExtPrebidBidderConfig() { final ObjectNode doohWithVenueType = mapper.valueToTree(Dooh.builder().venuetype(List.of("venuetype")).build()); final ExtBidderConfig extBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(null, null, doohWithVenueType, null)); + ExtBidderConfigOrtb.of(null, null, doohWithVenueType, null, null)); final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of( singletonList("someBidder"), extBidderConfig); final ObjectNode doohWithDomain = mapper.valueToTree(Dooh.builder().domain("notUsed").build()); final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(null, null, doohWithDomain, null)); + ExtBidderConfigOrtb.of(null, null, doohWithDomain, null, null)); final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of( singletonList("*"), allExtBidderConfig); @@ -2775,7 +2775,7 @@ public void shouldUseConcreteOverGeneralAppWithExtPrebidBidderConfigIgnoringCase final Publisher publisherWithId = Publisher.builder().id("testId").build(); final ObjectNode appWithPublisherId = mapper.valueToTree(App.builder().publisher(publisherWithId).build()); final ExtBidderConfig extBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(null, appWithPublisherId, null, null)); + ExtBidderConfigOrtb.of(null, appWithPublisherId, null, null, null)); final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of( singletonList("SoMeBiDdEr"), extBidderConfig); @@ -2783,7 +2783,7 @@ public void shouldUseConcreteOverGeneralAppWithExtPrebidBidderConfigIgnoringCase final ObjectNode appWithUpdatedPublisher = mapper.valueToTree( App.builder().publisher(publisherWithIdAndDomain).build()); final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(null, appWithUpdatedPublisher, null, null)); + ExtBidderConfigOrtb.of(null, appWithUpdatedPublisher, null, null, null)); final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"), allExtBidderConfig); @@ -2815,6 +2815,55 @@ public void shouldUseConcreteOverGeneralAppWithExtPrebidBidderConfigIgnoringCase .containsOnly(mergedApp); } + @Test + public void shouldUseConcreteOverGeneralDeviceWithExtPrebidBidderConfig() { + // given + final Bidder bidder = mock(Bidder.class); + givenBidder("someBidder", bidder, givenEmptySeatBid()); + + final ObjectNode deviceWithMakeAndModel = mapper.valueToTree( + Device.builder().make("TestMake_001").model("TestModel_001").build()); + + final ExtBidderConfig extBidderConfig = ExtBidderConfig.of( + ExtBidderConfigOrtb.of(null, null, null, null, deviceWithMakeAndModel)); + final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of( + singletonList("SoMeBiDdEr"), extBidderConfig); + + + final ObjectNode deviceWithUpdatedMakeAndModel = mapper.valueToTree( + Device.builder().make("TestMake_002").model("TestModel_002").build()); + final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of( + ExtBidderConfigOrtb.of(null, null, null, null, deviceWithUpdatedMakeAndModel)); + final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"), + allExtBidderConfig); + + final Device requestDevice = Device.builder().build(); + + final ExtRequestPrebid extRequestPrebid = ExtRequestPrebid.builder() + .bidderconfig(asList(allFpdConfig, concreteFpdConfig)) + .build(); + final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("someBidder", 1)), + builder -> builder.device(requestDevice).ext(ExtRequest.of(extRequestPrebid))); + final Device mergedDevice = Device.builder() + .make("TestMake_001").model("TestModel_001").build(); + + given(fpdResolver.resolveDevice(any(), any())).willReturn(mergedDevice); + + // when + target.holdAuction(givenRequestContext(bidRequest)); + + // then + final ArgumentCaptor bidderRequestCaptor = ArgumentCaptor.forClass(BidderRequest.class); + verify(httpBidderRequester) + .requestBids(any(), bidderRequestCaptor.capture(), any(), any(), any(), any(), anyBoolean()); + final List capturedBidRequests = bidderRequestCaptor.getAllValues(); + + assertThat(capturedBidRequests) + .extracting(BidderRequest::getBidRequest) + .extracting(BidRequest::getDevice) + .containsOnly(mergedDevice); + } + @Test public void shouldUseConcreteOverGeneralUserWithExtPrebidBidderConfig() { // given @@ -2822,13 +2871,13 @@ public void shouldUseConcreteOverGeneralUserWithExtPrebidBidderConfig() { givenBidder("someBidder", bidder, givenEmptySeatBid()); final ObjectNode bidderConfigUser = mapper.valueToTree(User.builder().id("userFromConfig").build()); final ExtBidderConfig extBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(null, null, null, bidderConfigUser)); + ExtBidderConfigOrtb.of(null, null, null, bidderConfigUser, null)); final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of( singletonList("SomMeBiDdEr"), extBidderConfig); final ObjectNode emptyUser = mapper.valueToTree(User.builder().build()); final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of( - ExtBidderConfigOrtb.of(null, null, null, emptyUser)); + ExtBidderConfigOrtb.of(null, null, null, emptyUser, null)); final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"), allExtBidderConfig); final User requestUser = User.builder().id("erased").buyeruid("testBuyerId").build(); diff --git a/src/test/java/org/prebid/server/json/JsonMergerTest.java b/src/test/java/org/prebid/server/json/JsonMergerTest.java index 48957f12511..4172bb6437f 100644 --- a/src/test/java/org/prebid/server/json/JsonMergerTest.java +++ b/src/test/java/org/prebid/server/json/JsonMergerTest.java @@ -34,6 +34,7 @@ public void mergeShouldReturnMergedObject() { siteWithPage, appWithPublisherId, doohWithVenueType, + null, null); final ObjectNode siteWithDomain = mapper.valueToTree(Site.builder().domain("testDomain").build()); @@ -42,7 +43,7 @@ public void mergeShouldReturnMergedObject() { .publisher(publisherWithIdAndDomain).build()); final ObjectNode doohWithVenueTypeTax = mapper.valueToTree(Dooh.builder().venuetypetax(3).build()); final ExtBidderConfigOrtb secondBidderConfigFpd = - ExtBidderConfigOrtb.of(siteWithDomain, appWithUpdatedPublisher, doohWithVenueTypeTax, null); + ExtBidderConfigOrtb.of(siteWithDomain, appWithUpdatedPublisher, doohWithVenueTypeTax, null, null); // when final ExtBidderConfigOrtb result = target.merge( @@ -56,7 +57,7 @@ public void mergeShouldReturnMergedObject() { final ObjectNode mergedApp = mapper.valueToTree(App.builder().publisher(mergedPublisher).build()); final ObjectNode mergedDooh = mapper.valueToTree( Dooh.builder().venuetype(List.of("venuetype")).venuetypetax(3).build()); - final ExtBidderConfigOrtb mergedConfigFpd = ExtBidderConfigOrtb.of(mergedSite, mergedApp, mergedDooh, null); + final ExtBidderConfigOrtb mergedConfigFpd = ExtBidderConfigOrtb.of(mergedSite, mergedApp, mergedDooh, null, null); assertThat(result).isEqualTo(mergedConfigFpd); } From 6140fd76a842741b13b91f5445c3b4adbaaa88bd Mon Sep 17 00:00:00 2001 From: andrea Date: Thu, 6 Mar 2025 11:45:21 +0100 Subject: [PATCH 20/20] PREB-38: FPD device in bidder config impl --- .../org/prebid/server/auction/ExchangeService.java | 10 ++++++++++ .../java/org/prebid/server/auction/FpdResolver.java | 12 ++++++------ .../prebid/server/auction/ExchangeServiceTest.java | 1 - .../java/org/prebid/server/json/JsonMergerTest.java | 11 ++++++++--- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java index 56cd8192501..beafe575b48 100644 --- a/src/main/java/org/prebid/server/auction/ExchangeService.java +++ b/src/main/java/org/prebid/server/auction/ExchangeService.java @@ -5,6 +5,7 @@ import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Content; +import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Eid; import com.iab.openrtb.request.Imp; @@ -768,12 +769,15 @@ private BidRequest prepareBidRequest(BidderPrivacyResult bidderPrivacyResult, final App app = bidRequest.getApp(); final Site site = bidRequest.getSite(); final Dooh dooh = bidRequest.getDooh(); + final Device device = bidRequest.getDevice(); final ObjectNode fpdSite = fpdConfig != null ? fpdConfig.getSite() : null; final ObjectNode fpdApp = fpdConfig != null ? fpdConfig.getApp() : null; final ObjectNode fpdDooh = fpdConfig != null ? fpdConfig.getDooh() : null; + final ObjectNode fpdDevice = fpdConfig != null ? fpdConfig.getDevice() : null; final App preparedApp = prepareApp(app, fpdApp, useFirstPartyData); final Site preparedSite = prepareSite(site, fpdSite, useFirstPartyData); final Dooh preparedDooh = prepareDooh(dooh, fpdDooh, useFirstPartyData); + final Device preparedDevice = prepareDevice(device, fpdDevice, useFirstPartyData); final List distributionChannels = new ArrayList<>(); Optional.ofNullable(preparedApp).ifPresent(ignored -> distributionChannels.add("app")); @@ -800,6 +804,7 @@ private BidRequest prepareBidRequest(BidderPrivacyResult bidderPrivacyResult, final boolean isApp = preparedApp != null; final boolean isDooh = !isApp && preparedDooh != null; final boolean isSite = !isApp && !isDooh && preparedSite != null; + final boolean isDevice = preparedDevice != null; final List preparedImps = prepareImps( bidder, @@ -818,6 +823,7 @@ private BidRequest prepareBidRequest(BidderPrivacyResult bidderPrivacyResult, .app(isApp ? preparedApp : null) .dooh(isDooh ? preparedDooh : null) .site(isSite ? preparedSite : null) + .device(isDevice ? preparedDevice : null) .source(prepareSource(bidder, bidRequest, transmitTid)) .ext(prepareExt(bidder, bidderToPrebidBidders, bidderToMultiBid, bidRequest.getExt())) .build(); @@ -932,6 +938,10 @@ private App prepareApp(App app, ObjectNode fpdApp, boolean useFirstPartyData) { return useFirstPartyData ? fpdResolver.resolveApp(maskedApp, fpdApp) : maskedApp; } + private Device prepareDevice(Device device, ObjectNode fpdDevice, boolean useFirstPartyData) { + return useFirstPartyData ? fpdResolver.resolveDevice(device, fpdDevice) : device; + } + private static ExtApp maskExtApp(ExtApp appExt) { final ExtApp maskedExtApp = ExtApp.of(appExt.getPrebid(), null); return maskedExtApp.isEmpty() ? null : maskedExtApp; diff --git a/src/main/java/org/prebid/server/auction/FpdResolver.java b/src/main/java/org/prebid/server/auction/FpdResolver.java index 14ea841d35f..5263f9541e8 100644 --- a/src/main/java/org/prebid/server/auction/FpdResolver.java +++ b/src/main/java/org/prebid/server/auction/FpdResolver.java @@ -24,7 +24,8 @@ public class FpdResolver { private static final String BIDDERS = "bidders"; private static final String APP = "app"; private static final String DOOH = "dooh"; - private static final Set KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, BIDDERS); + private static final String DEVICE = "device"; + private static final Set KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, DEVICE, BIDDERS); private static final String CONTEXT = "context"; private static final String DATA = "data"; @@ -44,6 +45,10 @@ public App resolveApp(App originApp, ObjectNode fpdApp) { return mergeFpd(originApp, fpdApp, App.class); } + public Device resolveDevice(Device originDevice, ObjectNode fpdDevice) { + return mergeFpd(originDevice, fpdDevice, Device.class); + } + public Site resolveSite(Site originSite, ObjectNode fpdSite) { return mergeFpd(originSite, fpdSite, Site.class); } @@ -151,9 +156,4 @@ private void removeOrReplace(ObjectNode impExt, String field, JsonNode jsonNode) impExt.set(field, jsonNode); } } - - public Object resolveDevice(Device originDevice, ObjectNode fpdDevice) { - return mergeFpd(originDevice, fpdDevice, Device.class); - - } } diff --git a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java index 8ee027fea26..b1ef6d9ee05 100644 --- a/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java +++ b/src/test/java/org/prebid/server/auction/ExchangeServiceTest.java @@ -2829,7 +2829,6 @@ public void shouldUseConcreteOverGeneralDeviceWithExtPrebidBidderConfig() { final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of( singletonList("SoMeBiDdEr"), extBidderConfig); - final ObjectNode deviceWithUpdatedMakeAndModel = mapper.valueToTree( Device.builder().make("TestMake_002").model("TestModel_002").build()); final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of( diff --git a/src/test/java/org/prebid/server/json/JsonMergerTest.java b/src/test/java/org/prebid/server/json/JsonMergerTest.java index 4172bb6437f..c6deba41dfe 100644 --- a/src/test/java/org/prebid/server/json/JsonMergerTest.java +++ b/src/test/java/org/prebid/server/json/JsonMergerTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.App; +import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Publisher; import com.iab.openrtb.request.Site; @@ -30,12 +31,13 @@ public void mergeShouldReturnMergedObject() { final Publisher publisherWithId = Publisher.builder().id("testId").build(); final ObjectNode appWithPublisherId = mapper.valueToTree(App.builder().publisher(publisherWithId).build()); final ObjectNode doohWithVenueType = mapper.valueToTree(Dooh.builder().venuetype(List.of("venuetype")).build()); + final ObjectNode deviceWithDeviceType = mapper.valueToTree(Device.builder().devicetype(2).build()); final ExtBidderConfigOrtb firstBidderConfigFpd = ExtBidderConfigOrtb.of( siteWithPage, appWithPublisherId, doohWithVenueType, null, - null); + deviceWithDeviceType); final ObjectNode siteWithDomain = mapper.valueToTree(Site.builder().domain("testDomain").build()); final Publisher publisherWithIdAndDomain = Publisher.builder().id("shouldNotBe").domain("domain").build(); @@ -43,7 +45,8 @@ public void mergeShouldReturnMergedObject() { .publisher(publisherWithIdAndDomain).build()); final ObjectNode doohWithVenueTypeTax = mapper.valueToTree(Dooh.builder().venuetypetax(3).build()); final ExtBidderConfigOrtb secondBidderConfigFpd = - ExtBidderConfigOrtb.of(siteWithDomain, appWithUpdatedPublisher, doohWithVenueTypeTax, null, null); + ExtBidderConfigOrtb.of(siteWithDomain, appWithUpdatedPublisher, doohWithVenueTypeTax, null, + deviceWithDeviceType); // when final ExtBidderConfigOrtb result = target.merge( @@ -57,7 +60,9 @@ public void mergeShouldReturnMergedObject() { final ObjectNode mergedApp = mapper.valueToTree(App.builder().publisher(mergedPublisher).build()); final ObjectNode mergedDooh = mapper.valueToTree( Dooh.builder().venuetype(List.of("venuetype")).venuetypetax(3).build()); - final ExtBidderConfigOrtb mergedConfigFpd = ExtBidderConfigOrtb.of(mergedSite, mergedApp, mergedDooh, null, null); + final ObjectNode mergedDevice = mapper.valueToTree(Device.builder().devicetype(2).build()); + final ExtBidderConfigOrtb mergedConfigFpd = ExtBidderConfigOrtb.of(mergedSite, mergedApp, mergedDooh, null, + mergedDevice); assertThat(result).isEqualTo(mergedConfigFpd); }