diff --git a/core-api/src/main/java/com/optimizely/ab/Optimizely.java b/core-api/src/main/java/com/optimizely/ab/Optimizely.java index a471340b6..b96fcb716 100644 --- a/core-api/src/main/java/com/optimizely/ab/Optimizely.java +++ b/core-api/src/main/java/com/optimizely/ab/Optimizely.java @@ -35,7 +35,7 @@ import com.optimizely.ab.event.internal.BuildVersionInfo; import com.optimizely.ab.event.internal.EventFactory; import com.optimizely.ab.event.internal.payload.EventBatch.ClientEngine; -import com.optimizely.ab.notification.DecisionInfoEnums; +import com.optimizely.ab.notification.DecisionNotification; import com.optimizely.ab.notification.NotificationCenter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -653,7 +653,7 @@ T getFeatureVariableValueForType(@Nonnull String featureKey, Map copiedAttributes = copyAttributes(attributes); FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes); Boolean featureEnabled = false; - if (featureDecision.variation != null && featureDecision.variation.getFeatureEnabled()) { + if (featureDecision.variation != null) { FeatureVariableUsageInstance featureVariableUsageInstance = featureDecision.variation.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId()); if (featureVariableUsageInstance != null) { @@ -670,26 +670,20 @@ T getFeatureVariableValueForType(@Nonnull String featureKey, } Object convertedValue = convertStringToType(variableValue, variableType); - Map decisionInfo = new HashMap<>(); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), featureKey); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), featureEnabled); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), variableKey); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), variableType); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), convertedValue); - if (featureDecision.decisionSource != null && featureDecision.decisionSource.equals(FeatureDecision.DecisionSource.EXPERIMENT)) { - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), featureDecision.experiment.getKey()); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), featureDecision.variation.getKey()); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), featureDecision.decisionSource); - } else { - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), null); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), null); - decisionInfo.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.ROLLOUT); - } - notificationCenter.sendNotifications(NotificationCenter.NotificationType.Decision, - NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), - userId, - copiedAttributes, - decisionInfo); + + DecisionNotification decisionNotification = DecisionNotification.newFeatureVariableBuilder() + .withUserId(userId) + .withAttributes(copiedAttributes) + .withFeatureKey(featureKey) + .withFeatureEnabled(featureEnabled) + .withVariableKey(variableKey) + .withVariableType(variableType) + .withVariableValue(convertedValue) + .withFeatureDecision(featureDecision) + .build(); + + + notificationCenter.sendNotifications(decisionNotification); return (T) convertedValue; } diff --git a/core-api/src/main/java/com/optimizely/ab/notification/DecisionInfoEnums.java b/core-api/src/main/java/com/optimizely/ab/notification/DecisionInfoEnums.java deleted file mode 100644 index 98b4522ba..000000000 --- a/core-api/src/main/java/com/optimizely/ab/notification/DecisionInfoEnums.java +++ /dev/null @@ -1,41 +0,0 @@ -/**************************************************************************** - * Copyright 2019, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ - -package com.optimizely.ab.notification; - -public class DecisionInfoEnums { - public enum FeatureVariableDecisionInfo { - FEATURE_KEY("feature_key"), - FEATURE_ENABLED("feature_enabled"), - SOURCE("source"), - SOURCE_EXPERIMENT_KEY("source_experiment_key"), - SOURCE_VARIATION_KEY("source_variation_key"), - VARIABLE_KEY("variable_key"), - VARIABLE_TYPE("variable_type"), - VARIABLE_VALUE("variable_value"); - - private final String key; - - FeatureVariableDecisionInfo(String key) { - this.key = key; - } - - @Override - public String toString() { - return key; - } - } -} diff --git a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java new file mode 100644 index 000000000..7279866e2 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java @@ -0,0 +1,158 @@ +/**************************************************************************** + * Copyright 2019, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package com.optimizely.ab.notification; + + +import com.optimizely.ab.bucketing.FeatureDecision; +import com.optimizely.ab.config.FeatureVariable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +public class DecisionNotification { + protected String type; + protected String userId; + protected Map attributes; + protected Map decisionInfo; + + protected DecisionNotification() { + } + + protected DecisionNotification(@Nonnull String type, + @Nonnull String userId, + @Nullable Map attributes, + @Nonnull Map decisionInfo) { + this.type = type; + this.userId = userId; + if (attributes == null) { + attributes = new HashMap<>(); + } + this.attributes = attributes; + this.decisionInfo = decisionInfo; + } + + public String getType() { + return type; + } + + public String getUserId() { + return userId; + } + + public Map getAttributes() { + return attributes; + } + + public Map getDecisionInfo() { + return decisionInfo; + } + + public static FeatureVariableDecisionNotificationBuilder newFeatureVariableBuilder() { + return new FeatureVariableDecisionNotificationBuilder(); + } + + public static class FeatureVariableDecisionNotificationBuilder { + + public static final String FEATURE_KEY = "feature_key"; + public static final String FEATURE_ENABLED = "feature_enabled"; + public static final String SOURCE = "source"; + public static final String SOURCE_EXPERIMENT_KEY = "source_experiment_key"; + public static final String SOURCE_VARIATION_KEY = "source_variation_key"; + public static final String VARIABLE_KEY = "variable_key"; + public static final String VARIABLE_TYPE = "variable_type"; + public static final String VARIABLE_VALUE = "variable_value"; + + private String featureKey; + private Boolean featureEnabled; + private FeatureDecision featureDecision; + private String variableKey; + private FeatureVariable.VariableType variableType; + private Object variableValue; + private String userId; + private Map attributes; + private Map decisionInfo; + + protected FeatureVariableDecisionNotificationBuilder() { + } + + public FeatureVariableDecisionNotificationBuilder withUserId(String userId) { + this.userId = userId; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withAttributes(Map attributes) { + this.attributes = attributes; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withFeatureKey(String featureKey) { + this.featureKey = featureKey; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withFeatureEnabled(boolean featureEnabled) { + this.featureEnabled = featureEnabled; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withFeatureDecision(FeatureDecision featureDecision) { + this.featureDecision = featureDecision; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withVariableKey(String variableKey) { + this.variableKey = variableKey; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withVariableType(FeatureVariable.VariableType variableType) { + this.variableType = variableType; + return this; + } + + public FeatureVariableDecisionNotificationBuilder withVariableValue(Object variableValue) { + this.variableValue = variableValue; + return this; + } + + public DecisionNotification build() { + decisionInfo = new HashMap<>(); + decisionInfo.put(FEATURE_KEY, featureKey); + decisionInfo.put(FEATURE_ENABLED, featureEnabled); + decisionInfo.put(VARIABLE_KEY, variableKey); + decisionInfo.put(VARIABLE_TYPE, variableType); + decisionInfo.put(VARIABLE_VALUE, variableValue); + if (featureDecision != null && featureDecision.decisionSource != null && FeatureDecision.DecisionSource.EXPERIMENT.equals(featureDecision.decisionSource)) { + decisionInfo.put(SOURCE_EXPERIMENT_KEY, featureDecision.experiment.getKey()); + decisionInfo.put(SOURCE_VARIATION_KEY, featureDecision.variation.getKey()); + decisionInfo.put(SOURCE, featureDecision.decisionSource); + } else { + decisionInfo.put(SOURCE_EXPERIMENT_KEY, null); + decisionInfo.put(SOURCE_VARIATION_KEY, null); + decisionInfo.put(SOURCE, FeatureDecision.DecisionSource.ROLLOUT); + } + + return new DecisionNotification( + NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), + userId, + attributes, + decisionInfo); + } + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListener.java b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListener.java index 4f583b73c..f86b1ef27 100644 --- a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListener.java +++ b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListener.java @@ -17,37 +17,17 @@ package com.optimizely.ab.notification; import javax.annotation.Nonnull; -import java.util.HashMap; -import java.util.Map; -public abstract class DecisionNotificationListener implements NotificationListener, DecisionNotificationListenerInterface { +public interface DecisionNotificationListener { /** - * Base notify called with var args. This method parses the parameters and calls the abstract method. + * onDecision called when an activate was triggered * - * @param args - variable argument list based on the type of notification. + * @param decisionNotification - The decision notification object containing: + * type - The notification type. + * userId - The userId passed to the API. + * attributes - The attribute map passed to the API. + * decisionInfo - The decision information containing all parameters passed in API. */ - @Override - public final void notify(Object... args) { - assert (args[0] instanceof String); - String type = (String) args[0]; - assert (args[1] instanceof String); - String userId = (String) args[1]; - Map attributes = null; - if (args[2] != null) { - assert (args[2] instanceof java.util.Map); - attributes = (Map) args[2]; - } else { - attributes = new HashMap<>(); - } - assert (args[3] instanceof java.util.Map); - Map decisionInfo = (Map) args[3]; - onDecision(type, userId, attributes, decisionInfo); - } - - @Override - public abstract void onDecision(@Nonnull String type, - @Nonnull String userId, - @Nonnull Map attributes, - @Nonnull Map decisionInfo); + void onDecision(@Nonnull DecisionNotification decisionNotification); } diff --git a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListenerInterface.java b/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListenerInterface.java deleted file mode 100644 index ed566c46c..000000000 --- a/core-api/src/main/java/com/optimizely/ab/notification/DecisionNotificationListenerInterface.java +++ /dev/null @@ -1,36 +0,0 @@ -/**************************************************************************** - * Copyright 2019, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ - -package com.optimizely.ab.notification; - -import javax.annotation.Nonnull; -import java.util.Map; - -public interface DecisionNotificationListenerInterface { - - /** - * onDecision called when an activate was triggered - * - * @param type - The notification type. - * @param userId - The userId passed to the API. - * @param attributes - The attribute map passed to the API. - * @param decisionInfo - The decision information containing all parameters passed in API. - */ - void onDecision(@Nonnull String type, - @Nonnull String userId, - @Nonnull Map attributes, - @Nonnull Map decisionInfo); -} diff --git a/core-api/src/main/java/com/optimizely/ab/notification/NotificationCenter.java b/core-api/src/main/java/com/optimizely/ab/notification/NotificationCenter.java index b17bc0939..cce5bc426 100644 --- a/core-api/src/main/java/com/optimizely/ab/notification/NotificationCenter.java +++ b/core-api/src/main/java/com/optimizely/ab/notification/NotificationCenter.java @@ -25,7 +25,9 @@ import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** @@ -57,8 +59,7 @@ public String toString() { public enum NotificationType { Activate(ActivateNotificationListener.class), // Activate was called. Track an impression event - Track(TrackNotificationListener.class), // Track was called. Track a conversion event - Decision(DecisionNotificationListener.class); // Decision was made. + Track(TrackNotificationListener.class); // Track was called. Track a conversion event private Class notificationTypeClass; @@ -75,7 +76,7 @@ public Class getNotificationTypeClass() { // the notification id is incremented and is assigned as the callback id, it can then be used to remove the notification. - private int notificationListenerID = 1; + private AtomicInteger notificationListenerID = new AtomicInteger(); final private static Logger logger = LoggerFactory.getLogger(NotificationCenter.class); @@ -83,11 +84,17 @@ public Class getNotificationTypeClass() { private static class NotificationHolder { int notificationId; NotificationListener notificationListener; + DecisionNotificationListener decisionNotificationListener; NotificationHolder(int id, NotificationListener notificationListener) { notificationId = id; this.notificationListener = notificationListener; } + + NotificationHolder(int id, DecisionNotificationListener decisionNotificationListener) { + notificationId = id; + this.decisionNotificationListener = decisionNotificationListener; + } } /** @@ -96,29 +103,34 @@ private static class NotificationHolder { public NotificationCenter() { notificationsListeners.put(NotificationType.Activate, new ArrayList()); notificationsListeners.put(NotificationType.Track, new ArrayList()); - notificationsListeners.put(NotificationType.Decision, new ArrayList()); } // private list of notification by notification type. // we used a list so that notification order can mean something. private Map> notificationsListeners = new HashMap>(); + private List decisionListenerHolder = new ArrayList<>(); /** * Convenience method to support lambdas as callbacks in later version of Java (8+). * - * @param decisionNotificationListenerInterface + * @param decisionNotificationListener * @return greater than zero if added. */ - public int addDecisionNotificationListener(final DecisionNotificationListenerInterface decisionNotificationListenerInterface) { - if (decisionNotificationListenerInterface instanceof DecisionNotificationListener) { - return addNotificationListener(NotificationType.Decision, (NotificationListener) decisionNotificationListenerInterface); - } else { - return addNotificationListener(NotificationType.Decision, new DecisionNotificationListener() { - @Override - public void onDecision(@Nonnull String type, @Nonnull String userId, @Nonnull Map attributes, @Nonnull Map decisionInfo) { - decisionNotificationListenerInterface.onDecision(type, userId, attributes, decisionInfo); + public int addDecisionNotificationListener(DecisionNotificationListener decisionNotificationListener) { + if (decisionNotificationListener != null) { + for (NotificationHolder holder : decisionListenerHolder) { + if (holder.decisionNotificationListener == decisionNotificationListener) { + // TODO: 3/27/2019 change log level from warn to info and to return existing listener ID + logger.warn("Notification listener was already added"); + return -1; } - }); + } + int id = this.notificationListenerID.incrementAndGet(); + decisionListenerHolder.add(new NotificationHolder(id, decisionNotificationListener)); + return id; + } else { + logger.warn("Notification listener was the wrong type. It was not added to the notification center."); + return -1; } } @@ -176,12 +188,12 @@ public int addNotificationListener(NotificationType notificationType, Notificati } for (NotificationHolder holder : notificationsListeners.get(notificationType)) { - if (holder.notificationListener == notificationListener ) { + if (holder.notificationListener == notificationListener) { logger.warn("Notification listener was already added"); return -1; } } - int id = notificationListenerID++; + int id = this.notificationListenerID.incrementAndGet(); notificationsListeners.get(notificationType).add(new NotificationHolder(id, notificationListener)); logger.info("Notification listener {} was added with id {}", notificationListener.toString(), id); return id; @@ -194,25 +206,46 @@ public int addNotificationListener(NotificationType notificationType, Notificati * @return true if removed otherwise false (if the notification is already registered, it returns false). */ public boolean removeNotificationListener(int notificationID) { - for (NotificationType type : NotificationType.values()) { - for (NotificationHolder holder : notificationsListeners.get(type)) { - if (holder.notificationId == notificationID) { - notificationsListeners.get(type).remove(holder); - logger.info("Notification listener removed {}", notificationID); - return true; - } + + for (List notificationHolders : notificationsListeners.values()) { + if (removeNotificationListener(notificationID, notificationHolders)) { + return true; } } + if (removeNotificationListener(notificationID, decisionListenerHolder)) { + return true; + } + logger.warn("Notification listener with id {} not found", notificationID); return false; } + /** + * Helper method to iterate find NotificationHolder in an List identified by the notificationId + * + * @param notificationID the id passed back from add notification. + * @param notificationHolderList list from which to remove notification listener. + * @return true if removed otherwise false + */ + private boolean removeNotificationListener(int notificationID, List notificationHolderList) { + for (NotificationHolder holder : notificationHolderList) { + if (holder.notificationId == notificationID) { + notificationHolderList.remove(holder); + logger.info("Notification listener removed {}", notificationID); + return true; + } + } + + return false; + } + /** * Clear out all the notification listeners. */ public void clearAllNotificationListeners() { + decisionListenerHolder.clear(); for (NotificationType type : NotificationType.values()) { clearNotificationListeners(type); } @@ -227,6 +260,21 @@ public void clearNotificationListeners(NotificationType notificationType) { notificationsListeners.get(notificationType).clear(); } + /** + * fire a notificaiton of Decision Notification type. + * + * @param decision containing Decision Notification object + */ + public void sendNotifications(DecisionNotification decision) { + for (NotificationHolder holder : decisionListenerHolder) { + try { + holder.decisionNotificationListener.onDecision(decision); + } catch (Exception e) { + logger.error("Unexpected exception calling notification listener {}", holder.notificationId, e); + } + } + } + // fire a notificaiton of a certain type. The arg list changes depending on the type of notification sent. public void sendNotifications(NotificationType notificationType, Object... args) { ArrayList holders = notificationsListeners.get(notificationType); diff --git a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java index 992df9e0a..142a80d62 100644 --- a/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java +++ b/core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java @@ -39,11 +39,8 @@ import com.optimizely.ab.event.internal.payload.EventBatch; import com.optimizely.ab.internal.LogbackVerifier; import com.optimizely.ab.internal.ControlAttribute; -import com.optimizely.ab.notification.ActivateNotificationListener; -import com.optimizely.ab.notification.NotificationCenter; -import com.optimizely.ab.notification.TrackNotificationListener; -import com.optimizely.ab.notification.DecisionNotificationListener; -import com.optimizely.ab.notification.DecisionInfoEnums; +import com.optimizely.ab.notification.*; +import com.optimizely.ab.notification.DecisionNotification; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; @@ -69,6 +66,14 @@ import static com.optimizely.ab.config.ProjectConfigTestUtils.*; import static com.optimizely.ab.config.ValidProjectConfigV4.*; import static com.optimizely.ab.event.LogEvent.RequestMethod; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.FEATURE_ENABLED; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.FEATURE_KEY; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.SOURCE; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.SOURCE_EXPERIMENT_KEY; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.SOURCE_VARIATION_KEY; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.VARIABLE_KEY; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.VARIABLE_VALUE; +import static com.optimizely.ab.notification.DecisionNotification.FeatureVariableDecisionNotificationBuilder.VARIABLE_TYPE; import static java.util.Arrays.asList; import static junit.framework.TestCase.assertTrue; import static org.hamcrest.CoreMatchers.is; @@ -2649,14 +2654,14 @@ private DecisionNotificationListener getDecisionListener(final String testType, final Map testDecisionInfo) { return new DecisionNotificationListener() { @Override - public void onDecision(@Nonnull String type, @Nonnull String userId, @Nonnull Map attributes, @Nonnull Map decisionInfo) { - assertEquals(type, testType); - assertEquals(userId, testUserId); - assertEquals(attributes, testUserAttributes); - for (Map.Entry entry : attributes.entrySet()) { + public void onDecision(@Nonnull DecisionNotification decisionNotification) { + assertEquals(decisionNotification.getType(), testType); + assertEquals(decisionNotification.getUserId(), testUserId); + assertEquals(decisionNotification.getAttributes(), testUserAttributes); + for (Map.Entry entry : decisionNotification.getAttributes().entrySet()) { assertEquals(testUserAttributes.get(entry.getKey()), entry.getValue()); } - for (Map.Entry entry : decisionInfo.entrySet()) { + for (Map.Entry entry : decisionNotification.getDecisionInfo().entrySet()) { assertEquals(testDecisionInfo.get(entry.getKey()), entry.getValue()); } isListenerCalled = true; @@ -2687,16 +2692,16 @@ public void getFeatureVariableWithListenerUserInExperimentFeatureOn() throws Exc testUserAttributes.put(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE); final Map testDecisionInfoMap = new HashMap<>(); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), "multivariate_experiment"); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), "Fred"); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), validFeatureKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), true); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), validVariableKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), FeatureVariable.VariableType.STRING); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), expectedValue); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.EXPERIMENT); - - int notificationId = optimizely.notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, + testDecisionInfoMap.put(SOURCE_EXPERIMENT_KEY, "multivariate_experiment"); + testDecisionInfoMap.put(SOURCE_VARIATION_KEY, "Fred"); + testDecisionInfoMap.put(FEATURE_KEY, validFeatureKey); + testDecisionInfoMap.put(FEATURE_ENABLED, true); + testDecisionInfoMap.put(VARIABLE_KEY, validVariableKey); + testDecisionInfoMap.put(VARIABLE_TYPE, FeatureVariable.VariableType.STRING); + testDecisionInfoMap.put(VARIABLE_VALUE, expectedValue); + testDecisionInfoMap.put(SOURCE, FeatureDecision.DecisionSource.EXPERIMENT); + + int notificationId = optimizely.notificationCenter.addDecisionNotificationListener( getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), testUserId, testUserAttributes, @@ -2726,7 +2731,7 @@ public void getFeatureVariableWithListenerUserInExperimentFeatureOff() { isListenerCalled = false; final String validFeatureKey = FEATURE_MULTI_VARIATE_FEATURE_KEY; String validVariableKey = VARIABLE_FIRST_LETTER_KEY; - String expectedValue = "H"; + String expectedValue = "G"; String userID = "Gred"; Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) @@ -2736,16 +2741,16 @@ public void getFeatureVariableWithListenerUserInExperimentFeatureOff() { final Map testUserAttributes = new HashMap<>(); final Map testDecisionInfoMap = new HashMap<>(); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), "multivariate_experiment"); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), "Gred"); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), validFeatureKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), false); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), validVariableKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), FeatureVariable.VariableType.STRING); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), expectedValue); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.EXPERIMENT); - - int notificationId = optimizely.notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, + testDecisionInfoMap.put(SOURCE_EXPERIMENT_KEY, "multivariate_experiment"); + testDecisionInfoMap.put(SOURCE_VARIATION_KEY, "Gred"); + testDecisionInfoMap.put(FEATURE_KEY, validFeatureKey); + testDecisionInfoMap.put(FEATURE_ENABLED, false); + testDecisionInfoMap.put(VARIABLE_KEY, validVariableKey); + testDecisionInfoMap.put(VARIABLE_TYPE, FeatureVariable.VariableType.STRING); + testDecisionInfoMap.put(VARIABLE_VALUE, expectedValue); + testDecisionInfoMap.put(SOURCE, FeatureDecision.DecisionSource.EXPERIMENT); + + int notificationId = optimizely.notificationCenter.addDecisionNotificationListener( getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), userID, testUserAttributes, @@ -2786,15 +2791,15 @@ public void getFeatureVariableWithListenerUserInRollOutFeatureOn() throws Except final Map testDecisionInfoMap = new HashMap<>(); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), null); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), validFeatureKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), true); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), validVariableKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), FeatureVariable.VariableType.STRING); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), expectedValue); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.ROLLOUT); + testDecisionInfoMap.put(SOURCE_EXPERIMENT_KEY, null); + testDecisionInfoMap.put(FEATURE_KEY, validFeatureKey); + testDecisionInfoMap.put(FEATURE_ENABLED, true); + testDecisionInfoMap.put(VARIABLE_KEY, validVariableKey); + testDecisionInfoMap.put(VARIABLE_TYPE, FeatureVariable.VariableType.STRING); + testDecisionInfoMap.put(VARIABLE_VALUE, expectedValue); + testDecisionInfoMap.put(SOURCE, FeatureDecision.DecisionSource.ROLLOUT); - int notificationId = optimizely.notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, + int notificationId = optimizely.notificationCenter.addDecisionNotificationListener( getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), genericUserId, testUserAttributes, @@ -2835,15 +2840,15 @@ public void getFeatureVariableWithListenerUserNotInRollOutFeatureOff() { final Map testDecisionInfoMap = new HashMap<>(); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), null); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), validFeatureKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), false); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), validVariableKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), FeatureVariable.VariableType.BOOLEAN); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), expectedValue); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.ROLLOUT); + testDecisionInfoMap.put(SOURCE_EXPERIMENT_KEY, null); + testDecisionInfoMap.put(FEATURE_KEY, validFeatureKey); + testDecisionInfoMap.put(FEATURE_ENABLED, false); + testDecisionInfoMap.put(VARIABLE_KEY, validVariableKey); + testDecisionInfoMap.put(VARIABLE_TYPE, FeatureVariable.VariableType.BOOLEAN); + testDecisionInfoMap.put(VARIABLE_VALUE, expectedValue); + testDecisionInfoMap.put(SOURCE, FeatureDecision.DecisionSource.ROLLOUT); - int notificationId = optimizely.notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, + int notificationId = optimizely.notificationCenter.addDecisionNotificationListener( getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), genericUserId, testUserAttributes, @@ -2883,15 +2888,15 @@ public void getFeatureVariableIntegerWithListenerUserInRollOutFeatureOn() { testUserAttributes.put(ATTRIBUTE_HOUSE_KEY, AUDIENCE_GRYFFINDOR_VALUE); final Map testDecisionInfoMap = new HashMap<>(); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), null); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), validFeatureKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), true); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), validVariableKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), FeatureVariable.VariableType.INTEGER); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), expectedValue); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.ROLLOUT); - - int notificationId = optimizely.notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, + testDecisionInfoMap.put(SOURCE_EXPERIMENT_KEY, null); + testDecisionInfoMap.put(FEATURE_KEY, validFeatureKey); + testDecisionInfoMap.put(FEATURE_ENABLED, true); + testDecisionInfoMap.put(VARIABLE_KEY, validVariableKey); + testDecisionInfoMap.put(VARIABLE_TYPE, FeatureVariable.VariableType.INTEGER); + testDecisionInfoMap.put(VARIABLE_VALUE, expectedValue); + testDecisionInfoMap.put(SOURCE, FeatureDecision.DecisionSource.ROLLOUT); + + int notificationId = optimizely.notificationCenter.addDecisionNotificationListener( getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), genericUserId, testUserAttributes, @@ -2930,16 +2935,16 @@ public void getFeatureVariableDoubleWithListenerUserInExperimentFeatureOn() thro testUserAttributes.put(ATTRIBUTE_HOUSE_KEY, AUDIENCE_SLYTHERIN_VALUE); final Map testDecisionInfoMap = new HashMap<>(); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_EXPERIMENT_KEY.toString(), "double_single_variable_feature_experiment"); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE_VARIATION_KEY.toString(), "pi_variation"); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_KEY.toString(), validFeatureKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.FEATURE_ENABLED.toString(), true); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_KEY.toString(), validVariableKey); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_TYPE.toString(), FeatureVariable.VariableType.DOUBLE); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.VARIABLE_VALUE.toString(), 3.14); - testDecisionInfoMap.put(DecisionInfoEnums.FeatureVariableDecisionInfo.SOURCE.toString(), FeatureDecision.DecisionSource.EXPERIMENT); - - int notificationId = optimizely.notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, + testDecisionInfoMap.put(SOURCE_EXPERIMENT_KEY, "double_single_variable_feature_experiment"); + testDecisionInfoMap.put(SOURCE_VARIATION_KEY, "pi_variation"); + testDecisionInfoMap.put(FEATURE_KEY, validFeatureKey); + testDecisionInfoMap.put(FEATURE_ENABLED, true); + testDecisionInfoMap.put(VARIABLE_KEY, validVariableKey); + testDecisionInfoMap.put(VARIABLE_TYPE, FeatureVariable.VariableType.DOUBLE); + testDecisionInfoMap.put(VARIABLE_VALUE, 3.14); + testDecisionInfoMap.put(SOURCE, FeatureDecision.DecisionSource.EXPERIMENT); + + int notificationId = optimizely.notificationCenter.addDecisionNotificationListener( getDecisionListener(NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(), genericUserId, testUserAttributes, @@ -3681,7 +3686,8 @@ public void getFeatureVariableValueReturnsVariationValueWhenUserGetsBucketedToVa String validFeatureKey = FEATURE_MULTI_VARIATE_FEATURE_KEY; String validVariableKey = VARIABLE_FIRST_LETTER_KEY; - String expectedValue = VARIABLE_FIRST_LETTER_DEFAULT_VALUE; + FeatureVariable variable = FEATURE_FLAG_MULTI_VARIATE_FEATURE.getVariableKeyToFeatureVariableMap().get(validVariableKey); + String expectedValue = VARIATION_MULTIVARIATE_EXPERIMENT_GRED.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId()).getValue();; Experiment multivariateExperiment = validProjectConfig.getExperimentKeyMapping().get(EXPERIMENT_MULTIVARIATE_EXPERIMENT_KEY); Optimizely optimizely = Optimizely.builder(validDatafile, mockEventHandler) diff --git a/core-api/src/test/java/com/optimizely/ab/notification/NotificationCenterTest.java b/core-api/src/test/java/com/optimizely/ab/notification/NotificationCenterTest.java index 405e930ce..70bba7627 100644 --- a/core-api/src/test/java/com/optimizely/ab/notification/NotificationCenterTest.java +++ b/core-api/src/test/java/com/optimizely/ab/notification/NotificationCenterTest.java @@ -70,12 +70,12 @@ public void testAddWrongActivateNotificationListener() { public void testAddDecisionNotificationTwice() { DecisionNotificationListener listener = new DecisionNotificationListener() { @Override - public void onDecision(@Nonnull String type, @Nonnull String userId, @Nonnull Map attributes, @Nonnull Map decisionInfo) { + public void onDecision(@Nonnull DecisionNotification decisionNotification) { } }; - int notificationId = notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, listener); - int notificationId2 = notificationCenter.addNotificationListener(NotificationCenter.NotificationType.Decision, listener); + int notificationId = notificationCenter.addDecisionNotificationListener(listener); + int notificationId2 = notificationCenter.addDecisionNotificationListener(listener); logbackVerifier.expectMessage(Level.WARN, "Notification listener was already added"); assertEquals(notificationId2, -1); assertTrue(notificationCenter.removeNotificationListener(notificationId)); @@ -115,7 +115,7 @@ public void onActivate(@Nonnull Experiment experiment, @Nonnull String userId, @ public void testAddDecisionNotification() { int notificationId = notificationCenter.addDecisionNotificationListener(new DecisionNotificationListener() { @Override - public void onDecision(@Nonnull String type, @Nonnull String userId, @Nonnull Map attributes, @Nonnull Map decisionInfo) { + public void onDecision(@Nonnull DecisionNotification decisionNotification) { } }); @@ -159,9 +159,9 @@ public void onTrack(@Nonnull String eventKey, @Nonnull String userId, @Nonnull M @Test public void testAddDecisionNotificationInterface() { - int notificationId = notificationCenter.addDecisionNotificationListener(new DecisionNotificationListenerInterface() { + int notificationId = notificationCenter.addDecisionNotificationListener(new DecisionNotificationListener() { @Override - public void onDecision(@Nonnull String type, @Nonnull String userId, @Nonnull Map attributes, @Nonnull Map decisionInfo) { + public void onDecision(@Nonnull DecisionNotification decisionNotification) { } });