Skip to content

Commit f6e5b0f

Browse files
committed
add JSON apis and notification support
1 parent b5db429 commit f6e5b0f

File tree

4 files changed

+172
-12
lines changed

4 files changed

+172
-12
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import com.optimizely.ab.optimizelyconfig.OptimizelyConfig;
3232
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigManager;
3333
import com.optimizely.ab.optimizelyconfig.OptimizelyConfigService;
34+
import com.optimizely.ab.optimizelyjson.OptimizelyJSON;
35+
import com.sun.org.apache.xpath.internal.operations.Variable;
3436
import org.slf4j.Logger;
3537
import org.slf4j.LoggerFactory;
3638

@@ -601,6 +603,46 @@ public String getFeatureVariableString(@Nonnull String featureKey,
601603
FeatureVariable.STRING_TYPE);
602604
}
603605

606+
/**
607+
* Get the JSON value of the specified variable in the feature.
608+
*
609+
* @param featureKey The unique key of the feature.
610+
* @param variableKey The unique key of the variable.
611+
* @param userId The ID of the user.
612+
* @return An OptimizelyJSON instance for the JSON variable value.
613+
* Null if the feature or variable could not be found.
614+
*/
615+
@Nullable
616+
public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
617+
@Nonnull String variableKey,
618+
@Nonnull String userId) {
619+
return getFeatureVariableJSON(featureKey, variableKey, userId, Collections.<String, String>emptyMap());
620+
}
621+
622+
/**
623+
* Get the JSON value of the specified variable in the feature.
624+
*
625+
* @param featureKey The unique key of the feature.
626+
* @param variableKey The unique key of the variable.
627+
* @param userId The ID of the user.
628+
* @param attributes The user's attributes.
629+
* @return An OptimizelyJSON instance for the JSON variable value.
630+
* Null if the feature or variable could not be found.
631+
*/
632+
@Nullable
633+
public OptimizelyJSON getFeatureVariableJSON(@Nonnull String featureKey,
634+
@Nonnull String variableKey,
635+
@Nonnull String userId,
636+
@Nonnull Map<String, ?> attributes) {
637+
638+
return getFeatureVariableValueForType(
639+
featureKey,
640+
variableKey,
641+
userId,
642+
attributes,
643+
FeatureVariable.JSON_TYPE);
644+
}
645+
604646
@VisibleForTesting
605647
<T> T getFeatureVariableValueForType(@Nonnull String featureKey,
606648
@Nonnull String variableKey,
@@ -714,6 +756,8 @@ Object convertStringToType(String variableValue, String type) {
714756
"\" as Integer. " + exception.toString());
715757
}
716758
break;
759+
case FeatureVariable.JSON_TYPE:
760+
return new OptimizelyJSON(variableValue);
717761
default:
718762
return variableValue;
719763
}
@@ -722,6 +766,103 @@ Object convertStringToType(String variableValue, String type) {
722766
return null;
723767
}
724768

