Skip to content

Commit c4c07a6

Browse files
Merge branch 'farhan-anjum/FSSDK-11170-decision-service-methods-for-cmab' into farhan-anjum/FSSDK-11179-update-impression-event
2 parents 59b90d7 + 4b35768 commit c4c07a6

File tree

21 files changed

+852
-1191
lines changed

21 files changed

+852
-1191
lines changed

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

Lines changed: 80 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616
package com.optimizely.ab;
1717

1818
import com.optimizely.ab.annotations.VisibleForTesting;
19-
import com.optimizely.ab.bucketing.Bucketer;
20-
import com.optimizely.ab.bucketing.DecisionService;
21-
import com.optimizely.ab.bucketing.FeatureDecision;
22-
import com.optimizely.ab.bucketing.UserProfileService;
19+
import com.optimizely.ab.bucketing.*;
20+
import com.optimizely.ab.cmab.service.CmabCacheValue;
2321
import com.optimizely.ab.cmab.service.CmabService;
22+
import com.optimizely.ab.cmab.service.DefaultCmabService;
2423
import com.optimizely.ab.config.AtomicProjectConfigManager;
2524
import com.optimizely.ab.config.DatafileProjectConfig;
2625
import com.optimizely.ab.config.EventType;
@@ -46,6 +45,7 @@
4645
import com.optimizely.ab.event.internal.UserEvent;
4746
import com.optimizely.ab.event.internal.UserEventFactory;
4847
import com.optimizely.ab.event.internal.payload.EventBatch;
48+
import com.optimizely.ab.internal.DefaultLRUCache;
4949
import com.optimizely.ab.internal.NotificationRegistry;
5050
import com.optimizely.ab.notification.ActivateNotification;
5151
import com.optimizely.ab.notification.DecisionNotification;
@@ -63,19 +63,16 @@
6363
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
6464
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
6565
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
66-
import com.optimizely.ab.optimizelydecision.DecisionMessage;
67-
import com.optimizely.ab.optimizelydecision.DecisionReasons;
68-
import com.optimizely.ab.optimizelydecision.DecisionResponse;
69-
import com.optimizely.ab.optimizelydecision.DefaultDecisionReasons;
70-
import com.optimizely.ab.optimizelydecision.OptimizelyDecideOption;
71-
import com.optimizely.ab.optimizelydecision.OptimizelyDecision;
66+
import com.optimizely.ab.optimizelydecision.*;
7267
import com.optimizely.ab.optimizelyjson.OptimizelyJSON;
68+
7369
import org.slf4j.Logger;
7470
import org.slf4j.LoggerFactory;
7571

7672
import javax.annotation.Nonnull;
7773
import javax.annotation.Nullable;
7874
import javax.annotation.concurrent.ThreadSafe;
75+
7976
import java.io.Closeable;
8077
import java.util.ArrayList;
8178
import java.util.Arrays;
@@ -85,6 +82,7 @@
8582
import java.util.Map;
8683
import java.util.concurrent.locks.ReentrantLock;
8784

85+
import com.optimizely.ab.cmab.client.CmabClient;
8886
import static com.optimizely.ab.internal.SafetyUtils.tryClose;
8987

