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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## 0.1.71

- Add support for v2 backend endpoint and datafile

## 0.1.70

- Adds a `UserExperimentRecord` interface
- Add a `UserExperimentRecord` interface
- Implementors will get a chance to save and restore activations during bucketing
- Can be used to make bucketing persistent or to keep a bucketing history
- Pass implementations to `Optimizely.Builder#withUserExperimentRecord(UserExperimentRecord)` when creating `Optimizely` instances
Expand Down
15 changes: 10 additions & 5 deletions core-api/src/main/java/com/optimizely/ab/Optimizely.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.optimizely.ab.event.LogEvent;
import com.optimizely.ab.event.internal.EventBuilder;
import com.optimizely.ab.event.internal.EventBuilderV1;
import com.optimizely.ab.event.internal.EventBuilderV2;
import com.optimizely.ab.internal.ProjectValidationUtils;

import org.slf4j.Logger;
Expand Down Expand Up @@ -166,8 +167,8 @@ private Optimizely(@Nonnull ProjectConfig projectConfig,
LogEvent impressionEvent =
eventBuilder.createImpressionEvent(projectConfig, experiment, variation, userId, attributes);
logger.info("Activating user \"{}\" in experiment \"{}\".", userId, experiment.getKey());
logger.debug("Dispatching impression event to URL {} with params {}.", impressionEvent.getEndpointUrl(),
impressionEvent.getRequestParams());
logger.debug("Dispatching impression event to URL {} with params {} and payload \"{}\".",
impressionEvent.getEndpointUrl(), impressionEvent.getRequestParams(), impressionEvent.getBody());
eventHandler.dispatchEvent(impressionEvent);

return variation;
Expand Down Expand Up @@ -236,8 +237,8 @@ private void track(@Nonnull String eventName,
}

logger.info("Tracking event \"{}\" for user \"{}\".", eventName, userId);
logger.debug("Dispatching conversion event to URL {} with params {}.", conversionEvent.getEndpointUrl(),
conversionEvent.getRequestParams());
logger.debug("Dispatching conversion event to URL {} with params {} and payload \"{}\".",
conversionEvent.getEndpointUrl(), conversionEvent.getRequestParams(), conversionEvent.getBody());
eventHandler.dispatchEvent(conversionEvent);
}

Expand Down Expand Up @@ -476,7 +477,11 @@ public Optimizely build() {
}

if (eventBuilder == null) {
eventBuilder = new EventBuilderV1();
if (projectConfig.getVersion().equals(ProjectConfig.V1)) {
eventBuilder = new EventBuilderV1();
} else {
eventBuilder = new EventBuilderV2();
}
}

