Skip to content

Commit f63cff5

Browse files
authored
fix: ODP - getQualifiedSegments should return null when fetch fails (#496)
1 parent 3c14dde commit f63cff5

File tree

3 files changed

+33
-17
lines changed

3 files changed

+33
-17
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2016-2022, Optimizely, Inc. and contributors *
2+
* Copyright 2016-2023, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -436,7 +436,7 @@ private Boolean isFeatureEnabled(@Nonnull ProjectConfig projectConfig,
436436

437437
Map<String, ?> copiedAttributes = copyAttributes(attributes);
438438
FeatureDecision.DecisionSource decisionSource = FeatureDecision.DecisionSource.ROLLOUT;
439-
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContext(userId, copiedAttributes), projectConfig).getResult();
439+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContextCopy(userId, copiedAttributes), projectConfig).getResult();
440440
Boolean featureEnabled = false;
441441
SourceInfo sourceInfo = new RolloutSourceInfo();
442442
if (featureDecision.decisionSource != null) {
@@ -745,7 +745,7 @@ <T> T getFeatureVariableValueForType(@Nonnull String featureKey,
745745

746746
String variableValue = variable.getDefaultValue();
747747
Map<String, ?> copiedAttributes = copyAttributes(attributes);
748-
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContext(userId, copiedAttributes), projectConfig).getResult();
748+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContextCopy(userId, copiedAttributes), projectConfig).getResult();
749749
Boolean featureEnabled = false;
750750
if (featureDecision.variation != null) {
751751
if (featureDecision.variation.getFeatureEnabled()) {
@@ -880,7 +880,7 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
880880
}
881881

882882
Map<String, ?> copiedAttributes = copyAttributes(attributes);
883-
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContext(userId, copiedAttributes), projectConfig, Collections.emptyList()).getResult();
883+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, createUserContextCopy(userId, copiedAttributes), projectConfig, Collections.emptyList()).getResult();
884884
Boolean featureEnabled = false;
885885
Variation variation = featureDecision.variation;
886886

@@ -982,7 +982,7 @@ private Variation getVariation(@Nonnull ProjectConfig projectConfig,
982982
@Nonnull String userId,
983983
@Nonnull Map<String, ?> attributes) throws UnknownExperimentException {
984984
Map<String, ?> copiedAttributes = copyAttributes(attributes);
985-
Variation variation = decisionService.getVariation(experiment, createUserContext(userId, copiedAttributes), projectConfig).getResult();
985+
Variation variation = decisionService.getVariation(experiment, createUserContextCopy(userId, copiedAttributes), projectConfig).getResult();
986986
String notificationType = NotificationCenter.DecisionNotificationType.AB_TEST.toString();
987987

988988
if (projectConfig.getExperimentFeatureKeyMapping().get(experiment.getId()) != null) {
@@ -1172,6 +1172,14 @@ public OptimizelyUserContext createUserContext(@Nonnull String userId) {
11721172
return new OptimizelyUserContext(this, userId);
11731173
}
11741174

1175+
private OptimizelyUserContext createUserContextCopy(@Nonnull String userId, @Nonnull Map<String, ?> attributes) {
1176+
if (userId == null) {
1177+
logger.warn("The userId parameter must be nonnull.");
1178+
return null;
1179+
}
1180+
return new OptimizelyUserContext(this, userId, attributes, Collections.EMPTY_MAP, null, false);
1181+
}
1182+
11751183
OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
11761184
@Nonnull String key,
11771185
@Nonnull List<OptimizelyDecideOption> options) {

core-api/src/main/java/com/optimizely/ab/OptimizelyUserContext.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2020-2022, Optimizely and contributors
3+
* Copyright 2020-2023, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -76,7 +76,9 @@ public OptimizelyUserContext(@Nonnull Optimizely optimizely,
7676
this.forcedDecisionsMap = new ConcurrentHashMap<>(forcedDecisionsMap);
7777
}
7878

79-
this.qualifiedSegments = Collections.synchronizedList( qualifiedSegments == null ? new LinkedList<>(): qualifiedSegments);
79+
if (qualifiedSegments != null) {
80+
this.qualifiedSegments = Collections.synchronizedList(new LinkedList<>(qualifiedSegments));
81+
}
8082

8183
if (shouldIdentifyUser == null || shouldIdentifyUser) {
8284
optimizely.identifyUser(userId);
@@ -109,6 +111,10 @@ public OptimizelyUserContext copy() {
109111
* @return boolean Is user qualified for a segment.
110112
*/
111113
public boolean isQualifiedFor(@Nonnull String segment) {
114+
if (qualifiedSegments == null) {
115+
return false;
116+
}
117+
112118
return qualifiedSegments.contains(segment);
113119
}
114120

@@ -293,8 +299,14 @@ public List<String> getQualifiedSegments() {
293299
}
294300

295301
public void setQualifiedSegments(List<String> qualifiedSegments) {
296-
this.qualifiedSegments.clear();
297-
this.qualifiedSegments.addAll(qualifiedSegments);
302+
if (qualifiedSegments == null) {
303+
this.qualifiedSegments = null;
304+
} else if (this.qualifiedSegments == null) {
305+
this.qualifiedSegments = Collections.synchronizedList(new LinkedList<>(qualifiedSegments));
306+
} else {
307+
this.qualifiedSegments.clear();
308+
this.qualifiedSegments.addAll(qualifiedSegments);
309+
}
298310
}
299311

300312
/**
@@ -315,9 +327,7 @@ public Boolean fetchQualifiedSegments() {
315327
*/
316328
public Boolean fetchQualifiedSegments(@Nonnull List<ODPSegmentOption> segmentOptions) {
317329
List<String> segments = optimizely.fetchQualifiedSegments(userId, segmentOptions);
318-
if (segments != null) {
319-
setQualifiedSegments(segments);
320-
}
330+
setQualifiedSegments(segments);
321331
return segments != null;
322332
}
323333

@@ -332,9 +342,7 @@ public Boolean fetchQualifiedSegments(@Nonnull List<ODPSegmentOption> segmentOpt
332342
*/
333343
public void fetchQualifiedSegments(ODPSegmentCallback callback, List<ODPSegmentOption> segmentOptions) {
334344
optimizely.fetchQualifiedSegments(userId, segments -> {
335-
if (segments != null) {
336-
setQualifiedSegments(segments);
337-
}
345+
setQualifiedSegments(segments);
338346
callback.onCompleted(segments != null);
339347
}, segmentOptions);
340348
}

core-api/src/test/java/com/optimizely/ab/OptimizelyUserContextTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2021-2022, Optimizely and contributors
3+
* Copyright 2021-2023, Optimizely and contributors
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -1709,7 +1709,7 @@ public void fetchQualifiedSegmentsAsyncError() throws InterruptedException {
17091709
});
17101710

17111711
countDownLatch.await();
1712-
assertEquals(Collections.emptyList(), userContext.getQualifiedSegments());
1712+
assertEquals(null, userContext.getQualifiedSegments());
17131713
logbackVerifier.expectMessage(Level.ERROR, "Audience segments fetch failed (ODP is not enabled).");
17141714
}
17151715

0 commit comments

Comments
 (0)