11/**************************************************************************** 
2-  * Copyright 2016-2023 , Optimizely, Inc. and contributors                   * 
2+  * Copyright 2016-2024 , 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.         * 
4242import  javax .annotation .Nonnull ;
4343import  javax .annotation .Nullable ;
4444import  javax .annotation .concurrent .ThreadSafe ;
45+ 
4546import  java .io .Closeable ;
4647import  java .util .*;
48+ import  java .util .stream .Collectors ;
4749
4850import  static  com .optimizely .ab .internal .SafetyUtils .tryClose ;
4951
@@ -1194,55 +1196,39 @@ private OptimizelyUserContext createUserContextCopy(@Nonnull String userId, @Non
11941196    OptimizelyDecision  decide (@ Nonnull  OptimizelyUserContext  user ,
11951197                              @ Nonnull  String  key ,
11961198                              @ Nonnull  List <OptimizelyDecideOption > options ) {
1197- 
11981199        ProjectConfig  projectConfig  = getProjectConfig ();
11991200        if  (projectConfig  == null ) {
12001201            return  OptimizelyDecision .newErrorDecision (key , user , DecisionMessage .SDK_NOT_READY .reason ());
12011202        }
12021203
1203-         FeatureFlag  flag  = projectConfig .getFeatureKeyMapping ().get (key );
1204-         if  (flag  == null ) {
1205-             return  OptimizelyDecision .newErrorDecision (key , user , DecisionMessage .FLAG_KEY_INVALID .reason (key ));
1206-         }
1207- 
1208-         String  userId  = user .getUserId ();
1209-         Map <String , Object > attributes  = user .getAttributes ();
1210-         Boolean  decisionEventDispatched  = false ;
12111204        List <OptimizelyDecideOption > allOptions  = getAllOptions (options );
1212-         DecisionReasons   decisionReasons  =  DefaultDecisionReasons . newInstance ( allOptions );
1205+         allOptions . remove ( OptimizelyDecideOption . ENABLED_FLAGS_ONLY );
12131206
1214-         Map <String , ?> copiedAttributes  = new  HashMap <>(attributes );
1215-         FeatureDecision  flagDecision ;
1216- 
1217-         // Check Forced Decision 
1218-         OptimizelyDecisionContext  optimizelyDecisionContext  = new  OptimizelyDecisionContext (flag .getKey (), null );
1219-         DecisionResponse <Variation > forcedDecisionVariation  = decisionService .validatedForcedDecision (optimizelyDecisionContext , projectConfig , user );
1220-         decisionReasons .merge (forcedDecisionVariation .getReasons ());
1221-         if  (forcedDecisionVariation .getResult () != null ) {
1222-             flagDecision  = new  FeatureDecision (null , forcedDecisionVariation .getResult (), FeatureDecision .DecisionSource .FEATURE_TEST );
1223-         } else  {
1224-             // Regular decision 
1225-             DecisionResponse <FeatureDecision > decisionVariation  = decisionService .getVariationForFeature (
1226-                 flag ,
1227-                 user ,
1228-                 projectConfig ,
1229-                 allOptions );
1230-             flagDecision  = decisionVariation .getResult ();
1231-             decisionReasons .merge (decisionVariation .getReasons ());
1232-         }
1207+         return  decideForKeys (user , Arrays .asList (key ), allOptions , true ).get (key );
1208+     }
1209+ 
1210+     private  OptimizelyDecision  createOptimizelyDecision (
1211+         OptimizelyUserContext  user ,
1212+         String  flagKey ,
1213+         FeatureDecision  flagDecision ,
1214+         DecisionReasons  decisionReasons ,
1215+         List <OptimizelyDecideOption > allOptions ,
1216+         ProjectConfig  projectConfig 
1217+     ) {
1218+         String  userId  = user .getUserId ();
12331219
12341220        Boolean  flagEnabled  = false ;
12351221        if  (flagDecision .variation  != null ) {
12361222            if  (flagDecision .variation .getFeatureEnabled ()) {
12371223                flagEnabled  = true ;
12381224            }
12391225        }
1240-         logger .info ("Feature \" {}\"  is enabled for user \" {}\" ? {}" , key , userId , flagEnabled );
1226+         logger .info ("Feature \" {}\"  is enabled for user \" {}\" ? {}" , flagKey , userId , flagEnabled );
12411227
12421228        Map <String , Object > variableMap  = new  HashMap <>();
12431229        if  (!allOptions .contains (OptimizelyDecideOption .EXCLUDE_VARIABLES )) {
12441230            DecisionResponse <Map <String , Object >> decisionVariables  = getDecisionVariableMap (
1245-                 flag ,
1231+                 projectConfig . getFeatureKeyMapping (). get ( flagKey ) ,
12461232                flagDecision .variation ,
12471233                flagEnabled );
12481234            variableMap  = decisionVariables .getResult ();
@@ -1261,22 +1247,28 @@ OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
12611247        //       add to event metadata as well (currently set to experimentKey) 
12621248        String  ruleKey  = flagDecision .experiment  != null  ? flagDecision .experiment .getKey () : null ;
12631249
1250+ 
1251+         Boolean  decisionEventDispatched  = false ;
1252+ 
1253+         Map <String , Object > attributes  = user .getAttributes ();
1254+         Map <String , ?> copiedAttributes  = new  HashMap <>(attributes );
1255+ 
12641256        if  (!allOptions .contains (OptimizelyDecideOption .DISABLE_DECISION_EVENT )) {
12651257            decisionEventDispatched  = sendImpression (
12661258                projectConfig ,
12671259                flagDecision .experiment ,
12681260                userId ,
12691261                copiedAttributes ,
12701262                flagDecision .variation ,
1271-                 key ,
1263+                 flagKey ,
12721264                decisionSource .toString (),
12731265                flagEnabled );
12741266        }
12751267
12761268        DecisionNotification  decisionNotification  = DecisionNotification .newFlagDecisionNotificationBuilder ()
12771269            .withUserId (userId )
12781270            .withAttributes (copiedAttributes )
1279-             .withFlagKey (key )
1271+             .withFlagKey (flagKey )
12801272            .withEnabled (flagEnabled )
12811273            .withVariables (variableMap )
12821274            .withVariationKey (variationKey )
@@ -1291,30 +1283,84 @@ OptimizelyDecision decide(@Nonnull OptimizelyUserContext user,
12911283            flagEnabled ,
12921284            optimizelyJSON ,
12931285            ruleKey ,
1294-             key ,
1286+             flagKey ,
12951287            user ,
12961288            reasonsToReport );
12971289    }
12981290
12991291    Map <String , OptimizelyDecision > decideForKeys (@ Nonnull  OptimizelyUserContext  user ,
1292+                                                           @ Nonnull  List <String > keys ,
1293+                                                           @ Nonnull  List <OptimizelyDecideOption > options ) {
1294+         return  decideForKeys (user , keys , options , false );
1295+     }
1296+ 
1297+     private  Map <String , OptimizelyDecision > decideForKeys (@ Nonnull  OptimizelyUserContext  user ,
13001298                                                  @ Nonnull  List <String > keys ,
1301-                                                   @ Nonnull  List <OptimizelyDecideOption > options ) {
1299+                                                   @ Nonnull  List <OptimizelyDecideOption > options ,
1300+                                                   boolean  ignoreDefaultOptions ) {
13021301        Map <String , OptimizelyDecision > decisionMap  = new  HashMap <>();
13031302
13041303        ProjectConfig  projectConfig  = getProjectConfig ();
13051304        if  (projectConfig  == null ) {
1306-             logger .error ("Optimizely instance is not valid, failing isFeatureEnabled  call." );
1305+             logger .error ("Optimizely instance is not valid, failing decideForKeys  call." );
13071306            return  decisionMap ;
13081307        }
13091308
13101309        if  (keys .isEmpty ()) return  decisionMap ;
13111310
1312-         List <OptimizelyDecideOption > allOptions  = getAllOptions (options );
1311+         List <OptimizelyDecideOption > allOptions  = ignoreDefaultOptions  ? options : getAllOptions (options );
1312+ 
1313+         Map <String , FeatureDecision > flagDecisions  = new  HashMap <>();
1314+         Map <String , DecisionReasons > decisionReasonsMap  = new  HashMap <>();
1315+ 
1316+         List <FeatureFlag > flagsWithoutForcedDecision  = new  ArrayList <>();
1317+ 
1318+         List <String > validKeys  = new  ArrayList <>();
13131319
13141320        for  (String  key  : keys ) {
1315-             OptimizelyDecision  decision  = decide (user , key , options );
1316-             if  (!allOptions .contains (OptimizelyDecideOption .ENABLED_FLAGS_ONLY ) || decision .getEnabled ()) {
1317-                 decisionMap .put (key , decision );
1321+             FeatureFlag  flag  = projectConfig .getFeatureKeyMapping ().get (key );
1322+             if  (flag  == null ) {
1323+                 decisionMap .put (key , OptimizelyDecision .newErrorDecision (key , user , DecisionMessage .FLAG_KEY_INVALID .reason (key )));
1324+                 continue ;
1325+             }
1326+ 
1327+             validKeys .add (key );
1328+ 
1329+             DecisionReasons  decisionReasons  = DefaultDecisionReasons .newInstance (allOptions );
1330+             decisionReasonsMap .put (key , decisionReasons );
1331+ 
1332+             OptimizelyDecisionContext  optimizelyDecisionContext  = new  OptimizelyDecisionContext (key , null );
1333+             DecisionResponse <Variation > forcedDecisionVariation  = decisionService .validatedForcedDecision (optimizelyDecisionContext , projectConfig , user );
1334+             decisionReasons .merge (forcedDecisionVariation .getReasons ());
1335+ 
1336+             if  (forcedDecisionVariation .getResult () != null ) {
1337+                 flagDecisions .put (key ,
1338+                     new  FeatureDecision (null , forcedDecisionVariation .getResult (), FeatureDecision .DecisionSource .FEATURE_TEST ));
1339+             } else  {
1340+                 flagsWithoutForcedDecision .add (flag );
1341+             }
1342+         }
1343+ 
1344+         List <DecisionResponse <FeatureDecision >> decisionList  =
1345+             decisionService .getVariationsForFeatureList (flagsWithoutForcedDecision , user , projectConfig , allOptions );
1346+ 
1347+         for  (int  i  = 0 ; i  < flagsWithoutForcedDecision .size (); i ++) {
1348+             DecisionResponse <FeatureDecision > decision  = decisionList .get (i );
1349+             String  flagKey  = flagsWithoutForcedDecision .get (i ).getKey ();
1350+             flagDecisions .put (flagKey , decision .getResult ());
1351+             decisionReasonsMap .get (flagKey ).merge (decision .getReasons ());
1352+         }
1353+ 
1354+         for  (String  key : validKeys ) {
1355+             FeatureDecision  flagDecision  = flagDecisions .get (key );
1356+             DecisionReasons  decisionReasons  = decisionReasonsMap .get ((key ));
1357+ 
1358+             OptimizelyDecision  optimizelyDecision  = createOptimizelyDecision (
1359+                 user , key , flagDecision , decisionReasons , allOptions , projectConfig 
1360+             );
1361+ 
1362+             if  (!allOptions .contains (OptimizelyDecideOption .ENABLED_FLAGS_ONLY ) || optimizelyDecision .getEnabled ()) {
1363+                 decisionMap .put (key , optimizelyDecision );
13181364            }
13191365        }
13201366
0 commit comments