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
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/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/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/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/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