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