769+
/**
770+
* Get the values of all variables in the feature.
771+
*
772+
* @param featureKey The unique key of the feature.
773+
* @param userId The ID of the user.
774+
* @return An OptimizelyJSON instance for all variable values.
775+
* Null if the feature could not be found.
776+
*/
777+
@Nullable
778+
public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
779+
@Nonnull String userId) {
780+
return getAllFeatureVariables(featureKey, userId, Collections.<String, String>emptyMap());
781+
}
782+
783+
/**
784+
* Get the values of all variables in the feature.
785+
*
786+
* @param featureKey The unique key of the feature.
787+
* @param userId The ID of the user.
788+
* @param attributes The user's attributes.
789+
* @return An OptimizelyJSON instance for all variable values.
790+
* Null if the feature could not be found.
791+
*/
792+
@Nullable
793+
public OptimizelyJSON getAllFeatureVariables(@Nonnull String featureKey,
794+
@Nonnull String userId,
795+
@Nonnull Map<String, ?> attributes) {
796+
797+
if (featureKey == null) {
798+
logger.warn("The featureKey parameter must be nonnull.");
799+
return null;
800+
} else if (userId == null) {
801+
logger.warn("The userId parameter must be nonnull.");
802+
return null;
803+
}
804+
805+
ProjectConfig projectConfig = getProjectConfig();
806+
if (projectConfig == null) {
807+
logger.error("Optimizely instance is not valid, failing getAllFeatureVariableValues call. type");
808+
return null;
809+
}
810+
811+
FeatureFlag featureFlag = projectConfig.getFeatureKeyMapping().get(featureKey);
812+
if (featureFlag == null) {
813+
logger.info("No feature flag was found for key \"{}\".", featureKey);
814+
return null;
815+
}
816+
817+
Map<String, ?> copiedAttributes = copyAttributes(attributes);
818+
FeatureDecision featureDecision = decisionService.getVariationForFeature(featureFlag, userId, copiedAttributes, projectConfig);
819+
Boolean featureEnabled = false;
820+
Variation variation = featureDecision.variation;
821+
822+
if (variation != null) {
823+
if (!variation.getFeatureEnabled()) {
824+
logger.info("Feature \"{}\" for variation \"{}\" was not enabled. " +
825+
"The default value is being returned.", featureKey, featureDecision.variation.getKey());
826+
}
827+
828+
featureEnabled = variation.getFeatureEnabled();
829+
} else {
830+
logger.info("User \"{}\" was not bucketed into any variation for feature flag \"{}\". " +
831+
"The default values are being returned.", userId, featureKey);
832+
}
833+
834+
Map<String, Object> valuesMap = new HashMap<String, Object>();
835+
for (FeatureVariable variable : featureFlag.getVariables()) {
836+
String value = variable.getDefaultValue();
837+
if (featureEnabled && variation != null) {
838+
FeatureVariableUsageInstance instance = variation.getVariableIdToFeatureVariableUsageInstanceMap().get(variable.getId());
839+
if (instance != null) {
840+
value = instance.getValue();
841+
}
842+
}
843+
844+
Object convertedValue = convertStringToType(value, variable.getType());
845+
if (convertedValue instanceof OptimizelyJSON) {
846+
convertedValue = ((OptimizelyJSON) convertedValue).toMap();
847+
}
848+
849+
valuesMap.put(variable.getKey(), value);
850+
}
851+
852+
DecisionNotification decisionNotification = DecisionNotification.newFeatureVariableDecisionNotificationBuilder()
853+
.withUserId(userId)
854+
.withAttributes(copiedAttributes)
855+
.withFeatureKey(featureKey)
856+
.withFeatureEnabled(featureEnabled)
857+
.withVariableValues(valuesMap)
858+
.withFeatureDecision(featureDecision)
859+
.build();
860+
861+
notificationCenter.send(decisionNotification);
862+
863+
return new OptimizelyJSON(valuesMap);
864+
}
865+
725866
/**
726867
* Get the list of features that are enabled for the user.
727868
* TODO revisit this method. Calling this as-is can dramatically increase visitor impression counts.

core-api/src/main/java/com/optimizely/ab/config/FeatureVariable.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public static VariableStatus fromString(String variableStatusString) {
6565
public static final String INTEGER_TYPE = "integer";
6666
public static final String DOUBLE_TYPE = "double";
6767
public static final String BOOLEAN_TYPE = "boolean";
68+
public static final String JSON_TYPE = "json";
6869

6970
private final String id;
7071
private final String key;

core-api/src/main/java/com/optimizely/ab/notification/DecisionNotification.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,16 @@ public static class FeatureVariableDecisionNotificationBuilder {
239239
public static final String VARIABLE_KEY = "variableKey";
240240
public static final String VARIABLE_TYPE = "variableType";
241241
public static final String VARIABLE_VALUE = "variableValue";
242+
public static final String VARIABLE_VALUES = "variableValues";
242243

244+
private NotificationCenter.DecisionNotificationType notificationType;
243245
private String featureKey;
244246
private Boolean featureEnabled;
245247
private FeatureDecision featureDecision;
246248
private String variableKey;
247249
private String variableType;
248250
private Object variableValue;
251+
private Object variableValues;
249252
private String userId;
250253
private Map<String, ?> attributes;
251254
private Map<String, Object> decisionInfo;
@@ -293,6 +296,11 @@ public FeatureVariableDecisionNotificationBuilder withVariableValue(Object varia
293296
return this;
294297
}
295298

299+
public FeatureVariableDecisionNotificationBuilder withVariableValues(Object variableValues) {
300+
this.variableValues = variableValues;
301+
return this;
302+
}
303+
296304
public DecisionNotification build() {
297305
if (featureKey == null) {
298306
throw new OptimizelyRuntimeException("featureKey not set");
@@ -302,20 +310,29 @@ public DecisionNotification build() {
302310
throw new OptimizelyRuntimeException("featureEnabled not set");
303311
}
304312

305-
if (variableKey == null) {
306-
throw new OptimizelyRuntimeException("variableKey not set");
307-
}
308-
309-
if (variableType == null) {
310-
throw new OptimizelyRuntimeException("variableType not set");
311-
}
313+
notificationType = (variableValues != null) ? NotificationCenter.DecisionNotificationType.ALL_FEATURE_VARIABLES :
314+
NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE;
312315

313316
decisionInfo = new HashMap<>();
314317
decisionInfo.put(FEATURE_KEY, featureKey);
315318
decisionInfo.put(FEATURE_ENABLED, featureEnabled);
316-
decisionInfo.put(VARIABLE_KEY, variableKey);
317-
decisionInfo.put(VARIABLE_TYPE, variableType.toString());
318-
decisionInfo.put(VARIABLE_VALUE, variableValue);
319+
320+
if (notificationType == NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE) {
321+
if (variableKey == null) {
322+
throw new OptimizelyRuntimeException("variableKey not set");
323+
}
324+
325+
if (variableType == null) {
326+
throw new OptimizelyRuntimeException("variableType not set");
327+
}
328+
329+
decisionInfo.put(VARIABLE_KEY, variableKey);
330+
decisionInfo.put(VARIABLE_TYPE, variableType.toString());
331+
decisionInfo.put(VARIABLE_VALUE, variableValue);
332+
} else if (notificationType == NotificationCenter.DecisionNotificationType.ALL_FEATURE_VARIABLES) {
333+
decisionInfo.put(VARIABLE_VALUES, variableValues);
334+
}
335+
319336
SourceInfo sourceInfo = new RolloutSourceInfo();
320337

321338
if (featureDecision != null && FeatureDecision.DecisionSource.FEATURE_TEST.equals(featureDecision.decisionSource)) {
@@ -327,7 +344,7 @@ public DecisionNotification build() {
327344
decisionInfo.put(SOURCE_INFO, sourceInfo.get());
328345

329346
return new DecisionNotification(
330-
NotificationCenter.DecisionNotificationType.FEATURE_VARIABLE.toString(),
347+
notificationType.toString(),
331348
userId,
332349
attributes,
333350
decisionInfo);

core-api/src/main/java/com/optimizely/ab/notification/NotificationCenter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public enum DecisionNotificationType {
5454
AB_TEST("ab-test"),
5555
FEATURE("feature"),
5656
FEATURE_TEST("feature-test"),
57-
FEATURE_VARIABLE("feature-variable");
57+
FEATURE_VARIABLE("feature-variable"),
58+
ALL_FEATURE_VARIABLES("all-feature-variables");
5859

5960
private final String key;
6061

0 commit comments

Comments
 (0)