Skip to content
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
72 changes: 72 additions & 0 deletions core-api/src/main/java/com/optimizely/ab/config/Cmab.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
*
* Copyright 2025 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 java.util.List;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Represents the Optimizely Traffic Allocation configuration.
*
* @see <a href="http://developers.optimizely.com/server/reference/index.html#json">Project JSON</a>
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Cmab {

private final List<String> attributeIds;
private final int trafficAllocation;

@JsonCreator
public Cmab(@JsonProperty("attributeIds") List<String> attributeIds,
@JsonProperty("trafficAllocation") int trafficAllocation) {
this.attributeIds = attributeIds;
this.trafficAllocation = trafficAllocation;
}

public List<String> getAttributeIds() {
return attributeIds;
}

public int getTrafficAllocation() {
return trafficAllocation;
}

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Cmab cmab = (Cmab) obj;
return trafficAllocation == cmab.trafficAllocation &&
Objects.equals(attributeIds, cmab.attributeIds);
}

@Override
public int hashCode() {
return Objects.hash(attributeIds, trafficAllocation);
}

@Override
public String toString() {
return "Cmab{" +
"attributeIds=" + attributeIds +
", trafficAllocation=" + trafficAllocation +
'}';
}
}
35 changes: 31 additions & 4 deletions core-api/src/main/java/com/optimizely/ab/config/Experiment.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class Experiment implements ExperimentCore {
private final String status;
private final String layerId;
private final String groupId;
private final Cmab cmab;

private final List<String> audienceIds;
private final Condition<AudienceIdCondition> audienceConditions;
Expand Down Expand Up @@ -71,7 +72,25 @@ public String toString() {

@VisibleForTesting
public Experiment(String id, String key, String layerId) {
this(id, key, null, layerId, Collections.emptyList(), null, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), "");
this(id, key, null, layerId, Collections.emptyList(), null, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), "", null);
}

@VisibleForTesting
public Experiment(String id, String key, String status, String layerId,
List<String> audienceIds, Condition audienceConditions,
List<Variation> variations, Map<String, String> userIdToVariationKeyMap,
List<TrafficAllocation> trafficAllocation, String groupId) {
this(id, key, status, layerId, audienceIds, audienceConditions, variations,
userIdToVariationKeyMap, trafficAllocation, groupId, null); // Default cmab=null
}

@VisibleForTesting
public Experiment(String id, String key, String status, String layerId,
List<String> audienceIds, Condition audienceConditions,
List<Variation> variations, Map<String, String> userIdToVariationKeyMap,
List<TrafficAllocation> trafficAllocation) {
this(id, key, status, layerId, audienceIds, audienceConditions, variations,
userIdToVariationKeyMap, trafficAllocation, "", null); // Default groupId="" and cmab=null
}

@JsonCreator
Expand All @@ -83,8 +102,9 @@ public Experiment(@JsonProperty("id") String id,
@JsonProperty("audienceConditions") Condition audienceConditions,
@JsonProperty("variations") List<Variation> variations,
@JsonProperty("forcedVariations") Map<String, String> userIdToVariationKeyMap,
@JsonProperty("trafficAllocation") List<TrafficAllocation> trafficAllocation) {
this(id, key, status, layerId, audienceIds, audienceConditions, variations, userIdToVariationKeyMap, trafficAllocation, "");
@JsonProperty("trafficAllocation") List<TrafficAllocation> trafficAllocation,
@JsonProperty("cmab") Cmab cmab) {
this(id, key, status, layerId, audienceIds, audienceConditions, variations, userIdToVariationKeyMap, trafficAllocation, "", cmab);
}