9088
/**
@@ -1408,7 +1406,8 @@ Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext use
14081406
private Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext user,
14091407
@Nonnull List<String> keys,
14101408
@Nonnull List<OptimizelyDecideOption> options,
1411-
boolean ignoreDefaultOptions) {
1409+
boolean ignoreDefaultOptions,
1410+
DecisionPath decisionPath) {
14121411
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
14131412

14141413
ProjectConfig projectConfig = getProjectConfig();
@@ -1453,7 +1452,7 @@ private Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserCon
14531452
}
14541453

14551454
List<DecisionResponse<FeatureDecision>> decisionList =
1456-
decisionService.getVariationsForFeatureList(flagsWithoutForcedDecision, user, projectConfig, allOptions);
1455+
decisionService.getVariationsForFeatureList(flagsWithoutForcedDecision, user, projectConfig, allOptions, decisionPath);
14571456

14581457
for (int i = 0; i < flagsWithoutForcedDecision.size(); i++) {
14591458
DecisionResponse<FeatureDecision> decision = decisionList.get(i);
@@ -1492,6 +1491,13 @@ private Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserCon
14921491
return decisionMap;
14931492
}
14941493

1494+
private Map<String, OptimizelyDecision> decideForKeys(@Nonnull OptimizelyUserContext user,
1495+
@Nonnull List<String> keys,
1496+
@Nonnull List<OptimizelyDecideOption> options,
1497+
boolean ignoreDefaultOptions) {
1498+
return decideForKeys(user, keys, options, ignoreDefaultOptions, DecisionPath.WITH_CMAB);
1499+
}
1500+
14951501
Map<String, OptimizelyDecision> decideAll(@Nonnull OptimizelyUserContext user,
14961502
@Nonnull List<OptimizelyDecideOption> options) {
14971503
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
@@ -1512,15 +1518,16 @@ Map<String, OptimizelyDecision> decideAll(@Nonnull OptimizelyUserContext user,
15121518
/**
15131519
* Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context,
15141520
* skipping CMAB logic and using only traditional A/B testing.
1521+
* This will be called by mobile apps which will make synchronous decisions only (for backward compatibility with android-sdk)
15151522
*
15161523
* @param user An OptimizelyUserContext associated with this OptimizelyClient.
15171524
* @param key A flag key for which a decision will be made.
15181525
* @param options A list of options for decision-making.
15191526
* @return A decision result using traditional A/B testing logic only.
15201527
*/
15211528
OptimizelyDecision decideSync(@Nonnull OptimizelyUserContext user,
1522-
@Nonnull String key,
1523-
@Nonnull List<OptimizelyDecideOption> options) {
1529+
@Nonnull String key,
1530+
@Nonnull List<OptimizelyDecideOption> options) {
15241531
ProjectConfig projectConfig = getProjectConfig();
15251532
if (projectConfig == null) {
15261533
return OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.SDK_NOT_READY.reason());
@@ -1534,27 +1541,36 @@ OptimizelyDecision decideSync(@Nonnull OptimizelyUserContext user,
15341541

15351542
/**
15361543
* Returns decision results for multiple flag keys, skipping CMAB logic and using only traditional A/B testing.
1544+
* This will be called by mobile apps which will make synchronous decisions only (for backward compatibility with android-sdk)
15371545
*
15381546
* @param user An OptimizelyUserContext associated with this OptimizelyClient.
15391547
* @param keys A list of flag keys for which decisions will be made.
15401548
* @param options A list of options for decision-making.
15411549
* @return All decision results mapped by flag keys, using traditional A/B testing logic only.
15421550
*/
15431551
Map<String, OptimizelyDecision> decideForKeysSync(@Nonnull OptimizelyUserContext user,
1544-
@Nonnull List<String> keys,
1545-
@Nonnull List<OptimizelyDecideOption> options) {
1552+
@Nonnull List<String> keys,
1553+
@Nonnull List<OptimizelyDecideOption> options) {
15461554
return decideForKeysSync(user, keys, options, false);
15471555
}
15481556

1557+
private Map<String, OptimizelyDecision> decideForKeysSync(@Nonnull OptimizelyUserContext user,
1558+
@Nonnull List<String> keys,
1559+
@Nonnull List<OptimizelyDecideOption> options,
1560+
boolean ignoreDefaultOptions) {
1561+
return decideForKeys(user, keys, options, ignoreDefaultOptions, DecisionPath.WITHOUT_CMAB);
1562+
}
1563+
15491564
/**
15501565
* Returns decision results for all active flag keys, skipping CMAB logic and using only traditional A/B testing.
1566+
* This will be called by mobile apps which will make synchronous decisions only (for backward compatibility with android-sdk)
15511567
*
15521568
* @param user An OptimizelyUserContext associated with this OptimizelyClient.
15531569
* @param options A list of options for decision-making.
15541570
* @return All decision results mapped by flag keys, using traditional A/B testing logic only.
15551571
*/
15561572
Map<String, OptimizelyDecision> decideAllSync(@Nonnull OptimizelyUserContext user,
1557-
@Nonnull List<OptimizelyDecideOption> options) {
1573+
@Nonnull List<OptimizelyDecideOption> options) {
15581574
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
15591575

15601576
ProjectConfig projectConfig = getProjectConfig();
@@ -1570,79 +1586,53 @@ Map<String, OptimizelyDecision> decideAllSync(@Nonnull OptimizelyUserContext use
15701586
return decideForKeysSync(user, allFlagKeys, options);
15711587
}
15721588

1573-
private Map<String, OptimizelyDecision> decideForKeysSync(@Nonnull OptimizelyUserContext user,
1574-
@Nonnull List<String> keys,
1575-
@Nonnull List<OptimizelyDecideOption> options,
1576-
boolean ignoreDefaultOptions) {
1577-
Map<String, OptimizelyDecision> decisionMap = new HashMap<>();
1589+
//============ decide async ============//
15781590

1579-
ProjectConfig projectConfig = getProjectConfig();
1580-
if (projectConfig == null) {
1581-
logger.error("Optimizely instance is not valid, failing decideForKeysSync call.");
1582-
return decisionMap;
1583-
}
1584-
1585-
if (keys.isEmpty()) return decisionMap;
1586-
1587-
List<OptimizelyDecideOption> allOptions = ignoreDefaultOptions ? options : getAllOptions(options);
1588-
1589-
Map<String, FeatureDecision> flagDecisions = new HashMap<>();
1590-
Map<String, DecisionReasons> decisionReasonsMap = new HashMap<>();
1591-
1592-
List<FeatureFlag> flagsWithoutForcedDecision = new ArrayList<>();
1593-
1594-
List<String> validKeys = new ArrayList<>();
1595-
1596-
for (String key : keys) {
1597-
FeatureFlag flag = projectConfig.getFeatureKeyMapping().get(key);
1598-
if (flag == null) {
1599-
decisionMap.put(key, OptimizelyDecision.newErrorDecision(key, user, DecisionMessage.FLAG_KEY_INVALID.reason(key)));
1600-
continue;
1601-
}
1602-
1603-
validKeys.add(key);
1604-
1605-
DecisionReasons decisionReasons = DefaultDecisionReasons.newInstance(allOptions);
1606-
decisionReasonsMap.put(key, decisionReasons);
1607-
1608-
OptimizelyDecisionContext optimizelyDecisionContext = new OptimizelyDecisionContext(key, null);
1609-
DecisionResponse<Variation> forcedDecisionVariation = decisionService.validatedForcedDecision(optimizelyDecisionContext, projectConfig, user);
1610-
decisionReasons.merge(forcedDecisionVariation.getReasons());
1611-
if (forcedDecisionVariation.getResult() != null) {
1612-
flagDecisions.put(key,
1613-
new FeatureDecision(null, forcedDecisionVariation.getResult(), FeatureDecision.DecisionSource.FEATURE_TEST));
1614-
} else {
1615-
flagsWithoutForcedDecision.add(flag);
1616-
}
1617-
}
1618-
1619-
// Use DecisionService method that skips CMAB logic
1620-
List<DecisionResponse<FeatureDecision>> decisionList =
1621-
decisionService.getVariationsForFeatureList(flagsWithoutForcedDecision, user, projectConfig, allOptions, false);
1622-
1623-
for (int i = 0; i < flagsWithoutForcedDecision.size(); i++) {
1624-
DecisionResponse<FeatureDecision> decision = decisionList.get(i);
1625-
String flagKey = flagsWithoutForcedDecision.get(i).getKey();
1626-
flagDecisions.put(flagKey, decision.getResult());
1627-
decisionReasonsMap.get(flagKey).merge(decision.getReasons());
1628-
}
1629-
1630-
for (String key : validKeys) {
1631-
FeatureDecision flagDecision = flagDecisions.get(key);
1632-
DecisionReasons decisionReasons = decisionReasonsMap.get((key));
1633-
1634-
OptimizelyDecision optimizelyDecision = createOptimizelyDecision(
1635-
user, key, flagDecision, decisionReasons, allOptions, projectConfig
1636-
);
1637-
1638-
if (!allOptions.contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) || optimizelyDecision.getEnabled()) {
1639-
decisionMap.put(key, optimizelyDecision);
1640-
}
1641-
}
1591+
/**
1592+
* Returns a decision result asynchronously for a given flag key and a user context.
1593+
*
1594+
* @param userContext The user context to make decisions for
1595+
* @param key A flag key for which a decision will be made
1596+
* @param callback A callback to invoke when the decision is available
1597+
* @param options A list of options for decision-making
1598+
*/
1599+
void decideAsync(@Nonnull OptimizelyUserContext userContext,
1600+
@Nonnull String key,
1601+
@Nonnull List<OptimizelyDecideOption> options,
1602+
@Nonnull OptimizelyDecisionCallback callback) {
1603+
AsyncDecisionFetcher fetcher = new AsyncDecisionFetcher(userContext, key, options, callback);
1604+
fetcher.start();
1605+
}
16421606

1643-
return decisionMap;
1607+
/**
1608+
* Returns decision results asynchronously for multiple flag keys.
1609+
*
1610+
* @param userContext The user context to make decisions for
1611+
* @param keys A list of flag keys for which decisions will be made
1612+
* @param callback A callback to invoke when decisions are available
1613+
* @param options A list of options for decision-making
1614+
*/
1615+
void decideForKeysAsync(@Nonnull OptimizelyUserContext userContext,
1616+
@Nonnull List<String> keys,
1617+
@Nonnull List<OptimizelyDecideOption> options,
1618+
@Nonnull OptimizelyDecisionsCallback callback) {
1619+
AsyncDecisionFetcher fetcher = new AsyncDecisionFetcher(userContext, keys, options, callback);
1620+
fetcher.start();
16441621
}
16451622

1623+
/**
1624+
* Returns decision results asynchronously for all active flag keys.
1625+
*
1626+
* @param userContext The user context to make decisions for
1627+
* @param callback A callback to invoke when decisions are available
1628+
* @param options A list of options for decision-making
1629+
*/
1630+
void decideAllAsync(@Nonnull OptimizelyUserContext userContext,
1631+
@Nonnull List<OptimizelyDecideOption> options,
1632+
@Nonnull OptimizelyDecisionsCallback callback) {
1633+
AsyncDecisionFetcher fetcher = new AsyncDecisionFetcher(userContext, options, callback);
1634+
fetcher.start();
1635+
}
16461636

16471637
private List<OptimizelyDecideOption> getAllOptions(List<OptimizelyDecideOption> options) {
16481638
List<OptimizelyDecideOption> copiedOptions = new ArrayList(defaultDecideOptions);
@@ -2040,6 +2030,10 @@ public Optimizely build() {
20402030
bucketer = new Bucketer();
20412031
}
20422032

2033+
if (cmabService == null) {
2034+
logger.warn("CMAB service is not initiated. CMAB functionality will not be available.");
2035+
}
2036+
20432037
if (decisionService == null) {
20442038
decisionService = new DecisionService(bucketer, errorHandler, userProfileService, cmabService);
20452039
}

0 commit comments

Comments
 (0)