Skip to content

Commit b16dff5

Browse files
authored
feat: Integrated ODPManager with UserContext and Optimizely client (#490)
## Summary This PR integrates ODP functionality with UserContext and Optimizely client to make it available for users. There are two ways to integrate ODPManager. 1. ODPManager.Builder. 2. Use default instance from OptimizelyFactory. ## Test plan 1. Manually tested thoroughly 2. Will add unit tests after the first review passes. ## Issues FSSDK-8389, FS-8683
1 parent 7428c4c commit b16dff5

21 files changed

+1045
-126
lines changed

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

+81-13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.optimizely.ab.event.internal.*;
2929
import com.optimizely.ab.event.internal.payload.EventBatch;
3030
import com.optimizely.ab.notification.*;
31+
import com.optimizely.ab.odp.*;
3132
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
3233
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
3334
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
@@ -96,6 +97,9 @@ public class Optimizely implements AutoCloseable {
9697
@Nullable
9798
private final UserProfileService userProfileService;
9899

100+
@Nullable
101+
private final ODPManager odpManager;
102+
99103
private Optimizely(@Nonnull EventHandler eventHandler,
100104
@Nonnull EventProcessor eventProcessor,
101105
@Nonnull ErrorHandler errorHandler,
@@ -104,7 +108,8 @@ private Optimizely(@Nonnull EventHandler eventHandler,
104108
@Nonnull ProjectConfigManager projectConfigManager,
105109
@Nullable OptimizelyConfigManager optimizelyConfigManager,
106110
@Nonnull NotificationCenter notificationCenter,
107-
@Nonnull List<OptimizelyDecideOption> defaultDecideOptions
111+
@Nonnull List<OptimizelyDecideOption> defaultDecideOptions,
112+
@Nullable ODPManager odpManager
108113
) {
109114
this.eventHandler = eventHandler;
110115
this.eventProcessor = eventProcessor;
@@ -115,6 +120,15 @@ private Optimizely(@Nonnull EventHandler eventHandler,
115120
this.optimizelyConfigManager = optimizelyConfigManager;
116121
this.notificationCenter = notificationCenter;
117122
this.defaultDecideOptions = defaultDecideOptions;
123+
this.odpManager = odpManager;
124+
125+
if (odpManager != null) {
126+
odpManager.getEventManager().start();
127+
if (getProjectConfig() != null) {
128+
updateODPSettings();
129+
}
130+
addUpdateConfigNotificationHandler(configNotification -> { updateODPSettings(); });
131+
}
118132
}
119133

120134
/**
@@ -128,8 +142,6 @@ public boolean isValid() {
128142
return getProjectConfig() != null;
129143
}
130144

131-
132-
133145
/**
134146
* Checks if eventHandler {@link EventHandler} and projectConfigManager {@link ProjectConfigManager}
135147
* are Closeable {@link Closeable} and calls close on them.
@@ -141,6 +153,9 @@ public void close() {
141153
tryClose(eventProcessor);
142154
tryClose(eventHandler);
143155
tryClose(projectConfigManager);
156+
if (odpManager != null) {
157+
tryClose(odpManager);
158+
}
144159
}
145160

146161
//======== activate calls ========//
@@ -674,9 +689,9 @@ public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
674689
*/
675690
@Nullable
676691
public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
677-
@Nonnull String variableKey,
678-
@Nonnull String userId,
679-
@Nonnull Map<String, ?> attributes) {
692+
@Nonnull String variableKey,
693+
@Nonnull String userId,
694+
@Nonnull Map<String, ?> attributes) {
680695

681696
return getFeatureVariableValueForType(
682697
featureKey,
@@ -688,10 +703,10 @@ public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
688703

689704
@VisibleForTesting
690705
<T> T getFeatureVariableValueForType(@Nonnull String featureKey,
691-
@Nonnull String variableKey,
692-
@Nonnull String userId,
693-
@Nonnull Map<String, ?> attributes,
694-
@Nonnull String variableType) {
706+
@Nonnull String variableKey,
707+
@Nonnull String userId,
708+
@Nonnull Map<String, ?> attributes,
709+
@Nonnull String variableType) {
695710
if (featureKey == null) {
696711
logger.warn("The featureKey parameter must be nonnull.");
697712
return null;
@@ -878,7 +893,7 @@ public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
878893
}
879894
} else {
880895
logger.info("User \"{}\" was not bucketed into any variation for feature flag \"{}\". " +
881-
"The default values are being returned.", userId, featureKey);
896+
"The default values are being returned.", userId, featureKey);
882897
}
883898

884899
Map<String, Object> valuesMap = new HashMap<String, Object>();
@@ -1142,7 +1157,7 @@ public OptimizelyConfig getOptimizelyConfig() {
11421157
* @param userId The user ID to be used for bucketing.
11431158
* @param attributes: A map of attribute names to current user attribute values.
11441159
* @return An OptimizelyUserContext associated with this OptimizelyClient.
1145-
*/
1160+
*/
11461161
public OptimizelyUserContext createUserContext(@Nonnull String userId,
11471162
@Nonnull Map<String, ?> attributes) {
11481163
if (userId == null) {
@@ -1413,6 +1428,53 @@ public <T> int addNotificationHandler(Class<T> clazz, NotificationHandler<T> han
14131428
return notificationCenter.addNotificationHandler(clazz, handler);
14141429
}
14151430

1431+
public List<String> fetchQualifiedSegments(String userId, @Nonnull List<ODPSegmentOption> segmentOptions) {
1432+
if (odpManager != null) {
1433+
synchronized (odpManager) {
1434+
return odpManager.getSegmentManager().getQualifiedSegments(userId, segmentOptions);
1435+
}
1436+
}
1437+
logger.error("Audience segments fetch failed (ODP is not enabled).");
1438+
return null;
1439+
}
1440+
1441+
public void fetchQualifiedSegments(String userId, ODPSegmentManager.ODPSegmentFetchCallback callback, List<ODPSegmentOption> segmentOptions) {
1442+
if (odpManager == null) {
1443+
logger.error("Audience segments fetch failed (ODP is not enabled).");
1444+
callback.onCompleted(null);
1445+
} else {
1446+
odpManager.getSegmentManager().getQualifiedSegments(userId, callback, segmentOptions);
1447+
}
1448+
}
1449+
1450+
@Nullable
1451+
public ODPManager getODPManager() {
1452+
return odpManager;
1453+
}
1454+
1455+
public void sendODPEvent(@Nullable String type, @Nonnull String action, @Nullable Map<String, String> identifiers, @Nullable Map<String, Object> data) {
1456+
if (odpManager != null) {
1457+
ODPEvent event = new ODPEvent(type, action, identifiers, data);
1458+
odpManager.getEventManager().sendEvent(event);
1459+
} else {
1460+
logger.error("ODP event send failed (ODP is not enabled)");
1461+
}
1462+
}
1463+
1464+
public void identifyUser(@Nonnull String userId) {
1465+
ODPManager odpManager = getODPManager();
1466+
if (odpManager != null) {
1467+
odpManager.getEventManager().identifyUser(userId);
1468+
}
1469+
}
1470+
1471+
private void updateODPSettings() {
1472+
if (odpManager != null && getProjectConfig() != null) {
1473+
ProjectConfig projectConfig = getProjectConfig();
1474+
odpManager.updateSettings(projectConfig.getHostForODP(), projectConfig.getPublicKeyForODP(), projectConfig.getAllSegments());
1475+
}
1476+
}
1477+
14161478
//======== Builder ========//
14171479

14181480
/**
@@ -1467,6 +1529,7 @@ public static class Builder {
14671529
private UserProfileService userProfileService;
14681530
private NotificationCenter notificationCenter;
14691531
private List<OptimizelyDecideOption> defaultDecideOptions;
1532+
private ODPManager odpManager;
14701533

14711534
// For backwards compatibility
14721535
private AtomicProjectConfigManager fallbackConfigManager = new AtomicProjectConfigManager();
@@ -1562,6 +1625,11 @@ public Builder withDefaultDecideOptions(List<OptimizelyDecideOption> defaultDeci
15621625
return this;
15631626
}
15641627

1628+
public Builder withODPManager(ODPManager odpManager) {
1629+
this.odpManager = odpManager;
1630+
return this;
1631+
}
1632+
15651633
// Helper functions for making testing easier
15661634
protected Builder withBucketing(Bucketer bucketer) {
15671635
this.bucketer = bucketer;
@@ -1636,7 +1704,7 @@ public Optimizely build() {
16361704
defaultDecideOptions = Collections.emptyList();
16371705
}
16381706

1639-
return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, optimizelyConfigManager, notificationCenter, defaultDecideOptions);
1707+
return new Optimizely(eventHandler, eventProcessor, errorHandler, decisionService, userProfileService, projectConfigManager, optimizelyConfigManager, notificationCenter, defaultDecideOptions, odpManager);
16401708
}
16411709
}
16421710
}

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

+71-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
*/
1717
package com.optimizely.ab;
1818

19-
import com.optimizely.ab.config.Variation;
19+
import com.optimizely.ab.odp.ODPManager;
20+
import com.optimizely.ab.odp.ODPSegmentCallback;
21+
import com.optimizely.ab.odp.ODPSegmentOption;
2022
import com.optimizely.ab.optimizelydecision.*;
2123
import org.slf4j.Logger;
2224
import org.slf4j.LoggerFactory;
@@ -54,6 +56,15 @@ public OptimizelyUserContext(@Nonnull Optimizely optimizely,
5456
@Nonnull Map<String, ?> attributes,
5557
@Nullable Map<String, OptimizelyForcedDecision> forcedDecisionsMap,
5658
@Nullable List<String> qualifiedSegments) {
59+
this(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments, true);
60+
}
61+
62+
public OptimizelyUserContext(@Nonnull Optimizely optimizely,
63+
@Nonnull String userId,
64+
@Nonnull Map<String, ?> attributes,
65+
@Nullable Map<String, OptimizelyForcedDecision> forcedDecisionsMap,
66+
@Nullable List<String> qualifiedSegments,
67+
@Nullable Boolean shouldIdentifyUser) {
5768
this.optimizely = optimizely;
5869
this.userId = userId;
5970
if (attributes != null) {
@@ -66,6 +77,10 @@ public OptimizelyUserContext(@Nonnull Optimizely optimizely,
6677
}
6778

6879
this.qualifiedSegments = Collections.synchronizedList( qualifiedSegments == null ? new LinkedList<>(): qualifiedSegments);
80+
81+
if (shouldIdentifyUser == null || shouldIdentifyUser) {
82+
optimizely.identifyUser(userId);
83+
}
6984
}
7085

7186
public OptimizelyUserContext(@Nonnull Optimizely optimizely, @Nonnull String userId) {
@@ -85,7 +100,7 @@ public Optimizely getOptimizely() {
85100
}
86101

87102
public OptimizelyUserContext copy() {
88-
return new OptimizelyUserContext(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments);
103+
return new OptimizelyUserContext(optimizely, userId, attributes, forcedDecisionsMap, qualifiedSegments, false);
89104
}
90105

91106
/**
@@ -282,6 +297,60 @@ public void setQualifiedSegments(List<String> qualifiedSegments) {
282297
this.qualifiedSegments.addAll(qualifiedSegments);
283298
}
284299

300+
/**
301+
* Fetch all qualified segments for the user context.
302+
* <p>
303+
* The segments fetched will be saved and can be accessed at any time by calling {@link #getQualifiedSegments()}.
304+
*/
305+
public Boolean fetchQualifiedSegments() {
306+
return fetchQualifiedSegments(Collections.emptyList());
307+
}
308+
309+
/**
310+
* Fetch all qualified segments for the user context.
311+
* <p>
312+
* The segments fetched will be saved and can be accessed at any time by calling {@link #getQualifiedSegments()}.
313+
*
314+
* @param segmentOptions A set of options for fetching qualified segments.
315+
*/
316+
public Boolean fetchQualifiedSegments(@Nonnull List<ODPSegmentOption> segmentOptions) {
317+
List<String> segments = optimizely.fetchQualifiedSegments(userId, segmentOptions);
318+
if (segments != null) {
319+
setQualifiedSegments(segments);
320+
}
321+
return segments != null;
322+
}
323+
324+
/**
325+
* Fetch all qualified segments for the user context in a non-blocking manner. This method will fetch segments
326+
* in a separate thread and invoke the provided callback when results are available.
327+
* <p>
328+
* The segments fetched will be saved and can be accessed at any time by calling {@link #getQualifiedSegments()}.
329+
*
330+
* @param callback A callback to invoke when results are available.
331+
* @param segmentOptions A set of options for fetching qualified segments.
332+
*/
333+
public void fetchQualifiedSegments(ODPSegmentCallback callback, List<ODPSegmentOption> segmentOptions) {
334+
optimizely.fetchQualifiedSegments(userId, segments -> {
335+
if (segments != null) {
336+
setQualifiedSegments(segments);
337+
}
338+
callback.onCompleted(segments != null);
339+
}, segmentOptions);
340+
}
341+
342+
/**
343+
* Fetch all qualified segments for the user context in a non-blocking manner. This method will fetch segments
344+
* in a separate thread and invoke the provided callback when results are available.
345+
* <p>
346+
* The segments fetched will be saved and can be accessed at any time by calling {@link #getQualifiedSegments()}.
347+
*
348+
* @param callback A callback to invoke when results are available.
349+
*/
350+
public void fetchQualifiedSegments(ODPSegmentCallback callback) {
351+
fetchQualifiedSegments(callback, Collections.emptyList());
352+
}
353+
285354
// Utils
286355

287356
@Override
@@ -309,5 +378,4 @@ public String toString() {
309378
", attributes='" + attributes + '\'' +
310379
'}';
311380
}
312-
313381
}

Diff for: core-api/src/main/java/com/optimizely/ab/config/DatafileProjectConfig.java

+1
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ public List<Experiment> getExperiments() {
434434
return experiments;
435435
}
436436

437+
@Override
437438
public Set<String> getAllSegments() {
438439
return this.allSegments;
439440
}

Diff for: core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.annotation.Nullable;
2525
import java.util.List;
2626
import java.util.Map;
27+
import java.util.Set;
2728

2829
/**
2930
* ProjectConfig is an interface capturing the experiment, variation and feature definitions.
@@ -69,6 +70,8 @@ Experiment getExperimentForKey(@Nonnull String experimentKey,
6970

7071
List<Experiment> getExperiments();
7172

73+
Set<String> getAllSegments();
74+
7275
List<Experiment> getExperimentsForEventKey(String eventKey);
7376

7477
List<FeatureFlag> getFeatureFlags();

Diff for: core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
*/
1616
package com.optimizely.ab.odp;
1717

18-
import java.util.List;
18+
import java.util.Set;
1919

2020
public interface ODPApiManager {
21-
String fetchQualifiedSegments(String apiKey, String apiEndpoint, String userKey, String userValue, List<String> segmentsToCheck);
21+
String fetchQualifiedSegments(String apiKey, String apiEndpoint, String userKey, String userValue, Set<String> segmentsToCheck);
2222

2323
Integer sendEvents(String apiKey, String apiEndpoint, String eventPayload);
2424
}

Diff for: core-api/src/main/java/com/optimizely/ab/odp/ODPConfig.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,24 @@
1717
package com.optimizely.ab.odp;
1818

1919
import java.util.Collections;
20-
import java.util.List;
20+
import java.util.Set;
2121

2222
public class ODPConfig {
2323

2424
private String apiKey;
2525

2626
private String apiHost;
2727

28-
private List<String> allSegments;
28+
private Set<String> allSegments;
2929

30-
public ODPConfig(String apiKey, String apiHost, List<String> allSegments) {
30+
public ODPConfig(String apiKey, String apiHost, Set<String> allSegments) {
3131
this.apiKey = apiKey;
3232
this.apiHost = apiHost;
3333
this.allSegments = allSegments;
3434
}
3535

3636
public ODPConfig(String apiKey, String apiHost) {
37-
this(apiKey, apiHost, Collections.emptyList());
37+
this(apiKey, apiHost, Collections.emptySet());
3838
}
3939

4040
public synchronized Boolean isReady() {
@@ -64,11 +64,11 @@ public synchronized String getApiHost() {
6464
return apiHost;
6565
}
6666

67-
public synchronized List<String> getAllSegments() {
67+
public synchronized Set<String> getAllSegments() {
6868
return allSegments;
6969
}
7070

71-
public synchronized void setAllSegments(List<String> allSegments) {
71+
public synchronized void setAllSegments(Set<String> allSegments) {
7272
this.allSegments = allSegments;
7373
}
7474

0 commit comments

Comments
 (0)