public Experiment(@Nonnull String id,
Expand All @@ -96,7 +116,8 @@ public Experiment(@Nonnull String id,
@Nonnull List<Variation> variations,
@Nonnull Map<String, String> userIdToVariationKeyMap,
@Nonnull List<TrafficAllocation> trafficAllocation,
@Nonnull String groupId) {
@Nonnull String groupId,
@Nullable Cmab cmab) {
this.id = id;
this.key = key;
this.status = status == null ? ExperimentStatus.NOT_STARTED.toString() : status;
Expand All @@ -109,6 +130,7 @@ public Experiment(@Nonnull String id,
this.userIdToVariationKeyMap = userIdToVariationKeyMap;
this.variationKeyToVariationMap = ProjectConfigUtils.generateNameMapping(variations);
this.variationIdToVariationMap = ProjectConfigUtils.generateIdMapping(variations);
this.cmab = cmab;
}

public String getId() {
Expand Down Expand Up @@ -159,6 +181,10 @@ public String getGroupId() {
return groupId;
}

public Cmab getCmab() {
return cmab;
}

public boolean isActive() {
return status.equals(ExperimentStatus.RUNNING.toString()) ||
status.equals(ExperimentStatus.LAUNCHED.toString());
Expand All @@ -185,6 +211,7 @@ public String toString() {
", variationKeyToVariationMap=" + variationKeyToVariationMap +
", userIdToVariationKeyMap=" + userIdToVariationKeyMap +
", trafficAllocation=" + trafficAllocation +
", cmab=" + cmab +
'}';
}
}
3 changes: 2 additions & 1 deletion core-api/src/main/java/com/optimizely/ab/config/Group.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public Group(@JsonProperty("id") String id,
experiment.getVariations(),
experiment.getUserIdToVariationKeyMap(),
experiment.getTrafficAllocation(),
id
id,
experiment.getCmab()
);
}
this.experiments.add(experiment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,8 @@
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.Holdout;
import com.optimizely.ab.config.*;
import com.optimizely.ab.config.Experiment.ExperimentStatus;
import com.optimizely.ab.config.Holdout.HoldoutStatus;
import com.optimizely.ab.config.FeatureFlag;
import com.optimizely.ab.config.FeatureVariable;
import com.optimizely.ab.config.FeatureVariableUsageInstance;
import com.optimizely.ab.config.TrafficAllocation;
import com.optimizely.ab.config.Variation;
import com.optimizely.ab.config.audience.AudienceIdCondition;
import com.optimizely.ab.config.audience.Condition;
import com.optimizely.ab.internal.ConditionUtils;
Expand Down Expand Up @@ -120,6 +113,27 @@ static Condition parseAudienceConditions(JsonObject experimentJson) {

}

static Cmab parseCmab(JsonObject cmabJson, JsonDeserializationContext context) {
if (cmabJson == null) {
return null;
}

JsonArray attributeIdsJson = cmabJson.getAsJsonArray("attributeIds");
List<String> attributeIds = new ArrayList<>();
if (attributeIdsJson != null) {
for (JsonElement attributeIdElement : attributeIdsJson) {
attributeIds.add(attributeIdElement.getAsString());
}
}

int trafficAllocation = 0;
if (cmabJson.has("trafficAllocation")) {
trafficAllocation = cmabJson.get("trafficAllocation").getAsInt();
}

return new Cmab(attributeIds, trafficAllocation);
}

static Experiment parseExperiment(JsonObject experimentJson, String groupId, JsonDeserializationContext context) {
String id = experimentJson.get("id").getAsString();
String key = experimentJson.get("key").getAsString();
Expand All @@ -145,8 +159,17 @@ static Experiment parseExperiment(JsonObject experimentJson, String groupId, Jso
List<TrafficAllocation> trafficAllocations =
parseTrafficAllocation(experimentJson.getAsJsonArray("trafficAllocation"));

Cmab cmab = null;
if (experimentJson.has("cmab")) {
JsonElement cmabElement = experimentJson.get("cmab");
if (!cmabElement.isJsonNull()) {
JsonObject cmabJson = cmabElement.getAsJsonObject();
cmab = parseCmab(cmabJson, context);
}
}

return new Experiment(id, key, status, layerId, audienceIds, conditions, variations, userIdToVariationKeyMap,
trafficAllocations, groupId);
trafficAllocations, groupId, cmab);
}

static Experiment parseExperiment(JsonObject experimentJson, JsonDeserializationContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,14 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
List<TrafficAllocation> trafficAllocations =
parseTrafficAllocation(experimentObject.getJSONArray("trafficAllocation"));

Cmab cmab = null;
if (experimentObject.has("cmab")) {
JSONObject cmabObject = experimentObject.optJSONObject("cmab");
cmab = parseCmab(cmabObject);
}

experiments.add(new Experiment(id, key, status, layerId, audienceIds, conditions, variations, userIdToVariationKeyMap,
trafficAllocations, groupId));
trafficAllocations, groupId, cmab));
}

return experiments;
Expand Down Expand Up @@ -332,6 +338,23 @@ private List<TrafficAllocation> parseTrafficAllocation(JSONArray trafficAllocati
return trafficAllocation;
}

private Cmab parseCmab(JSONObject cmabObject) {
if (cmabObject == null) {
return null;
}

JSONArray attributeIdsJson = cmabObject.optJSONArray("attributeIds");
List<String> attributeIds = new ArrayList<String>();
if (attributeIdsJson != null) {
for (int i = 0; i < attributeIdsJson.length(); i++) {
attributeIds.add(attributeIdsJson.getString(i));
}
}

int trafficAllocation = cmabObject.optInt("trafficAllocation", 0);
return new Cmab(attributeIds, trafficAllocation);
}

private List<Attribute> parseAttributes(JSONArray attributeJson) {
List<Attribute> attributes = new ArrayList<Attribute>(attributeJson.length());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,17 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
List<TrafficAllocation> trafficAllocations =
parseTrafficAllocation((JSONArray) experimentObject.get("trafficAllocation"));

experiments.add(new Experiment(id, key, status, layerId, audienceIds, conditions, variations, userIdToVariationKeyMap,
trafficAllocations, groupId));
// Add cmab parsing
Cmab cmab = null;
if (experimentObject.containsKey("cmab")) {
JSONObject cmabObject = (JSONObject) experimentObject.get("cmab");
if (cmabObject != null) {
cmab = parseCmab(cmabObject);
}
}

experiments.add(new Experiment(id, key, status, layerId, audienceIds, conditions, variations,
userIdToVariationKeyMap, trafficAllocations, groupId, cmab));
}

return experiments;
Expand Down Expand Up @@ -465,6 +474,26 @@ private List<Integration> parseIntegrations(JSONArray integrationsJson) {
return integrations;
}

private Cmab parseCmab(JSONObject cmabObject) {
if (cmabObject == null) {
return null;
}

JSONArray attributeIdsJson = (JSONArray) cmabObject.get("attributeIds");
List<String> attributeIds = new ArrayList<>();
if (attributeIdsJson != null) {
for (Object idObj : attributeIdsJson) {
attributeIds.add((String) idObj);
}
}

Object trafficAllocationObj = cmabObject.get("trafficAllocation");
int trafficAllocation = trafficAllocationObj != null ?
((Long) trafficAllocationObj).intValue() : 0;

return new Cmab(attributeIds, trafficAllocation);
}

@Override
public String toJson(Object src) {
return JSONValue.toJSONString(src);
Expand Down
Loading
Loading