Skip to content

feature flag parsing #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions core-api/src/main/java/com/optimizely/ab/config/FeatureFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;
import java.util.Map;

/**
* Represents a FeatureFlag definition at the project level
Expand All @@ -33,6 +34,7 @@ public class FeatureFlag implements IdKeyMapped{
private final String layerId;
private final List<String> experimentIds;
private final List<LiveVariable> variables;
private final Map<String, LiveVariable> variableKeyToLiveVariableMap;

@JsonCreator
public FeatureFlag(@JsonProperty("id") String id,
Expand All @@ -45,6 +47,7 @@ public FeatureFlag(@JsonProperty("id") String id,
this.layerId = layerId;
this.experimentIds = experimentIds;
this.variables = variables;
this.variableKeyToLiveVariableMap = ProjectConfigUtils.generateNameMapping(variables);
}

public String getId() {
Expand All @@ -67,6 +70,10 @@ public List<LiveVariable> getVariables() {
return variables;
}

public Map<String, LiveVariable> getVariableKeyToLiveVariableMap() {
return variableKeyToLiveVariableMap;
}

@Override
public String toString() {
return "FeatureFlag{" +
Expand All @@ -75,6 +82,33 @@ public String toString() {
", layerId='" + layerId + '\'' +
", experimentIds=" + experimentIds +
", variables=" + variables +
", variableKeyToLiveVariableMap=" + variableKeyToLiveVariableMap +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

FeatureFlag that = (FeatureFlag) o;

if (!id.equals(that.id)) return false;
if (!key.equals(that.key)) return false;
if (!layerId.equals(that.layerId)) return false;
if (!experimentIds.equals(that.experimentIds)) return false;
if (!variables.equals(that.variables)) return false;
return variableKeyToLiveVariableMap.equals(that.variableKeyToLiveVariableMap);
}

@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + key.hashCode();
result = 31 * result + layerId.hashCode();
result = 31 * result + experimentIds.hashCode();
result = 31 * result + variables.hashCode();
result = 31 * result + variableKeyToLiveVariableMap.hashCode();
return result;
}
}
37 changes: 29 additions & 8 deletions core-api/src/main/java/com/optimizely/ab/config/LiveVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.gson.annotations.SerializedName;

import javax.annotation.Nullable;

/**
* Represents a live variable definition at the project level
*/
Expand Down Expand Up @@ -100,7 +102,7 @@ public static VariableType fromString(String variableTypeString) {
private final String key;
private final String defaultValue;
private final VariableType type;
private final VariableStatus status;
@Nullable private final VariableStatus status;

@JsonCreator
public LiveVariable(@JsonProperty("id") String id,
Expand All @@ -111,16 +113,11 @@ public LiveVariable(@JsonProperty("id") String id,
this.id = id;
this.key = key;
this.defaultValue = defaultValue;
if (status == null) {
this.status = VariableStatus.ACTIVE;
}
else {
this.status = status;
}
this.status = status;
this.type = type;
}

public VariableStatus getStatus() {
public @Nullable VariableStatus getStatus() {
return status;
}

Expand Down Expand Up @@ -150,4 +147,28 @@ public String toString() {
", status=" + status +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

LiveVariable variable = (LiveVariable) o;

if (!id.equals(variable.id)) return false;
if (!key.equals(variable.key)) return false;
if (!defaultValue.equals(variable.defaultValue)) return false;
if (type != variable.type) return false;
return status == variable.status;
}

@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + key.hashCode();
result = 31 * result + defaultValue.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + status.hashCode();
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* Represents the value of a live variable for a variation
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class LiveVariableUsageInstance {
public class LiveVariableUsageInstance implements IdMapped {

private final String id;
private final String value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ public List<Experiment> getExperimentsForEventKey(String eventKey) {
return Collections.emptyList();
}

public List<FeatureFlag> getFeatureFlags() {
return featureFlags;
}

public List<Attribute> getAttributes() {
return attributes;
}
Expand Down
15 changes: 14 additions & 1 deletion core-api/src/main/java/com/optimizely/ab/config/Variation.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
* Represents the Optimizely Variation configuration.
Expand All @@ -36,6 +38,7 @@ public class Variation implements IdKeyMapped {
private final String id;
private final String key;
private final List<LiveVariableUsageInstance> liveVariableUsageInstances;
private final Map<String, LiveVariableUsageInstance> variableIdToLiveVariableUsageInstanceMap;

public Variation(String id, String key) {
this(id, key, null);
Expand All @@ -47,7 +50,13 @@ public Variation(@JsonProperty("id") String id,
@JsonProperty("variables") List<LiveVariableUsageInstance> liveVariableUsageInstances) {
this.id = id;
this.key = key;
this.liveVariableUsageInstances = liveVariableUsageInstances;
if (liveVariableUsageInstances == null) {
this.liveVariableUsageInstances = Collections.emptyList();
}
else {
this.liveVariableUsageInstances = liveVariableUsageInstances;
}
this.variableIdToLiveVariableUsageInstanceMap = ProjectConfigUtils.generateIdMapping(this.liveVariableUsageInstances);
}

public @Nonnull String getId() {
Expand All @@ -62,6 +71,10 @@ public Variation(@JsonProperty("id") String id,
return liveVariableUsageInstances;
}

public Map<String, LiveVariableUsageInstance> getVariableIdToLiveVariableUsageInstanceMap() {
return variableIdToLiveVariableUsageInstanceMap;
}

public boolean is(String otherKey) {
return key.equals(otherKey);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
*
* Copyright 2017, Optimizely 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.config.parser;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.optimizely.ab.config.FeatureFlag;

import java.lang.reflect.Type;

public class FeatureFlagGsonDeserializer implements JsonDeserializer<FeatureFlag> {
@Override
public FeatureFlag deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {

JsonObject jsonObject = json.getAsJsonObject();
return GsonHelpers.parseFeatureFlag(jsonObject, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import com.optimizely.ab.config.Experiment;
import com.optimizely.ab.config.FeatureFlag;
import com.optimizely.ab.config.Group;
import com.optimizely.ab.config.audience.Audience;
import com.optimizely.ab.config.ProjectConfig;
import com.optimizely.ab.config.audience.Audience;

import javax.annotation.Nonnull;

Expand All @@ -40,11 +40,12 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
throw new ConfigParseException("Unable to parse empty json.");
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(ProjectConfig.class, new ProjectConfigGsonDeserializer())
.registerTypeAdapter(Audience.class, new AudienceGsonDeserializer())
.registerTypeAdapter(Group.class, new GroupGsonDeserializer())
.registerTypeAdapter(Experiment.class, new ExperimentGsonDeserializer())
.create();
.registerTypeAdapter(Audience.class, new AudienceGsonDeserializer())
.registerTypeAdapter(Experiment.class, new ExperimentGsonDeserializer())
.registerTypeAdapter(FeatureFlag.class, new FeatureFlagGsonDeserializer())
.registerTypeAdapter(Group.class, new GroupGsonDeserializer())
.registerTypeAdapter(ProjectConfig.class, new ProjectConfigGsonDeserializer())
.create();

try {
return gson.fromJson(json, ProjectConfig.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,30 @@
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;

import com.optimizely.ab.bucketing.DecisionService;
import com.optimizely.ab.config.Experiment;
import com.optimizely.ab.config.Experiment.ExperimentStatus;
import com.optimizely.ab.config.FeatureFlag;
import com.optimizely.ab.config.LiveVariable;
import com.optimizely.ab.config.LiveVariableUsageInstance;
import com.optimizely.ab.config.TrafficAllocation;
import com.optimizely.ab.config.Variation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

final class GsonHelpers {

private static final Logger logger = LoggerFactory.getLogger(DecisionService.class);

private static List<Variation> parseVariations(JsonArray variationJson, JsonDeserializationContext context) {
List<Variation> variations = new ArrayList<Variation>(variationJson.size());
for (Object obj : variationJson) {
Expand Down Expand Up @@ -114,4 +121,35 @@ static Experiment parseExperiment(JsonObject experimentJson, String groupId, Jso
static Experiment parseExperiment(JsonObject experimentJson, JsonDeserializationContext context) {
return parseExperiment(experimentJson, "", context);
}

static FeatureFlag parseFeatureFlag(JsonObject featureFlagJson, JsonDeserializationContext context) {
String id = featureFlagJson.get("id").getAsString();
String key = featureFlagJson.get("key").getAsString();
String layerId = featureFlagJson.get("layerId").getAsString();

JsonArray experimentIdsJson = featureFlagJson.getAsJsonArray("experimentIds");
List<String> experimentIds = new ArrayList<String>();
for (JsonElement experimentIdObj : experimentIdsJson) {
experimentIds.add(experimentIdObj.getAsString());
}

List<LiveVariable> liveVariables = new ArrayList<LiveVariable>();
try {
Type liveVariableType = new TypeToken<List<LiveVariable>>() {}.getType();
liveVariables = context.deserialize(featureFlagJson.getAsJsonArray("variables"),
liveVariableType);
}
catch (JsonParseException exception) {
logger.warn("Unable to parse variables for feature \"" + key
+ "\". JsonParseException: " + exception);
}

return new FeatureFlag(
id,
key,
layerId,
experimentIds,
liveVariables
);
}
}
Loading