diff --git a/core-api/src/main/java/com/optimizely/ab/config/Layer.java b/core-api/src/main/java/com/optimizely/ab/config/Layer.java
deleted file mode 100644
index dc4c476b4..000000000
--- a/core-api/src/main/java/com/optimizely/ab/config/Layer.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- *
- * 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;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import java.util.List;
-
-import javax.annotation.concurrent.Immutable;
-
-/**
- * Represents a Optimizely Layer configuration
- *
- * @see Project JSON
- */
-@Immutable
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class Layer implements IdMapped {
-
- protected final String id;
- protected final String policy;
- protected final List experiments;
-
- public static final String SINGLE_EXPERIMENT_POLICY = "single_experiment";
-
- @JsonCreator
- public Layer(@JsonProperty("id") String id,
- @JsonProperty("policy") String policy,
- @JsonProperty("experiments") List experiments) {
- this.id = id;
- this.policy = policy;
- this.experiments = experiments;
- }
-
- public String getId() {
- return id;
- }
-
- public String getPolicy() {
- return policy;
- }
-
- public List getExperiments() {
- return experiments;
- }
-
- @Override
- public String toString() {
- return "Layer{" +
- "id='" + id + '\'' +
- ", policy='" + policy + '\'' +
- ", experiments=" + experiments +
- '}';
- }
-}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java b/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java
index ffb891e29..77e69ad2e 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java
@@ -66,6 +66,7 @@ public String toString() {
private final List featureFlags;
private final List groups;
private final List liveVariables;
+ private final List rollouts;
// key to entity mappings
private final Map attributeKeyMapping;
@@ -108,7 +109,8 @@ public ProjectConfig(String accountId, String projectId, String version, String
experiments,
null,
groups,
- liveVariables
+ liveVariables,
+ null
);
}
@@ -124,7 +126,8 @@ public ProjectConfig(String accountId,
List experiments,
List featureFlags,
List groups,
- List liveVariables) {
+ List liveVariables,
+ List rollouts) {
this.accountId = accountId;
this.projectId = projectId;
@@ -141,6 +144,12 @@ public ProjectConfig(String accountId,
else {
this.featureFlags = Collections.unmodifiableList(featureFlags);
}
+ if (rollouts == null) {
+ this.rollouts = Collections.emptyList();
+ }
+ else {
+ this.rollouts = Collections.unmodifiableList(rollouts);
+ }
this.groups = Collections.unmodifiableList(groups);
@@ -243,6 +252,10 @@ public List getFeatureFlags() {
return featureFlags;
}
+ public List getRollouts() {
+ return rollouts;
+ }
+
public List getAttributes() {
return attributes;
}
@@ -312,22 +325,26 @@ public String toString() {
", projectId='" + projectId + '\'' +
", revision='" + revision + '\'' +
", version='" + version + '\'' +
- ", anonymizeIP='" + anonymizeIP + '\'' +
- ", groups=" + groups +
- ", experiments=" + experiments +
+ ", anonymizeIP=" + anonymizeIP +
", attributes=" + attributes +
- ", events=" + events +
", audiences=" + audiences +
+ ", events=" + events +
+ ", experiments=" + experiments +
+ ", featureFlags=" + featureFlags +
+ ", groups=" + groups +
", liveVariables=" + liveVariables +
- ", experimentKeyMapping=" + experimentKeyMapping +
+ ", rollouts=" + rollouts +
", attributeKeyMapping=" + attributeKeyMapping +
- ", liveVariableKeyMapping=" + liveVariableKeyMapping +
", eventNameMapping=" + eventNameMapping +
+ ", experimentKeyMapping=" + experimentKeyMapping +
+ ", featureKeyMapping=" + featureKeyMapping +
+ ", liveVariableKeyMapping=" + liveVariableKeyMapping +
", audienceIdMapping=" + audienceIdMapping +
", experimentIdMapping=" + experimentIdMapping +
", groupIdMapping=" + groupIdMapping +
", liveVariableIdToExperimentsMapping=" + liveVariableIdToExperimentsMapping +
", variationToLiveVariableUsageInstanceMapping=" + variationToLiveVariableUsageInstanceMapping +
+ ", variationIdToExperimentMapping=" + variationIdToExperimentMapping +
'}';
}
}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/Rollout.java b/core-api/src/main/java/com/optimizely/ab/config/Rollout.java
index 06b8af1b3..b36f33838 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/Rollout.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/Rollout.java
@@ -16,6 +16,10 @@
*/
package com.optimizely.ab.config;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
import javax.annotation.concurrent.Immutable;
import java.util.List;
@@ -25,19 +29,32 @@
* @see Project JSON
*/
@Immutable
-public class Rollout extends Layer implements IdMapped {
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Rollout implements IdMapped {
+
+ private final String id;
+ private final List experiments;
+
+ @JsonCreator
+ public Rollout(@JsonProperty("id") String id,
+ @JsonProperty("experiments") List experiments) {
+ this.id = id;
+ this.experiments = experiments;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
- public Rollout(String id,
- String policy,
- List experiments) {
- super(id, policy, experiments);
+ public List getExperiments() {
+ return experiments;
}
@Override
public String toString() {
return "Rollout{" +
"id='" + id + '\'' +
- ", policy='" + policy + '\'' +
", experiments=" + experiments +
'}';
}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java
index 79d486f09..1b2af1079 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonConfigParser.java
@@ -27,6 +27,7 @@
import com.optimizely.ab.config.LiveVariable.VariableType;
import com.optimizely.ab.config.LiveVariableUsageInstance;
import com.optimizely.ab.config.ProjectConfig;
+import com.optimizely.ab.config.Rollout;
import com.optimizely.ab.config.TrafficAllocation;
import com.optimizely.ab.config.Variation;
import com.optimizely.ab.config.audience.AndCondition;
@@ -79,8 +80,10 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
}
List featureFlags = null;
+ List rollouts = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = parseFeatureFlags(rootObject.getJSONArray("featureFlags"));
+ rollouts = parseRollouts(rootObject.getJSONArray("rollouts"));
}
return new ProjectConfig(
@@ -95,7 +98,8 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
experiments,
featureFlags,
groups,
- liveVariables
+ liveVariables,
+ rollouts
);
} catch (Exception e) {
throw new ConfigParseException("Unable to parse datafile: " + json, e);
@@ -344,4 +348,18 @@ private List parseLiveVariableInstances(JSONArray liv
return liveVariableUsageInstances;
}
+
+ private List parseRollouts(JSONArray rolloutsJson) {
+ List rollouts = new ArrayList(rolloutsJson.length());
+
+ for (Object obj : rolloutsJson) {
+ JSONObject rolloutObject = (JSONObject) obj;
+ String id = rolloutObject.getString("id");
+ List experiments = parseExperiments(rolloutObject.getJSONArray("experiments"));
+
+ rollouts.add(new Rollout(id, experiments));
+ }
+
+ return rollouts;
+ }
}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java
index 736ab80ad..be106665d 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/JsonSimpleConfigParser.java
@@ -27,6 +27,7 @@
import com.optimizely.ab.config.LiveVariable.VariableType;
import com.optimizely.ab.config.LiveVariableUsageInstance;
import com.optimizely.ab.config.ProjectConfig;
+import com.optimizely.ab.config.Rollout;
import com.optimizely.ab.config.TrafficAllocation;
import com.optimizely.ab.config.Variation;
import com.optimizely.ab.config.audience.AndCondition;
@@ -81,8 +82,10 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
}
List featureFlags = null;
+ List rollouts = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = parseFeatureFlags((JSONArray) rootObject.get("featureFlags"));
+ rollouts = parseRollouts((JSONArray) rootObject.get("rollouts"));
}
return new ProjectConfig(
@@ -97,7 +100,8 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
experiments,
featureFlags,
groups,
- liveVariables
+ liveVariables,
+ rollouts
);
} catch (Exception e) {
throw new ConfigParseException("Unable to parse datafile: " + json, e);
@@ -348,5 +352,19 @@ private List parseLiveVariableInstances(JSONArray liv
return liveVariableUsageInstances;
}
+
+ private List parseRollouts(JSONArray rolloutsJson) {
+ List rollouts = new ArrayList(rolloutsJson.size());
+
+ for (Object obj : rolloutsJson) {
+ JSONObject rolloutObject = (JSONObject) obj;
+ String id = (String) rolloutObject.get("id");
+ List experiments = parseExperiments((JSONArray) rolloutObject.get("experiments"));
+
+ rollouts.add(new Rollout(id, experiments));
+ }
+
+ return rollouts;
+ }
}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java
index 3f4df5210..c9718d851 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigGsonDeserializer.java
@@ -29,6 +29,7 @@
import com.optimizely.ab.config.Group;
import com.optimizely.ab.config.LiveVariable;
import com.optimizely.ab.config.ProjectConfig;
+import com.optimizely.ab.config.Rollout;
import com.optimizely.ab.config.audience.Audience;
import java.lang.reflect.Type;
@@ -80,9 +81,12 @@ public ProjectConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializa
}
List featureFlags = null;
+ List rollouts = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
Type featureFlagsType = new TypeToken>() {}.getType();
featureFlags = context.deserialize(jsonObject.getAsJsonArray("featureFlags"), featureFlagsType);
+ Type rolloutsType = new TypeToken>() {}.getType();
+ rollouts = context.deserialize(jsonObject.get("rollouts").getAsJsonArray(), rolloutsType);
}
return new ProjectConfig(
@@ -97,7 +101,8 @@ public ProjectConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializa
experiments,
featureFlags,
groups,
- liveVariables
+ liveVariables,
+ rollouts
);
}
}
diff --git a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java
index 04503c150..6ebd3c4ec 100644
--- a/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java
+++ b/core-api/src/main/java/com/optimizely/ab/config/parser/ProjectConfigJacksonDeserializer.java
@@ -30,6 +30,7 @@
import com.optimizely.ab.config.Group;
import com.optimizely.ab.config.LiveVariable;
import com.optimizely.ab.config.ProjectConfig;
+import com.optimizely.ab.config.Rollout;
import com.optimizely.ab.config.audience.Audience;
import java.io.IOException;
@@ -74,9 +75,12 @@ public ProjectConfig deserialize(JsonParser parser, DeserializationContext conte
}
List featureFlags = null;
+ List rollouts = null;
if (datafileVersion >= Integer.parseInt(ProjectConfig.Version.V4.toString())) {
featureFlags = mapper.readValue(node.get("featureFlags").toString(),
new TypeReference>() {});
+ rollouts = mapper.readValue(node.get("rollouts").toString(),
+ new TypeReference>(){});
}
return new ProjectConfig(
@@ -91,7 +95,8 @@ public ProjectConfig deserialize(JsonParser parser, DeserializationContext conte
experiments,
featureFlags,
groups,
- liveVariables
+ liveVariables,
+ rollouts
);
}
}
\ No newline at end of file
diff --git a/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java b/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java
index fa4a43a25..c072d79ee 100644
--- a/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java
+++ b/core-api/src/test/java/com/optimizely/ab/config/ProjectConfigTestUtils.java
@@ -457,6 +457,7 @@ public static void verifyProjectConfig(@CheckForNull ProjectConfig actual, @Nonn
verifyFeatureFlags(actual.getFeatureFlags(), expected.getFeatureFlags());
verifyLiveVariables(actual.getLiveVariables(), expected.getLiveVariables());
verifyGroups(actual.getGroups(), expected.getGroups());
+ verifyRollouts(actual.getRollouts(), expected.getRollouts());
}
/**
@@ -617,6 +618,23 @@ private static void verifyLiveVariables(List actual, List actual, List expected) {
+ if (expected == null) {
+ assertNull(actual);
+ }
+ else {
+ assertEquals(expected.size(), actual.size());
+
+ for (int i = 0; i < actual.size(); i++) {
+ Rollout actualRollout = actual.get(i);
+ Rollout expectedRollout = expected.get(i);
+
+ assertEquals(expectedRollout.getId(), actualRollout.getId());
+ verifyExperiments(actualRollout.getExperiments(), expectedRollout.getExperiments());
+ }
+ }
+ }
+
/**
* Verify that the provided variation-level live variable usage instances are equivalent.
*/
diff --git a/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java b/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java
index e163abd52..b073b04d6 100644
--- a/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java
+++ b/core-api/src/test/java/com/optimizely/ab/config/ValidProjectConfigV4.java
@@ -143,7 +143,7 @@ public class ValidProjectConfigV4 {
private static final FeatureFlag FEATURE_FLAG_SINGLE_VARIABLE_STRING = new FeatureFlag(
FEATURE_SINGLE_VARIABLE_STRING_ID,
FEATURE_SINGLE_VARIABLE_STRING_KEY,
- "",
+ "1058508303",
Collections.emptyList(),
Collections.singletonList(
VARIABLE_STRING_VARIABLE
@@ -667,6 +667,43 @@ public class ValidProjectConfigV4 {
)
);
+ private static final String ROLLOUT_1_ID = "1058508303";
+ private static final String ROLLOUT_1_EVERYONE_ELSE_EXPERIMENT_ID = "1785077004";
+ private static final String ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID = "1566407342";
+ private static final String ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION_STRING_VALUE = "lumos";
+ private static final Variation ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION = new Variation(
+ ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID,
+ ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID,
+ Collections.singletonList(
+ new LiveVariableUsageInstance(
+ VARIABLE_STRING_VARIABLE_ID,
+ ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION_STRING_VALUE
+ )
+ )
+ );
+ private static final Experiment ROLLOUT_1_EVERYONE_ELSE_RULE = new Experiment(
+ ROLLOUT_1_EVERYONE_ELSE_EXPERIMENT_ID,
+ ROLLOUT_1_EVERYONE_ELSE_EXPERIMENT_ID,
+ Experiment.ExperimentStatus.RUNNING.toString(),
+ ROLLOUT_1_ID,
+ Collections.emptyList(),
+ Collections.singletonList(
+ ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION
+ ),
+ Collections.emptyMap(),
+ Collections.singletonList(
+ new TrafficAllocation(
+ ROLLOUT_1_EVERYONE_ELSE_RULE_ENABLED_VARIATION_ID,
+ 5000
+ )
+ )
+ );
+ private static final Rollout ROLLOUT_1 = new Rollout(
+ ROLLOUT_1_ID,
+ Collections.singletonList(
+ ROLLOUT_1_EVERYONE_ELSE_RULE
+ )
+ );
public static ProjectConfig generateValidProjectConfigV4() {
@@ -705,6 +742,10 @@ public static ProjectConfig generateValidProjectConfigV4() {
groups.add(GROUP_1);
groups.add(GROUP_2);
+ // list rollouts
+ List rollouts = new ArrayList();
+ rollouts.add(ROLLOUT_1);
+
return new ProjectConfig(
ACCOUNT_ID,
ANONYMIZE_IP,
@@ -717,7 +758,8 @@ public static ProjectConfig generateValidProjectConfigV4() {
experiments,
featureFlags,
groups,
- Collections.emptyList()
+ Collections.emptyList(),
+ rollouts
);
}
}
diff --git a/core-api/src/test/resources/config/valid-project-config-v4.json b/core-api/src/test/resources/config/valid-project-config-v4.json
index 165704d20..e56b804ed 100644
--- a/core-api/src/test/resources/config/valid-project-config-v4.json
+++ b/core-api/src/test/resources/config/valid-project-config-v4.json
@@ -423,7 +423,7 @@
{
"id": "2079378557",
"key": "string_single_variable_feature",
- "rolloutId": "",
+ "rolloutId": "1058508303",
"experimentIds": [],
"variables": [
{
@@ -469,5 +469,38 @@
]
}
],
+ "rollouts": [
+ {
+ "id": "1058508303",
+ "experiments": [
+ {
+ "id": "1785077004",
+ "key": "1785077004",
+ "status": "Running",
+ "layerId": "1058508303",
+ "audienceIds": [],
+ "forcedVariations": {},
+ "variations": [
+ {
+ "id": "1566407342",
+ "key": "1566407342",
+ "variables": [
+ {
+ "id": "2077511132",
+ "value": "lumos"
+ }
+ ]
+ }
+ ],
+ "trafficAllocation": [
+ {
+ "entityId": "1566407342",
+ "endOfRange": 5000
+ }
+ ]
+ }
+ ]
+ }
+ ],
"variables": []
}