1616package com .optimizely .ab ;
1717
1818import 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 ;
2321import com .optimizely .ab .cmab .service .CmabService ;
22+ import com .optimizely .ab .cmab .service .DefaultCmabService ;
2423import com .optimizely .ab .config .AtomicProjectConfigManager ;
2524import com .optimizely .ab .config .DatafileProjectConfig ;
2625import com .optimizely .ab .config .EventType ;
4645import com .optimizely .ab .event .internal .UserEvent ;
4746import com .optimizely .ab .event .internal .UserEventFactory ;
4847import com .optimizely .ab .event .internal .payload .EventBatch ;
48+ import com .optimizely .ab .internal .DefaultLRUCache ;
4949import com .optimizely .ab .internal .NotificationRegistry ;
5050import com .optimizely .ab .notification .ActivateNotification ;
5151import com .optimizely .ab .notification .DecisionNotification ;
6363import com .optimizely .ab .optimizelyconfig .OptimizelyConfig ;
6464import com .optimizely .ab .optimizelyconfig .OptimizelyConfigManager ;
6565import 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 .*;
7267import com .optimizely .ab .optimizelyjson .OptimizelyJSON ;
68+
7369import org .slf4j .Logger ;
7470import org .slf4j .LoggerFactory ;
7571
7672import javax .annotation .Nonnull ;
7773import javax .annotation .Nullable ;
7874import javax .annotation .concurrent .ThreadSafe ;
75+
7976import java .io .Closeable ;
8077import java .util .ArrayList ;
8178import java .util .Arrays ;
8582import java .util .Map ;
8683import java .util .concurrent .locks .ReentrantLock ;
8784
85+ import com .optimizely .ab .cmab .client .CmabClient ;
8886import 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