if (errorHandler == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ public class Attribute implements IdKeyMapped {
private final String key;
private final String segmentId;

public Attribute(String id, String key) {
this(id, key, null);
}

@JsonCreator
public Attribute(@JsonProperty("is") String id,
public Attribute(@JsonProperty("id") String id,
@JsonProperty("key") String key,
@JsonProperty("segmentId") String segmentId) {
this.id = id;
Expand Down
23 changes: 19 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 @@ -24,6 +24,8 @@
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

/**
Expand All @@ -38,6 +40,7 @@ public class Experiment implements IdKeyMapped {
private final String id;
private final String key;
private final String status;
private final String layerId;
private final String groupId;

private final List<String> audienceIds;
Expand All @@ -56,19 +59,27 @@ public class Experiment implements IdKeyMapped {
public Experiment(@JsonProperty("id") String id,
@JsonProperty("key") String key,
@JsonProperty("status") String status,
@JsonProperty("layerId") String layerId,
@JsonProperty("audienceIds") List<String> audienceIds,
@JsonProperty("variations") List<Variation> variations,
@JsonProperty("forcedVariations") Map<String, String> userIdToVariationKeyMap,
@JsonProperty("trafficAllocation") List<TrafficAllocation> trafficAllocation) {
this(id, key, status, audienceIds, variations, userIdToVariationKeyMap, trafficAllocation, "");
this(id, key, status, layerId, audienceIds, variations, userIdToVariationKeyMap, trafficAllocation, "");
}

public Experiment(String id, String key, String status, List<String> audienceIds, List<Variation> variations,
Map<String, String> userIdToVariationKeyMap, List<TrafficAllocation> trafficAllocation,
String groupId) {
public Experiment(@Nonnull String id,
@Nonnull String key,
@Nonnull String status,
@Nullable String layerId,
@Nonnull List<String> audienceIds,
@Nonnull List<Variation> variations,
@Nonnull Map<String, String> userIdToVariationKeyMap,
@Nonnull List<TrafficAllocation> trafficAllocation,
@Nonnull String groupId) {
this.id = id;
this.key = key;
this.status = status;
this.layerId = layerId;
this.audienceIds = Collections.unmodifiableList(audienceIds);
this.variations = Collections.unmodifiableList(variations);
this.trafficAllocation = Collections.unmodifiableList(trafficAllocation);
Expand All @@ -90,6 +101,10 @@ public String getStatus() {
return status;
}

public String getLayerId() {
return layerId;
}

public List<String> getAudienceIds() {
return audienceIds;
}
Expand Down
18 changes: 6 additions & 12 deletions core-api/src/main/java/com/optimizely/ab/config/ProjectConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
*/
package com.optimizely.ab.config;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import com.optimizely.ab.config.audience.Audience;
import com.optimizely.ab.config.audience.Condition;
Expand All @@ -39,6 +37,9 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProjectConfig {

public static final String V1 = "1";
public static final String V2 = "2";

private final String accountId;
private final String projectId;
private final String revision;
Expand All @@ -57,16 +58,9 @@ public class ProjectConfig {
private final Map<String, Experiment> experimentIdMapping;
private final Map<String, Group> groupIdMapping;

@JsonCreator
public ProjectConfig(@JsonProperty("accountId") String accountId,
@JsonProperty("projectId") String projectId,
@JsonProperty("version") String version,
@JsonProperty("revision") String revision,
@JsonProperty("groups") List<Group> groups,
@JsonProperty("experiments") List<Experiment> experiments,
@JsonProperty("dimensions") List<Attribute> attributes,
@JsonProperty("events") List<EventType> eventType,
@JsonProperty("audiences") List<Audience> audiences) {
public ProjectConfig(String accountId, String projectId, String version, String revision, List<Group> groups,
List<Experiment> experiments, List<Attribute> attributes, List<EventType> eventType,
List<Audience> audiences) {

this.accountId = accountId;
this.projectId = projectId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ private Experiment parseExperiment(JsonNode experimentJson, String groupId) thro
String id = experimentJson.get("id").textValue();
String key = experimentJson.get("key").textValue();
String status = experimentJson.get("status").textValue();
JsonNode layerIdJson = experimentJson.get("layerId");
String layerId = layerIdJson == null ? null : layerIdJson.textValue();
List<String> audienceIds = mapper.readValue(experimentJson.get("audienceIds").toString(),
new TypeReference<List<String>>(){});
List<Variation> variations = mapper.readValue(experimentJson.get("variations").toString(),
Expand All @@ -71,8 +73,8 @@ private Experiment parseExperiment(JsonNode experimentJson, String groupId) thro
Map<String, String> userIdToVariationKeyMap = mapper.readValue(
experimentJson.get("forcedVariations").toString(), new TypeReference<Map<String, String>>(){});

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ static Experiment parseExperiment(JsonObject experimentJson, String groupId) {
String id = experimentJson.get("id").getAsString();
String key = experimentJson.get("key").getAsString();
String status = experimentJson.get("status").getAsString();
JsonElement layerIdJson = experimentJson.get("layerId");
String layerId = layerIdJson == null ? null : layerIdJson.getAsString();

JsonArray audienceIdsJson = experimentJson.getAsJsonArray("audienceIds");
List<String> audienceIds = new ArrayList<String>(audienceIdsJson.size());
Expand All @@ -88,8 +90,8 @@ static Experiment parseExperiment(JsonObject experimentJson, String groupId) {
List<TrafficAllocation> trafficAllocations =
parseTrafficAllocation(experimentJson.getAsJsonArray("trafficAllocation"));

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

static Experiment parseExperiment(JsonObject experimentJson) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

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

import java.io.IOException;
Expand All @@ -36,8 +34,7 @@ final class JacksonConfigParser implements ConfigParser {
public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParseException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Audience.class, new AudienceJacksonDeserializer());
module.addDeserializer(Group.class, new GroupJacksonDeserializer());
module.addDeserializer(ProjectConfig.class, new ProjectConfigJacksonDeserializer());
mapper.registerModule(module);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
String version = rootObject.getString("version");

List<Experiment> experiments = parseExperiments(rootObject.getJSONArray("experiments"));
List<Attribute> attributes = parseAttributes(rootObject.getJSONArray("dimensions"));

List<Attribute> attributes;
if (version.equals(ProjectConfig.V1)) {
attributes = parseAttributes(rootObject.getJSONArray("dimensions"));
} else {
attributes = parseAttributes(rootObject.getJSONArray("attributes"));
}

List<EventType> events = parseEvents(rootObject.getJSONArray("events"));
List<Audience> audiences = parseAudiences(rootObject.getJSONArray("audiences"));
List<Group> groups = parseGroups(rootObject.getJSONArray("groups"));
Expand All @@ -85,6 +92,7 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
String id = experimentObject.getString("id");
String key = experimentObject.getString("key");
String status = experimentObject.getString("status");
String layerId = experimentObject.has("layerId") ? experimentObject.getString("layerId") : null;

JSONArray audienceIdsJson = experimentObject.getJSONArray("audienceIds");
List<String> audienceIds = new ArrayList<String>(audienceIdsJson.length());
Expand All @@ -100,7 +108,7 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
List<TrafficAllocation> trafficAllocations =
parseTrafficAllocation(experimentObject.getJSONArray("trafficAllocation"));

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

Expand Down Expand Up @@ -153,9 +161,8 @@ private List<Attribute> parseAttributes(JSONArray attributeJson) {
JSONObject attributeObject = (JSONObject)obj;
String id = attributeObject.getString("id");
String key = attributeObject.getString("key");
String segmentId = attributeObject.getString("segmentId");

attributes.add(new Attribute(id, key, segmentId));
attributes.add(new Attribute(id, key, attributeObject.optString("segmentId", null)));
}

return attributes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ public ProjectConfig parseProjectConfig(@Nonnull String json) throws ConfigParse
String version = (String)rootObject.get("version");

List<Experiment> experiments = parseExperiments((JSONArray)rootObject.get("experiments"));
List<Attribute> attributes = parseAttributes((JSONArray)rootObject.get("dimensions"));

List<Attribute> attributes;
if (version.equals(ProjectConfig.V1)) {
attributes = parseAttributes((JSONArray)rootObject.get("dimensions"));
} else {
attributes = parseAttributes((JSONArray)rootObject.get("attributes"));
}

List<EventType> events = parseEvents((JSONArray)rootObject.get("events"));
List<Audience> audiences = parseAudiences((JSONArray)parser.parse(rootObject.get("audiences").toString()));
List<Group> groups = parseGroups((JSONArray)rootObject.get("groups"));
Expand All @@ -87,6 +94,8 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
String id = (String)experimentObject.get("id");
String key = (String)experimentObject.get("key");
String status = (String)experimentObject.get("status");
Object layerIdObject = experimentObject.get("layerId");
String layerId = layerIdObject == null ? null : (String)layerIdObject;

JSONArray audienceIdsJson = (JSONArray)experimentObject.get("audienceIds");
List<String> audienceIds = new ArrayList<String>(audienceIdsJson.size());
Expand All @@ -102,7 +111,7 @@ private List<Experiment> parseExperiments(JSONArray experimentJson, String group
List<TrafficAllocation> trafficAllocations =
parseTrafficAllocation((JSONArray)experimentObject.get("trafficAllocation"));

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,14 @@ public ProjectConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializa
List<Group> groups = context.deserialize(jsonObject.get("groups").getAsJsonArray(), groupsType);
List<Experiment> experiments =
context.deserialize(jsonObject.get("experiments").getAsJsonArray(), experimentsType);
List<Attribute> attributes =
context.deserialize(jsonObject.get("dimensions").getAsJsonArray(), attributesType);

List<Attribute> attributes;
if (version.equals(ProjectConfig.V1)) {
attributes = context.deserialize(jsonObject.get("dimensions"), attributesType);
} else {
attributes = context.deserialize(jsonObject.get("attributes"), attributesType);
}

List<EventType> events =
context.deserialize(jsonObject.get("events").getAsJsonArray(), eventsType);
List<Audience> audiences =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
*
* Copyright 2016, Optimizely
*
* 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.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

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

import java.io.IOException;
import java.util.List;

class ProjectConfigJacksonDeserializer extends JsonDeserializer<ProjectConfig> {

@Override
public ProjectConfig deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Audience.class, new AudienceJacksonDeserializer());
module.addDeserializer(Group.class, new GroupJacksonDeserializer());
mapper.registerModule(module);

JsonNode node = parser.getCodec().readTree(parser);

String accountId = node.get("accountId").textValue();
String projectId = node.get("projectId").textValue();
String revision = node.get("revision").textValue();
String version = node.get("version").textValue();

List<Group> groups = mapper.readValue(node.get("groups").toString(), new TypeReference<List<Group>>() {});
List<Experiment> experiments = mapper.readValue(node.get("experiments").toString(),
new TypeReference<List<Experiment>>() {});

List<Attribute> attributes;
if (version.equals(ProjectConfig.V1)) {
attributes = mapper.readValue(node.get("dimensions").toString(), new TypeReference<List<Attribute>>() {});
} else {
attributes = mapper.readValue(node.get("attributes").toString(), new TypeReference<List<Attribute>>() {});
}

List<EventType> events = mapper.readValue(node.get("events").toString(),
new TypeReference<List<EventType>>() {});
List<Audience> audiences = mapper.readValue(node.get("audiences").toString(),
new TypeReference<List<Audience>>() {});

return new ProjectConfig(accountId, projectId, version, revision, groups, experiments, attributes, events,
audiences);
}
}
Loading