From 9c7fe2b1a5270dbf420ea3d9b749d96dc79ef0bc Mon Sep 17 00:00:00 2001
From: Oleksandr Zhevedenko <720803+Net-burst@users.noreply.github.com>
Date: Thu, 4 Sep 2025 06:45:20 -0400
Subject: [PATCH 1/4] Dependencies: Bump Spring Boot and Vert.x minor version
(#4164)
---
extra/pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/extra/pom.xml b/extra/pom.xml
index 7b11154539d..1de56ba5e27 100644
--- a/extra/pom.xml
+++ b/extra/pom.xml
@@ -33,8 +33,8 @@
10.17.0
- 3.4.5
- 4.5.14
+ 3.5.5
+ 4.5.20
2.0.1.Final
4.4
1.27.1
From 3af45077e5c2e5b1cdaf56b10e691077236e3b2b Mon Sep 17 00:00:00 2001
From: Anton Babak <76536883+AntoxaAntoxic@users.noreply.github.com>
Date: Thu, 4 Sep 2025 12:45:30 +0200
Subject: [PATCH 2/4] Smartadserver: Add second endpoint for programmatic
guaranteed (#4163)
---
.../smartadserver/SmartadserverBidder.java | 60 ++++++---
.../smartadserver/ExtImpSmartadserver.java | 6 +-
.../bidder/SmartadserverConfiguration.java | 22 +++-
.../bidder-config/smartadserver.yaml | 1 +
.../SmartadserverBidderTest.java | 115 ++++++++++++++++--
.../prebid/server/it/SmartadserverTest.java | 2 +-
.../test-auction-smartadserver-request.json | 3 +-
.../test-smartadserver-bid-request.json | 2 +-
.../server/it/test-application.properties | 1 +
9 files changed, 177 insertions(+), 35 deletions(-)
diff --git a/src/main/java/org/prebid/server/bidder/smartadserver/SmartadserverBidder.java b/src/main/java/org/prebid/server/bidder/smartadserver/SmartadserverBidder.java
index e2d353fb003..150c37fd255 100644
--- a/src/main/java/org/prebid/server/bidder/smartadserver/SmartadserverBidder.java
+++ b/src/main/java/org/prebid/server/bidder/smartadserver/SmartadserverBidder.java
@@ -1,6 +1,7 @@
package org.prebid.server.bidder.smartadserver;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Publisher;
@@ -30,6 +31,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
@@ -40,38 +42,50 @@ public class SmartadserverBidder implements Bidder {
};
private final String endpointUrl;
+ private final String secondaryEndpointUrl;
private final JacksonMapper mapper;
- public SmartadserverBidder(String endpointUrl, JacksonMapper mapper) {
+ public SmartadserverBidder(String endpointUrl, String secondaryEndpointUrl, JacksonMapper mapper) {
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
+ this.secondaryEndpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(secondaryEndpointUrl));
this.mapper = Objects.requireNonNull(mapper);
}
@Override
public Result>> makeHttpRequests(BidRequest request) {
final List errors = new ArrayList<>();
- final List imps = new ArrayList<>();
- ExtImpSmartadserver extImp = null;
+ final List modifiedImps = new ArrayList<>();
+ final LinkedHashMap impToExtImpMap = new LinkedHashMap<>();
+
+ boolean isProgrammaticGuaranteed = false;
for (Imp imp : request.getImp()) {
try {
- extImp = parseImpExt(imp);
- imps.add(imp);
+ final ExtImpSmartadserver extImp = parseImpExt(imp);
+ isProgrammaticGuaranteed |= extImp.isProgrammaticGuaranteed();
+ impToExtImpMap.put(imp, extImp);
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
}
}
- if (imps.isEmpty()) {
+ if (impToExtImpMap.isEmpty()) {
return Result.withErrors(errors);
}
+ final String extImpKey = isProgrammaticGuaranteed ? "smartadserver" : "bidder";
+ impToExtImpMap.forEach((imp, extImp) -> modifiedImps.add(modifyImp(imp, extImp, extImpKey)));
+
+ final ExtImpSmartadserver lastExtImp = impToExtImpMap.lastEntry().getValue();
final BidRequest outgoingRequest = request.toBuilder()
- .imp(imps)
- .site(modifySite(request.getSite(), extImp.getNetworkId()))
+ .imp(modifiedImps)
+ .site(modifySite(request.getSite(), lastExtImp.getNetworkId()))
.build();
- final HttpRequest httpRequest = BidderUtil.defaultRequest(outgoingRequest, makeUrl(), mapper);
+ final HttpRequest httpRequest = BidderUtil.defaultRequest(
+ outgoingRequest,
+ makeUrl(isProgrammaticGuaranteed),
+ mapper);
return Result.of(Collections.singletonList(httpRequest), errors);
}
@@ -83,6 +97,13 @@ private ExtImpSmartadserver parseImpExt(Imp imp) {
}
}
+ private Imp modifyImp(Imp imp, ExtImpSmartadserver extImp, String impExtKey) {
+ final ObjectNode impExt = imp.getExt().deepCopy();
+ impExt.remove("bidder");
+ impExt.set(impExtKey, mapper.mapper().valueToTree(extImp));
+ return imp.toBuilder().ext(impExt).build();
+ }
+
private static Site modifySite(Site site, Integer networkId) {
final Site.SiteBuilder siteBuilder = site != null ? site.toBuilder() : Site.builder();
final Publisher sitePublisher = site != null ? site.getPublisher() : null;
@@ -98,17 +119,22 @@ private static Publisher modifyPublisher(Publisher publisher, Integer networkId)
return publisherBuilder.id(String.valueOf(networkId)).build();
}
- private String makeUrl() {
- final URI uri;
+ private String makeUrl(boolean isProgrammaticGuaranteed) {
+ final String url = isProgrammaticGuaranteed ? secondaryEndpointUrl : endpointUrl;
try {
- uri = new URI(endpointUrl);
+ final URI uri = new URI(url);
+ final String path = isProgrammaticGuaranteed ? "/ortb" : "/api/bid";
+ final URIBuilder uriBuilder = new URIBuilder(uri)
+ .setPath(StringUtils.removeEnd(uri.getPath(), "/") + path);
+
+ if (!isProgrammaticGuaranteed) {
+ uriBuilder.addParameter("callerId", "5");
+ }
+
+ return uriBuilder.toString();
} catch (URISyntaxException e) {
- throw new PreBidException("Malformed URL: %s.".formatted(endpointUrl));
+ throw new PreBidException("Malformed URL: %s.".formatted(url));
}
- return new URIBuilder(uri)
- .setPath(StringUtils.removeEnd(uri.getPath(), "/") + "/api/bid")
- .addParameter("callerId", "5")
- .toString();
}
@Override
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smartadserver/ExtImpSmartadserver.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smartadserver/ExtImpSmartadserver.java
index 0d2e3c25487..efc8de91a5f 100644
--- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/smartadserver/ExtImpSmartadserver.java
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/smartadserver/ExtImpSmartadserver.java
@@ -3,9 +3,6 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Value;
-/**
- * Defines the contract for bidrequest.imp[i].ext.smartadserver
- */
@Value(staticConstructor = "of")
public class ExtImpSmartadserver {
@@ -20,4 +17,7 @@ public class ExtImpSmartadserver {
@JsonProperty("networkId")
Integer networkId;
+
+ @JsonProperty(value = "programmaticGuaranteed", access = JsonProperty.Access.WRITE_ONLY)
+ boolean programmaticGuaranteed;
}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/SmartadserverConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/SmartadserverConfiguration.java
index 5264a6a3e77..3d831685aff 100644
--- a/src/main/java/org/prebid/server/spring/config/bidder/SmartadserverConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/bidder/SmartadserverConfiguration.java
@@ -1,5 +1,8 @@
package org.prebid.server.spring.config.bidder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.smartadserver.SmartadserverBidder;
import org.prebid.server.json.JacksonMapper;
@@ -23,19 +26,28 @@ public class SmartadserverConfiguration {
@Bean("smartadserverConfigurationProperties")
@ConfigurationProperties("adapters.smartadserver")
- BidderConfigurationProperties configurationProperties() {
- return new BidderConfigurationProperties();
+ SmartadserverConfigurationProperties configurationProperties() {
+ return new SmartadserverConfigurationProperties();
}
@Bean
- BidderDeps smartadserverBidderDeps(BidderConfigurationProperties smartadserverConfigurationProperties,
+ BidderDeps smartadserverBidderDeps(SmartadserverConfigurationProperties smartadserverConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
JacksonMapper mapper) {
- return BidderDepsAssembler.forBidder(BIDDER_NAME)
+ return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(smartadserverConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
- .bidderCreator(config -> new SmartadserverBidder(config.getEndpoint(), mapper))
+ .bidderCreator(config -> new SmartadserverBidder(
+ config.getEndpoint(), config.getSecondaryEndpoint(), mapper))
.assemble();
}
+
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ @NoArgsConstructor
+ private static class SmartadserverConfigurationProperties extends BidderConfigurationProperties {
+
+ private String secondaryEndpoint;
+ }
}
diff --git a/src/main/resources/bidder-config/smartadserver.yaml b/src/main/resources/bidder-config/smartadserver.yaml
index f1233b2e754..343bec392de 100644
--- a/src/main/resources/bidder-config/smartadserver.yaml
+++ b/src/main/resources/bidder-config/smartadserver.yaml
@@ -1,6 +1,7 @@
adapters:
smartadserver:
endpoint: https://ssb-global.smartadserver.com
+ secondary-endpoint: https://prebid-global.smartadserver.com
endpoint-compression: gzip
aliases:
equativ:
diff --git a/src/test/java/org/prebid/server/bidder/smartadserver/SmartadserverBidderTest.java b/src/test/java/org/prebid/server/bidder/smartadserver/SmartadserverBidderTest.java
index 4565c7581de..c9f64cd8a0c 100644
--- a/src/test/java/org/prebid/server/bidder/smartadserver/SmartadserverBidderTest.java
+++ b/src/test/java/org/prebid/server/bidder/smartadserver/SmartadserverBidderTest.java
@@ -1,6 +1,7 @@
package org.prebid.server.bidder.smartadserver;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
@@ -37,12 +38,20 @@
public class SmartadserverBidderTest extends VertxTest {
private static final String ENDPOINT_URL = "https://test.endpoint.com/path?testParam=testVal";
+ private static final String SECONDARY_URL = "https://test.endpoint2.com/path?testParam=testVal";
- private final SmartadserverBidder target = new SmartadserverBidder(ENDPOINT_URL, jacksonMapper);
+ private final SmartadserverBidder target = new SmartadserverBidder(ENDPOINT_URL, SECONDARY_URL, jacksonMapper);
@Test
public void creationShouldFailOnInvalidEndpointUrl() {
- assertThatIllegalArgumentException().isThrownBy(() -> new SmartadserverBidder("invalid_url", jacksonMapper));
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new SmartadserverBidder("invalid_url", SECONDARY_URL, jacksonMapper));
+ }
+
+ @Test
+ public void creationShouldFailOnInvalidSecondaryEndpointUrl() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new SmartadserverBidder(ENDPOINT_URL, "invalid_url", jacksonMapper));
}
@Test
@@ -64,7 +73,7 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtCouldNotBeParsed() {
}
@Test
- public void makeHttpRequestsShouldCreateCorrectURL() {
+ public void makeHttpRequestsShouldCreateCorrectPrimaryURLWhenProgrammaticGuaranteedIsAbsent() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(givenImp(identity())))
@@ -80,6 +89,46 @@ public void makeHttpRequestsShouldCreateCorrectURL() {
.isEqualTo("https://test.endpoint.com/path/api/bid?testParam=testVal&callerId=5");
}
+ @Test
+ public void makeHttpRequestsShouldCreateCorrectSecondaryURLWhenProgrammaticGuaranteedIsTrue() {
+ // given
+ final ObjectNode givenImpExt = mapper.createObjectNode()
+ .set("bidder", mapper.createObjectNode().put("programmaticGuaranteed", true));
+
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(givenImp(imp -> imp.ext(givenImpExt))))
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1);
+ assertThat(result.getValue().getFirst().getUri())
+ .isEqualTo("https://test.endpoint2.com/path/ortb?testParam=testVal");
+ }
+
+ @Test
+ public void makeHttpRequestsShouldCreateCorrectPrimaryURLWhenProgrammaticGuaranteedIsFalse() {
+ // given
+ final ObjectNode givenImpExt = mapper.createObjectNode()
+ .set("bidder", mapper.createObjectNode().put("programmaticGuaranteed", false));
+
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(singletonList(givenImp(imp -> imp.ext(givenImpExt))))
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1);
+ assertThat(result.getValue().getFirst().getUri())
+ .isEqualTo("https://test.endpoint.com/path/api/bid?testParam=testVal&callerId=5");
+ }
+
@Test
public void makeHttpRequestsShouldUpdateSiteObjectIfPresent() {
// given
@@ -152,6 +201,56 @@ public void makeHttpRequestsShouldCreateSingleRequestWithValidImpsOnly() {
.containsExactly("123");
}
+ @Test
+ public void makeHttpRequestsShouldModifyImpWhenProgrammaticGuaranteedIsTrueAtLeastInOneValidImp() {
+ // given
+ final ObjectNode givenImpExt1 = mapper.createObjectNode()
+ .set("bidder", mapper.createObjectNode()
+ .put("programmaticGuaranteed", false)
+ .put("networkId", 1)
+ .put("siteId", 2)
+ .put("formatId", 3)
+ .put("pageId", 4));
+ final ObjectNode givenImpExt2 = mapper.createObjectNode()
+ .set("bidder", mapper.createObjectNode()
+ .put("programmaticGuaranteed", true)
+ .put("networkId", 5)
+ .put("siteId", 6)
+ .put("formatId", 7)
+ .put("pageId", 8));
+
+ final BidRequest bidRequest = BidRequest.builder()
+ .imp(List.of(
+ givenImp(imp -> imp.id("impId1").ext(givenImpExt1)),
+ givenImp(imp -> imp.id("impId2").ext(givenImpExt2))))
+ .build();
+
+ // when
+ final Result>> result = target.makeHttpRequests(bidRequest);
+
+ // then
+ final ObjectNode expectedImpExt1 = mapper.createObjectNode()
+ .set("smartadserver", mapper.createObjectNode()
+ .put("networkId", 1)
+ .put("siteId", 2)
+ .put("formatId", 3)
+ .put("pageId", 4));
+ final ObjectNode expectedImpExt2 = mapper.createObjectNode()
+ .set("smartadserver", mapper.createObjectNode()
+ .put("networkId", 5)
+ .put("siteId", 6)
+ .put("formatId", 7)
+ .put("pageId", 8));
+
+ assertThat(result.getErrors()).isEmpty();
+ assertThat(result.getValue()).hasSize(1);
+ assertThat(result.getValue())
+ .extracting(HttpRequest::getPayload)
+ .flatExtracting(BidRequest::getImp)
+ .extracting(Imp::getExt)
+ .containsExactly(expectedImpExt1, expectedImpExt2);
+ }
+
@Test
public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() {
// given
@@ -299,10 +398,12 @@ public void makeBidsShouldReturnNativeBidIfMarkupTypeIsNative() throws JsonProce
private static Imp givenImp(Function impCustomizer) {
return impCustomizer.apply(Imp.builder()
- .id("123"))
- .banner(Banner.builder().build())
- .video(Video.builder().build())
- .ext(mapper.valueToTree(ExtPrebid.of(null, ExtImpSmartadserver.of(1, 2, 3, 4))))
+ .id("123")
+ .banner(Banner.builder().build())
+ .video(Video.builder().build())
+ .ext(mapper.valueToTree(ExtPrebid.of(
+ null,
+ ExtImpSmartadserver.of(1, 2, 3, 4, false)))))
.build();
}
diff --git a/src/test/java/org/prebid/server/it/SmartadserverTest.java b/src/test/java/org/prebid/server/it/SmartadserverTest.java
index 88b5e57c9cf..296883918e8 100644
--- a/src/test/java/org/prebid/server/it/SmartadserverTest.java
+++ b/src/test/java/org/prebid/server/it/SmartadserverTest.java
@@ -18,7 +18,7 @@ public class SmartadserverTest extends IntegrationTest {
@Test
public void openrtb2AuctionShouldRespondWithBidsFromSmartadserver() throws IOException, JSONException {
// given
- WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smartadserver-exchange/api/bid"))
+ WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/smartadserver-secondary-exchange/ortb"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/smartadserver/test-smartadserver-bid-request.json")))
.willReturn(aResponse()
.withBody(jsonFrom("openrtb2/smartadserver/test-smartadserver-bid-response.json"))));
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-auction-smartadserver-request.json b/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-auction-smartadserver-request.json
index bd93b91baf0..b02d4d0a642 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-auction-smartadserver-request.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-auction-smartadserver-request.json
@@ -14,7 +14,8 @@
"siteId": 1,
"pageId": 2,
"formatId": 3,
- "networkId": 73
+ "networkId": 73,
+ "programmaticGuaranteed": true
}
}
}
diff --git a/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-smartadserver-bid-request.json b/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-smartadserver-bid-request.json
index 05fec0b445d..f53c88fe858 100644
--- a/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-smartadserver-bid-request.json
+++ b/src/test/resources/org/prebid/server/it/openrtb2/smartadserver/test-smartadserver-bid-request.json
@@ -10,7 +10,7 @@
},
"ext": {
"tid": "${json-unit.any-string}",
- "bidder": {
+ "smartadserver": {
"siteId": 1,
"pageId": 2,
"formatId": 3,
diff --git a/src/test/resources/org/prebid/server/it/test-application.properties b/src/test/resources/org/prebid/server/it/test-application.properties
index 4cedf6582f2..76def6dbc96 100644
--- a/src/test/resources/org/prebid/server/it/test-application.properties
+++ b/src/test/resources/org/prebid/server/it/test-application.properties
@@ -493,6 +493,7 @@ adapters.smaato.enabled=true
adapters.smaato.endpoint=http://localhost:8090/smaato-exchange
adapters.smartadserver.enabled=true
adapters.smartadserver.endpoint=http://localhost:8090/smartadserver-exchange
+adapters.smartadserver.secondary-endpoint=http://localhost:8090/smartadserver-secondary-exchange
adapters.smartadserver.aliases.equativ.enabled=true
adapters.smartrtb.enabled=true
adapters.smartrtb.endpoint=http://localhost:8090/smartrtb-exchange/
From fc0fd73f0aa1792ea7e2705a3144f5c47566efe7 Mon Sep 17 00:00:00 2001
From: osulzhenko <125548596+osulzhenko@users.noreply.github.com>
Date: Thu, 4 Sep 2025 15:22:08 +0300
Subject: [PATCH 3/4] Fix issue with invalid functional tests (#4172)
---
.../server/functional/model/request/auction/IxDiag.groovy | 1 +
.../groovy/org/prebid/server/functional/util/PBSUtils.groovy | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/IxDiag.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/IxDiag.groovy
index 7bc0adc1054..d68075b6668 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/IxDiag.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/IxDiag.groovy
@@ -6,6 +6,7 @@ import groovy.transform.ToString
class IxDiag {
String pbsv
+ String pbsp
String pbjsv
String multipleSiteIds
}
diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy
index e1e7750ea05..7445a8e13ed 100644
--- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy
+++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy
@@ -107,7 +107,7 @@ class PBSUtils implements ObjectMapperWrapper {
}
}
- static BigDecimal getRandomPrice(int min = 0, int max = 10, int scale = 3) {
+ static BigDecimal getRandomPrice(int min = 1, int max = 10, int scale = 3) {
getRandomDecimal(min, max).setScale(scale, HALF_UP)
}
From a526e376cdf259086edd39bac712c43d5280731e Mon Sep 17 00:00:00 2001
From: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com>
Date: Thu, 4 Sep 2025 14:58:49 +0200
Subject: [PATCH 4/4] Add profiles (#4007)
---
sample/configs/prebid-config.yaml | 1 +
.../server/auction/BidResponseCreator.java | 1 +
.../server/auction/ExchangeService.java | 11 +-
.../server/auction/SkippedAuctionService.java | 1 +
.../auction/VideoStoredRequestProcessor.java | 10 +-
.../externalortb/ProfilesProcessor.java | 303 ++++
.../StoredRequestProcessor.java | 26 +-
.../StoredResponseProcessor.java | 14 +-
.../requestfactory/AmpRequestFactory.java | 7 +-
.../requestfactory/AuctionRequestFactory.java | 8 +-
.../requestfactory/Ortb2RequestFactory.java | 96 +-
.../exception/InvalidProfileException.java | 14 +
.../SettingsCacheNotificationHandler.java | 20 +-
.../prebid/server/metric/AccountMetrics.java | 6 +
.../org/prebid/server/metric/MetricName.java | 5 +-
.../org/prebid/server/metric/Metrics.java | 10 +
.../prebid/server/metric/ProfileMetrics.java | 28 +
.../openrtb/ext/request/ExtImpPrebid.java | 5 +
.../openrtb/ext/request/ExtRequestPrebid.java | 5 +
.../server/settings/ApplicationSettings.java | 59 +-
.../settings/CacheNotificationListener.java | 4 +-
.../settings/CachingApplicationSettings.java | 218 +--
.../CompositeApplicationSettings.java | 230 +--
.../settings/DatabaseApplicationSettings.java | 187 ++-
.../EnrichingApplicationSettings.java | 84 +-
.../settings/FileApplicationSettings.java | 329 ++--
.../settings/HttpApplicationSettings.java | 264 +--
.../settings/S3ApplicationSettings.java | 57 +-
.../prebid/server/settings/SettingsCache.java | 30 +-
.../helper/DatabaseProfilesResultMapper.java | 176 ++
.../DatabaseStoredDataResultMapper.java | 208 ++-
.../DatabaseStoredResponseResultMapper.java | 34 +-
.../helper/ParametrizedQueryHelper.java | 3 +-
.../helper/ParametrizedQueryMySqlHelper.java | 10 +-
.../ParametrizedQueryPostgresHelper.java | 11 +-
.../settings/helper/StoredDataFetcher.java | 17 +-
.../settings/helper/StoredItemResolver.java | 19 +-
.../helper/StoredResponseFetcher.java | 13 +
.../settings/model/AccountAuctionConfig.java | 2 +
.../settings/model/AccountProfilesConfig.java | 13 +
.../prebid/server/settings/model/Profile.java | 48 +
.../settings/model/StoredDataResult.java | 6 +-
.../server/settings/model/StoredItem.java | 4 +-
.../DatabasePeriodicRefreshService.java | 8 +-
.../service/HttpPeriodicRefreshService.java | 4 +-
.../service/S3PeriodicRefreshService.java | 8 +-
.../spring/config/ServiceConfiguration.java | 38 +-
.../spring/config/SettingsConfiguration.java | 78 +-
.../admin/AdminEndpointsConfiguration.java | 16 +-
src/main/resources/application.yaml | 3 +
.../model/config/AccountAuctionConfig.groovy | 1 +
.../config/AccountProfilesConfigs.groovy | 17 +
.../model/db/StoredProfileImp.groovy | 46 +
.../model/db/StoredProfileRequest.groovy | 46 +
.../functional/model/db/StoredRequest.groovy | 4 +-
.../functional/model/db/StoredResponse.groovy | 4 +-
...y => BidRequestConfigTypeConverter.groovy} | 2 +-
... => BidResponseConfigTypeConverter.groovy} | 2 +-
.../ProfileMergePrecedenceConvert.groovy | 17 +
.../typeconverter/ProfileTypeConvert.groovy | 17 +
.../FileSystemAccountsConfig.groovy | 10 +
.../model/request/auction/Banner.groovy | 2 +
.../request/auction/BidRequestExt.groovy | 2 +
.../model/request/auction/Device.groovy | 13 +
.../model/request/auction/Format.groovy | 8 +
.../model/request/auction/ImpExtPrebid.groovy | 3 +-
.../model/request/auction/Prebid.groovy | 2 +
.../model/request/auction/Site.groovy | 2 +
.../model/request/auction/SiteExtData.groovy | 2 +
.../model/request/profile/ImpProfile.groovy | 26 +
.../model/request/profile/Profile.groovy | 24 +
.../profile/ProfileMergePrecedence.groovy | 28 +
.../model/request/profile/ProfileType.groovy | 28 +
.../request/profile/RequestProfile.groovy | 40 +
.../model/response/auction/BidResponse.groovy | 4 +-
.../HibernateRepositoryService.groovy | 12 +
.../repository/dao/ProfileImpDao.groovy | 11 +
.../repository/dao/ProfileRequestDao.groovy | 11 +
.../testcontainers/PbsConfig.groovy | 3 +-
.../container/PrebidServerContainer.groovy | 9 +
.../functional/tests/ProfileSpec.groovy | 1426 +++++++++++++++++
.../util/ObjectMapperWrapper.groovy | 6 +
.../server/functional/util/PBSUtils.groovy | 8 +
.../auction/BidResponseCreatorTest.java | 4 +-
.../server/auction/ExchangeServiceTest.java | 1 +
.../auction/SkippedAuctionServiceTest.java | 1 +
.../VideoStoredRequestProcessorTest.java | 8 +-
.../externalortb/ProfilesProcessorTest.java | 496 ++++++
.../StoredRequestProcessorTest.java | 2 +-
.../StoredResponseProcessorTest.java | 2 +-
.../requestfactory/AmpRequestFactoryTest.java | 40 +-
.../AuctionRequestFactoryTest.java | 33 +-
.../Ortb2RequestFactoryTest.java | 76 +-
.../SettingsCacheNotificationHandlerTest.java | 4 +-
.../org/prebid/server/metric/MetricsTest.java | 20 +
.../CachingApplicationSettingsTest.java | 15 +-
.../CompositeApplicationSettingsTest.java | 213 ++-
.../DatabaseApplicationSettingsTest.java | 109 +-
.../settings/FileApplicationSettingsTest.java | 451 ++++--
.../settings/HttpApplicationSettingsTest.java | 89 +-
.../settings/S3ApplicationSettingsTest.java | 13 +-
.../server/settings/SettingsCacheTest.java | 4 +-
.../DatabaseProfilesResultMapperTest.java | 387 +++++
.../DatabaseStoredDataResultMapperTest.java | 37 +-
...atabaseStoredResponseResultMapperTest.java | 6 +-
.../helper/StoredItemResolverTest.java | 43 +-
.../DatabasePeriodicRefreshServiceTest.java | 6 +-
.../HttpPeriodicRefreshServiceTest.java | 12 +-
.../service/S3PeriodicRefreshServiceTest.java | 2 +-
.../server/functional/db_mysql_schema.sql | 9 +
.../server/it/profiles/test-profile.json | 9 +
.../server/it/test-application.properties | 1 +
112 files changed, 5439 insertions(+), 1242 deletions(-)
create mode 100644 src/main/java/org/prebid/server/auction/externalortb/ProfilesProcessor.java
rename src/main/java/org/prebid/server/auction/{ => externalortb}/StoredRequestProcessor.java (94%)
rename src/main/java/org/prebid/server/auction/{ => externalortb}/StoredResponseProcessor.java (97%)
create mode 100644 src/main/java/org/prebid/server/exception/InvalidProfileException.java
create mode 100644 src/main/java/org/prebid/server/metric/ProfileMetrics.java
create mode 100644 src/main/java/org/prebid/server/settings/helper/DatabaseProfilesResultMapper.java
create mode 100644 src/main/java/org/prebid/server/settings/helper/StoredResponseFetcher.java
create mode 100644 src/main/java/org/prebid/server/settings/model/AccountProfilesConfig.java
create mode 100644 src/main/java/org/prebid/server/settings/model/Profile.java
create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AccountProfilesConfigs.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/db/StoredProfileImp.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/db/StoredProfileRequest.groovy
rename src/test/groovy/org/prebid/server/functional/model/db/typeconverter/{StoredRequestConfigTypeConverter.groovy => BidRequestConfigTypeConverter.groovy} (81%)
rename src/test/groovy/org/prebid/server/functional/model/db/typeconverter/{StoredBidResponseConfigTypeConverter.groovy => BidResponseConfigTypeConverter.groovy} (81%)
create mode 100644 src/test/groovy/org/prebid/server/functional/model/db/typeconverter/ProfileMergePrecedenceConvert.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/db/typeconverter/ProfileTypeConvert.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/filesystem/FileSystemAccountsConfig.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/profile/ImpProfile.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/profile/Profile.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/profile/ProfileMergePrecedence.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/profile/ProfileType.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/profile/RequestProfile.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/repository/dao/ProfileImpDao.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/repository/dao/ProfileRequestDao.groovy
create mode 100644 src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy
create mode 100644 src/test/java/org/prebid/server/auction/externalortb/ProfilesProcessorTest.java
rename src/test/java/org/prebid/server/auction/{ => externalortb}/StoredRequestProcessorTest.java (99%)
rename src/test/java/org/prebid/server/auction/{ => externalortb}/StoredResponseProcessorTest.java (99%)
create mode 100644 src/test/java/org/prebid/server/settings/helper/DatabaseProfilesResultMapperTest.java
create mode 100644 src/test/resources/org/prebid/server/it/profiles/test-profile.json
diff --git a/sample/configs/prebid-config.yaml b/sample/configs/prebid-config.yaml
index 93a53b7952b..0d8b5c90ff2 100644
--- a/sample/configs/prebid-config.yaml
+++ b/sample/configs/prebid-config.yaml
@@ -24,6 +24,7 @@ settings:
settings-filename: sample/configs/sample-app-settings.yaml
stored-requests-dir: sample
stored-imps-dir: sample
+ profiles-dir: sample
stored-responses-dir: sample
categories-dir:
gdpr:
diff --git a/src/main/java/org/prebid/server/auction/BidResponseCreator.java b/src/main/java/org/prebid/server/auction/BidResponseCreator.java
index dd8f79c1920..aec0c82831b 100644
--- a/src/main/java/org/prebid/server/auction/BidResponseCreator.java
+++ b/src/main/java/org/prebid/server/auction/BidResponseCreator.java
@@ -25,6 +25,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.prebid.server.auction.categorymapping.CategoryMappingService;
+import org.prebid.server.auction.externalortb.StoredRequestProcessor;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.AuctionParticipation;
import org.prebid.server.auction.model.BidInfo;
diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java
index 04191d0839d..d9561ccc868 100644
--- a/src/main/java/org/prebid/server/auction/ExchangeService.java
+++ b/src/main/java/org/prebid/server/auction/ExchangeService.java
@@ -23,6 +23,7 @@
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
import org.prebid.server.activity.Activity;
import org.prebid.server.activity.ComponentType;
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
@@ -31,6 +32,7 @@
import org.prebid.server.activity.infrastructure.payload.impl.BidRequestActivityInvocationPayload;
import org.prebid.server.auction.aliases.AlternateBidderCodesConfig;
import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.externalortb.StoredResponseProcessor;
import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult;
import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor;
import org.prebid.server.auction.model.AuctionContext;
@@ -101,7 +103,6 @@
import org.prebid.server.util.ListUtil;
import org.prebid.server.util.PbsUtil;
import org.prebid.server.util.StreamUtil;
-import org.apache.commons.lang3.tuple.Pair;
import java.math.BigDecimal;
import java.time.Clock;
@@ -570,10 +571,10 @@ private static List firstPartyDataBidders(ExtRequest requestExt) {
}
private Map> prepareUsersAndDevices(List bidders,
- AuctionContext context,
- BidderAliases aliases,
- Map biddersToConfigs,
- Map> eidPermissions) {
+ AuctionContext context,
+ BidderAliases aliases,
+ Map biddersToConfigs,
+ Map> eidPermissions) {
final BidRequest bidRequest = context.getBidRequest();
final List firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt());
diff --git a/src/main/java/org/prebid/server/auction/SkippedAuctionService.java b/src/main/java/org/prebid/server/auction/SkippedAuctionService.java
index 1eb1ee1318b..78e87a44799 100644
--- a/src/main/java/org/prebid/server/auction/SkippedAuctionService.java
+++ b/src/main/java/org/prebid/server/auction/SkippedAuctionService.java
@@ -7,6 +7,7 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.auction.externalortb.StoredResponseProcessor;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.StoredResponseResult;
import org.prebid.server.bidder.model.BidderError;
diff --git a/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java b/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java
index f6bcb5599af..f3beeb57e6c 100644
--- a/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java
+++ b/src/main/java/org/prebid/server/auction/VideoStoredRequestProcessor.java
@@ -129,9 +129,9 @@ private static BidRequest readBidRequest(String defaultBidRequestPath,
: null;
}
- private StoredDataResult updateMetrics(StoredDataResult storedDataResult,
- Set requestIds,
- Set impIds) {
+ private StoredDataResult updateMetrics(StoredDataResult storedDataResult,
+ Set requestIds,
+ Set impIds) {
requestIds.forEach(
id -> metrics.updateStoredRequestMetric(storedDataResult.getStoredIdToRequest().containsKey(id)));
@@ -142,7 +142,7 @@ private StoredDataResult updateMetrics(StoredDataResult storedDataResult,
return storedDataResult;
}
- private WithPodErrors toBidRequestWithPodErrors(StoredDataResult storedResult,
+ private WithPodErrors toBidRequestWithPodErrors(StoredDataResult storedResult,
BidRequestVideo videoRequest,
String storedBidRequestId) {
@@ -161,7 +161,7 @@ private WithPodErrors toBidRequestWithPodErrors(StoredDataResult sto
private BidRequestVideo mergeBidRequest(BidRequestVideo originalRequest,
String storedRequestId,
- StoredDataResult storedDataResult) {
+ StoredDataResult storedDataResult) {
final String storedRequest = storedDataResult.getStoredIdToRequest().get(storedRequestId);
if (enforceStoredRequest && StringUtils.isBlank(storedRequest)) {
diff --git a/src/main/java/org/prebid/server/auction/externalortb/ProfilesProcessor.java b/src/main/java/org/prebid/server/auction/externalortb/ProfilesProcessor.java
new file mode 100644
index 00000000000..4f8603a8287
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/externalortb/ProfilesProcessor.java
@@ -0,0 +1,303 @@
+package org.prebid.server.auction.externalortb;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import io.vertx.core.Future;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.exception.InvalidProfileException;
+import org.prebid.server.exception.InvalidRequestException;
+import org.prebid.server.execution.timeout.Timeout;
+import org.prebid.server.execution.timeout.TimeoutFactory;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.json.JsonMerger;
+import org.prebid.server.log.ConditionalLogger;
+import org.prebid.server.log.LoggerFactory;
+import org.prebid.server.metric.MetricName;
+import org.prebid.server.metric.Metrics;
+import org.prebid.server.proto.openrtb.ext.request.ExtImpPrebid;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
+import org.prebid.server.settings.ApplicationSettings;
+import org.prebid.server.settings.model.Account;
+import org.prebid.server.settings.model.AccountAuctionConfig;
+import org.prebid.server.settings.model.AccountProfilesConfig;
+import org.prebid.server.settings.model.Profile;
+import org.prebid.server.settings.model.StoredDataResult;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class ProfilesProcessor {
+
+ private static final ConditionalLogger conditionalLogger =
+ new ConditionalLogger(LoggerFactory.getLogger(ProfilesProcessor.class));
+
+ private final int maxProfiles;
+ private final long defaultTimeoutMillis;
+ private final boolean failOnUnknown;
+ private final double logSamplingRate;
+ private final ApplicationSettings applicationSettings;
+ private final TimeoutFactory timeoutFactory;
+ private final Metrics metrics;
+ private final JacksonMapper mapper;
+ private final JsonMerger jsonMerger;
+
+ public ProfilesProcessor(int maxProfiles,
+ long defaultTimeoutMillis,
+ boolean failOnUnknown,
+ double logSamplingRate,
+ ApplicationSettings applicationSettings,
+ TimeoutFactory timeoutFactory,
+ Metrics metrics,
+ JacksonMapper mapper,
+ JsonMerger jsonMerger) {
+
+ this.maxProfiles = maxProfiles;
+ this.defaultTimeoutMillis = defaultTimeoutMillis;
+ this.failOnUnknown = failOnUnknown;
+ this.logSamplingRate = logSamplingRate;
+ this.applicationSettings = Objects.requireNonNull(applicationSettings);
+ this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
+ this.metrics = Objects.requireNonNull(metrics);
+ this.mapper = Objects.requireNonNull(mapper);
+ this.jsonMerger = Objects.requireNonNull(jsonMerger);
+ }
+
+ public Future process(AuctionContext auctionContext, BidRequest bidRequest) {
+ final String accountId = Optional.ofNullable(auctionContext.getAccount())
+ .map(Account::getId)
+ .orElse(StringUtils.EMPTY);
+
+ final AllProfilesIds profilesIds = profilesIds(bidRequest, auctionContext, accountId);
+ if (profilesIds.isEmpty()) {
+ return Future.succeededFuture(bidRequest);
+ }
+
+ final boolean failOnUnknown = isFailOnUnknown(auctionContext.getAccount());
+
+ return fetchProfiles(accountId, profilesIds, timeoutMillis(bidRequest))
+ .compose(profiles -> emitMetrics(accountId, profiles, auctionContext, failOnUnknown))
+ .map(profiles -> mergeResults(
+ applyRequestProfiles(
+ profilesIds.request(),
+ profiles.getStoredIdToRequest(),
+ bidRequest,
+ failOnUnknown),
+ applyImpsProfiles(
+ profilesIds.imps(),
+ profiles.getStoredIdToImp(),
+ bidRequest.getImp(),
+ failOnUnknown)))
+ .recover(error -> Future.failedFuture(
+ new InvalidRequestException("Error during processing profiles: " + error.getMessage())));
+ }
+
+ private AllProfilesIds profilesIds(BidRequest bidRequest, AuctionContext auctionContext, String accountId) {
+ final AllProfilesIds initialProfilesIds = new AllProfilesIds(
+ requestProfilesIds(bidRequest),
+ bidRequest.getImp().stream().map(this::impProfilesIds).toList());
+
+ final AllProfilesIds profilesIds = truncate(
+ initialProfilesIds,
+ Optional.ofNullable(auctionContext.getAccount())
+ .map(Account::getAuction)
+ .map(AccountAuctionConfig::getProfiles)
+ .map(AccountProfilesConfig::getLimit)
+ .orElse(maxProfiles));
+
+ if (auctionContext.getDebugContext().isDebugEnabled() && !profilesIds.equals(initialProfilesIds)) {
+ auctionContext.getDebugWarnings().add("Profiles exceeded the limit.");
+ metrics.updateAccountProfileMetric(accountId, MetricName.limit_exceeded);
+ }
+
+ return profilesIds;
+ }
+
+ private static List requestProfilesIds(BidRequest bidRequest) {
+ return Optional.ofNullable(bidRequest)
+ .map(BidRequest::getExt)
+ .map(ExtRequest::getPrebid)
+ .map(ExtRequestPrebid::getProfiles)
+ .orElse(Collections.emptyList());
+ }
+
+ private List impProfilesIds(Imp imp) {
+ return Optional.ofNullable(imp.getExt())
+ .map(ext -> ext.get("prebid"))
+ .map(this::parseImpExt)
+ .map(ExtImpPrebid::getProfiles)
+ .orElse(Collections.emptyList());
+ }
+
+ private ExtImpPrebid parseImpExt(JsonNode jsonNode) {
+ try {
+ return mapper.mapper().treeToValue(jsonNode, ExtImpPrebid.class);
+ } catch (JsonProcessingException e) {
+ throw new InvalidRequestException(e.getMessage());
+ }
+ }
+
+ private static AllProfilesIds truncate(AllProfilesIds profilesIds, int maxProfiles) {
+ final List requestProfiles = profilesIds.request();
+ final int impProfilesLimit = Math.max(0, maxProfiles - requestProfiles.size());
+
+ return new AllProfilesIds(
+ truncate(requestProfiles, maxProfiles),
+ profilesIds.imps().stream()
+ .map(impProfiles -> truncate(impProfiles, impProfilesLimit))
+ .toList());
+ }
+
+ private static List truncate(List list, int maxSize) {
+ return list.size() > maxSize ? list.subList(0, maxSize) : list;
+ }
+
+ private long timeoutMillis(BidRequest bidRequest) {
+ final Long tmax = bidRequest.getTmax();
+ return tmax != null && tmax > 0 ? tmax : defaultTimeoutMillis;
+ }
+
+ private boolean isFailOnUnknown(Account account) {
+ return Optional.ofNullable(account)
+ .map(Account::getAuction)
+ .map(AccountAuctionConfig::getProfiles)
+ .map(AccountProfilesConfig::getFailOnUnknown)
+ .orElse(failOnUnknown);
+ }
+
+ private Future> fetchProfiles(String accountId,
+ AllProfilesIds allProfilesIds,
+ long timeoutMillis) {
+
+ final Set requestProfilesIds = new HashSet<>(allProfilesIds.request());
+ final Set impProfilesIds = allProfilesIds.imps().stream()
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ final Timeout timeout = timeoutFactory.create(timeoutMillis);
+
+ return applicationSettings.getProfiles(accountId, requestProfilesIds, impProfilesIds, timeout);
+ }
+
+ private Future> emitMetrics(String accountId,
+ StoredDataResult fetchResult,
+ AuctionContext auctionContext,
+ boolean failOnUnknown) {
+
+ final List errors = fetchResult.getErrors();
+ if (!errors.isEmpty()) {
+ metrics.updateProfileMetric(MetricName.missing);
+
+ if (auctionContext.getDebugContext().isDebugEnabled()) {
+ metrics.updateAccountProfileMetric(accountId, MetricName.missing);
+ auctionContext.getDebugWarnings().addAll(errors);
+ }
+
+ if (failOnUnknown) {
+ return Future.failedFuture(new InvalidProfileException(errors));
+ }
+ }
+
+ return Future.succeededFuture(fetchResult);
+ }
+
+ private BidRequest applyRequestProfiles(List profilesIds,
+ Map idToRequestProfile,
+ BidRequest bidRequest,
+ boolean failOnUnknown) {
+
+ return !idToRequestProfile.isEmpty()
+ ? applyProfiles(profilesIds, idToRequestProfile, bidRequest, failOnUnknown)
+ : bidRequest;
+ }
+
+ private T applyProfiles(List profilesIds,
+ Map idToProfile,
+ T original,
+ boolean failOnUnknown) {
+
+ if (profilesIds.isEmpty()) {
+ return original;
+ }
+
+ ObjectNode result = mapper.mapper().valueToTree(original);
+ for (String profileId : profilesIds) {
+ try {
+ final Profile profile = idToProfile.get(profileId);
+ result = profile != null ? mergeProfile(result, profile) : result;
+ } catch (InvalidRequestException e) {
+ final String message = "Can't merge with profile %s: %s".formatted(profileId, e.getMessage());
+
+ metrics.updateProfileMetric(MetricName.invalid);
+ conditionalLogger.error(message, logSamplingRate);
+ if (failOnUnknown) {
+ throw new InvalidProfileException(message);
+ }
+ }
+ }
+
+ try {
+ return mapper.mapper().treeToValue(result, (Class) original.getClass());
+ } catch (JsonProcessingException e) {
+ throw new InvalidProfileException(e.getMessage());
+ }
+ }
+
+ private ObjectNode mergeProfile(ObjectNode original, Profile profile) {
+ return switch (profile.getMergePrecedence()) {
+ case REQUEST -> merge(original, profile.getBody());
+ case PROFILE -> merge(profile.getBody(), original);
+ };
+ }
+
+ private ObjectNode merge(JsonNode takePrecedence, JsonNode other) {
+ if (!takePrecedence.isObject() || !other.isObject()) {
+ throw new InvalidRequestException("One of the merge arguments is not an object.");
+ }
+
+ return (ObjectNode) jsonMerger.merge(takePrecedence, other);
+ }
+
+ private List applyImpsProfiles(List> profilesIds,
+ Map idToImpProfile,
+ List imps,
+ boolean failOnUnknown) {
+
+ if (idToImpProfile.isEmpty()) {
+ return imps;
+ }
+
+ final List updatedImps = new ArrayList<>(imps);
+ for (int i = 0; i < profilesIds.size(); i++) {
+ updatedImps.set(i, applyProfiles(
+ profilesIds.get(i),
+ idToImpProfile,
+ imps.get(i),
+ failOnUnknown));
+ }
+
+ return Collections.unmodifiableList(updatedImps);
+ }
+
+ private static BidRequest mergeResults(BidRequest bidRequest, List imps) {
+ return bidRequest.toBuilder().imp(imps).build();
+ }
+
+ private record AllProfilesIds(List request, List> imps) {
+
+ public boolean isEmpty() {
+ return request.isEmpty() && imps.stream().allMatch(List::isEmpty);
+ }
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/StoredRequestProcessor.java b/src/main/java/org/prebid/server/auction/externalortb/StoredRequestProcessor.java
similarity index 94%
rename from src/main/java/org/prebid/server/auction/StoredRequestProcessor.java
rename to src/main/java/org/prebid/server/auction/externalortb/StoredRequestProcessor.java
index 3729c5da661..a725c02b228 100644
--- a/src/main/java/org/prebid/server/auction/StoredRequestProcessor.java
+++ b/src/main/java/org/prebid/server/auction/externalortb/StoredRequestProcessor.java
@@ -1,4 +1,4 @@
-package org.prebid.server.auction;
+package org.prebid.server.auction.externalortb;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.iab.openrtb.request.BidRequest;
@@ -104,7 +104,7 @@ private Future processAuctionStoredRequest(String accountId
return Future.succeededFuture(AuctionStoredResult.of(false, bidRequest));
}
- final Future storedDataFuture =
+ final Future> storedDataFuture =
applicationSettings.getStoredData(accountId, requestIds, impIds, timeout(bidRequest))
.onSuccess(storedDataResult -> updateStoredResultMetrics(storedDataResult, requestIds, impIds));
@@ -121,7 +121,7 @@ public Future processAmpRequest(String accountId, String ampRequestI
}
private Future processAmpStoredRequest(String accountId, String ampRequestId, BidRequest bidRequest) {
- final Future ampStoredDataFuture = applicationSettings.getAmpStoredData(
+ final Future> ampStoredDataFuture = applicationSettings.getAmpStoredData(
accountId, Collections.singleton(ampRequestId), Collections.emptySet(), timeout(bidRequest))
.onSuccess(storedDataResult -> updateStoredResultMetrics(
storedDataResult, Collections.singleton(ampRequestId), Collections.emptySet()));
@@ -130,10 +130,10 @@ private Future processAmpStoredRequest(String accountId, String ampR
.map(this::generateBidRequestId);
}
- Future videoStoredDataResult(String accountId,
- List imps,
- List errors,
- Timeout timeout) {
+ public Future videoStoredDataResult(String accountId,
+ List imps,
+ List errors,
+ Timeout timeout) {
return videoStoredDataResultInternal(accountId, imps, errors, timeout)
.onFailure(cause -> updateInvalidStoredResultMetrics(accountId, cause))
@@ -172,7 +172,7 @@ private static Future stripToInvalidRequestException(Throwable cause) {
"Stored request processing failed: " + cause.getMessage()));
}
- private void updateStoredResultMetrics(StoredDataResult storedDataResult,
+ private void updateStoredResultMetrics(StoredDataResult storedDataResult,
Set requestIds,
Set impIds) {
@@ -192,7 +192,7 @@ private static BidRequest readBidRequest(String defaultBidRequestPath,
: null;
}
- private VideoStoredDataResult makeVideoStoredDataResult(StoredDataResult storedDataResult,
+ private VideoStoredDataResult makeVideoStoredDataResult(StoredDataResult storedDataResult,
Map storedIdToImpId,
List errors) {
@@ -232,7 +232,7 @@ private Video parseVideoFromImp(String storedJson) {
return null;
}
- private Future storedRequestsToBidRequest(Future storedDataFuture,
+ private Future storedRequestsToBidRequest(Future> storedDataFuture,
BidRequest bidRequest,
String storedBidRequestId,
Map impsToStoredRequestId) {
@@ -253,7 +253,7 @@ private Future storedRequestsToBidRequest(Future s
private BidRequest mergeBidRequestAndImps(BidRequest bidRequest,
String storedRequestId,
Map impToStoredId,
- StoredDataResult storedDataResult) {
+ StoredDataResult storedDataResult) {
final BidRequest mergedWithStoredRequest = mergeBidRequest(bidRequest, storedRequestId, storedDataResult);
@@ -272,7 +272,7 @@ private BidRequest mergeDefaultRequest(BidRequest bidRequest) {
*/
private BidRequest mergeBidRequest(BidRequest originalRequest,
String storedRequestId,
- StoredDataResult storedDataResult) {
+ StoredDataResult storedDataResult) {
final String storedRequest = storedDataResult.getStoredIdToRequest().get(storedRequestId);
return StringUtils.isNotBlank(storedRequestId)
@@ -286,7 +286,7 @@ private BidRequest mergeBidRequest(BidRequest originalRequest,
*/
private BidRequest mergeImps(BidRequest bidRequest,
Map impToStoredId,
- StoredDataResult storedDataResult) {
+ StoredDataResult storedDataResult) {
if (impToStoredId.isEmpty()) {
return bidRequest;
diff --git a/src/main/java/org/prebid/server/auction/StoredResponseProcessor.java b/src/main/java/org/prebid/server/auction/externalortb/StoredResponseProcessor.java
similarity index 97%
rename from src/main/java/org/prebid/server/auction/StoredResponseProcessor.java
rename to src/main/java/org/prebid/server/auction/externalortb/StoredResponseProcessor.java
index 2257320ef69..b44939a38e6 100644
--- a/src/main/java/org/prebid/server/auction/StoredResponseProcessor.java
+++ b/src/main/java/org/prebid/server/auction/externalortb/StoredResponseProcessor.java
@@ -1,4 +1,4 @@
-package org.prebid.server.auction;
+package org.prebid.server.auction.externalortb;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -67,7 +67,7 @@ public StoredResponseProcessor(ApplicationSettings applicationSettings,
this.mapper = Objects.requireNonNull(mapper);
}
- Future getStoredResponseResult(List imps, Timeout timeout) {
+ public Future getStoredResponseResult(List imps, Timeout timeout) {
final Map impExtPrebids = getImpsExtPrebid(imps);
final Map impIdsToStoredResponses = getAuctionStoredResponses(impExtPrebids);
final List requiredRequestImps = excludeStoredAuctionResponseImps(imps, impIdsToStoredResponses);
@@ -96,7 +96,7 @@ Future getStoredResponseResult(List imps, Timeout tim
impToBidderToStoredBidResponseId)));
}
- Future getStoredResponseResult(String storedId, Timeout timeout) {
+ public Future getStoredResponseResult(String storedId, Timeout timeout) {
return applicationSettings.getStoredResponses(Collections.singleton(storedId), timeout)
.recover(exception -> Future.failedFuture(new InvalidRequestException(
"Stored response fetching failed with reason: " + exception.getMessage())))
@@ -345,10 +345,10 @@ private static BidderBid resolveBidImpId(BidderBid bidderBid, String impId) {
.build();
}
- List mergeWithBidderResponses(List auctionParticipations,
- List storedAuctionResponses,
- List imps,
- Map bidRejectionTrackers) {
+ public List mergeWithBidderResponses(List auctionParticipations,
+ List storedAuctionResponses,
+ List imps,
+ Map bidRejectionTrackers) {
if (CollectionUtils.isEmpty(storedAuctionResponses)) {
return auctionParticipations;
diff --git a/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java
index b8fcd6d6301..e1c5e4240ce 100644
--- a/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java
+++ b/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java
@@ -23,7 +23,8 @@
import org.prebid.server.auction.ImplicitParametersExtractor;
import org.prebid.server.auction.OrtbTypesResolver;
import org.prebid.server.auction.PriceGranularity;
-import org.prebid.server.auction.StoredRequestProcessor;
+import org.prebid.server.auction.externalortb.ProfilesProcessor;
+import org.prebid.server.auction.externalortb.StoredRequestProcessor;
import org.prebid.server.auction.gpp.AmpGppService;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.ConsentType;
@@ -90,6 +91,7 @@ public class AmpRequestFactory {
private final Ortb2RequestFactory ortb2RequestFactory;
private final StoredRequestProcessor storedRequestProcessor;
+ private final ProfilesProcessor profilesProcessor;
private final BidRequestOrtbVersionConversionManager ortbVersionConversionManager;
private final AmpGppService gppService;
private final OrtbTypesResolver ortbTypesResolver;
@@ -103,6 +105,7 @@ public class AmpRequestFactory {
public AmpRequestFactory(Ortb2RequestFactory ortb2RequestFactory,
StoredRequestProcessor storedRequestProcessor,
+ ProfilesProcessor profilesProcessor,
BidRequestOrtbVersionConversionManager ortbVersionConversionManager,
AmpGppService gppService,
OrtbTypesResolver ortbTypesResolver,
@@ -116,6 +119,7 @@ public AmpRequestFactory(Ortb2RequestFactory ortb2RequestFactory,
this.ortb2RequestFactory = Objects.requireNonNull(ortb2RequestFactory);
this.storedRequestProcessor = Objects.requireNonNull(storedRequestProcessor);
+ this.profilesProcessor = Objects.requireNonNull(profilesProcessor);
this.ortbVersionConversionManager = Objects.requireNonNull(ortbVersionConversionManager);
this.gppService = Objects.requireNonNull(gppService);
this.ortbTypesResolver = Objects.requireNonNull(ortbTypesResolver);
@@ -407,6 +411,7 @@ private Future updateBidRequest(AuctionContext auctionContext) {
final HttpRequestContext httpRequest = auctionContext.getHttpRequest();
return storedRequestProcessor.processAmpRequest(accountId, storedRequestId, receivedBidRequest)
+ .compose(bidRequest -> profilesProcessor.process(auctionContext, bidRequest))
.map(ortbVersionConversionManager::convertToAuctionSupportedVersion)
.map(bidRequest -> gppService.updateBidRequest(bidRequest, auctionContext))
.map(bidRequest -> validateStoredBidRequest(storedRequestId, bidRequest))
diff --git a/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java
index 13df5f4df82..d873af3e696 100644
--- a/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java
+++ b/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java
@@ -11,7 +11,8 @@
import org.prebid.server.auction.ImplicitParametersExtractor;
import org.prebid.server.auction.InterstitialProcessor;
import org.prebid.server.auction.OrtbTypesResolver;
-import org.prebid.server.auction.StoredRequestProcessor;
+import org.prebid.server.auction.externalortb.ProfilesProcessor;
+import org.prebid.server.auction.externalortb.StoredRequestProcessor;
import org.prebid.server.auction.gpp.AuctionGppService;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.AuctionStoredResult;
@@ -40,6 +41,7 @@ public class AuctionRequestFactory {
private final long maxRequestSize;
private final Ortb2RequestFactory ortb2RequestFactory;
private final StoredRequestProcessor storedRequestProcessor;
+ private final ProfilesProcessor profilesProcessor;
private final BidRequestOrtbVersionConversionManager ortbVersionConversionManager;
private final AuctionGppService gppService;
private final CookieDeprecationService cookieDeprecationService;
@@ -58,6 +60,7 @@ public class AuctionRequestFactory {
public AuctionRequestFactory(long maxRequestSize,
Ortb2RequestFactory ortb2RequestFactory,
StoredRequestProcessor storedRequestProcessor,
+ ProfilesProcessor profilesProcessor,
BidRequestOrtbVersionConversionManager ortbVersionConversionManager,
AuctionGppService gppService,
CookieDeprecationService cookieDeprecationService,
@@ -74,6 +77,7 @@ public AuctionRequestFactory(long maxRequestSize,
this.maxRequestSize = maxRequestSize;
this.ortb2RequestFactory = Objects.requireNonNull(ortb2RequestFactory);
this.storedRequestProcessor = Objects.requireNonNull(storedRequestProcessor);
+ this.profilesProcessor = Objects.requireNonNull(profilesProcessor);
this.ortbVersionConversionManager = Objects.requireNonNull(ortbVersionConversionManager);
this.gppService = Objects.requireNonNull(gppService);
this.cookieDeprecationService = Objects.requireNonNull(cookieDeprecationService);
@@ -252,7 +256,7 @@ private Future updateBidRequest(AuctionStoredResult auctionStoredRes
final boolean hasStoredBidRequest = auctionStoredResult.hasStoredBidRequest();
- return Future.succeededFuture(auctionStoredResult.bidRequest())
+ return profilesProcessor.process(auctionContext, auctionStoredResult.bidRequest())
.map(ortbVersionConversionManager::convertToAuctionSupportedVersion)
.map(bidRequest -> gppService.updateBidRequest(bidRequest, auctionContext))
.map(bidRequest -> paramsResolver.resolve(bidRequest, auctionContext, ENDPOINT, hasStoredBidRequest))
diff --git a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java
index d50b35d3717..ac9619f6b83 100644
--- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java
+++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java
@@ -23,9 +23,11 @@
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
import org.prebid.server.activity.infrastructure.creator.ActivityInfrastructureCreator;
import org.prebid.server.auction.IpAddressHelper;
-import org.prebid.server.auction.StoredRequestProcessor;
import org.prebid.server.auction.TimeoutResolver;
+import org.prebid.server.auction.externalortb.ProfilesProcessor;
+import org.prebid.server.auction.externalortb.StoredRequestProcessor;
import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.AuctionStoredResult;
import org.prebid.server.auction.model.IpAddress;
import org.prebid.server.auction.model.TimeoutContext;
import org.prebid.server.auction.model.debug.DebugContext;
@@ -101,6 +103,7 @@ public class Ortb2RequestFactory {
private final TimeoutResolver timeoutResolver;
private final TimeoutFactory timeoutFactory;
private final StoredRequestProcessor storedRequestProcessor;
+ private final ProfilesProcessor profilesProcessor;
private final ApplicationSettings applicationSettings;
private final IpAddressHelper ipAddressHelper;
private final HookStageExecutor hookStageExecutor;
@@ -116,6 +119,7 @@ public Ortb2RequestFactory(int timeoutAdjustmentFactor,
TimeoutResolver timeoutResolver,
TimeoutFactory timeoutFactory,
StoredRequestProcessor storedRequestProcessor,
+ ProfilesProcessor profilesProcessor,
ApplicationSettings applicationSettings,
IpAddressHelper ipAddressHelper,
HookStageExecutor hookStageExecutor,
@@ -135,6 +139,7 @@ public Ortb2RequestFactory(int timeoutAdjustmentFactor,
this.timeoutResolver = Objects.requireNonNull(timeoutResolver);
this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
this.storedRequestProcessor = Objects.requireNonNull(storedRequestProcessor);
+ this.profilesProcessor = Objects.requireNonNull(profilesProcessor);
this.applicationSettings = Objects.requireNonNull(applicationSettings);
this.ipAddressHelper = Objects.requireNonNull(ipAddressHelper);
this.hookStageExecutor = Objects.requireNonNull(hookStageExecutor);
@@ -181,7 +186,7 @@ private Future fetchAccount(AuctionContext auctionContext, boolean isLo
final Timeout timeout = auctionContext.getTimeoutContext().getTimeout();
final HttpRequestContext httpRequest = auctionContext.getHttpRequest();
- return findAccountIdFrom(bidRequest, isLookupStoredRequest)
+ return findAccountIdFrom(auctionContext, bidRequest, isLookupStoredRequest)
.map(this::validateIfAccountBlocklisted)
.compose(accountId -> loadAccount(timeout, httpRequest, accountId));
}
@@ -468,12 +473,53 @@ private Timeout timeout(BidRequest bidRequest, long startTime) {
return timeoutFactory.create(startTime, timeout);
}
- private Future findAccountIdFrom(BidRequest bidRequest, boolean isLookupStoredRequest) {
- final String accountId = accountIdFrom(bidRequest);
- return StringUtils.isNotBlank(accountId) || !isLookupStoredRequest
- ? Future.succeededFuture(accountId)
- : storedRequestProcessor.processAuctionRequest(accountId, bidRequest)
- .map(storedAuctionResult -> accountIdFrom(storedAuctionResult.bidRequest()));
+ private Future findAccountIdFrom(AuctionContext auctionContext,
+ BidRequest bidRequest,
+ boolean isLookupStoredRequest) {
+
+ final String accountId = accountIdFromBidRequest(bidRequest);
+ if (StringUtils.isNotBlank(accountId) || !isLookupStoredRequest) {
+ return Future.succeededFuture(accountId);
+ }
+
+ return accountIdFromStoredRequest(bidRequest)
+ .compose(id -> StringUtils.isBlank(id)
+ ? accountIdFromProfiles(auctionContext, bidRequest)
+ : Future.succeededFuture(id));
+ }
+
+ private String accountIdFromBidRequest(BidRequest bidRequest) {
+ final App app = bidRequest.getApp();
+ final Publisher appPublisher = app != null ? app.getPublisher() : null;
+ final Site site = bidRequest.getSite();
+ final Publisher sitePublisher = site != null ? site.getPublisher() : null;
+ final Dooh dooh = bidRequest.getDooh();
+ final Publisher doohPublisher = dooh != null ? dooh.getPublisher() : null;
+
+ final Publisher publisher = ObjectUtils.firstNonNull(appPublisher, doohPublisher, sitePublisher);
+ final String publisherId = publisher != null ? resolvePublisherId(publisher) : null;
+ return ObjectUtils.defaultIfNull(publisherId, StringUtils.EMPTY);
+ }
+
+ private String resolvePublisherId(Publisher publisher) {
+ final String parentAccountId = parentAccountIdFromExtPublisher(publisher.getExt());
+ return ObjectUtils.defaultIfNull(parentAccountId, publisher.getId());
+ }
+
+ private String parentAccountIdFromExtPublisher(ExtPublisher extPublisher) {
+ final ExtPublisherPrebid extPublisherPrebid = extPublisher != null ? extPublisher.getPrebid() : null;
+ return extPublisherPrebid != null ? StringUtils.stripToNull(extPublisherPrebid.getParentAccount()) : null;
+ }
+
+ private Future accountIdFromStoredRequest(BidRequest bidRequest) {
+ return storedRequestProcessor.processAuctionRequest(StringUtils.EMPTY, bidRequest)
+ .map(AuctionStoredResult::bidRequest)
+ .map(this::accountIdFromBidRequest);
+ }
+
+ private Future accountIdFromProfiles(AuctionContext auctionContext, BidRequest bidRequest) {
+ return profilesProcessor.process(auctionContext, bidRequest)
+ .map(this::accountIdFromBidRequest);
}
private String validateIfAccountBlocklisted(String accountId) {
@@ -508,40 +554,6 @@ private Future ensureAccountActive(Account account) {
: Future.succeededFuture(account);
}
- /**
- * Extracts publisher id either from {@link BidRequest}.app.publisher or {@link BidRequest}.site.publisher.
- * If neither is present returns empty string.
- */
- private String accountIdFrom(BidRequest bidRequest) {
- final App app = bidRequest.getApp();
- final Publisher appPublisher = app != null ? app.getPublisher() : null;
- final Site site = bidRequest.getSite();
- final Publisher sitePublisher = site != null ? site.getPublisher() : null;
- final Dooh dooh = bidRequest.getDooh();
- final Publisher doohPublisher = dooh != null ? dooh.getPublisher() : null;
-
- final Publisher publisher = ObjectUtils.firstNonNull(appPublisher, doohPublisher, sitePublisher);
- final String publisherId = publisher != null ? resolvePublisherId(publisher) : null;
- return ObjectUtils.defaultIfNull(publisherId, StringUtils.EMPTY);
- }
-
- /**
- * Resolves what value should be used as a publisher id - either taken from publisher.ext.parentAccount
- * or publisher.id in this respective priority.
- */
- private String resolvePublisherId(Publisher publisher) {
- final String parentAccountId = parentAccountIdFromExtPublisher(publisher.getExt());
- return ObjectUtils.defaultIfNull(parentAccountId, publisher.getId());
- }
-
- /**
- * Parses publisher.ext and returns parentAccount value from it. Returns null if any parsing error occurs.
- */
- private String parentAccountIdFromExtPublisher(ExtPublisher extPublisher) {
- final ExtPublisherPrebid extPublisherPrebid = extPublisher != null ? extPublisher.getPrebid() : null;
- return extPublisherPrebid != null ? StringUtils.stripToNull(extPublisherPrebid.getParentAccount()) : null;
- }
-
private Future wrapFailure(Throwable exception, String accountId, HttpRequestContext httpRequest) {
if (exception instanceof UnauthorizedAccountException) {
return Future.failedFuture(exception);
diff --git a/src/main/java/org/prebid/server/exception/InvalidProfileException.java b/src/main/java/org/prebid/server/exception/InvalidProfileException.java
new file mode 100644
index 00000000000..d8a3ebc3aae
--- /dev/null
+++ b/src/main/java/org/prebid/server/exception/InvalidProfileException.java
@@ -0,0 +1,14 @@
+package org.prebid.server.exception;
+
+import java.util.List;
+
+public class InvalidProfileException extends RuntimeException {
+
+ public InvalidProfileException(String message) {
+ super(message);
+ }
+
+ public InvalidProfileException(List messages) {
+ super(String.join("\n", messages));
+ }
+}
diff --git a/src/main/java/org/prebid/server/handler/admin/SettingsCacheNotificationHandler.java b/src/main/java/org/prebid/server/handler/admin/SettingsCacheNotificationHandler.java
index 9b89009a962..fd652ebb8ff 100644
--- a/src/main/java/org/prebid/server/handler/admin/SettingsCacheNotificationHandler.java
+++ b/src/main/java/org/prebid/server/handler/admin/SettingsCacheNotificationHandler.java
@@ -19,15 +19,17 @@
*/
public class SettingsCacheNotificationHandler implements Handler {
- private final CacheNotificationListener cacheNotificationListener;
- private final JacksonMapper mapper;
private final String endpoint;
+ private final CacheNotificationListener cacheNotificationListener;
+ private final JacksonMapper mapper;
+
+ public SettingsCacheNotificationHandler(String endpoint,
+ CacheNotificationListener cacheNotificationListener,
+ JacksonMapper mapper) {
- public SettingsCacheNotificationHandler(CacheNotificationListener cacheNotificationListener, JacksonMapper mapper,
- String endpoint) {
+ this.endpoint = Objects.requireNonNull(endpoint);
this.cacheNotificationListener = Objects.requireNonNull(cacheNotificationListener);
this.mapper = Objects.requireNonNull(mapper);
- this.endpoint = Objects.requireNonNull(endpoint);
}
@Override
@@ -94,14 +96,18 @@ private void doFail(RoutingContext routingContext) {
}
private void respondWithBadRequest(RoutingContext routingContext, String body) {
- HttpUtil.executeSafely(routingContext, endpoint,
+ HttpUtil.executeSafely(
+ routingContext,
+ endpoint,
response -> response
.setStatusCode(HttpResponseStatus.BAD_REQUEST.code())
.end(body));
}
private void respondWith(RoutingContext routingContext, HttpResponseStatus status) {
- HttpUtil.executeSafely(routingContext, endpoint,
+ HttpUtil.executeSafely(
+ routingContext,
+ endpoint,
response -> response
.setStatusCode(status.code())
.end());
diff --git a/src/main/java/org/prebid/server/metric/AccountMetrics.java b/src/main/java/org/prebid/server/metric/AccountMetrics.java
index f2cdb29f328..6bd479b7ea3 100644
--- a/src/main/java/org/prebid/server/metric/AccountMetrics.java
+++ b/src/main/java/org/prebid/server/metric/AccountMetrics.java
@@ -23,6 +23,7 @@ class AccountMetrics extends UpdatableMetrics {
private final ResponseMetrics responseMetrics;
private final HooksMetrics hooksMetrics;
private final ActivitiesMetrics activitiesMetrics;
+ private final ProfileMetrics profileMetrics;
AccountMetrics(MetricRegistry metricRegistry, CounterType counterType, String account) {
super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType),
@@ -36,6 +37,7 @@ class AccountMetrics extends UpdatableMetrics {
responseMetrics = new ResponseMetrics(metricRegistry, counterType, createPrefix(account));
hooksMetrics = new HooksMetrics(metricRegistry, counterType, createPrefix(account));
activitiesMetrics = new ActivitiesMetrics(metricRegistry, counterType, createPrefix(account));
+ profileMetrics = new ProfileMetrics(metricRegistry, counterType, createPrefix(account));
}
private static String createPrefix(String account) {
@@ -73,4 +75,8 @@ HooksMetrics hooks() {
ActivitiesMetrics activities() {
return activitiesMetrics;
}
+
+ ProfileMetrics profiles() {
+ return profileMetrics;
+ }
}
diff --git a/src/main/java/org/prebid/server/metric/MetricName.java b/src/main/java/org/prebid/server/metric/MetricName.java
index ab32c446226..4d562aa4122 100644
--- a/src/main/java/org/prebid/server/metric/MetricName.java
+++ b/src/main/java/org/prebid/server/metric/MetricName.java
@@ -156,7 +156,10 @@ public enum MetricName {
// activity
disallowed_count("disallowed.count"),
- processed_rules_count("processedrules.count");
+ processed_rules_count("processedrules.count"),
+
+ // profiles
+ limit_exceeded;
private final String name;
diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java
index 9010a7018c5..c10d3301391 100644
--- a/src/main/java/org/prebid/server/metric/Metrics.java
+++ b/src/main/java/org/prebid/server/metric/Metrics.java
@@ -59,6 +59,7 @@ public class Metrics extends UpdatableMetrics {
private final CurrencyRatesMetrics currencyRatesMetrics;
private final Map settingsCacheMetrics;
private final HooksMetrics hooksMetrics;
+ private final ProfileMetrics profileMetrics;
public Metrics(MetricRegistry metricRegistry,
CounterType counterType,
@@ -97,6 +98,7 @@ public Metrics(MetricRegistry metricRegistry,
currencyRatesMetrics = new CurrencyRatesMetrics(metricRegistry, counterType);
settingsCacheMetrics = new HashMap<>();
hooksMetrics = new HooksMetrics(metricRegistry, counterType);
+ profileMetrics = new ProfileMetrics(metricRegistry, counterType);
}
RequestsMetrics requests() {
@@ -727,6 +729,14 @@ public void updateAccountActivityProcessedRulesCount(String account) {
forAccount(account).activities().incCounter(MetricName.processed_rules_count);
}
+ public void updateProfileMetric(MetricName metricName) {
+ profileMetrics.incCounter(metricName);
+ }
+
+ public void updateAccountProfileMetric(String account, MetricName metricName) {
+ forAccount(account).profiles().incCounter(metricName);
+ }
+
private static class HookMetricMapper {
private static final EnumMap STATUS_TO_METRIC =
diff --git a/src/main/java/org/prebid/server/metric/ProfileMetrics.java b/src/main/java/org/prebid/server/metric/ProfileMetrics.java
new file mode 100644
index 00000000000..3601938b1b1
--- /dev/null
+++ b/src/main/java/org/prebid/server/metric/ProfileMetrics.java
@@ -0,0 +1,28 @@
+package org.prebid.server.metric;
+
+import com.codahale.metrics.MetricRegistry;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+class ProfileMetrics extends UpdatableMetrics {
+
+ ProfileMetrics(MetricRegistry metricRegistry, CounterType counterType) {
+ super(Objects.requireNonNull(metricRegistry), Objects.requireNonNull(counterType), nameCreator());
+ }
+
+ ProfileMetrics(MetricRegistry metricRegistry, CounterType counterType, String prefix) {
+ super(
+ Objects.requireNonNull(metricRegistry),
+ Objects.requireNonNull(counterType),
+ nameCreator(Objects.requireNonNull(prefix)));
+ }
+
+ private static Function nameCreator() {
+ return "profiles.%s"::formatted;
+ }
+
+ private static Function nameCreator(String prefix) {
+ return metricName -> "%s.profiles.%s".formatted(prefix, metricName);
+ }
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtImpPrebid.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtImpPrebid.java
index 0d626029c9f..33ac57c13c0 100644
--- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtImpPrebid.java
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtImpPrebid.java
@@ -20,6 +20,11 @@ public class ExtImpPrebid {
*/
ExtStoredRequest storedrequest;
+ /**
+ * Defines the contract for bidrequest.imp[i].ext.prebid.profiles
+ */
+ List profiles;
+
/**
* Defines the contract for bidrequest.imp[i].ext.prebid.storedauctionresponse
*/
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebid.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebid.java
index 98754a459a5..25dffdc486c 100644
--- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebid.java
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebid.java
@@ -76,6 +76,11 @@ public class ExtRequestPrebid {
*/
ExtStoredRequest storedrequest;
+ /**
+ * Defines the contract for bidrequest.ext.prebid.profiles
+ */
+ List profiles;
+
/**
* Defines the contract for bidrequest.ext.prebid.storedauctionresponse
*/
diff --git a/src/main/java/org/prebid/server/settings/ApplicationSettings.java b/src/main/java/org/prebid/server/settings/ApplicationSettings.java
index 7a6582ccd42..2f3ca855668 100644
--- a/src/main/java/org/prebid/server/settings/ApplicationSettings.java
+++ b/src/main/java/org/prebid/server/settings/ApplicationSettings.java
@@ -3,55 +3,38 @@
import io.vertx.core.Future;
import org.prebid.server.execution.timeout.Timeout;
import org.prebid.server.settings.model.Account;
+import org.prebid.server.settings.model.Profile;
import org.prebid.server.settings.model.StoredDataResult;
import org.prebid.server.settings.model.StoredResponseDataResult;
import java.util.Map;
import java.util.Set;
-/**
- * Defines the contract of getting application settings (account, stored ad unit configurations and
- * stored requests and imps) from the source.
- *
- * @see FileApplicationSettings
- * @see DatabaseApplicationSettings
- * @see HttpApplicationSettings
- * @see CachingApplicationSettings
- * @see CompositeApplicationSettings
- */
public interface ApplicationSettings {
- /**
- * Returns {@link Account} for the given account ID.
- */
Future getAccountById(String accountId, Timeout timeout);
- /**
- * Fetches stored requests and imps by IDs.
- */
- Future getStoredData(String accountId, Set requestIds, Set impIds,
- Timeout timeout);
-
- /**
- * Fetches AMP stored requests and imps by IDs.
- */
- Future getAmpStoredData(String accountId, Set requestIds, Set impIds,
- Timeout timeout);
-
- /**
- * Fetches Video stored requests and imps by IDs.
- */
- Future getVideoStoredData(String accountId, Set requestIds, Set impIds,
- Timeout timeout);
-
- /**
- * Fetches stored response by IDs.
- */
- Future getStoredResponses(Set responseIds, Timeout timeout);
+ Future> getStoredData(String accountId,
+ Set requestIds,
+ Set impIds,
+ Timeout timeout);
+
+ Future> getAmpStoredData(String accountId,
+ Set requestIds,
+ Set impIds,
+ Timeout timeout);
+
+ Future> getVideoStoredData(String accountId,
+ Set requestIds,
+ Set impIds,
+ Timeout timeout);
+ Future> getProfiles(String accountId,
+ Set requestIds,
+ Set impIds,
+ Timeout timeout);
+
+ Future getStoredResponses(Set responseIds, Timeout timeout);
- /**
- * Fetches video category
- */
Future