11/****************************************************************************
2- * Copyright 2016-2017 , Optimizely, Inc. and contributors *
2+ * Copyright 2016-2018 , 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. *
1818import com .optimizely .ab .annotations .VisibleForTesting ;
1919import com .optimizely .ab .bucketing .Bucketer ;
2020import com .optimizely .ab .bucketing .DecisionService ;
21+ import com .optimizely .ab .bucketing .FeatureDecision ;
2122import com .optimizely .ab .bucketing .UserProfileService ;
2223import com .optimizely .ab .config .Attribute ;
2324import com .optimizely .ab .config .EventType ;
4041import com .optimizely .ab .event .internal .payload .Event .ClientEngine ;
4142import com .optimizely .ab .internal .EventTagUtils ;
4243import com .optimizely .ab .notification .NotificationBroadcaster ;
44+ import com .optimizely .ab .notification .NotificationCenter ;
4345import com .optimizely .ab .notification .NotificationListener ;
4446import org .slf4j .Logger ;
4547import org .slf4j .LoggerFactory ;
@@ -90,6 +92,8 @@ public class Optimizely {
9092 @ VisibleForTesting final EventHandler eventHandler ;
9193 @ VisibleForTesting final ErrorHandler errorHandler ;
9294 @ VisibleForTesting final NotificationBroadcaster notificationBroadcaster = new NotificationBroadcaster ();
95+ public final NotificationCenter notificationCenter = new NotificationCenter ();
96+
9397 @ Nullable private final UserProfileService userProfileService ;
9498
9599 private Optimizely (@ Nonnull ProjectConfig projectConfig ,
@@ -203,6 +207,9 @@ private void sendImpression(@Nonnull ProjectConfig projectConfig,
203207 }
204208
205209 notificationBroadcaster .broadcastExperimentActivated (experiment , userId , filteredAttributes , variation );
210+
211+ notificationCenter .sendNotifications (NotificationCenter .NotificationType .Activate , experiment , userId ,
212+ filteredAttributes , variation , impressionEvent );
206213 } else {
207214 logger .info ("Experiment has \" Launched\" status so not dispatching event during activation." );
208215 }
@@ -289,6 +296,8 @@ public void track(@Nonnull String eventName,
289296
290297 notificationBroadcaster .broadcastEventTracked (eventName , userId , filteredAttributes , eventValue ,
291298 conversionEvent );
299+ notificationCenter .sendNotifications (NotificationCenter .NotificationType .Track , eventName , userId ,
300+ filteredAttributes , eventTags , conversionEvent );
292301 }
293302
294303 //======== FeatureFlag APIs ========//
@@ -322,36 +331,39 @@ public void track(@Nonnull String eventName,
322331 public @ Nonnull Boolean isFeatureEnabled (@ Nonnull String featureKey ,
323332 @ Nonnull String userId ,
324333 @ 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+ }
325342 FeatureFlag featureFlag = projectConfig .getFeatureKeyMapping ().get (featureKey );
326343 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 );
328345 return false ;
329346 }
330347
331348 Map <String , String > filteredAttributes = filterAttributes (projectConfig , attributes );
332349
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 )) {
339353 sendImpression (
340354 projectConfig ,
341- experiment ,
355+ featureDecision . experiment ,
342356 userId ,
343357 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 );
349362 }
350- logger .info ("Feature \" " + featureKey + " \" is enabled for user \" " + userId + " \" ." );
363+ logger .info ("Feature \" {} \" is enabled for user \" {} \" ." , featureKey , userId );
351364 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 );
355367 return false ;
356368 }
357369 }
@@ -433,10 +445,9 @@ public void track(@Nonnull String eventName,
433445 if (variableValue != null ) {
434446 try {
435447 return Double .parseDouble (variableValue );
436- }
437- catch (NumberFormatException exception ) {
448+ } catch (NumberFormatException exception ) {
438449 logger .error ("NumberFormatException while trying to parse \" " + variableValue +
439- "\" as Double. " + exception );
450+ "\" as Double. " + exception );
440451 }
441452 }
442453 return null ;
@@ -479,10 +490,9 @@ public void track(@Nonnull String eventName,
479490 if (variableValue != null ) {
480491 try {
481492 return Integer .parseInt (variableValue );
482- }
483- catch (NumberFormatException exception ) {
493+ } catch (NumberFormatException exception ) {
484494 logger .error ("NumberFormatException while trying to parse \" " + variableValue +
485- "\" as Integer. " + exception .toString ());
495+ "\" as Integer. " + exception .toString ());
486496 }
487497 }
488498 return null ;
@@ -529,19 +539,30 @@ String getFeatureVariableValueForType(@Nonnull String featureKey,
529539 @ Nonnull String userId ,
530540 @ Nonnull Map <String , String > attributes ,
531541 @ 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+ }
532554 FeatureFlag featureFlag = projectConfig .getFeatureKeyMapping ().get (featureKey );
533555 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 );
535557 return null ;
536558 }
537559
538560 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 );
542564 return null ;
543- }
544- else if (!variable .getType ().equals (variableType )) {
565+ } else if (!variable .getType ().equals (variableType )) {
545566 logger .info ("The feature variable \" " + variableKey +
546567 "\" is actually of type \" " + variable .getType ().toString () +
547568 "\" type. You tried to access it as type \" " + variableType .toString () +
@@ -551,23 +572,19 @@ else if (!variable.getType().equals(variableType)) {
551572
552573 String variableValue = variable .getDefaultValue ();
553574
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 ) {
557577 LiveVariableUsageInstance liveVariableUsageInstance =
558- variation .getVariableIdToLiveVariableUsageInstanceMap ().get (variable .getId ());
578+ featureDecision . variation .getVariableIdToLiveVariableUsageInstanceMap ().get (variable .getId ());
559579 if (liveVariableUsageInstance != null ) {
560580 variableValue = liveVariableUsageInstance .getValue ();
561- }
562- else {
581+ } else {
563582 variableValue = variable .getDefaultValue ();
564583 }
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
571588 );
572589 }
573590
@@ -697,6 +714,7 @@ public UserProfileService getUserProfileService() {
697714 *
698715 * @param listener listener to add
699716 */
717+ @ Deprecated
700718 public void addNotificationListener (@ Nonnull NotificationListener listener ) {
701719 notificationBroadcaster .addListener (listener );
702720 }
@@ -706,13 +724,15 @@ public void addNotificationListener(@Nonnull NotificationListener listener) {
706724 *
707725 * @param listener listener to remove
708726 */
727+ @ Deprecated
709728 public void removeNotificationListener (@ Nonnull NotificationListener listener ) {
710729 notificationBroadcaster .removeListener (listener );
711730 }
712731
713732 /**
714733 * Remove all {@link NotificationListener}.
715734 */
735+ @ Deprecated
716736 public void clearNotificationListeners () {
717737 notificationBroadcaster .clearListeners ();
718738 }
@@ -782,7 +802,8 @@ private EventType getEventTypeOrThrow(ProjectConfig projectConfig, String eventN
782802 * {@link ProjectConfig}.
783803 *
784804 * @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.
786807 * @return the filtered attributes map (containing only attributes that are present in the project config) or an
787808 * empty map if a null attributes object is passed in
788809 */
@@ -797,7 +818,8 @@ private Map<String, String> filterAttributes(@Nonnull ProjectConfig projectConfi
797818
798819 Map <String , Attribute > attributeKeyMapping = projectConfig .getAttributeKeyMapping ();
799820 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 ) {
801823 if (unknownAttributes == null ) {
802824 unknownAttributes = new ArrayList <String >();
803825 }
0 commit comments