Skip to content

Commit a78e783

Browse files
committed
Update cookie sync logic to allow duplicate cookie family name if glvid and usersync config are identical
1 parent 17d7aed commit a78e783

5 files changed

Lines changed: 278 additions & 85 deletions

File tree

src/main/java/org/prebid/server/bidder/BidderCatalog.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public Optional<Usersyncer> usersyncerByName(String name) {
187187
.map(BidderInstanceDeps::getUsersyncer);
188188
}
189189

190-
private Optional<String> aliasOf(String bidder) {
190+
public Optional<String> aliasOf(String bidder) {
191191
return Optional.ofNullable(bidder)
192192
.map(bidderDepsMap::get)
193193
.map(BidderInstanceDeps::getBidderInfo)

src/main/java/org/prebid/server/cookie/CookieSyncService.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import java.util.Collection;
5050
import java.util.Collections;
5151
import java.util.HashSet;
52-
import java.util.Iterator;
5352
import java.util.LinkedHashSet;
5453
import java.util.List;
5554
import java.util.Map;
@@ -382,14 +381,16 @@ private Set<String> biddersToSync(CookieSyncContext cookieSyncContext) {
382381

383382
final Set<String> cookieFamiliesToSync = new HashSet<>(); // multiple bidders may have same cookie families
384383
final Set<String> biddersToSync = new LinkedHashSet<>();
385-
final Iterator<String> biddersIterator = allowedBiddersByPriority.iterator();
386384

387-
while (cookieFamiliesToSync.size() < cookieSyncContext.getLimit() && biddersIterator.hasNext()) {
388-
final String bidder = biddersIterator.next();
385+
for (String bidder : allowedBiddersByPriority) {
389386
final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElseThrow();
390387

391-
cookieFamiliesToSync.add(cookieFamilyName);
392-
biddersToSync.add(bidder);
388+
if (cookieFamiliesToSync.size() < cookieSyncContext.getLimit()) {
389+
cookieFamiliesToSync.add(cookieFamilyName);
390+
biddersToSync.add(bidder);
391+
} else if (cookieFamiliesToSync.contains(cookieFamilyName)) {
392+
biddersToSync.add(bidder);
393+
}
393394
}
394395

395396
return biddersToSync;

src/main/java/org/prebid/server/cookie/PrioritizedCoopSyncProvider.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,23 @@
1111
import java.util.HashSet;
1212
import java.util.LinkedHashSet;
1313
import java.util.List;
14-
import java.util.Map;
1514
import java.util.Objects;
1615
import java.util.Optional;
1716
import java.util.Set;
18-
import java.util.function.Function;
1917
import java.util.stream.Collectors;
2018

2119
public class PrioritizedCoopSyncProvider {
2220

2321
private static final Logger logger = LoggerFactory.getLogger(PrioritizedCoopSyncProvider.class);
2422

2523
private final Set<String> prioritizedBidders;
26-
private final Map<String, String> prioritizedCookieFamilyNameToBidderName;
24+
private final Set<String> prioritizedCookieFamilyNames;
2725

2826
public PrioritizedCoopSyncProvider(Set<String> bidders, BidderCatalog bidderCatalog) {
2927
this.prioritizedBidders = validCoopSyncBidders(Objects.requireNonNull(bidders), bidderCatalog);
30-
this.prioritizedCookieFamilyNameToBidderName = prioritizedBidders.stream()
31-
.collect(Collectors.toMap(
32-
bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow(),
33-
Function.identity()));
28+
this.prioritizedCookieFamilyNames = prioritizedBidders.stream()
29+
.map(bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow())
30+
.collect(Collectors.toSet());
3431
}
3532

3633
private static Set<String> validCoopSyncBidders(Set<String> bidders, BidderCatalog bidderCatalog) {
@@ -71,7 +68,6 @@ public Set<String> prioritizedBidders(Account account) {
7168
}
7269

7370
public boolean isPrioritizedFamily(String cookieFamilyName) {
74-
final String bidder = prioritizedCookieFamilyNameToBidderName.get(cookieFamilyName);
75-
return prioritizedBidders.contains(bidder);
71+
return prioritizedCookieFamilyNames.contains(cookieFamilyName);
7672
}
7773
}

src/main/java/org/prebid/server/handler/SetuidHandler.java

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
import io.vertx.core.http.HttpServerRequest;
1111
import io.vertx.core.http.HttpServerResponse;
1212
import io.vertx.ext.web.RoutingContext;
13+
import lombok.Value;
1314
import org.apache.commons.lang3.BooleanUtils;
1415
import org.apache.commons.lang3.ObjectUtils;
1516
import org.apache.commons.lang3.StringUtils;
16-
import org.apache.commons.lang3.tuple.Pair;
1717
import org.prebid.server.activity.Activity;
1818
import org.prebid.server.activity.ComponentType;
1919
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
@@ -54,19 +54,16 @@
5454
import org.prebid.server.settings.model.AccountGdprConfig;
5555
import org.prebid.server.settings.model.AccountPrivacyConfig;
5656
import org.prebid.server.util.HttpUtil;
57-
import org.prebid.server.util.StreamUtil;
5857
import org.prebid.server.vertx.verticles.server.HttpEndpoint;
5958
import org.prebid.server.vertx.verticles.server.application.ApplicationResource;
6059

6160
import java.util.Collections;
62-
import java.util.Comparator;
6361
import java.util.List;
6462
import java.util.Map;
6563
import java.util.Objects;
6664
import java.util.Optional;
65+
import java.util.Set;
6766
import java.util.function.Consumer;
68-
import java.util.function.Function;
69-
import java.util.function.Predicate;
7067
import java.util.stream.Collectors;
7168

7269
public class SetuidHandler implements ApplicationResource {
@@ -89,7 +86,7 @@ public class SetuidHandler implements ApplicationResource {
8986
private final AnalyticsReporterDelegator analyticsDelegator;
9087
private final Metrics metrics;
9188
private final TimeoutFactory timeoutFactory;
92-
private final Map<String, Pair<String, UsersyncMethodType>> cookieNameToBidderAndSyncType;
89+
private final Map<String, UsersyncConfig> cookieFamilyNameToUsersyncConfig;
9390

9491
public SetuidHandler(long defaultTimeout,
9592
UidsCookieService uidsCookieService,
@@ -113,49 +110,58 @@ public SetuidHandler(long defaultTimeout,
113110
this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator);
114111
this.metrics = Objects.requireNonNull(metrics);
115112
this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
116-
this.cookieNameToBidderAndSyncType = collectUsersyncers(bidderCatalog);
113+
this.cookieFamilyNameToUsersyncConfig = collectUserSyncConfigs(bidderCatalog);
117114
}
118115

119-
private static Map<String, Pair<String, UsersyncMethodType>> collectUsersyncers(BidderCatalog bidderCatalog) {
120-
validateUsersyncersDuplicates(bidderCatalog);
121-
116+
private static Map<String, UsersyncConfig> collectUserSyncConfigs(BidderCatalog bidderCatalog) {
122117
return bidderCatalog.usersyncReadyBidders().stream()
123-
.sorted(Comparator.comparing(bidderName -> BooleanUtils.toInteger(bidderCatalog.isAlias(bidderName))))
124-
.filter(StreamUtil.distinctBy(bidderCatalog::cookieFamilyName))
125-
.map(bidderName -> bidderCatalog.usersyncerByName(bidderName)
126-
.map(usersyncer -> Pair.of(bidderName, usersyncer)))
127-
.flatMap(Optional::stream)
128-
.collect(Collectors.toMap(
129-
pair -> pair.getRight().getCookieFamilyName(),
130-
pair -> Pair.of(pair.getLeft(), preferredUserSyncType(pair.getRight()))));
118+
.collect(Collectors.groupingBy(
119+
bidderName -> bidderCatalog.cookieFamilyName(bidderName).orElseThrow(),
120+
Collectors.collectingAndThen(Collectors.toSet(), bidderNames ->
121+
usersyncConfigForSingleCookieFamilyName(bidderNames, bidderCatalog))));
131122
}
132123

133-
private static void validateUsersyncersDuplicates(BidderCatalog bidderCatalog) {
134-
final List<String> duplicatedCookieFamilyNames = bidderCatalog.usersyncReadyBidders().stream()
135-
.filter(bidderName -> !isAliasWithRootCookieFamilyName(bidderCatalog, bidderName))
124+
private static UsersyncConfig usersyncConfigForSingleCookieFamilyName(Set<String> bidderNames,
125+
BidderCatalog bidderCatalog) {
126+
127+
final Set<String> biddersWithoutAliases = bidderNames.stream()
128+
.filter(bidderName -> bidderCatalog.aliasOf(bidderName).filter(bidderNames::contains).isEmpty())
129+
.collect(Collectors.toSet());
130+
131+
validateBiddersHaveTheSameVendorId(biddersWithoutAliases, bidderCatalog);
132+
validateBiddersHaveTheSameUsersyncConfig(biddersWithoutAliases, bidderCatalog);
133+
134+
final Usersyncer usersyncer = biddersWithoutAliases.stream()
136135
.map(bidderCatalog::usersyncerByName)
137-
.flatMap(Optional::stream)
138-
.map(Usersyncer::getCookieFamilyName)
139-
.filter(Predicate.not(StreamUtil.distinctBy(Function.identity())))
140-
.distinct()
141-
.sorted()
142-
.toList();
143-
144-
if (!duplicatedCookieFamilyNames.isEmpty()) {
136+
.map(Optional::orElseThrow)
137+
.findAny().orElseThrow();
138+
139+
return UsersyncConfig.of(biddersWithoutAliases, preferredUserSyncType(usersyncer));
140+
}
141+
142+
private static void validateBiddersHaveTheSameVendorId(Set<String> bidders, BidderCatalog bidderCatalog) {
143+
final Set<Integer> vendorIds = bidders.stream()
144+
.map(bidderCatalog::vendorIdByName)
145+
.collect(Collectors.toSet());
146+
147+
if (vendorIds.size() > 1) {
145148
throw new IllegalArgumentException(
146-
"Duplicated \"cookie-family-name\" found, values: "
147-
+ String.join(", ", duplicatedCookieFamilyNames));
149+
"Found bidders with the same cookie family name but different vendor ids. "
150+
+ "Bidders: %s. Vendor ids: %s".formatted(bidders, vendorIds));
148151
}
149152
}
150153

151-
private static boolean isAliasWithRootCookieFamilyName(BidderCatalog bidderCatalog, String bidder) {
152-
final String bidderCookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElse(StringUtils.EMPTY);
153-
final String parentCookieFamilyName =
154-
bidderCatalog.cookieFamilyName(bidderCatalog.resolveBaseBidder(bidder)).orElse(null);
154+
private static void validateBiddersHaveTheSameUsersyncConfig(Set<String> bidders, BidderCatalog bidderCatalog) {
155+
final Set<Usersyncer> usersyncers = bidders.stream()
156+
.map(bidderCatalog::usersyncerByName)
157+
.map(Optional::orElseThrow)
158+
.collect(Collectors.toSet());
155159

156-
return bidderCatalog.isAlias(bidder)
157-
&& parentCookieFamilyName != null
158-
&& parentCookieFamilyName.equals(bidderCookieFamilyName);
160+
if (usersyncers.size() > 1) {
161+
throw new IllegalArgumentException(
162+
"Found bidders with the same cookie family name but different usersync configs. "
163+
+ "Bidders: %s. Usersync configs: %s".formatted(bidders, usersyncers));
164+
}
159165
}
160166

161167
private static UsersyncMethodType preferredUserSyncType(Usersyncer usersyncer) {
@@ -181,8 +187,8 @@ private Future<SetuidContext> toSetuidContext(RoutingContext routingContext) {
181187
final Timeout timeout = timeoutFactory.create(defaultTimeout);
182188

183189
final UsersyncMethodType syncType = Optional.ofNullable(cookieName)
184-
.map(cookieNameToBidderAndSyncType::get)
185-
.map(Pair::getRight)
190+
.map(cookieFamilyNameToUsersyncConfig::get)
191+
.map(UsersyncConfig::getUsersyncMethodType)
186192
.orElse(null);
187193

188194
return accountById(requestAccount, timeout)
@@ -236,15 +242,14 @@ private void handleSetuidContextResult(AsyncResult<SetuidContext> setuidContextR
236242
final AccountPrivacyConfig privacyConfig = setuidContext.getAccount().getPrivacy();
237243
final AccountGdprConfig accountGdprConfig = privacyConfig != null ? privacyConfig.getGdpr() : null;
238244

239-
final String bidderName = cookieNameToBidderAndSyncType.get(bidderCookieFamily).getLeft();
245+
final Set<String> bidderNames = cookieFamilyNameToUsersyncConfig.get(bidderCookieFamily).getBidders();
240246

241247
Future.all(
242248
tcfDefinerService.isAllowedForHostVendorId(tcfContext),
243-
tcfDefinerService.resultForBidderNames(
244-
Collections.singleton(bidderName), tcfContext, accountGdprConfig))
249+
tcfDefinerService.resultForBidderNames(bidderNames, tcfContext, accountGdprConfig))
245250
.onComplete(hostTcfResponseResult -> respondByTcfResponse(
246251
hostTcfResponseResult,
247-
bidderName,
252+
bidderNames,
248253
setuidContext));
249254
} else {
250255
final Throwable error = setuidContextResult.cause();
@@ -255,7 +260,7 @@ private void handleSetuidContextResult(AsyncResult<SetuidContext> setuidContextR
255260
private void validateSetuidContext(SetuidContext setuidContext, String bidderCookieFamily) {
256261
final String cookieName = setuidContext.getCookieName();
257262
final boolean isCookieNameBlank = StringUtils.isBlank(cookieName);
258-
if (isCookieNameBlank || !cookieNameToBidderAndSyncType.containsKey(cookieName)) {
263+
if (isCookieNameBlank || !cookieFamilyNameToUsersyncConfig.containsKey(cookieName)) {
259264
final String cookieNameError = isCookieNameBlank ? "required" : "invalid";
260265
throw new InvalidRequestException("\"bidder\" query param is " + cookieNameError);
261266
}
@@ -282,7 +287,7 @@ private void validateSetuidContext(SetuidContext setuidContext, String bidderCoo
282287
}
283288

284289
private void respondByTcfResponse(AsyncResult<CompositeFuture> hostTcfResponseResult,
285-
String bidderName,
290+
Set<String> bidderNames,
286291
SetuidContext setuidContext) {
287292

288293
final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext();
@@ -293,9 +298,11 @@ private void respondByTcfResponse(AsyncResult<CompositeFuture> hostTcfResponseRe
293298
final HostVendorTcfResponse hostVendorTcfResponse = compositeFuture.resultAt(0);
294299
final TcfResponse<String> bidderTcfResponse = compositeFuture.resultAt(1);
295300

301+
// bidders corresponding to the same cookie family name should have the same vendor id
302+
// so we can use any one of them here
296303
final Map<String, PrivacyEnforcementAction> vendorIdToAction = bidderTcfResponse.getActions();
297304
final PrivacyEnforcementAction action = vendorIdToAction != null
298-
? vendorIdToAction.get(bidderName)
305+
? vendorIdToAction.get(bidderNames.iterator().next())
299306
: null;
300307

301308
final boolean notInGdprScope = BooleanUtils.isFalse(bidderTcfResponse.getUserInGdprScope());
@@ -416,4 +423,12 @@ private void handleErrors(Throwable error, RoutingContext routingContext, TcfCon
416423
private void addCookie(RoutingContext routingContext, Cookie cookie) {
417424
routingContext.response().headers().add(HttpUtil.SET_COOKIE_HEADER, cookie.encode());
418425
}
426+
427+
@Value(staticConstructor = "of")
428+
private static class UsersyncConfig {
429+
430+
Set<String> bidders;
431+
432+
UsersyncMethodType usersyncMethodType;
433+
}
419434
}

0 commit comments

Comments
 (0)