1
1
/****************************************************************************
2
- * Copyright 2016-2017 , Optimizely, Inc. and contributors *
2
+ * Copyright 2016-2018 , Optimizely, Inc. and contributors *
3
3
* *
4
4
* Licensed under the Apache License, Version 2.0 (the "License"); *
5
5
* you may not use this file except in compliance with the License. *
18
18
import com .optimizely .ab .annotations .VisibleForTesting ;
19
19
import com .optimizely .ab .bucketing .Bucketer ;
20
20
import com .optimizely .ab .bucketing .DecisionService ;
21
+ import com .optimizely .ab .bucketing .FeatureDecision ;
21
22
import com .optimizely .ab .bucketing .UserProfileService ;
22
23
import com .optimizely .ab .config .Attribute ;
23
24
import com .optimizely .ab .config .EventType ;
40
41
import com .optimizely .ab .event .internal .payload .Event .ClientEngine ;
41
42
import com .optimizely .ab .internal .EventTagUtils ;
42
43
import com .optimizely .ab .notification .NotificationBroadcaster ;
44
+ import com .optimizely .ab .notification .NotificationCenter ;
43
45
import com .optimizely .ab .notification .NotificationListener ;
44
46
import org .slf4j .Logger ;
45
47
import org .slf4j .LoggerFactory ;
@@ -90,6 +92,8 @@ public class Optimizely {
90
92
@ VisibleForTesting final EventHandler eventHandler ;
91
93
@ VisibleForTesting final ErrorHandler errorHandler ;
92
94
@ VisibleForTesting final NotificationBroadcaster notificationBroadcaster = new NotificationBroadcaster ();
95
+ public final NotificationCenter notificationCenter = new NotificationCenter ();
96
+
93
97
@ Nullable private final UserProfileService userProfileService ;
94
98
95
99
private Optimizely (@ Nonnull ProjectConfig projectConfig ,
@@ -203,6 +207,9 @@ private void sendImpression(@Nonnull ProjectConfig projectConfig,
203
207
}
204
208
205
209
notificationBroadcaster .broadcastExperimentActivated (experiment , userId , filteredAttributes , variation );
210
+
211
+ notificationCenter .sendNotifications (NotificationCenter .NotificationType .Activate , experiment , userId ,
212
+ filteredAttributes , variation , impressionEvent );
206
213
} else {
207
214
logger .info ("Experiment has \" Launched\" status so not dispatching event during activation." );
208
215
}
@@ -289,6 +296,8 @@ public void track(@Nonnull String eventName,
289
296
290
297
notificationBroadcaster .broadcastEventTracked (eventName , userId , filteredAttributes , eventValue ,
291
298
conversionEvent );
299
+ notificationCenter .sendNotifications (NotificationCenter .NotificationType .Track , eventName , userId ,
300
+ filteredAttributes , eventTags , conversionEvent );
292
301
}
293
302
294
303
//======== FeatureFlag APIs ========//
@@ -322,36 +331,39 @@ public void track(@Nonnull String eventName,
322
331
public @ Nonnull Boolean isFeatureEnabled (@ Nonnull String featureKey ,
323
332
@ Nonnull String userId ,
324
333
@ Nonnull Map <String , String > attributes ) {
334
+ if (featureKey == null ) {
335
+ logger .warn ("The featureKey parameter must be nonnull." );
336
+ return false ;
337
+ }
338
+ else if (userId == null ) {
339
+ logger .warn ("The userId parameter must be nonnull." );
340
+ return false ;
341
+ }
325
342
FeatureFlag featureFlag = projectConfig .getFeatureKeyMapping ().get (featureKey );
326
343
if (featureFlag == null ) {
327
- logger .info ("No feature flag was found for key \" " + featureKey + " \" ." );
344
+ logger .info ("No feature flag was found for key \" {} \" ." , featureKey );
328
345
return false ;
329
346
}
330
347
331
348
Map <String , String > filteredAttributes = filterAttributes (projectConfig , attributes );
332
349
333
- Variation variation = decisionService .getVariationForFeature (featureFlag , userId , filteredAttributes );
334
-
335
- if (variation != null ) {
336
- Experiment experiment = projectConfig .getExperimentForVariationId (variation .getId ());
337
- if (experiment != null ) {
338
- // the user is in an experiment for the feature
350
+ FeatureDecision featureDecision = decisionService .getVariationForFeature (featureFlag , userId , filteredAttributes );
351
+ if (featureDecision .variation != null ) {
352
+ if (featureDecision .decisionSource .equals (FeatureDecision .DecisionSource .EXPERIMENT )) {
339
353
sendImpression (
340
354
projectConfig ,
341
- experiment ,
355
+ featureDecision . experiment ,
342
356
userId ,
343
357
filteredAttributes ,
344
- variation );
345
- }
346
- else {
347
- logger .info ("The user \" " + userId +
348
- "\" is not being experimented on in feature \" " + featureKey + "\" ." );
358
+ featureDecision .variation );
359
+ } else {
360
+ logger .info ("The user \" {}\" is not included in an experiment for feature \" {}\" ." ,
361
+ userId , featureKey );
349
362
}
350
- logger .info ("Feature \" " + featureKey + " \" is enabled for user \" " + userId + " \" ." );
363
+ logger .info ("Feature \" {} \" is enabled for user \" {} \" ." , featureKey , userId );
351
364
return true ;
352
- }
353
- else {
354
- logger .info ("Feature \" " + featureKey + "\" is not enabled for user \" " + userId + "\" ." );
365
+ } else {
366
+ logger .info ("Feature \" {}\" is not enabled for user \" {}\" ." , featureKey , userId );
355
367
return false ;
356
368
}
357
369
}
@@ -433,10 +445,9 @@ public void track(@Nonnull String eventName,
433
445
if (variableValue != null ) {
434
446
try {
435
447
return Double .parseDouble (variableValue );
436
- }
437
- catch (NumberFormatException exception ) {
448
+ } catch (NumberFormatException exception ) {
438
449
logger .error ("NumberFormatException while trying to parse \" " + variableValue +
439
- "\" as Double. " + exception );
450
+ "\" as Double. " + exception );
440
451
}
441
452
}
442
453
return null ;
@@ -479,10 +490,9 @@ public void track(@Nonnull String eventName,
479
490
if (variableValue != null ) {
480
491
try {
481
492
return Integer .parseInt (variableValue );
482
- }
483
- catch (NumberFormatException exception ) {
493
+ } catch (NumberFormatException exception ) {
484
494
logger .error ("NumberFormatException while trying to parse \" " + variableValue +
485
- "\" as Integer. " + exception .toString ());
495
+ "\" as Integer. " + exception .toString ());
486
496
}
487
497
}
488
498
return null ;
@@ -529,19 +539,30 @@ String getFeatureVariableValueForType(@Nonnull String featureKey,
529
539
@ Nonnull String userId ,
530
540
@ Nonnull Map <String , String > attributes ,
531
541
@ Nonnull LiveVariable .VariableType variableType ) {
542
+ if (featureKey == null ) {
543
+ logger .warn ("The featureKey parameter must be nonnull." );
544
+ return null ;
545
+ }
546
+ else if (variableKey == null ) {
547
+ logger .warn ("The variableKey parameter must be nonnull." );
548
+ return null ;
549
+ }
550
+ else if (userId == null ) {
551
+ logger .warn ("The userId parameter must be nonnull." );
552
+ return null ;
553
+ }
532
554
FeatureFlag featureFlag = projectConfig .getFeatureKeyMapping ().get (featureKey );
533
555
if (featureFlag == null ) {
534
- logger .info ("No feature flag was found for key \" " + featureKey + " \" ." );
556
+ logger .info ("No feature flag was found for key \" {} \" ." , featureKey );
535
557
return null ;
536
558
}
537
559
538
560
LiveVariable variable = featureFlag .getVariableKeyToLiveVariableMap ().get (variableKey );
539
- if (variable == null ) {
540
- logger .info ("No feature variable was found for key \" " + variableKey + " \" in feature flag \" " +
541
- featureKey + " \" ." );
561
+ if (variable == null ) {
562
+ logger .info ("No feature variable was found for key \" {} \" in feature flag \" {} \" ." ,
563
+ variableKey , featureKey );
542
564
return null ;
543
- }
544
- else if (!variable .getType ().equals (variableType )) {
565
+ } else if (!variable .getType ().equals (variableType )) {
545
566
logger .info ("The feature variable \" " + variableKey +
546
567
"\" is actually of type \" " + variable .getType ().toString () +
547
568
"\" type. You tried to access it as type \" " + variableType .toString () +
@@ -551,23 +572,19 @@ else if (!variable.getType().equals(variableType)) {
551
572
552
573
String variableValue = variable .getDefaultValue ();
553
574
554
- Variation variation = decisionService .getVariationForFeature (featureFlag , userId , attributes );
555
-
556
- if (variation != null ) {
575
+ FeatureDecision featureDecision = decisionService .getVariationForFeature (featureFlag , userId , attributes );
576
+ if (featureDecision .variation != null ) {
557
577
LiveVariableUsageInstance liveVariableUsageInstance =
558
- variation .getVariableIdToLiveVariableUsageInstanceMap ().get (variable .getId ());
578
+ featureDecision . variation .getVariableIdToLiveVariableUsageInstanceMap ().get (variable .getId ());
559
579
if (liveVariableUsageInstance != null ) {
560
580
variableValue = liveVariableUsageInstance .getValue ();
561
- }
562
- else {
581
+ } else {
563
582
variableValue = variable .getDefaultValue ();
564
583
}
565
- }
566
- else {
567
- logger .info ("User \" " + userId +
568
- "\" was not bucketed into any variation for feature flag \" " + featureKey +
569
- "\" . The default value \" " + variableValue +
570
- "\" for \" " + variableKey + "\" is being returned."
584
+ } else {
585
+ logger .info ("User \" {}\" was not bucketed into any variation for feature flag \" {}\" . " +
586
+ "The default value \" {}\" for \" {}\" is being returned." ,
587
+ userId , featureKey , variableValue , variableKey
571
588
);
572
589
}
573
590
@@ -697,6 +714,7 @@ public UserProfileService getUserProfileService() {
697
714
*
698
715
* @param listener listener to add
699
716
*/
717
+ @ Deprecated
700
718
public void addNotificationListener (@ Nonnull NotificationListener listener ) {
701
719
notificationBroadcaster .addListener (listener );
702
720
}
@@ -706,13 +724,15 @@ public void addNotificationListener(@Nonnull NotificationListener listener) {
706
724
*
707
725
* @param listener listener to remove
708
726
*/
727
+ @ Deprecated
709
728
public void removeNotificationListener (@ Nonnull NotificationListener listener ) {
710
729
notificationBroadcaster .removeListener (listener );
711
730
}
712
731
713
732
/**
714
733
* Remove all {@link NotificationListener}.
715
734
*/
735
+ @ Deprecated
716
736
public void clearNotificationListeners () {
717
737
notificationBroadcaster .clearListeners ();
718
738
}
@@ -782,7 +802,8 @@ private EventType getEventTypeOrThrow(ProjectConfig projectConfig, String eventN
782
802
* {@link ProjectConfig}.
783
803
*
784
804
* @param projectConfig the current project config
785
- * @param attributes the attributes map to validate and potentially filter
805
+ * @param attributes the attributes map to validate and potentially filter. The reserved key for bucketing id
806
+ * {@link DecisionService#BUCKETING_ATTRIBUTE} is kept.
786
807
* @return the filtered attributes map (containing only attributes that are present in the project config) or an
787
808
* empty map if a null attributes object is passed in
788
809
*/
@@ -797,7 +818,8 @@ private Map<String, String> filterAttributes(@Nonnull ProjectConfig projectConfi
797
818
798
819
Map <String , Attribute > attributeKeyMapping = projectConfig .getAttributeKeyMapping ();
799
820
for (Map .Entry <String , String > attribute : attributes .entrySet ()) {
800
- if (!attributeKeyMapping .containsKey (attribute .getKey ())) {
821
+ if (!attributeKeyMapping .containsKey (attribute .getKey ()) &&
822
+ attribute .getKey () != com .optimizely .ab .bucketing .DecisionService .BUCKETING_ATTRIBUTE ) {
801
823
if (unknownAttributes == null ) {
802
824
unknownAttributes = new ArrayList <String >();
803
825
}
0 commit comments